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