forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {type AppBskyLabelerDefs} from '@atproto/api'
2import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
3import {z} from 'zod'
4
5import {MAX_LABELERS} from '#/lib/constants'
6import {
7 PERSISTED_QUERY_GCTIME,
8 PERSISTED_QUERY_ROOT,
9 STALE,
10} from '#/state/queries'
11import {
12 preferencesQueryKey,
13 usePreferencesQuery,
14} from '#/state/queries/preferences'
15import {useAgent} from '#/state/session'
16
17const labelerInfoQueryKeyRoot = 'labeler-info'
18export const labelerInfoQueryKey = (did: string) => [
19 labelerInfoQueryKeyRoot,
20 did,
21]
22
23const labelersInfoQueryKeyRoot = 'labelers-info'
24export const labelersInfoQueryKey = (dids: string[]) => [
25 labelersInfoQueryKeyRoot,
26 dids.slice().sort(),
27]
28
29const persistedLabelersDetailedInfoQueryKey = (dids: string[]) => [
30 PERSISTED_QUERY_ROOT,
31 'labelers-detailed-info',
32 dids,
33]
34
35export function useLabelerInfoQuery({
36 did,
37 enabled,
38}: {
39 did?: string
40 enabled?: boolean
41}) {
42 const agent = useAgent()
43 return useQuery({
44 enabled: !!did && enabled !== false,
45 queryKey: labelerInfoQueryKey(did as string),
46 queryFn: async () => {
47 const res = await agent.app.bsky.labeler.getServices({
48 dids: [did!],
49 detailed: true,
50 })
51 return res.data.views[0] as AppBskyLabelerDefs.LabelerViewDetailed
52 },
53 })
54}
55
56export function useLabelersInfoQuery({dids}: {dids: string[]}) {
57 const agent = useAgent()
58 return useQuery({
59 enabled: !!dids.length,
60 queryKey: labelersInfoQueryKey(dids),
61 queryFn: async () => {
62 const res = await agent.app.bsky.labeler.getServices({dids})
63 return res.data.views as AppBskyLabelerDefs.LabelerView[]
64 },
65 })
66}
67
68export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) {
69 const agent = useAgent()
70 return useQuery({
71 enabled: !!dids.length,
72 queryKey: persistedLabelersDetailedInfoQueryKey(dids),
73 gcTime: PERSISTED_QUERY_GCTIME,
74 staleTime: STALE.MINUTES.ONE,
75 queryFn: async () => {
76 const res = await agent.app.bsky.labeler.getServices({
77 dids,
78 detailed: true,
79 })
80 return res.data.views as AppBskyLabelerDefs.LabelerViewDetailed[]
81 },
82 })
83}
84
85export function useRemoveLabelersMutation() {
86 const queryClient = useQueryClient()
87 const agent = useAgent()
88
89 return useMutation({
90 async mutationFn({dids}: {dids: string[]}) {
91 await Promise.all(dids.map(did => agent.removeLabeler(did)))
92 },
93 async onSuccess() {
94 await queryClient.invalidateQueries({
95 queryKey: preferencesQueryKey,
96 })
97 },
98 })
99}
100
101export function useLabelerSubscriptionMutation() {
102 const queryClient = useQueryClient()
103 const agent = useAgent()
104 const preferences = usePreferencesQuery()
105
106 return useMutation({
107 async mutationFn({did, subscribe}: {did: string; subscribe: boolean}) {
108 // TODO
109 z.object({
110 did: z.string(),
111 subscribe: z.boolean(),
112 }).parse({did, subscribe})
113
114 /**
115 * If a user has invalid/takendown/deactivated labelers, we need to
116 * remove them. We don't have a great way to do this atm on the server,
117 * so we do it here.
118 *
119 * We also need to push validation into this method, since we need to
120 * check {@link MAX_LABELERS} _after_ we've removed invalid or takendown
121 * labelers.
122 */
123 const labelerDids = (
124 preferences.data?.moderationPrefs?.labelers ?? []
125 ).map(l => l.did)
126 const invalidLabelers: string[] = []
127 if (labelerDids.length) {
128 const profiles = await agent.getProfiles({actors: labelerDids})
129 if (profiles.data) {
130 for (const did of labelerDids) {
131 const exists = profiles.data.profiles.find(p => p.did === did)
132 if (exists) {
133 // profile came back but it's not a valid labeler
134 if (exists.associated && !exists.associated.labeler) {
135 invalidLabelers.push(did)
136 }
137 } else {
138 // no response came back, might be deactivated or takendown
139 invalidLabelers.push(did)
140 }
141 }
142 }
143 }
144 if (invalidLabelers.length) {
145 await Promise.all(invalidLabelers.map(did => agent.removeLabeler(did)))
146 }
147
148 if (subscribe) {
149 const labelerCount = labelerDids.length - invalidLabelers.length
150 if (labelerCount >= MAX_LABELERS) {
151 throw new Error('MAX_LABELERS')
152 }
153 await agent.addLabeler(did)
154 } else {
155 await agent.removeLabeler(did)
156 }
157 },
158 async onSuccess() {
159 await queryClient.invalidateQueries({
160 queryKey: preferencesQueryKey,
161 })
162 },
163 })
164}