diff --git a/package.json b/package.json index 7bf2548757..b49932895c 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "icons:optimize": "svgo -f ./assets/icons" }, "dependencies": { - "@atproto/api": "^0.19.5", + "@atproto/api": "^0.19.6", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", "@bsky.app/alf": "^0.1.7", diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index ff23e3998f..36a3f40a73 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -7,7 +7,7 @@ import Animated, { LayoutAnimationConfig, LinearTransition, } from 'react-native-reanimated' -import {type AppBskyFeedDefs, AtUri} from '@atproto/api' +import {type AppBskyFeedDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -15,11 +15,9 @@ import {type NavigationProp} from '#/lib/routes/types' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useGetPopularFeedsQuery} from '#/state/queries/feed' import {type FeedDescriptor} from '#/state/queries/post-feed' -import {useProfilesQuery} from '#/state/queries/profile' import {useSuggestedFollowsByActorWithDismiss} from '#/state/queries/suggested-follows' +import {useGetSuggestedUsersForDiscoverQuery} from '#/state/queries/trending/useGetSuggestedUsersForDiscoverQuery' import {useSession} from '#/state/session' -import * as userActionHistory from '#/state/userActionHistory' -import {type SeenPost} from '#/state/userActionHistory' import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture' import { atoms as a, @@ -37,12 +35,12 @@ import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Has import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {InlineLinkText} from '#/components/Link' import * as ProfileCard from '#/components/ProfileCard' +import {ProgressGuideList} from '#/components/ProgressGuide/List' import {Text} from '#/components/Typography' import {type Metrics, useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' import type * as bsky from '#/types/bsky' import {FollowDialogWithoutGuide} from './ProgressGuide/FollowDialog' -import {ProgressGuideList} from './ProgressGuide/List' const DISMISS_ANIMATION_DURATION = 200 @@ -109,95 +107,6 @@ export function SuggestedFeedsCardPlaceholder() { ) } -function getRank(seenPost: SeenPost): string { - let tier: string - if (seenPost.feedContext === 'popfriends') { - tier = 'a' - } else if (seenPost.feedContext?.startsWith('cluster')) { - tier = 'b' - } else if (seenPost.feedContext === 'popcluster') { - tier = 'c' - } else if (seenPost.feedContext?.startsWith('ntpc')) { - tier = 'd' - } else if (seenPost.feedContext?.startsWith('t-')) { - tier = 'e' - } else if (seenPost.feedContext === 'nettop') { - tier = 'f' - } else { - tier = 'g' - } - let score = Math.round( - Math.log( - 1 + seenPost.likeCount + seenPost.repostCount + seenPost.replyCount, - ), - ) - if (seenPost.isFollowedBy || Math.random() > 0.9) { - score *= 2 - } - const rank = 100 - score - return `${tier}-${rank}` -} - -function sortSeenPosts(postA: SeenPost, postB: SeenPost): 0 | 1 | -1 { - const rankA = getRank(postA) - const rankB = getRank(postB) - // Yes, we're comparing strings here. - // The "larger" string means a worse rank. - if (rankA > rankB) { - return 1 - } else if (rankA < rankB) { - return -1 - } else { - return 0 - } -} - -function useExperimentalSuggestedUsersQuery() { - const {currentAccount} = useSession() - const userActionSnapshot = userActionHistory.useActionHistorySnapshot() - const dids = useMemo(() => { - const {likes, follows, followSuggestions, seen} = userActionSnapshot - const likeDids = likes - .map(l => new AtUri(l)) - .map(uri => uri.host) - .filter(did => !follows.includes(did)) - let suggestedDids: string[] = [] - if (followSuggestions.length > 0) { - suggestedDids = [ - // It's ok if these will pick the same item (weighed by its frequency) - /* eslint-disable react-hooks/purity */ - followSuggestions[Math.floor(Math.random() * followSuggestions.length)], - followSuggestions[Math.floor(Math.random() * followSuggestions.length)], - followSuggestions[Math.floor(Math.random() * followSuggestions.length)], - followSuggestions[Math.floor(Math.random() * followSuggestions.length)], - /* eslint-enable react-hooks/purity */ - ] - } - const seenDids = seen - .sort(sortSeenPosts) - .map(l => new AtUri(l.uri)) - .map(uri => uri.host) - return [...new Set([...suggestedDids, ...likeDids, ...seenDids])].filter( - did => did !== currentAccount?.did, - ) - }, [userActionSnapshot, currentAccount]) - const {data, isLoading, error} = useProfilesQuery({ - handles: dids.slice(0, 16), - }) - - const profiles = data - ? data.profiles.filter(profile => { - return !profile.viewer?.following - }) - : [] - - return { - isLoading, - error, - profiles: profiles.slice(0, 6), - } -} - export function SuggestedFollows({feed}: {feed: FeedDescriptor}) { const {currentAccount} = useSession() const [feedType, feedUriOrDid] = feed.split('|') @@ -229,11 +138,9 @@ export function SuggestedFollowsProfile({did}: {did: string}) { } export function SuggestedFollowsHome() { - const { - isLoading: isSuggestionsLoading, - profiles: experimentalProfiles, - error: experimentalError, - } = useExperimentalSuggestedUsersQuery() + const {isLoading, data, error} = useGetSuggestedUsersForDiscoverQuery() + + const profiles = data?.actors const [dismissedDids, setDismissedDids] = useState>(new Set()) @@ -247,12 +154,12 @@ export function SuggestedFollowsHome() { recId?: string }> = [] - for (const profile of experimentalProfiles) { - result.push({actor: profile, recId: undefined}) + for (const profile of profiles ?? []) { + result.push({actor: profile, recId: data?.recId}) } return result - }, [experimentalProfiles]) + }, [data?.recId, profiles]) const filteredProfiles = useMemo(() => { return allProfiles.filter(p => !dismissedDids.has(p.actor.did)) @@ -260,10 +167,10 @@ export function SuggestedFollowsHome() { return ( diff --git a/src/components/ProgressGuide/FollowDialog.tsx b/src/components/ProgressGuide/FollowDialog.tsx index 27b759ba7d..775eedd769 100644 --- a/src/components/ProgressGuide/FollowDialog.tsx +++ b/src/components/ProgressGuide/FollowDialog.tsx @@ -8,7 +8,7 @@ import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useActorSearch} from '#/state/queries/actor-search' import {usePreferencesQuery} from '#/state/queries/preferences' -import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery' +import {useGetSuggestedUsersForSeeMoreQuery} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' import {useSession} from '#/state/session' import {type Follow10ProgressGuide} from '#/state/shell/progress-guide' import {type ListMethods} from '#/view/com/util/List' @@ -141,7 +141,7 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) { data: suggestions, isFetching: isFetchingSuggestions, error: suggestionsError, - } = useGetSuggestedUsersQuery({ + } = useGetSuggestedUsersForSeeMoreQuery({ category: selectedInterest, limit: 50, }) diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx index 1129dca35b..e7401cfbf6 100644 --- a/src/screens/Search/Explore.tsx +++ b/src/screens/Search/Explore.tsx @@ -28,7 +28,10 @@ import { createGetSuggestedFeedsQueryKey, useGetSuggestedFeedsQuery, } from '#/state/queries/trending/useGetSuggestedFeedsQuery' -import {getSuggestedUsersQueryKeyRoot} from '#/state/queries/trending/useGetSuggestedUsersQuery' +import { + getSuggestedUsersForExploreQueryKeyRoot, + useGetSuggestedUsersForExploreQuery, +} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' import {createGetTrendsQueryKey} from '#/state/queries/trending/useGetTrendsQuery' import { createSuggestedStarterPacksQueryKey, @@ -48,7 +51,6 @@ import {ExploreInterestsCard} from '#/screens/Search/modules/ExploreInterestsCar import {ExploreRecommendations} from '#/screens/Search/modules/ExploreRecommendations' import {ExploreTrendingTopics} from '#/screens/Search/modules/ExploreTrendingTopics' import {ExploreTrendingVideos} from '#/screens/Search/modules/ExploreTrendingVideos' -import {useSuggestedUsers} from '#/screens/Search/util/useSuggestedUsers' import {atoms as a, native, platform, useTheme} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button} from '#/components/Button' @@ -242,9 +244,8 @@ export function Explore({ isLoading: suggestedUsersIsLoading, error: suggestedUsersError, isRefetching: suggestedUsersIsRefetching, - } = useSuggestedUsers({ + } = useGetSuggestedUsersForExploreQuery({ category: selectedInterest || (useFullExperience ? null : interests[0]), - search: !useFullExperience, }) /* End special language handling */ @@ -316,7 +317,7 @@ export function Explore({ queryKey: createSuggestedStarterPacksQueryKey(), }), qc.resetQueries({ - queryKey: [getSuggestedUsersQueryKeyRoot], + queryKey: [getSuggestedUsersForExploreQueryKeyRoot], }), qc.resetQueries({ queryKey: [useActorSearchQueryKeyRoot], diff --git a/src/screens/Search/util/useSuggestedUsers.ts b/src/screens/Search/util/useSuggestedUsers.ts deleted file mode 100644 index 557baff70b..0000000000 --- a/src/screens/Search/util/useSuggestedUsers.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {useMemo} from 'react' - -import {useInterestsDisplayNames} from '#/lib/interests' -import {useActorSearch} from '#/state/queries/actor-search' -import {useGetSuggestedUsersQuery} from '#/state/queries/trending/useGetSuggestedUsersQuery' - -/** - * Conditional hook, used in case a user is a non-english speaker, in which - * case we fall back to searching for users instead of our more curated set. - */ -export function useSuggestedUsers({ - category = null, - search = false, -}: { - category?: string | null - /** - * If true, we'll search for users using the translated value of `category`, - * based on the user's app language setting - */ - search?: boolean -}) { - const interestsDisplayNames = useInterestsDisplayNames() - const curated = useGetSuggestedUsersQuery({ - enabled: !search, - category, - }) - const searched = useActorSearch({ - enabled: !!search, - // use user's app language translation for this value - query: category ? interestsDisplayNames[category] : '', - limit: 10, - }) - - return useMemo(() => { - if (search) { - return { - // we're not paginating right now - data: searched?.data - ? { - actors: searched.data.pages.flatMap(p => p.actors) ?? [], - recId: undefined, - } - : undefined, - isLoading: searched.isLoading, - error: searched.error, - isRefetching: searched.isRefetching, - refetch: searched.refetch, - } - } else { - return { - data: curated.data, - isLoading: curated.isLoading, - error: curated.error, - isRefetching: curated.isRefetching, - refetch: curated.refetch, - } - } - }, [curated, searched, search]) -} diff --git a/src/screens/Settings/InterestsSettings.tsx b/src/screens/Settings/InterestsSettings.tsx index 8224bb133e..ced7148669 100644 --- a/src/screens/Settings/InterestsSettings.tsx +++ b/src/screens/Settings/InterestsSettings.tsx @@ -19,7 +19,9 @@ import { } from '#/state/queries/preferences' import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types' import {createGetSuggestedFeedsQueryKey} from '#/state/queries/trending/useGetSuggestedFeedsQuery' -import {createGetSuggestedUsersQueryKey} from '#/state/queries/trending/useGetSuggestedUsersQuery' +import {createGetSuggestedUsersForDiscoverQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForDiscoverQuery' +import {createGetSuggestedUsersForExploreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' +import {createGetSuggestedUsersForSeeMoreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery' import {useAgent} from '#/state/session' import {atoms as a, useGutters, useTheme} from '#/alf' @@ -120,7 +122,15 @@ function Inner({ await Promise.all([ qc.resetQueries({queryKey: createSuggestedStarterPacksQueryKey()}), qc.resetQueries({queryKey: createGetSuggestedFeedsQueryKey()}), - qc.resetQueries({queryKey: createGetSuggestedUsersQueryKey({})}), + qc.resetQueries({ + queryKey: createGetSuggestedUsersForDiscoverQueryKey({}), + }), + qc.resetQueries({ + queryKey: createGetSuggestedUsersForExploreQueryKey({}), + }), + qc.resetQueries({ + queryKey: createGetSuggestedUsersForSeeMoreQueryKey({}), + }), ]) Toast.show( diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts index b8a6c58c35..eae0b8b90c 100644 --- a/src/state/cache/profile-shadow.ts +++ b/src/state/cache/profile-shadow.ts @@ -26,7 +26,9 @@ import {findAllProfilesInQueryData as findAllProfilesInProfileFollowersQueryData import {findAllProfilesInQueryData as findAllProfilesInProfileFollowsQueryData} from '#/state/queries/profile-follows' import {findAllProfilesInQueryData as findAllProfilesInSuggestedFollowsQueryData} from '#/state/queries/suggested-follows' import {findAllProfilesInQueryData as findAllProfilesInSuggestedOnboardingUsersQueryData} from '#/state/queries/trending/useGetSuggestedOnboardingUsersQuery' -import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersQueryData} from '#/state/queries/trending/useGetSuggestedUsersQuery' +import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForDiscoverQueryData} from '#/state/queries/trending/useGetSuggestedUsersForDiscoverQuery' +import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForExploreQueryData} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' +import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForSeeMoreQueryData} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' import {findAllProfilesInQueryData as findAllProfilesInPostThreadV2QueryData} from '#/state/queries/usePostThread/queryCache' import type * as bsky from '#/types/bsky' import {castAsShadow, type Shadow} from './types' @@ -249,7 +251,9 @@ function* findProfilesInCache( yield* findAllProfilesInProfileFollowersQueryData(queryClient, did) yield* findAllProfilesInProfileFollowsQueryData(queryClient, did) yield* findAllProfilesInSuggestedOnboardingUsersQueryData(queryClient, did) - yield* findAllProfilesInSuggestedUsersQueryData(queryClient, did) + yield* findAllProfilesInSuggestedUsersForDiscoverQueryData(queryClient, did) + yield* findAllProfilesInSuggestedUsersForExploreQueryData(queryClient, did) + yield* findAllProfilesInSuggestedUsersForSeeMoreQueryData(queryClient, did) yield* findAllProfilesInSuggestedFollowsQueryData(queryClient, did) yield* findAllProfilesInActorSearchQueryData(queryClient, did) yield* findAllProfilesInListConvosQueryData(queryClient, did) diff --git a/src/state/queries/trending/useGetSuggestedOnboardingUsersQuery.ts b/src/state/queries/trending/useGetSuggestedOnboardingUsersQuery.ts index 0834063371..20ca8736af 100644 --- a/src/state/queries/trending/useGetSuggestedOnboardingUsersQuery.ts +++ b/src/state/queries/trending/useGetSuggestedOnboardingUsersQuery.ts @@ -5,7 +5,6 @@ import { import {type QueryClient, useQuery} from '@tanstack/react-query' import {createBskyTopicsHeader} from '#/lib/api/feed/utils' -import {logger} from '#/logger' import {getContentLanguages} from '#/state/preferences/languages' import {STALE} from '#/state/queries' import {usePreferencesQuery} from '#/state/queries/preferences' @@ -54,27 +53,8 @@ export function useGetSuggestedOnboardingUsersQuery(props: QueryProps) { }, }, ) - // FALLBACK: if no results for 'all', try again with no interests specified - if (!props.category && data.actors.length === 0) { - logger.error( - `Did not get any suggested onboarding users, falling back - interests: ${overrideInterests}`, - ) - const {data: fallbackData} = - await agent.app.bsky.unspecced.getSuggestedOnboardingUsers( - { - category: props.category ?? undefined, - limit: props.limit || 10, - }, - { - headers: { - 'Accept-Language': contentLangs, - }, - }, - ) - return fallbackData - } - return data + return {...data, recId: data.recIdStr} }, }) } diff --git a/src/state/queries/trending/useGetSuggestedUsersForDiscoverQuery.ts b/src/state/queries/trending/useGetSuggestedUsersForDiscoverQuery.ts new file mode 100644 index 0000000000..efd02a1dce --- /dev/null +++ b/src/state/queries/trending/useGetSuggestedUsersForDiscoverQuery.ts @@ -0,0 +1,75 @@ +import { + type AppBskyActorDefs, + type AppBskyUnspeccedGetSuggestedUsersForDiscover, +} from '@atproto/api' +import {type QueryClient, 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 {usePreferencesQuery} from '#/state/queries/preferences' +import {useAgent} from '#/state/session' + +export type QueryProps = { + limit?: number +} + +export const getSuggestedUsersForDiscoverQueryKeyRoot = + 'unspecced-suggested-users-for-explore' +export const createGetSuggestedUsersForDiscoverQueryKey = ( + props: QueryProps, +) => [getSuggestedUsersForDiscoverQueryKeyRoot, props.limit] + +export function useGetSuggestedUsersForDiscoverQuery(props: QueryProps = {}) { + const agent = useAgent() + const {data: preferences} = usePreferencesQuery() + + return useQuery({ + staleTime: STALE.MINUTES.THREE, + queryKey: createGetSuggestedUsersForDiscoverQueryKey(props), + queryFn: async () => { + const contentLangs = getContentLanguages().join(',') + const userInterests = aggregateUserInterests(preferences) + + const {data} = + await agent.app.bsky.unspecced.getSuggestedUsersForDiscover( + { + limit: props.limit || 10, + }, + { + headers: { + ...createBskyTopicsHeader(userInterests), + 'Accept-Language': contentLangs, + }, + }, + ) + return {...data, recId: data.recIdStr} + }, + }) +} + +export function* findAllProfilesInQueryData( + queryClient: QueryClient, + did: string, +): Generator { + const responses = + queryClient.getQueriesData( + { + queryKey: [getSuggestedUsersForDiscoverQueryKeyRoot], + }, + ) + for (const [_key, response] of responses) { + if (!response) { + continue + } + + for (const actor of response.actors) { + if (actor.did === did) { + yield actor + } + } + } +} diff --git a/src/state/queries/trending/useGetSuggestedUsersQuery.ts b/src/state/queries/trending/useGetSuggestedUsersForExploreQuery.ts similarity index 56% rename from src/state/queries/trending/useGetSuggestedUsersQuery.ts rename to src/state/queries/trending/useGetSuggestedUsersForExploreQuery.ts index 30090b9a54..26967fb87c 100644 --- a/src/state/queries/trending/useGetSuggestedUsersQuery.ts +++ b/src/state/queries/trending/useGetSuggestedUsersForExploreQuery.ts @@ -1,6 +1,6 @@ import { type AppBskyActorDefs, - type AppBskyUnspeccedGetSuggestedUsers, + type AppBskyUnspeccedGetSuggestedUsersForExplore, } from '@atproto/api' import {type QueryClient, useQuery} from '@tanstack/react-query' @@ -8,7 +8,6 @@ import { aggregateUserInterests, createBskyTopicsHeader, } from '#/lib/api/feed/utils' -import {logger} from '#/logger' import {getContentLanguages} from '#/state/preferences/languages' import {STALE} from '#/state/queries' import {usePreferencesQuery} from '#/state/queries/preferences' @@ -17,29 +16,26 @@ import {useAgent} from '#/state/session' export type QueryProps = { category?: string | null limit?: number - enabled?: boolean } -export const getSuggestedUsersQueryKeyRoot = 'unspecced-suggested-users' -export const createGetSuggestedUsersQueryKey = (props: QueryProps) => [ - getSuggestedUsersQueryKeyRoot, - props.category, - props.limit, -] +export const getSuggestedUsersForExploreQueryKeyRoot = + 'unspecced-suggested-users-for-explore' +export const createGetSuggestedUsersForExploreQueryKey = ( + props: QueryProps, +) => [getSuggestedUsersForExploreQueryKeyRoot, props.category, props.limit] -export function useGetSuggestedUsersQuery(props: QueryProps) { +export function useGetSuggestedUsersForExploreQuery(props: QueryProps = {}) { const agent = useAgent() const {data: preferences} = usePreferencesQuery() return useQuery({ - enabled: !!preferences && props.enabled !== false, staleTime: STALE.MINUTES.THREE, - queryKey: createGetSuggestedUsersQueryKey(props), + queryKey: createGetSuggestedUsersForExploreQueryKey(props), queryFn: async () => { const contentLangs = getContentLanguages().join(',') const userInterests = aggregateUserInterests(preferences) - const {data} = await agent.app.bsky.unspecced.getSuggestedUsers( + const {data} = await agent.app.bsky.unspecced.getSuggestedUsersForExplore( { category: props.category ?? undefined, limit: props.limit || 10, @@ -51,27 +47,8 @@ export function useGetSuggestedUsersQuery(props: QueryProps) { }, }, ) - // FALLBACK: if no results for 'all', try again with no interests specified - if (!props.category && data.actors.length === 0) { - logger.error( - `Did not get any suggested users, falling back - interests: ${userInterests}`, - ) - const {data: fallbackData} = - await agent.app.bsky.unspecced.getSuggestedUsers( - { - category: props.category ?? undefined, - limit: props.limit || 10, - }, - { - headers: { - 'Accept-Language': contentLangs, - }, - }, - ) - return fallbackData - } - return data + return {...data, recId: data.recIdStr} }, }) } @@ -81,9 +58,11 @@ export function* findAllProfilesInQueryData( did: string, ): Generator { const responses = - queryClient.getQueriesData({ - queryKey: [getSuggestedUsersQueryKeyRoot], - }) + queryClient.getQueriesData( + { + queryKey: [getSuggestedUsersForExploreQueryKeyRoot], + }, + ) for (const [_key, response] of responses) { if (!response) { continue diff --git a/src/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery.ts b/src/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery.ts new file mode 100644 index 0000000000..eec392d4c9 --- /dev/null +++ b/src/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery.ts @@ -0,0 +1,77 @@ +import { + type AppBskyActorDefs, + type AppBskyUnspeccedGetSuggestedUsersForSeeMore, +} from '@atproto/api' +import {type QueryClient, 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 {usePreferencesQuery} from '#/state/queries/preferences' +import {useAgent} from '#/state/session' + +export type QueryProps = { + category?: string | null + limit?: number +} + +export const getSuggestedUsersForSeeMoreQueryKeyRoot = + 'unspecced-suggested-users-for-explore' +export const createGetSuggestedUsersForSeeMoreQueryKey = ( + props: QueryProps, +) => [getSuggestedUsersForSeeMoreQueryKeyRoot, props.category, props.limit] + +export function useGetSuggestedUsersForSeeMoreQuery(props: QueryProps = {}) { + const agent = useAgent() + const {data: preferences} = usePreferencesQuery() + + return useQuery({ + staleTime: STALE.MINUTES.THREE, + queryKey: createGetSuggestedUsersForSeeMoreQueryKey(props), + queryFn: async () => { + const contentLangs = getContentLanguages().join(',') + const userInterests = aggregateUserInterests(preferences) + + const {data} = await agent.app.bsky.unspecced.getSuggestedUsersForSeeMore( + { + category: props.category ?? undefined, + limit: props.limit || 50, + }, + { + headers: { + ...createBskyTopicsHeader(userInterests), + 'Accept-Language': contentLangs, + }, + }, + ) + + return {...data, recId: data.recIdStr} + }, + }) +} + +export function* findAllProfilesInQueryData( + queryClient: QueryClient, + did: string, +): Generator { + const responses = + queryClient.getQueriesData( + { + queryKey: [getSuggestedUsersForSeeMoreQueryKeyRoot], + }, + ) + for (const [_key, response] of responses) { + if (!response) { + continue + } + + for (const actor of response.actors) { + if (actor.did === did) { + yield actor + } + } + } +} diff --git a/yarn.lock b/yarn.lock index 1a9ee22506..5a3e3f641c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20,14 +20,14 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@atproto/api@^0.19.5": - version "0.19.5" - resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.5.tgz#6388e5d6d3a1693fe04b5f37c705682bac8601d3" - integrity sha512-u6R5TecYJDO8l8QFN09AMuJASYnUkJ4HhYE5hg4/dha/z14a+OAil2/dli/208uM5AHPFLtlnB8kIK9XU5GgQQ== +"@atproto/api@^0.19.6": + version "0.19.6" + resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.19.6.tgz#c8fae3d792fe429c900ac0ba2609d60b9a89e28b" + integrity sha512-8L5dZvGaclB52b8msjtgDNx3uLWUY4PELA7KbFyAWBFVasCceE1txdrscCqDCLN+Fff9+Sm07OIjnjHYJXdETA== dependencies: "@atproto/common-web" "^0.4.19" "@atproto/lexicon" "^0.6.2" - "@atproto/syntax" "^0.5.2" + "@atproto/syntax" "^0.5.3" "@atproto/xrpc" "^0.7.7" await-lock "^2.2.2" multiformats "^9.9.0" @@ -73,13 +73,20 @@ multiformats "^9.9.0" zod "^3.23.8" -"@atproto/syntax@^0.5.0", "@atproto/syntax@^0.5.1", "@atproto/syntax@^0.5.2": +"@atproto/syntax@^0.5.0", "@atproto/syntax@^0.5.1": version "0.5.2" resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.5.2.tgz#d4b32c9feb421ceeb5ade1fa80bc42764d51e52e" integrity sha512-W41szOnkppoHr0iCUrzL8gy3OD6qmDyp1UvUgmTx2oFQfgbudpz51T/gznesiCcqiUT5obfHdx4PJ+WdlEOE7Q== dependencies: tslib "^2.8.1" +"@atproto/syntax@^0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.5.3.tgz#4331d01f63fe56c374dcf95d4432a22b62271a17" + integrity sha512-gzhlHOJHm5KXdCc17fXi1fXM81ccs5jJfNgCui84ay9JGvczxegpYHNqdMlv+iBuhtBzFIjgx6ChjRxN/kO8kQ== + dependencies: + tslib "^2.8.1" + "@atproto/xrpc@^0.7.7": version "0.7.7" resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.7.7.tgz#c0e3106c854cb9bc7d3129de2f31b8256eb0ed11"