forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {
2 type AppBskyActorDefs,
3 type AppBskyActorGetSuggestions,
4 type AppBskyGraphGetSuggestedFollowsByActor,
5 moderateProfile,
6} from '@atproto/api'
7import {
8 type InfiniteData,
9 type QueryClient,
10 type QueryKey,
11 useInfiniteQuery,
12 useQuery,
13} from '@tanstack/react-query'
14
15import {
16 aggregateUserInterests,
17 createBskyTopicsHeader,
18} from '#/lib/api/feed/utils'
19import {getContentLanguages} from '#/state/preferences/languages'
20import {STALE} from '#/state/queries'
21import {usePreferencesQuery} from '#/state/queries/preferences'
22import {useAgent, useSession} from '#/state/session'
23import {useModerationOpts} from '../preferences/moderation-opts'
24
25const suggestedFollowsQueryKeyRoot = 'suggested-follows'
26const suggestedFollowsQueryKey = (options?: SuggestedFollowsOptions) => [
27 suggestedFollowsQueryKeyRoot,
28 options,
29]
30
31const suggestedFollowsByActorQueryKeyRoot = 'suggested-follows-by-actor'
32const suggestedFollowsByActorQueryKey = (did: string) => [
33 suggestedFollowsByActorQueryKeyRoot,
34 did,
35]
36
37type SuggestedFollowsOptions = {limit?: number; subsequentPageLimit?: number}
38
39export function useSuggestedFollowsQuery(options?: SuggestedFollowsOptions) {
40 const {currentAccount} = useSession()
41 const agent = useAgent()
42 const moderationOpts = useModerationOpts()
43 const {data: preferences} = usePreferencesQuery()
44 const limit = options?.limit || 25
45
46 return useInfiniteQuery<
47 AppBskyActorGetSuggestions.OutputSchema,
48 Error,
49 InfiniteData<AppBskyActorGetSuggestions.OutputSchema>,
50 QueryKey,
51 string | undefined
52 >({
53 enabled: !!moderationOpts && !!preferences,
54 staleTime: STALE.HOURS.ONE,
55 queryKey: suggestedFollowsQueryKey(options),
56 queryFn: async ({pageParam}) => {
57 const contentLangs = getContentLanguages().join(',')
58 const maybeDifferentLimit =
59 options?.subsequentPageLimit && pageParam
60 ? options.subsequentPageLimit
61 : limit
62 const res = await agent.app.bsky.actor.getSuggestions(
63 {
64 limit: maybeDifferentLimit,
65 cursor: pageParam,
66 },
67 {
68 headers: {
69 ...createBskyTopicsHeader(aggregateUserInterests(preferences)),
70 'Accept-Language': contentLangs,
71 },
72 },
73 )
74
75 res.data.actors = res.data.actors
76 .filter(
77 actor =>
78 !moderateProfile(actor, moderationOpts!).ui('profileList').filter,
79 )
80 .filter(actor => {
81 const viewer = actor.viewer
82 if (viewer) {
83 if (
84 viewer.following ||
85 viewer.muted ||
86 viewer.mutedByList ||
87 viewer.blockedBy ||
88 viewer.blocking
89 ) {
90 return false
91 }
92 }
93 if (actor.did === currentAccount?.did) {
94 return false
95 }
96 return true
97 })
98
99 return res.data
100 },
101 initialPageParam: undefined,
102 getNextPageParam: lastPage => lastPage.cursor,
103 })
104}
105
106export function useSuggestedFollowsByActorQuery({
107 did,
108 enabled,
109 staleTime = STALE.MINUTES.FIVE,
110}: {
111 did: string
112 enabled?: boolean
113 staleTime?: number
114}) {
115 const agent = useAgent()
116 return useQuery({
117 staleTime,
118 queryKey: suggestedFollowsByActorQueryKey(did),
119 queryFn: async () => {
120 const res = await agent.app.bsky.graph.getSuggestedFollowsByActor({
121 actor: did,
122 })
123 const suggestions = res.data.isFallback
124 ? []
125 : res.data.suggestions.filter(profile => !profile.viewer?.following)
126 return {suggestions, recId: res.data.recId}
127 },
128 enabled,
129 })
130}
131
132export function* findAllProfilesInQueryData(
133 queryClient: QueryClient,
134 did: string,
135): Generator<AppBskyActorDefs.ProfileView, void> {
136 yield* findAllProfilesInSuggestedFollowsQueryData(queryClient, did)
137 yield* findAllProfilesInSuggestedFollowsByActorQueryData(queryClient, did)
138}
139
140function* findAllProfilesInSuggestedFollowsQueryData(
141 queryClient: QueryClient,
142 did: string,
143) {
144 const queryDatas = queryClient.getQueriesData<
145 InfiniteData<AppBskyActorGetSuggestions.OutputSchema>
146 >({
147 queryKey: [suggestedFollowsQueryKeyRoot],
148 })
149 for (const [_queryKey, queryData] of queryDatas) {
150 if (!queryData?.pages) {
151 continue
152 }
153 for (const page of queryData?.pages) {
154 for (const actor of page.actors) {
155 if (actor.did === did) {
156 yield actor
157 }
158 }
159 }
160 }
161}
162
163function* findAllProfilesInSuggestedFollowsByActorQueryData(
164 queryClient: QueryClient,
165 did: string,
166) {
167 const queryDatas =
168 queryClient.getQueriesData<AppBskyGraphGetSuggestedFollowsByActor.OutputSchema>(
169 {
170 queryKey: [suggestedFollowsByActorQueryKeyRoot],
171 },
172 )
173 for (const [_queryKey, queryData] of queryDatas) {
174 if (!queryData) {
175 continue
176 }
177 for (const suggestion of queryData.suggestions) {
178 if (suggestion.did === did) {
179 yield suggestion
180 }
181 }
182 }
183}