Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 164 lines 4.7 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 { 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}