about summary refs log tree commit diff
path: root/src/state/queries
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/queries')
-rw-r--r--src/state/queries/actor-autocomplete.ts9
-rw-r--r--src/state/queries/index.ts8
-rw-r--r--src/state/queries/messages/conversation.ts25
-rw-r--r--src/state/queries/messages/get-convo-for-members.ts39
-rw-r--r--src/state/queries/messages/leave-conversation.ts68
-rw-r--r--src/state/queries/messages/list-converations.ts29
-rw-r--r--src/state/queries/messages/mute-conversation.ts84
-rw-r--r--src/state/queries/messages/temp-headers.ts11
-rw-r--r--src/state/queries/notifications/feed.ts2
-rw-r--r--src/state/queries/notifications/unread.tsx2
-rw-r--r--src/state/queries/post-feed.ts16
-rw-r--r--src/state/queries/preferences/index.ts48
-rw-r--r--src/state/queries/suggested-follows.ts28
13 files changed, 303 insertions, 66 deletions
diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts
index 98b5aa17e..8708a244b 100644
--- a/src/state/queries/actor-autocomplete.ts
+++ b/src/state/queries/actor-autocomplete.ts
@@ -6,7 +6,8 @@ import {isJustAMute} from '#/lib/moderation'
 import {logger} from '#/logger'
 import {STALE} from '#/state/queries'
 import {useAgent} from '#/state/session'
