Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Use new endpoints for suggested users (#10185)

authored by

DS Boyce and committed by
GitHub
a049a653 3c21fee6

+281 -300
+1 -1
package.json
··· 81 81 "icons:optimize": "svgo -f ./assets/icons" 82 82 }, 83 83 "dependencies": { 84 - "@atproto/api": "^0.19.5", 84 + "@atproto/api": "^0.19.6", 85 85 "@bitdrift/react-native": "^0.6.8", 86 86 "@braintree/sanitize-url": "^6.0.2", 87 87 "@bsky.app/alf": "^0.1.7",
+11 -104
src/components/FeedInterstitials.tsx
··· 7 7 LayoutAnimationConfig, 8 8 LinearTransition, 9 9 } from 'react-native-reanimated' 10 - import {type AppBskyFeedDefs, AtUri} from '@atproto/api' 10 + import {type AppBskyFeedDefs} from '@atproto/api' 11 11 import {Trans, useLingui} from '@lingui/react/macro' 12 12 import {useNavigation} from '@react-navigation/native' 13 13 ··· 15 15 import {useModerationOpts} from '#/state/preferences/moderation-opts' 16 16 import {useGetPopularFeedsQuery} from '#/state/queries/feed' 17 17 import {type FeedDescriptor} from '#/state/queries/post-feed' 18 - import {useProfilesQuery} from '#/state/queries/profile' 19 18 import {useSuggestedFollowsByActorWithDismiss} from '#/state/queries/suggested-follows' 19 + import {useGetSuggestedUsersForDiscoverQuery} from '#/state/queries/trending/useGetSuggestedUsersForDiscoverQuery' 20 20 import {useSession} from '#/state/session' 21 - import * as userActionHistory from '#/state/userActionHistory' 22 - import {type SeenPost} from '#/state/userActionHistory' 23 21 import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' 24 22 import { 25 23 atoms as a, ··· 37 35 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 38 36 import {InlineLinkText} from '#/components/Link' 39 37 import * as ProfileCard from '#/components/ProfileCard' 38 + import {ProgressGuideList} from '#/components/ProgressGuide/List' 40 39 import {Text} from '#/components/Typography' 41 40 import {type Metrics, useAnalytics} from '#/analytics' 42 41 import {IS_IOS} from '#/env' 43 42 import type * as bsky from '#/types/bsky' 44 43 import {FollowDialogWithoutGuide} from './ProgressGuide/FollowDialog' 45 - import {ProgressGuideList} from './ProgressGuide/List' 46 44 47 45 const DISMISS_ANIMATION_DURATION = 200 48 46 ··· 109 107 ) 110 108 } 111 109 112 - function getRank(seenPost: SeenPost): string { 113 - let tier: string 114 - if (seenPost.feedContext === 'popfriends') { 115 - tier = 'a' 116 - } else if (seenPost.feedContext?.startsWith('cluster')) { 117 - tier = 'b' 118 - } else if (seenPost.feedContext === 'popcluster') { 119 - tier = 'c' 120 - } else if (seenPost.feedContext?.startsWith('ntpc')) { 121 - tier = 'd' 122 - } else if (seenPost.feedContext?.startsWith('t-')) { 123 - tier = 'e' 124 - } else if (seenPost.feedContext === 'nettop') { 125 - tier = 'f' 126 - } else { 127 - tier = 'g' 128 - } 129 - let score = Math.round( 130 - Math.log( 131 - 1 + seenPost.likeCount + seenPost.repostCount + seenPost.replyCount, 132 - ), 133 - ) 134 - if (seenPost.isFollowedBy || Math.random() > 0.9) { 135 - score *= 2 136 - } 137 - const rank = 100 - score 138 - return `${tier}-${rank}` 139 - } 140 - 141 - function sortSeenPosts(postA: SeenPost, postB: SeenPost): 0 | 1 | -1 { 142 - const rankA = getRank(postA) 143 - const rankB = getRank(postB) 144 - // Yes, we're comparing strings here. 145 - // The "larger" string means a worse rank. 146 - if (rankA > rankB) { 147 - return 1 148 - } else if (rankA < rankB) { 149 - return -1 150 - } else { 151 - return 0 152 - } 153 - } 154 - 155 - function useExperimentalSuggestedUsersQuery() { 156 - const {currentAccount} = useSession() 157 - const userActionSnapshot = userActionHistory.useActionHistorySnapshot() 158 - const dids = useMemo(() => { 159 - const {likes, follows, followSuggestions, seen} = userActionSnapshot 160 - const likeDids = likes 161 - .map(l => new AtUri(l)) 162 - .map(uri => uri.host) 163 - .filter(did => !follows.includes(did)) 164 - let suggestedDids: string[] = [] 165 - if (followSuggestions.length > 0) { 166 - suggestedDids = [ 167 - // It's ok if these will pick the same item (weighed by its frequency) 168 - /* eslint-disable react-hooks/purity */ 169 - followSuggestions[Math.floor(Math.random() * followSuggestions.length)], 170 - followSuggestions[Math.floor(Math.random() * followSuggestions.length)], 171 - followSuggestions[Math.floor(Math.random() * followSuggestions.length)], 172 - followSuggestions[Math.floor(Math.random() * followSuggestions.length)], 173 - /* eslint-enable react-hooks/purity */ 174 - ] 175 - } 176 - const seenDids = seen 177 - .sort(sortSeenPosts) 178 - .map(l => new AtUri(l.uri)) 179 - .map(uri => uri.host) 180 - return [...new Set([...suggestedDids, ...likeDids, ...seenDids])].filter( 181 - did => did !== currentAccount?.did, 182 - ) 183 - }, [userActionSnapshot, currentAccount]) 184 - const {data, isLoading, error} = useProfilesQuery({ 185 - handles: dids.slice(0, 16), 186 - }) 187 - 188 - const profiles = data 189 - ? data.profiles.filter(profile => { 190 - return !profile.viewer?.following 191 - }) 192 - : [] 193 - 194 - return { 195 - isLoading, 196 - error, 197 - profiles: profiles.slice(0, 6), 198 - } 199 - } 200 - 201 110 export function SuggestedFollows({feed}: {feed: FeedDescriptor}) { 202 111 const {currentAccount} = useSession() 203 112 const [feedType, feedUriOrDid] = feed.split('|') ··· 229 138 } 230 139 231 140 export function SuggestedFollowsHome() { 232 - const { 233 - isLoading: isSuggestionsLoading, 234 - profiles: experimentalProfiles, 235 - error: experimentalError, 236 - } = useExperimentalSuggestedUsersQuery() 141 + const {isLoading, data, error} = useGetSuggestedUsersForDiscoverQuery() 142 + 143 + const profiles = data?.actors 237 144 238 145 const [dismissedDids, setDismissedDids] = useState<Set<string>>(new Set()) 239 146 ··· 247 154 recId?: string 248 155 }> = [] 249 156 250 - for (const profile of experimentalProfiles) { 251 - result.push({actor: profile, recId: undefined}) 157 + for (const profile of profiles ?? []) { 158 + result.push({actor: profile, recId: data?.recId}) 252 159 } 253 160 254 161 return result 255 - }, [experimentalProfiles]) 162 + }, [data?.recId, profiles]) 256 163 257 164 const filteredProfiles = useMemo(() => { 258 165 return allProfiles.filter(p => !dismissedDids.has(p.actor.did)) ··· 260 167 261 168 return ( 262 169 <ProfileGrid 263 - isSuggestionsLoading={isSuggestionsLoading} 170 + isSuggestionsLoading={isLoading} 264 171 profiles={filteredProfiles} 265 172 totalProfileCount={allProfiles.length} 266 - error={experimentalError} 173 + error={error} 267 174 viewContext="feed" 268 175 onDismiss={onDismiss} 269 176 />
+2 -2
src/components/ProgressGuide/FollowDialog.tsx
··· 8 8 import {useModerationOpts} from '#/state/preferences/moderation-opts' 9 9 import {useActorSearch} from '#/state/queries/actor-search' 10 10 import {usePreferencesQuery} from '#/state/queries/preferences' 11 - import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery' 11 + import {useGetSuggestedUsersForSeeMoreQuery} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' 12 12 import {useSession} from '#/state/session' 13 13 import {type Follow10ProgressGuide} from '#/state/shell/progress-guide' 14 14 import {type ListMethods} from '#/view/com/util/List' ··· 141 141 data: suggestions, 142 142 isFetching: isFetchingSuggestions, 143 143 error: suggestionsError, 144 - } = useGetSuggestedUsersQuery({ 144 + } = useGetSuggestedUsersForSeeMoreQuery({ 145 145 category: selectedInterest, 146 146 limit: 50, 147 147 })
+6 -5
src/screens/Search/Explore.tsx
··· 28 28 createGetSuggestedFeedsQueryKey, 29 29 useGetSuggestedFeedsQuery, 30 30 } from '#/state/queries/trending/useGetSuggestedFeedsQuery' 31 - import {getSuggestedUsersQueryKeyRoot} from '#/state/queries/trending/useGetSuggestedUsersQuery' 31 + import { 32 + getSuggestedUsersForExploreQueryKeyRoot, 33 + useGetSuggestedUsersForExploreQuery, 34 + } from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' 32 35 import {createGetTrendsQueryKey} from '#/state/queries/trending/useGetTrendsQuery' 33 36 import { 34 37 createSuggestedStarterPacksQueryKey, ··· 48 51 import {ExploreRecommendations} from '#/screens/Search/modules/ExploreRecommendations' 49 52 import {ExploreTrendingTopics} from '#/screens/Search/modules/ExploreTrendingTopics' 50 53 import {ExploreTrendingVideos} from '#/screens/Search/modules/ExploreTrendingVideos' 51 - import {useSuggestedUsers} from '#/screens/Search/util/useSuggestedUsers' 52 54 import {atoms as a, native, platform, useTheme} from '#/alf' 53 55 import {Admonition} from '#/components/Admonition' 54 56 import {Button} from '#/components/Button' ··· 242 244 isLoading: suggestedUsersIsLoading, 243 245 error: suggestedUsersError, 244 246 isRefetching: suggestedUsersIsRefetching, 245 - } = useSuggestedUsers({ 247 + } = useGetSuggestedUsersForExploreQuery({ 246 248 category: selectedInterest || (useFullExperience ? null : interests[0]), 247 - search: !useFullExperience, 248 249 }) 249 250 /* End special language handling */ 250 251 ··· 316 317 queryKey: createSuggestedStarterPacksQueryKey(), 317 318 }), 318 319 qc.resetQueries({ 319 - queryKey: [getSuggestedUsersQueryKeyRoot], 320 + queryKey: [getSuggestedUsersForExploreQueryKeyRoot], 320 321 }), 321 322 qc.resetQueries({ 322 323 queryKey: [useActorSearchQueryKeyRoot],
-59
src/screens/Search/util/useSuggestedUsers.ts
··· 1 - import {useMemo} from 'react' 2 - 3 - import {useInterestsDisplayNames} from '#/lib/interests' 4 - import {useActorSearch} from '#/state/queries/actor-search' 5 - import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery' 6 - 7 - /** 8 - * Conditional hook, used in case a user is a non-english speaker, in which 9 - * case we fall back to searching for users instead of our more curated set. 10 - */ 11 - export function useSuggestedUsers({ 12 - category = null, 13 - search = false, 14 - }: { 15 - category?: string | null 16 - /** 17 - * If true, we'll search for users using the translated value of `category`, 18 - * based on the user's app language setting 19 - */ 20 - search?: boolean 21 - }) { 22 - const interestsDisplayNames = useInterestsDisplayNames() 23 - const curated = useGetSuggestedUsersQuery({ 24 - enabled: !search, 25 - category, 26 - }) 27 - const searched = useActorSearch({ 28 - enabled: !!search, 29 - // use user's app language translation for this value 30 - query: category ? interestsDisplayNames[category] : '', 31 - limit: 10, 32 - }) 33 - 34 - return useMemo(() => { 35 - if (search) { 36 - return { 37 - // we're not paginating right now 38 - data: searched?.data 39 - ? { 40 - actors: searched.data.pages.flatMap(p => p.actors) ?? [], 41 - recId: undefined, 42 - } 43 - : undefined, 44 - isLoading: searched.isLoading, 45 - error: searched.error, 46 - isRefetching: searched.isRefetching, 47 - refetch: searched.refetch, 48 - } 49 - } else { 50 - return { 51 - data: curated.data, 52 - isLoading: curated.isLoading, 53 - error: curated.error, 54 - isRefetching: curated.isRefetching, 55 - refetch: curated.refetch, 56 - } 57 - } 58 - }, [curated, searched, search]) 59 - }
+12 -2
src/screens/Settings/InterestsSettings.tsx
··· 19 19 } from '#/state/queries/preferences' 20 20 import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types' 21 21 import {createGetSuggestedFeedsQueryKey} from '#/state/queries/trending/useGetSuggestedFeedsQuery' 22 - import {createGetSuggestedUsersQueryKey} from '#/state/queries/trending/useGetSuggestedUsersQuery' 22 + import {createGetSuggestedUsersForDiscoverQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForDiscoverQuery' 23 + import {createGetSuggestedUsersForExploreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' 24 + import {createGetSuggestedUsersForSeeMoreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' 23 25 import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery' 24 26 import {useAgent} from '#/state/session' 25 27 import {atoms as a, useGutters, useTheme} from '#/alf' ··· 120 122 await Promise.all([ 121 123 qc.resetQueries({queryKey: createSuggestedStarterPacksQueryKey()}), 122 124 qc.resetQueries({queryKey: createGetSuggestedFeedsQueryKey()}), 123 - qc.resetQueries({queryKey: createGetSuggestedUsersQueryKey({})}), 125 + qc.resetQueries({ 126 + queryKey: createGetSuggestedUsersForDiscoverQueryKey({}), 127 + }), 128 + qc.resetQueries({ 129 + queryKey: createGetSuggestedUsersForExploreQueryKey({}), 130 + }), 131 + qc.resetQueries({ 132 + queryKey: createGetSuggestedUsersForSeeMoreQueryKey({}), 133 + }), 124 134 ]) 125 135 126 136 Toast.show(
+6 -2
src/state/cache/profile-shadow.ts
··· 26 26 import {findAllProfilesInQueryData as findAllProfilesInProfileFollowsQueryData} from '#/state/queries/profile-follows' 27 27 import {findAllProfilesInQueryData as findAllProfilesInSuggestedFollowsQueryData} from '#/state/queries/suggested-follows' 28 28 import {findAllProfilesInQueryData as findAllProfilesInSuggestedOnboardingUsersQueryData} from '#/state/queries/trending/useGetSuggestedOnboardingUsersQuery' 29 - import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersQueryData} from '#/state/queries/trending/useGetSuggestedUsersQuery' 29 + import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForDiscoverQueryData} from '#/state/queries/trending/useGetSuggestedUsersForDiscoverQuery' 30 + import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForExploreQueryData} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' 31 + import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForSeeMoreQueryData} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' 30 32 import {findAllProfilesInQueryData as findAllProfilesInPostThreadV2QueryData} from '#/state/queries/usePostThread/queryCache' 31 33 import type * as bsky from '#/types/bsky' 32 34 import {castAsShadow, type Shadow} from './types' ··· 249 251 yield* findAllProfilesInProfileFollowersQueryData(queryClient, did) 250 252 yield* findAllProfilesInProfileFollowsQueryData(queryClient, did) 251 253 yield* findAllProfilesInSuggestedOnboardingUsersQueryData(queryClient, did) 252 - yield* findAllProfilesInSuggestedUsersQueryData(queryClient, did) 254 + yield* findAllProfilesInSuggestedUsersForDiscoverQueryData(queryClient, did) 255 + yield* findAllProfilesInSuggestedUsersForExploreQueryData(queryClient, did) 256 + yield* findAllProfilesInSuggestedUsersForSeeMoreQueryData(queryClient, did) 253 257 yield* findAllProfilesInSuggestedFollowsQueryData(queryClient, did) 254 258 yield* findAllProfilesInActorSearchQueryData(queryClient, did) 255 259 yield* findAllProfilesInListConvosQueryData(queryClient, did)
+1 -21
src/state/queries/trending/useGetSuggestedOnboardingUsersQuery.ts
··· 5 5 import {type QueryClient, useQuery} from '@tanstack/react-query' 6 6 7 7 import {createBskyTopicsHeader} from '#/lib/api/feed/utils' 8 - import {logger} from '#/logger' 9 8 import {getContentLanguages} from '#/state/preferences/languages' 10 9 import {STALE} from '#/state/queries' 11 10 import {usePreferencesQuery} from '#/state/queries/preferences' ··· 54 53 }, 55 54 }, 56 55 ) 57 - // FALLBACK: if no results for 'all', try again with no interests specified 58 - if (!props.category && data.actors.length === 0) { 59 - logger.error( 60 - `Did not get any suggested onboarding users, falling back - interests: ${overrideInterests}`, 61 - ) 62 - const {data: fallbackData} = 63 - await agent.app.bsky.unspecced.getSuggestedOnboardingUsers( 64 - { 65 - category: props.category ?? undefined, 66 - limit: props.limit || 10, 67 - }, 68 - { 69 - headers: { 70 - 'Accept-Language': contentLangs, 71 - }, 72 - }, 73 - ) 74 - return fallbackData 75 - } 76 56 77 - return data 57 + return {...data, recId: data.recIdStr} 78 58 }, 79 59 }) 80 60 }
+75
src/state/queries/trending/useGetSuggestedUsersForDiscoverQuery.ts
··· 1 + import { 2 + type AppBskyActorDefs, 3 + type AppBskyUnspeccedGetSuggestedUsersForDiscover, 4 + } from '@atproto/api' 5 + import {type QueryClient, useQuery} from '@tanstack/react-query' 6 + 7 + import { 8 + aggregateUserInterests, 9 + createBskyTopicsHeader, 10 + } from '#/lib/api/feed/utils' 11 + import {getContentLanguages} from '#/state/preferences/languages' 12 + import {STALE} from '#/state/queries' 13 + import {usePreferencesQuery} from '#/state/queries/preferences' 14 + import {useAgent} from '#/state/session' 15 + 16 + export type QueryProps = { 17 + limit?: number 18 + } 19 + 20 + export const getSuggestedUsersForDiscoverQueryKeyRoot = 21 + 'unspecced-suggested-users-for-explore' 22 + export const createGetSuggestedUsersForDiscoverQueryKey = ( 23 + props: QueryProps, 24 + ) => [getSuggestedUsersForDiscoverQueryKeyRoot, props.limit] 25 + 26 + export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) { 27 + const agent = useAgent() 28 + const {data: preferences} = usePreferencesQuery() 29 + 30 + return useQuery({ 31 + staleTime: STALE.MINUTES.THREE, 32 + queryKey: createGetSuggestedUsersForDiscoverQueryKey(props), 33 + queryFn: async () => { 34 + const contentLangs = getContentLanguages().join(',') 35 + const userInterests = aggregateUserInterests(preferences) 36 + 37 + const {data} = 38 + await agent.app.bsky.unspecced.getSuggestedUsersForDiscover( 39 + { 40 + limit: props.limit || 10, 41 + }, 42 + { 43 + headers: { 44 + ...createBskyTopicsHeader(userInterests), 45 + 'Accept-Language': contentLangs, 46 + }, 47 + }, 48 + ) 49 + return {...data, recId: data.recIdStr} 50 + }, 51 + }) 52 + } 53 + 54 + export function* findAllProfilesInQueryData( 55 + queryClient: QueryClient, 56 + did: string, 57 + ): Generator<AppBskyActorDefs.ProfileView, void> { 58 + const responses = 59 + queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsersForDiscover.OutputSchema>( 60 + { 61 + queryKey: [getSuggestedUsersForDiscoverQueryKeyRoot], 62 + }, 63 + ) 64 + for (const [_key, response] of responses) { 65 + if (!response) { 66 + continue 67 + } 68 + 69 + for (const actor of response.actors) { 70 + if (actor.did === did) { 71 + yield actor 72 + } 73 + } 74 + } 75 + }
+77
src/state/queries/trending/useGetSuggestedUsersForExploreQuery.ts
··· 1 + import { 2 + type AppBskyActorDefs, 3 + type AppBskyUnspeccedGetSuggestedUsersForExplore, 4 + } from '@atproto/api' 5 + import {type QueryClient, useQuery} from '@tanstack/react-query' 6 + 7 + import { 8 + aggregateUserInterests, 9 + createBskyTopicsHeader, 10 + } from '#/lib/api/feed/utils' 11 + import {getContentLanguages} from '#/state/preferences/languages' 12 + import {STALE} from '#/state/queries' 13 + import {usePreferencesQuery} from '#/state/queries/preferences' 14 + import {useAgent} from '#/state/session' 15 + 16 + export type QueryProps = { 17 + category?: string | null 18 + limit?: number 19 + } 20 + 21 + export const getSuggestedUsersForExploreQueryKeyRoot = 22 + 'unspecced-suggested-users-for-explore' 23 + export const createGetSuggestedUsersForExploreQueryKey = ( 24 + props: QueryProps, 25 + ) => [getSuggestedUsersForExploreQueryKeyRoot, props.category, props.limit] 26 + 27 + export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) { 28 + const agent = useAgent() 29 + const {data: preferences} = usePreferencesQuery() 30 + 31 + return useQuery({ 32 + staleTime: STALE.MINUTES.THREE, 33 + queryKey: createGetSuggestedUsersForExploreQueryKey(props), 34 + queryFn: async () => { 35 + const contentLangs = getContentLanguages().join(',') 36 + const userInterests = aggregateUserInterests(preferences) 37 + 38 + const {data} = await agent.app.bsky.unspecced.getSuggestedUsersForExplore( 39 + { 40 + category: props.category ?? undefined, 41 + limit: props.limit || 10, 42 + }, 43 + { 44 + headers: { 45 + ...createBskyTopicsHeader(userInterests), 46 + 'Accept-Language': contentLangs, 47 + }, 48 + }, 49 + ) 50 + 51 + return {...data, recId: data.recIdStr} 52 + }, 53 + }) 54 + } 55 + 56 + export function* findAllProfilesInQueryData( 57 + queryClient: QueryClient, 58 + did: string, 59 + ): Generator<AppBskyActorDefs.ProfileView, void> { 60 + const responses = 61 + queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsersForExplore.OutputSchema>( 62 + { 63 + queryKey: [getSuggestedUsersForExploreQueryKeyRoot], 64 + }, 65 + ) 66 + for (const [_key, response] of responses) { 67 + if (!response) { 68 + continue 69 + } 70 + 71 + for (const actor of response.actors) { 72 + if (actor.did === did) { 73 + yield actor 74 + } 75 + } 76 + } 77 + }
+77
src/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery.ts
··· 1 + import { 2 + type AppBskyActorDefs, 3 + type AppBskyUnspeccedGetSuggestedUsersForSeeMore, 4 + } from '@atproto/api' 5 + import {type QueryClient, useQuery} from '@tanstack/react-query' 6 + 7 + import { 8 + aggregateUserInterests, 9 + createBskyTopicsHeader, 10 + } from '#/lib/api/feed/utils' 11 + import {getContentLanguages} from '#/state/preferences/languages' 12 + import {STALE} from '#/state/queries' 13 + import {usePreferencesQuery} from '#/state/queries/preferences' 14 + import {useAgent} from '#/state/session' 15 + 16 + export type QueryProps = { 17 + category?: string | null 18 + limit?: number 19 + } 20 + 21 + export const getSuggestedUsersForSeeMoreQueryKeyRoot = 22 + 'unspecced-suggested-users-for-explore' 23 + export const createGetSuggestedUsersForSeeMoreQueryKey = ( 24 + props: QueryProps, 25 + ) => [getSuggestedUsersForSeeMoreQueryKeyRoot, props.category, props.limit] 26 + 27 + export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) { 28 + const agent = useAgent() 29 + const {data: preferences} = usePreferencesQuery() 30 + 31 + return useQuery({ 32 + staleTime: STALE.MINUTES.THREE, 33 + queryKey: createGetSuggestedUsersForSeeMoreQueryKey(props), 34 + queryFn: async () => { 35 + const contentLangs = getContentLanguages().join(',') 36 + const userInterests = aggregateUserInterests(preferences) 37 + 38 + const {data} = await agent.app.bsky.unspecced.getSuggestedUsersForSeeMore( 39 + { 40 + category: props.category ?? undefined, 41 + limit: props.limit || 50, 42 + }, 43 + { 44 + headers: { 45 + ...createBskyTopicsHeader(userInterests), 46 + 'Accept-Language': contentLangs, 47 + }, 48 + }, 49 + ) 50 + 51 + return {...data, recId: data.recIdStr} 52 + }, 53 + }) 54 + } 55 + 56 + export function* findAllProfilesInQueryData( 57 + queryClient: QueryClient, 58 + did: string, 59 + ): Generator<AppBskyActorDefs.ProfileView, void> { 60 + const responses = 61 + queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsersForSeeMore.OutputSchema>( 62 + { 63 + queryKey: [getSuggestedUsersForSeeMoreQueryKeyRoot], 64 + }, 65 + ) 66 + for (const [_key, response] of responses) { 67 + if (!response) { 68 + continue 69 + } 70 + 71 + for (const actor of response.actors) { 72 + if (actor.did === did) { 73 + yield actor 74 + } 75 + } 76 + } 77 + }
-98
src/state/queries/trending/useGetSuggestedUsersQuery.ts
··· 1 - import { 2 - type AppBskyActorDefs, 3 - type AppBskyUnspeccedGetSuggestedUsers, 4 - } from '@atproto/api' 5 - import {type QueryClient, useQuery} from '@tanstack/react-query' 6 - 7 - import { 8 - aggregateUserInterests, 9 - createBskyTopicsHeader, 10 - } from '#/lib/api/feed/utils' 11 - import {logger} from '#/logger' 12 - import {getContentLanguages} from '#/state/preferences/languages' 13 - import {STALE} from '#/state/queries' 14 - import {usePreferencesQuery} from '#/state/queries/preferences' 15 - import {useAgent} from '#/state/session' 16 - 17 - export type QueryProps = { 18 - category?: string | null 19 - limit?: number 20 - enabled?: boolean 21 - } 22 - 23 - export const getSuggestedUsersQueryKeyRoot = 'unspecced-suggested-users' 24 - export const createGetSuggestedUsersQueryKey = (props: QueryProps) => [ 25 - getSuggestedUsersQueryKeyRoot, 26 - props.category, 27 - props.limit, 28 - ] 29 - 30 - export function useGetSuggestedUsersQuery(props: QueryProps) { 31 - const agent = useAgent() 32 - const {data: preferences} = usePreferencesQuery() 33 - 34 - return useQuery({ 35 - enabled: !!preferences && props.enabled !== false, 36 - staleTime: STALE.MINUTES.THREE, 37 - queryKey: createGetSuggestedUsersQueryKey(props), 38 - queryFn: async () => { 39 - const contentLangs = getContentLanguages().join(',') 40 - const userInterests = aggregateUserInterests(preferences) 41 - 42 - const {data} = await agent.app.bsky.unspecced.getSuggestedUsers( 43 - { 44 - category: props.category ?? undefined, 45 - limit: props.limit || 10, 46 - }, 47 - { 48 - headers: { 49 - ...createBskyTopicsHeader(userInterests), 50 - 'Accept-Language': contentLangs, 51 - }, 52 - }, 53 - ) 54 - // FALLBACK: if no results for 'all', try again with no interests specified 55 - if (!props.category && data.actors.length === 0) { 56 - logger.error( 57 - `Did not get any suggested users, falling back - interests: ${userInterests}`, 58 - ) 59 - const {data: fallbackData} = 60 - await agent.app.bsky.unspecced.getSuggestedUsers( 61 - { 62 - category: props.category ?? undefined, 63 - limit: props.limit || 10, 64 - }, 65 - { 66 - headers: { 67 - 'Accept-Language': contentLangs, 68 - }, 69 - }, 70 - ) 71 - return fallbackData 72 - } 73 - 74 - return data 75 - }, 76 - }) 77 - } 78 - 79 - export function* findAllProfilesInQueryData( 80 - queryClient: QueryClient, 81 - did: string, 82 - ): Generator<AppBskyActorDefs.ProfileView, void> { 83 - const responses = 84 - queryClient.getQueriesData<AppBskyUnspeccedGetSuggestedUsers.OutputSchema>({ 85 - queryKey: [getSuggestedUsersQueryKeyRoot], 86 - }) 87 - for (const [_key, response] of responses) { 88 - if (!response) { 89 - continue 90 - } 91 - 92 - for (const actor of response.actors) { 93 - if (actor.did === did) { 94 - yield actor 95 - } 96 - } 97 - } 98 - }
+13 -6
yarn.lock
··· 20 20 "@jridgewell/gen-mapping" "^0.3.0" 21 21 "@jridgewell/trace-mapping" "^0.3.9" 22 22 23 - "@atproto/api@^0.19.5": 24 - version "0.19.5" 25 - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.5.tgz#6388e5d6d3a1693fe04b5f37c705682bac8601d3" 26 - integrity sha512-u6R5TecYJDO8l8QFN09AMuJASYnUkJ4HhYE5hg4/dha/z14a+OAil2/dli/208uM5AHPFLtlnB8kIK9XU5GgQQ== 23 + "@atproto/api@^0.19.6": 24 + version "0.19.6" 25 + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.6.tgz#c8fae3d792fe429c900ac0ba2609d60b9a89e28b" 26 + integrity sha512-8L5dZvGaclB52b8msjtgDNx3uLWUY4PELA7KbFyAWBFVasCceE1txdrscCqDCLN+Fff9+Sm07OIjnjHYJXdETA== 27 27 dependencies: 28 28 "@atproto/common-web" "^0.4.19" 29 29 "@atproto/lexicon" "^0.6.2" 30 - "@atproto/syntax" "^0.5.2" 30 + "@atproto/syntax" "^0.5.3" 31 31 "@atproto/xrpc" "^0.7.7" 32 32 await-lock "^2.2.2" 33 33 multiformats "^9.9.0" ··· 73 73 multiformats "^9.9.0" 74 74 zod "^3.23.8" 75 75 76 - "@atproto/syntax@^0.5.0", "@atproto/syntax@^0.5.1", "@atproto/syntax@^0.5.2": 76 + "@atproto/syntax@^0.5.0", "@atproto/syntax@^0.5.1": 77 77 version "0.5.2" 78 78 resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.5.2.tgz#d4b32c9feb421ceeb5ade1fa80bc42764d51e52e" 79 79 integrity sha512-W41szOnkppoHr0iCUrzL8gy3OD6qmDyp1UvUgmTx2oFQfgbudpz51T/gznesiCcqiUT5obfHdx4PJ+WdlEOE7Q== 80 + dependencies: 81 + tslib "^2.8.1" 82 + 83 + "@atproto/syntax@^0.5.3": 84 + version "0.5.3" 85 + resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.5.3.tgz#4331d01f63fe56c374dcf95d4432a22b62271a17" 86 + integrity sha512-gzhlHOJHm5KXdCc17fXi1fXM81ccs5jJfNgCui84ay9JGvczxegpYHNqdMlv+iBuhtBzFIjgx6ChjRxN/kO8kQ== 80 87 dependencies: 81 88 tslib "^2.8.1" 82 89