diff options
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/cache/profile-shadow.ts | 2 | ||||
-rw-r--r-- | src/state/queries/feed.ts | 130 | ||||
-rw-r--r-- | src/state/queries/known-followers.ts | 32 | ||||
-rw-r--r-- | src/state/queries/suggested-follows.ts | 13 |
4 files changed, 166 insertions, 11 deletions
diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts index 0a618ab3b..dc907664e 100644 --- a/src/state/cache/profile-shadow.ts +++ b/src/state/cache/profile-shadow.ts @@ -5,6 +5,7 @@ import EventEmitter from 'eventemitter3' import {batchedUpdates} from '#/lib/batchedUpdates' import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search' +import {findAllProfilesInQueryData as findAllProfilesInKnownFollowersQueryData} from '../queries/known-followers' import {findAllProfilesInQueryData as findAllProfilesInListMembersQueryData} from '../queries/list-members' import {findAllProfilesInQueryData as findAllProfilesInListConvosQueryData} from '../queries/messages/list-converations' import {findAllProfilesInQueryData as findAllProfilesInMyBlockedAccountsQueryData} from '../queries/my-blocked-accounts' @@ -111,4 +112,5 @@ function* findProfilesInCache( yield* findAllProfilesInListConvosQueryData(queryClient, did) yield* findAllProfilesInFeedsQueryData(queryClient, did) yield* findAllProfilesInPostThreadQueryData(queryClient, did) + yield* findAllProfilesInKnownFollowersQueryData(queryClient, did) } 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, }, { |