-import {DEFAULT_LOGGED_OUT_PREFERENCES, useModerationOpts} from './preferences'
+import {useModerationOpts} from '../preferences/moderation-opts'
+import {DEFAULT_LOGGED_OUT_PREFERENCES} from './preferences'
 
 const DEFAULT_MOD_OPTS = {
   userDid: undefined,
@@ -23,7 +24,11 @@ export function useActorAutocompleteQuery(
   const moderationOpts = useModerationOpts()
   const {getAgent} = useAgent()
 
-  prefix = prefix.toLowerCase()
+  prefix = prefix.toLowerCase().trim()
+  if (prefix.endsWith('.')) {
+    // Going from "foo" to "foo." should not clear matches.
+    prefix = prefix.slice(0, -1)
+  }
 
   return useQuery<AppBskyActorDefs.ProfileViewBasic[]>({
     staleTime: STALE.MINUTES.ONE,
diff --git a/src/state/queries/index.ts b/src/state/queries/index.ts
index e30528ca1..0635bf316 100644
--- a/src/state/queries/index.ts
+++ b/src/state/queries/index.ts
@@ -1,11 +1,3 @@
-import {BskyAgent} from '@atproto/api'
-
-import {PUBLIC_BSKY_SERVICE} from '#/lib/constants'
-
-export const PUBLIC_BSKY_AGENT = new BskyAgent({
-  service: PUBLIC_BSKY_SERVICE,
-})
-
 export const STALE = {
   SECONDS: {
     FIFTEEN: 1e3 * 15,
diff --git a/src/state/queries/messages/conversation.ts b/src/state/queries/messages/conversation.ts
new file mode 100644
index 000000000..9456861d2
--- /dev/null
+++ b/src/state/queries/messages/conversation.ts
@@ -0,0 +1,25 @@
+import {BskyAgent} from '@atproto-labs/api'
+import {useQuery} from '@tanstack/react-query'
+
+import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage'
+import {useHeaders} from './temp-headers'
+
+const RQKEY_ROOT = 'convo'
+export const RQKEY = (convoId: string) => [RQKEY_ROOT, convoId]
+
+export function useConvoQuery(convoId: string) {
+  const headers = useHeaders()
+  const {serviceUrl} = useDmServiceUrlStorage()
+
+  return useQuery({
+    queryKey: RQKEY(convoId),
+    queryFn: async () => {
+      const agent = new BskyAgent({service: serviceUrl})
+      const {data} = await agent.api.chat.bsky.convo.getConvo(
+        {convoId},
+        {headers},
+      )
+      return data.convo
+    },
+  })
+}
diff --git a/src/state/queries/messages/get-convo-for-members.ts b/src/state/queries/messages/get-convo-for-members.ts
new file mode 100644
index 000000000..0a657c07e
--- /dev/null
+++ b/src/state/queries/messages/get-convo-for-members.ts
@@ -0,0 +1,39 @@
+import {BskyAgent, ChatBskyConvoGetConvoForMembers} from '@atproto-labs/api'
+import {useMutation, useQueryClient} from '@tanstack/react-query'
+
+import {logger} from '#/logger'
+import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage'
+import {RQKEY as CONVO_KEY} from './conversation'
+import {useHeaders} from './temp-headers'
+
+export function useGetConvoForMembers({
+  onSuccess,
+  onError,
+}: {
+  onSuccess?: (data: ChatBskyConvoGetConvoForMembers.OutputSchema) => void
+  onError?: (error: Error) => void
+}) {
+  const queryClient = useQueryClient()
+  const headers = useHeaders()
+  const {serviceUrl} = useDmServiceUrlStorage()
+
+  return useMutation({
+    mutationFn: async (members: string[]) => {
+      const agent = new BskyAgent({service: serviceUrl})
+      const {data} = await agent.api.chat.bsky.convo.getConvoForMembers(
+        {members: members},
+        {headers},
+      )
+
+      return data
+    },
+    onSuccess: data => {
+      queryClient.setQueryData(CONVO_KEY(data.convo.id), data.convo)
+      onSuccess?.(data)
+    },
+    onError: error => {
+      logger.error(error)
+      onError?.(error)
+    },
+  })
+}
diff --git a/src/state/queries/messages/leave-conversation.ts b/src/state/queries/messages/leave-conversation.ts
new file mode 100644
index 000000000..0dd67fa0b
--- /dev/null
+++ b/src/state/queries/messages/leave-conversation.ts
@@ -0,0 +1,68 @@
+import {
+  BskyAgent,
+  ChatBskyConvoLeaveConvo,
+  ChatBskyConvoListConvos,
+} from '@atproto-labs/api'
+import {useMutation, useQueryClient} from '@tanstack/react-query'
+
+import {logger} from '#/logger'
+import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage'
+import {RQKEY as CONVO_LIST_KEY} from './list-converations'
+import {useHeaders} from './temp-headers'
+
+export function useLeaveConvo(
+  convoId: string,
+  {
+    onSuccess,
+    onError,
+  }: {
+    onSuccess?: (data: ChatBskyConvoLeaveConvo.OutputSchema) => void
+    onError?: (error: Error) => void
+  },
+) {
+  const queryClient = useQueryClient()
+  const headers = useHeaders()
+  const {serviceUrl} = useDmServiceUrlStorage()
+
+  return useMutation({
+    mutationFn: async () => {
+      const agent = new BskyAgent({service: serviceUrl})
+      const {data} = await agent.api.chat.bsky.convo.leaveConvo(
+        {convoId},
+        {headers, encoding: 'application/json'},
+      )
+
+      return data
+    },
+    onMutate: () => {
+      queryClient.setQueryData(
+        CONVO_LIST_KEY,
+        (old?: {
+          pageParams: Array<string | undefined>
+          pages: Array<ChatBskyConvoListConvos.OutputSchema>
+        }) => {
+          console.log('old', old)
+          if (!old) return old
+          return {
+            ...old,
+            pages: old.pages.map(page => {
+              return {
+                ...page,
+                convos: page.convos.filter(convo => convo.id !== convoId),
+              }
+            }),
+          }
+        },
+      )
+    },
+    onSuccess: data => {
+      queryClient.invalidateQueries({queryKey: CONVO_LIST_KEY})
+      onSuccess?.(data)
+    },
+    onError: error => {
+      logger.error(error)
+      queryClient.invalidateQueries({queryKey: CONVO_LIST_KEY})
+      onError?.(error)
+    },
+  })
+}
diff --git a/src/state/queries/messages/list-converations.ts b/src/state/queries/messages/list-converations.ts
new file mode 100644
index 000000000..1e4ecb6d7
--- /dev/null
+++ b/src/state/queries/messages/list-converations.ts
@@ -0,0 +1,29 @@
+import {BskyAgent} from '@atproto-labs/api'
+import {useInfiniteQuery} from '@tanstack/react-query'
+
+import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage'
+import {useHeaders} from './temp-headers'
+
+export const RQKEY = ['convo-list']
+type RQPageParam = string | undefined
+
+export function useListConvos({refetchInterval}: {refetchInterval: number}) {
+  const headers = useHeaders()
+  const {serviceUrl} = useDmServiceUrlStorage()
+
+  return useInfiniteQuery({
+    queryKey: RQKEY,
+    queryFn: async ({pageParam}) => {
+      const agent = new BskyAgent({service: serviceUrl})
+      const {data} = await agent.api.chat.bsky.convo.listConvos(
+        {cursor: pageParam},
+        {headers},
+      )
+
+      return data
+    },
+    initialPageParam: undefined as RQPageParam,
+    getNextPageParam: lastPage => lastPage.cursor,
+    refetchInterval,
+  })
+}
diff --git a/src/state/queries/messages/mute-conversation.ts b/src/state/queries/messages/mute-conversation.ts
new file mode 100644
index 000000000..4840c65ad
--- /dev/null
+++ b/src/state/queries/messages/mute-conversation.ts
@@ -0,0 +1,84 @@
+import {
+  BskyAgent,
+  ChatBskyConvoMuteConvo,
+  ChatBskyConvoUnmuteConvo,
+} from '@atproto-labs/api'
+import {useMutation, useQueryClient} from '@tanstack/react-query'
+
+import {logger} from '#/logger'
+import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage'
+import {RQKEY as CONVO_KEY} from './conversation'
+import {RQKEY as CONVO_LIST_KEY} from './list-converations'
+import {useHeaders} from './temp-headers'
+
+export function useMuteConvo(
+  convoId: string,
+  {
+    onSuccess,
+    onError,
+  }: {
+    onSuccess?: (data: ChatBskyConvoMuteConvo.OutputSchema) => void
+    onError?: (error: Error) => void
+  },
+) {
+  const queryClient = useQueryClient()
+  const headers = useHeaders()
+  const {serviceUrl} = useDmServiceUrlStorage()
+
+  return useMutation({
+    mutationFn: async () => {
+      const agent = new BskyAgent({service: serviceUrl})
+      const {data} = await agent.api.chat.bsky.convo.muteConvo(
+        {convoId},
+        {headers, encoding: 'application/json'},
+      )
+
+      return data
+    },
+    onSuccess: data => {
+      queryClient.invalidateQueries({queryKey: CONVO_LIST_KEY})
+      queryClient.invalidateQueries({queryKey: CONVO_KEY(convoId)})
+      onSuccess?.(data)
+    },
+    onError: error => {
+      logger.error(error)
+      onError?.(error)
+    },
+  })
+}
+
+export function useUnmuteConvo(
+  convoId: string,
+  {
+    onSuccess,
+    onError,
+  }: {
+    onSuccess?: (data: ChatBskyConvoUnmuteConvo.OutputSchema) => void
+    onError?: (error: Error) => void
+  },
+) {
+  const queryClient = useQueryClient()
+  const headers = useHeaders()
+  const {serviceUrl} = useDmServiceUrlStorage()
+
+  return useMutation({
+    mutationFn: async () => {
+      const agent = new BskyAgent({service: serviceUrl})
+      const {data} = await agent.api.chat.bsky.convo.unmuteConvo(
+        {convoId},
+        {headers, encoding: 'application/json'},
+      )
+
+      return data
+    },
+    onSuccess: data => {
+      queryClient.invalidateQueries({queryKey: CONVO_LIST_KEY})
+      queryClient.invalidateQueries({queryKey: CONVO_KEY(convoId)})
+      onSuccess?.(data)
+    },
+    onError: error => {
+      logger.error(error)
+      onError?.(error)
+    },
+  })
+}
diff --git a/src/state/queries/messages/temp-headers.ts b/src/state/queries/messages/temp-headers.ts
new file mode 100644
index 000000000..9e46e8a61
--- /dev/null
+++ b/src/state/queries/messages/temp-headers.ts
@@ -0,0 +1,11 @@
+import {useSession} from '#/state/session'
+
+// toy auth
+export const useHeaders = () => {
+  const {currentAccount} = useSession()
+  return {
+    get Authorization() {
+      return currentAccount!.did
+    },
+  }
+}
diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts
index 1f2199901..80e5a4c47 100644
--- a/src/state/queries/notifications/feed.ts
+++ b/src/state/queries/notifications/feed.ts
@@ -28,8 +28,8 @@ import {
 
 import {useMutedThreads} from '#/state/muted-threads'
 import {useAgent} from '#/state/session'
+import {useModerationOpts} from '../../preferences/moderation-opts'
 import {STALE} from '..'
-import {useModerationOpts} from '../preferences'
 import {embedViewRecordToPostView, getEmbeddedPost} from '../util'
 import {FeedPage} from './types'
 import {useUnreadNotificationsApi} from './unread'
diff --git a/src/state/queries/notifications/unread.tsx b/src/state/queries/notifications/unread.tsx
index 1c569e2a0..80333b524 100644
--- a/src/state/queries/notifications/unread.tsx
+++ b/src/state/queries/notifications/unread.tsx
@@ -13,7 +13,7 @@ import {logger} from '#/logger'
 import {isNative} from '#/platform/detection'
 import {useMutedThreads} from '#/state/muted-threads'
 import {useAgent, useSession} from '#/state/session'
-import {useModerationOpts} from '../preferences'
+import {useModerationOpts} from '../../preferences/moderation-opts'
 import {truncateAndInvalidate} from '../util'
 import {RQKEY as RQKEY_NOTIFS} from './feed'
 import {CachedFeedPage, FeedPage} from './types'
diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts
index 747dba02e..827f8a2a8 100644
--- a/src/state/queries/post-feed.ts
+++ b/src/state/queries/post-feed.ts
@@ -15,6 +15,7 @@ import {
 } from '@tanstack/react-query'
 
 import {HomeFeedAPI} from '#/lib/api/feed/home'
+import {aggregateUserInterests} from '#/lib/api/feed/utils'
 import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
 import {logger} from '#/logger'
 import {STALE} from '#/state/queries'
@@ -31,7 +32,8 @@ import {FeedTuner, FeedTunerFn, NoopFeedTuner} from 'lib/api/feed-manip'
 import {BSKY_FEED_OWNER_DIDS} from 'lib/constants'
 import {KnownError} from '#/view/com/posts/FeedErrorMessage'
 import {useFeedTuners} from '../preferences/feed-tuners'
-import {useModerationOpts} from './preferences'
+import {useModerationOpts} from '../preferences/moderation-opts'
+import {usePreferencesQuery} from './preferences'
 import {embedViewRecordToPostView, getEmbeddedPost} from './util'
 
 type ActorDid = string
@@ -102,8 +104,11 @@ export function usePostFeedQuery(
 ) {
   const feedTuners = useFeedTuners(feedDesc)
   const moderationOpts = useModerationOpts()
+  const {data: preferences} = usePreferencesQuery()
+  const enabled =
+    opts?.enabled !== false && Boolean(moderationOpts) && Boolean(preferences)
+  const userInterests = aggregateUserInterests(preferences)
   const {getAgent} = useAgent()
-  const enabled = opts?.enabled !== false && Boolean(moderationOpts)
   const lastRun = useRef<{
     data: InfiniteData<FeedPageUnselected>
     args: typeof selectArgs
@@ -141,6 +146,7 @@ export function usePostFeedQuery(
               feedDesc,
               feedParams: params || {},
               feedTuners,
+              userInterests, // Not in the query key because they don't change.
               getAgent,
             }),
             cursor: undefined,
@@ -371,11 +377,13 @@ function createApi({
   feedDesc,
   feedParams,
   feedTuners,
+  userInterests,
   getAgent,
 }: {
   feedDesc: FeedDescriptor
   feedParams: FeedParams
   feedTuners: FeedTunerFn[]
+  userInterests?: string
   getAgent: () => BskyAgent
 }) {
   if (feedDesc === 'home') {
@@ -384,9 +392,10 @@ function createApi({
         getAgent,
         feedParams,
         feedTuners,
+        userInterests,
       })
     } else {
-      return new HomeFeedAPI({getAgent})
+      return new HomeFeedAPI({getAgent, userInterests})
     }
   } else if (feedDesc === 'following') {
     return new FollowingFeedAPI({getAgent})
@@ -401,6 +410,7 @@ function createApi({
     return new CustomFeedAPI({
       getAgent,
       feedParams: {feed},
+      userInterests,
     })
   } else if (feedDesc.startsWith('list')) {
     const [_, list] = feedDesc.split('|')
diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts
index 06e47391f..f51eaac2a 100644
--- a/src/state/queries/preferences/index.ts
+++ b/src/state/queries/preferences/index.ts
@@ -1,28 +1,24 @@
-import {createContext, useContext, useMemo} from 'react'
 import {
   AppBskyActorDefs,
-  BSKY_LABELER_DID,
   BskyFeedViewPreference,
   LabelPreference,
-  ModerationOpts,
 } from '@atproto/api'
 import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
 
 import {track} from '#/lib/analytics/analytics'
+import {replaceEqualDeep} from '#/lib/functions'
 import {getAge} from '#/lib/strings/time'
-import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences'
 import {STALE} from '#/state/queries'
 import {
   DEFAULT_HOME_FEED_PREFS,
   DEFAULT_LOGGED_OUT_PREFERENCES,
   DEFAULT_THREAD_VIEW_PREFS,
 } from '#/state/queries/preferences/const'
-import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation'
 import {
   ThreadViewPreferences,
   UsePreferencesQueryResponse,
 } from '#/state/queries/preferences/types'
-import {useAgent, useSession} from '#/state/session'
+import {useAgent} from '#/state/session'
 import {saveLabelers} from '#/state/session/agent-config'
 
 export * from '#/state/queries/preferences/const'
@@ -36,7 +32,7 @@ export function usePreferencesQuery() {
   const {getAgent} = useAgent()
   return useQuery({
     staleTime: STALE.SECONDS.FIFTEEN,
-    structuralSharing: true,
+    structuralSharing: replaceEqualDeep,
     refetchOnWindowFocus: true,
     queryKey: preferencesQueryKey,
     queryFn: async () => {
@@ -79,44 +75,6 @@ export function usePreferencesQuery() {
   })
 }
 
-// used in the moderation state devtool
-export const moderationOptsOverrideContext = createContext<
-  ModerationOpts | undefined
->(undefined)
-
-export function useModerationOpts() {
-  const override = useContext(moderationOptsOverrideContext)
-  const {currentAccount} = useSession()
-  const prefs = usePreferencesQuery()
-  const {labelDefs} = useLabelDefinitions()
-  const hiddenPosts = useHiddenPosts() // TODO move this into pds-stored prefs
-  const opts = useMemo<ModerationOpts | undefined>(() => {
-    if (override) {
-      return override
-    }
-    if (!prefs.data) {
-      return
-    }
-    return {
-      userDid: currentAccount?.did,
-      prefs: {
-        ...prefs.data.moderationPrefs,
-        labelers: prefs.data.moderationPrefs.labelers.length
-          ? prefs.data.moderationPrefs.labelers
-          : [
-              {
-                did: BSKY_LABELER_DID,
-                labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
-              },
-            ],
-        hiddenPosts: hiddenPosts || [],
-      },
-      labelDefs,
-    }
-  }, [override, currentAccount, labelDefs, prefs.data, hiddenPosts])
-  return opts
-}
-
 export function useClearPreferencesMutation() {
   const queryClient = useQueryClient()
   const {getAgent} = useAgent()
diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts
index 936912ab3..7740b1977 100644
--- a/src/state/queries/suggested-follows.ts
+++ b/src/state/queries/suggested-follows.ts
@@ -12,9 +12,15 @@ import {
   useQuery,
 } from '@tanstack/react-query'
 
+import {
+  aggregateUserInterests,
+  createBskyTopicsHeader,
+} from '#/lib/api/feed/utils'
+import {getContentLanguages} from '#/state/preferences/languages'
 import {STALE} from '#/state/queries'
-import {useModerationOpts} from '#/state/queries/preferences'
+import {usePreferencesQuery} from '#/state/queries/preferences'
 import {useAgent, useSession} from '#/state/session'
+import {useModerationOpts} from '../preferences/moderation-opts'
 
 const suggestedFollowsQueryKeyRoot = 'suggested-follows'
 const suggestedFollowsQueryKey = [suggestedFollowsQueryKeyRoot]
@@ -29,6 +35,7 @@ export function useSuggestedFollowsQuery() {
   const {currentAccount} = useSession()
   const {getAgent} = useAgent()
   const moderationOpts = useModerationOpts()
+  const {data: preferences} = usePreferencesQuery()
 
   return useInfiniteQuery<
     AppBskyActorGetSuggestions.OutputSchema,
@@ -37,14 +44,23 @@ export function useSuggestedFollowsQuery() {
     QueryKey,
     string | undefined
   >({
-    enabled: !!moderationOpts,
+    enabled: !!moderationOpts && !!preferences,
     staleTime: STALE.HOURS.ONE,
     queryKey: suggestedFollowsQueryKey,
     queryFn: async ({pageParam}) => {
-      const res = await getAgent().app.bsky.actor.getSuggestions({
-        limit: 25,
-        cursor: pageParam,
-      })
+      const contentLangs = getContentLanguages().join(',')
+      const res = await getAgent().app.bsky.actor.getSuggestions(
+        {
+          limit: 25,
+          cursor: pageParam,
+        },
+        {
+          headers: {
+            ...createBskyTopicsHeader(aggregateUserInterests(preferences)),
+            'Accept-Language': contentLangs,
+          },
+        },
+      )
 
       res.data.actors = res.data.actors
         .filter(