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/feed.ts130
-rw-r--r--src/state/queries/known-followers.ts32
-rw-r--r--src/state/queries/suggested-follows.ts13
3 files changed, 164 insertions, 11 deletions
diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts
index b599ac1a0..2981b41b4 100644
--- a/src/state/queries/feed.ts
+++ b/src/state/queries/feed.ts
@@ -1,3 +1,4 @@
+import {useCallback, useEffect, useMemo, useRef} from 'react'
 import {
   AppBskyActorDefs,
   AppBskyFeedDefs,
@@ -171,28 +172,119 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) {
   })
 }
 
-export const useGetPopularFeedsQueryKey = ['getPopularFeeds']
+// HACK
+// the protocol doesn't yet tell us which feeds are personalized
+// this list is used to filter out feed recommendations from logged out users
+// for the ones we know need it
+// -prf
+export const KNOWN_AUTHED_ONLY_FEEDS = [
+  'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/with-friends', // popular with friends, by bsky.app
+  'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/mutuals', // mutuals, by skyfeed
+  'at://did:plc:tenurhgjptubkk5zf5qhi3og/app.bsky.feed.generator/only-posts', // only posts, by skyfeed
+  'at://did:plc:wzsilnxf24ehtmmc3gssy5bu/app.bsky.feed.generator/mentions', // mentions, by flicknow
+  'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/bangers', // my bangers, by jaz
+  'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/mutuals', // mutuals, by bluesky
+  'at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.generator/my-followers', // followers, by jaz
+  'at://did:plc:vpkhqolt662uhesyj6nxm7ys/app.bsky.feed.generator/followpics', // the gram, by why
+]
+
+type GetPopularFeedsOptions = {limit?: number}
 
-export function useGetPopularFeedsQuery() {
+export function createGetPopularFeedsQueryKey(
+  options?: GetPopularFeedsOptions,
+) {
+  return ['getPopularFeeds', options]
+}
+
+export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
+  const {hasSession} = useSession()
   const agent = useAgent()
-  return useInfiniteQuery<
+  const limit = options?.limit || 10
+  const {data: preferences} = usePreferencesQuery()
+
+  // Make sure this doesn't invalidate unless really needed.
+  const selectArgs = useMemo(
+    () => ({
+      hasSession,
+      savedFeeds: preferences?.savedFeeds || [],
+    }),
+    [hasSession, preferences?.savedFeeds],
+  )
+  const lastPageCountRef = useRef(0)
+
+  const query = useInfiniteQuery<
     AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema,
     Error,
     InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
     QueryKey,
     string | undefined
   >({
-    queryKey: useGetPopularFeedsQueryKey,
+    queryKey: createGetPopularFeedsQueryKey(options),
     queryFn: async ({pageParam}) => {
       const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
-        limit: 10,
+        limit,
         cursor: pageParam,
       })
       return res.data
     },
     initialPageParam: undefined,
     getNextPageParam: lastPage => lastPage.cursor,
+    select: useCallback(
+      (
+        data: InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
+      ) => {
+        const {savedFeeds, hasSession: hasSessionInner} = selectArgs
+        data?.pages.map(page => {
+          page.feeds = page.feeds.filter(feed => {
+            if (
+              !hasSessionInner &&
+              KNOWN_AUTHED_ONLY_FEEDS.includes(feed.uri)
+            ) {
+              return false
+            }
+            const alreadySaved = Boolean(
+              savedFeeds?.find(f => {
+                return f.value === feed.uri
+              }),
+            )
+            return !alreadySaved
+          })
+
+          return page
+        })
+
+        return data
+      },
+      [selectArgs /* Don't change. Everything needs to go into selectArgs. */],
+    ),
   })
+
+  useEffect(() => {
+    const {isFetching, hasNextPage, data} = query
+    if (isFetching || !hasNextPage) {
+      return
+    }
+
+    // avoid double-fires of fetchNextPage()
+    if (
+      lastPageCountRef.current !== 0 &&
+      lastPageCountRef.current === data?.pages?.length
+    ) {
+      return
+    }
+
+    // fetch next page if we haven't gotten a full page of content
+    let count = 0
+    for (const page of data?.pages || []) {
+      count += page.feeds.length
+    }
+    if (count < limit && (data?.pages.length || 0) < 6) {
+      query.fetchNextPage()
+      lastPageCountRef.current = data?.pages?.length || 0
+    }
+  }, [query, limit])
+
+  return query
 }
 
 export function useSearchPopularFeedsMutation() {
@@ -209,6 +301,34 @@ export function useSearchPopularFeedsMutation() {
   })
 }
 
