forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {type AppBskyContactGetMatches} from '@atproto/api'
2import {
3 type InfiniteData,
4 type QueryClient,
5 useInfiniteQuery,
6 useQuery,
7} from '@tanstack/react-query'
8
9import {useAgent} from '#/state/session'
10import {type Match} from '#/components/contacts/state'
11import type * as bsky from '#/types/bsky'
12import {STALE} from '.'
13
14const RQ_KEY_ROOT = 'find-contacts'
15export const findContactsStatusQueryKey = [RQ_KEY_ROOT, 'sync-status']
16
17export function useContactsSyncStatusQuery() {
18 const agent = useAgent()
19
20 return useQuery({
21 queryKey: findContactsStatusQueryKey,
22 queryFn: async () => {
23 const status = await agent.app.bsky.contact.getSyncStatus()
24 return status.data
25 },
26 staleTime: STALE.SECONDS.THIRTY,
27 })
28}
29
30export const findContactsGetMatchesQueryKey = [RQ_KEY_ROOT, 'matches']
31
32export function useContactsMatchesQuery() {
33 const agent = useAgent()
34
35 return useInfiniteQuery({
36 queryKey: findContactsGetMatchesQueryKey,
37 queryFn: async ({pageParam}) => {
38 const matches = await agent.app.bsky.contact.getMatches({
39 cursor: pageParam,
40 })
41 return matches.data
42 },
43 initialPageParam: undefined as string | undefined,
44 getNextPageParam: lastPage => lastPage.cursor,
45 staleTime: STALE.MINUTES.ONE,
46 })
47}
48
49export function optimisticRemoveMatch(queryClient: QueryClient, did: string) {
50 queryClient.setQueryData<InfiniteData<AppBskyContactGetMatches.OutputSchema>>(
51 findContactsGetMatchesQueryKey,
52 old => {
53 if (!old) return old
54
55 return {
56 ...old,
57 pages: old.pages.map(page => ({
58 ...page,
59 matches: page.matches.filter(match => match.did !== did),
60 })),
61 }
62 },
63 )
64}
65
66export const findContactsMatchesPassthroughQueryKey = (dids: string[]) => [
67 RQ_KEY_ROOT,
68 'passthrough',
69 dids,
70]
71
72/**
73 * DIRTY HACK WARNING!
74 *
75 * The only way to get shadow state to work is to put it into React Query.
76 * However, when we get the matches it's via a POST, not a GET, so we use a mutation,
77 * which means we can't use shadowing!
78 *
79 * In lieu of any better ideas, I'm just going to take the contacts we have and
80 * "launder" them through a dummy query. This will then return "shadow-able" profiles.
81 */
82export function useMatchesPassthroughQuery(matches: Match[]) {
83 const dids = matches.map(match => match.profile.did)
84 const {data} = useQuery({
85 queryKey: findContactsMatchesPassthroughQueryKey(dids),
86 queryFn: () => {
87 return matches
88 },
89 })
90 return data ?? matches
91}
92
93export function* findAllProfilesInQueryData(
94 queryClient: QueryClient,
95 did: string,
96): Generator<bsky.profile.AnyProfileView, void> {
97 const queryDatas = queryClient.getQueriesData<
98 InfiniteData<AppBskyContactGetMatches.OutputSchema>
99 >({
100 queryKey: findContactsGetMatchesQueryKey,
101 })
102 for (const [_queryKey, queryData] of queryDatas) {
103 if (!queryData?.pages) {
104 continue
105 }
106 for (const page of queryData?.pages) {
107 for (const match of page.matches) {
108 if (match.did === did) {
109 yield match
110 }
111 }
112 }
113 }
114
115 const passthroughQueryDatas = queryClient.getQueriesData<Match[]>({
116 queryKey: [RQ_KEY_ROOT, 'passthrough'],
117 })
118 for (const [_queryKey, queryData] of passthroughQueryDatas) {
119 if (!queryData) {
120 continue
121 }
122 for (const match of queryData) {
123 if (match.profile.did === did) {
124 yield match.profile
125 }
126 }
127 }
128}