Bluesky app fork with some witchin' additions 馃挮
at linkat-integration 144 lines 4.3 kB view raw
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}