+const popularFeedsSearchQueryKeyRoot = 'popularFeedsSearch'
+export const createPopularFeedsSearchQueryKey = (query: string) => [
+  popularFeedsSearchQueryKeyRoot,
+  query,
+]
+
+export function usePopularFeedsSearch({
+  query,
+  enabled,
+}: {
+  query: string
+  enabled?: boolean
+}) {
+  const agent = useAgent()
+  return useQuery({
+    enabled,
+    queryKey: createPopularFeedsSearchQueryKey(query),
+    queryFn: async () => {
+      const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
+        limit: 10,
+        query: query,
+      })
+
+      return res.data.feeds
+    },
+  })
+}
+
 export type SavedFeedSourceInfo = FeedSourceInfo & {
   savedFeed: AppBskyActorDefs.SavedFeed
 }
diff --git a/src/state/queries/known-followers.ts b/src/state/queries/known-followers.ts
index adcbf4b50..fedd9b40f 100644
--- a/src/state/queries/known-followers.ts
+++ b/src/state/queries/known-followers.ts
@@ -1,5 +1,10 @@
-import {AppBskyGraphGetKnownFollowers} from '@atproto/api'
-import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query'
+import {AppBskyActorDefs, AppBskyGraphGetKnownFollowers} from '@atproto/api'
+import {
+  InfiniteData,
+  QueryClient,
+  QueryKey,
+  useInfiniteQuery,
+} from '@tanstack/react-query'
 
 import {useAgent} from '#/state/session'
 
@@ -32,3 +37,26 @@ export function useProfileKnownFollowersQuery(did: string | undefined) {
     enabled: !!did,
   })
 }
+
+export function* findAllProfilesInQueryData(
+  queryClient: QueryClient,
+  did: string,
+): Generator<AppBskyActorDefs.ProfileView, void> {
+  const queryDatas = queryClient.getQueriesData<
+    InfiniteData<AppBskyGraphGetKnownFollowers.OutputSchema>
+  >({
+    queryKey: [RQKEY_ROOT],
+  })
+  for (const [_queryKey, queryData] of queryDatas) {
+    if (!queryData?.pages) {
+      continue
+    }
+    for (const page of queryData?.pages) {
+      for (const follow of page.followers) {
+        if (follow.did === did) {
+          yield follow
+        }
+      }
+    }
+  }
+}
diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts
index 59b8f7ed5..40251d43d 100644
--- a/src/state/queries/suggested-follows.ts
+++ b/src/state/queries/suggested-follows.ts
@@ -23,7 +23,10 @@ import {useAgent, useSession} from '#/state/session'
 import {useModerationOpts} from '../preferences/moderation-opts'
 
 const suggestedFollowsQueryKeyRoot = 'suggested-follows'
-const suggestedFollowsQueryKey = [suggestedFollowsQueryKeyRoot]
+const suggestedFollowsQueryKey = (options?: SuggestedFollowsOptions) => [
+  suggestedFollowsQueryKeyRoot,
+  options,
+]
 
 const suggestedFollowsByActorQueryKeyRoot = 'suggested-follows-by-actor'
 const suggestedFollowsByActorQueryKey = (did: string) => [
@@ -31,7 +34,9 @@ const suggestedFollowsByActorQueryKey = (did: string) => [
   did,
 ]
 
-export function useSuggestedFollowsQuery() {
+type SuggestedFollowsOptions = {limit?: number}
+
+export function useSuggestedFollowsQuery(options?: SuggestedFollowsOptions) {
   const {currentAccount} = useSession()
   const agent = useAgent()
   const moderationOpts = useModerationOpts()
@@ -46,12 +51,12 @@ export function useSuggestedFollowsQuery() {
   >({
     enabled: !!moderationOpts && !!preferences,
     staleTime: STALE.HOURS.ONE,
-    queryKey: suggestedFollowsQueryKey,
+    queryKey: suggestedFollowsQueryKey(options),
     queryFn: async ({pageParam}) => {
       const contentLangs = getContentLanguages().join(',')
       const res = await agent.app.bsky.actor.getSuggestions(
         {
-          limit: 25,
+          limit: options?.limit || 25,
           cursor: pageParam,
         },
         {