Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
117
fork

Configure Feed

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

at a876aae44ea07494ebea9727350aa060b81f317b 154 lines 3.7 kB view raw
1import {useMemo} from 'react' 2import { 3 type AppBskyLabelerDefs, 4 BskyAgent, 5 type ComAtprotoLabelDefs, 6 type InterpretedLabelValueDefinition, 7 LABELS, 8 type ModerationCause, 9 type ModerationOpts, 10 type ModerationUI, 11} from '@atproto/api' 12 13import {sanitizeDisplayName} from '#/lib/strings/display-names' 14import {sanitizeHandle} from '#/lib/strings/handles' 15import {type AppModerationCause} from '#/components/Pills' 16 17export const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] as const 18export const OTHER_SELF_LABELS = ['graphic-media'] as const 19export const SELF_LABELS = [ 20 ...ADULT_CONTENT_LABELS, 21 ...OTHER_SELF_LABELS, 22] as const 23 24export type AdultSelfLabel = (typeof ADULT_CONTENT_LABELS)[number] 25export type OtherSelfLabel = (typeof OTHER_SELF_LABELS)[number] 26export type SelfLabel = (typeof SELF_LABELS)[number] 27 28export function getModerationCauseKey( 29 cause: ModerationCause | AppModerationCause, 30): string { 31 const source = 32 cause.source.type === 'labeler' 33 ? cause.source.did 34 : cause.source.type === 'list' 35 ? cause.source.list.uri 36 : 'user' 37 if (cause.type === 'label') { 38 return `label:${cause.label.val}:${source}` 39 } 40 return `${cause.type}:${source}` 41} 42 43export function isJustAMute(modui: ModerationUI): boolean { 44 return modui.filters.length === 1 && modui.filters[0].type === 'muted' 45} 46 47export function moduiContainsHideableOffense(modui: ModerationUI): boolean { 48 const label = modui.filters.at(0) 49 if (label && label.type === 'label') { 50 return labelIsHideableOffense(label.label) 51 } 52 return false 53} 54 55export function labelIsHideableOffense( 56 label: ComAtprotoLabelDefs.Label, 57): boolean { 58 return ['!hide', '!takedown'].includes(label.val) 59} 60 61export function getLabelingServiceTitle({ 62 displayName, 63 handle, 64}: { 65 displayName?: string 66 handle: string 67}) { 68 return displayName 69 ? sanitizeDisplayName(displayName) 70 : sanitizeHandle(handle, '@') 71} 72 73export function lookupLabelValueDefinition( 74 labelValue: string, 75 customDefs: InterpretedLabelValueDefinition[] | undefined, 76): InterpretedLabelValueDefinition | undefined { 77 let def 78 if (!labelValue.startsWith('!') && customDefs) { 79 def = customDefs.find(d => d.identifier === labelValue) 80 } 81 if (!def) { 82 def = LABELS[labelValue as keyof typeof LABELS] 83 } 84 return def 85} 86 87export function isAppLabeler( 88 labeler: 89 | string 90 | AppBskyLabelerDefs.LabelerView 91 | AppBskyLabelerDefs.LabelerViewDetailed, 92): boolean { 93 if (typeof labeler === 'string') { 94 return BskyAgent.appLabelers.includes(labeler) 95 } 96 return BskyAgent.appLabelers.includes(labeler.creator.did) 97} 98 99export function isLabelerSubscribed( 100 labeler: 101 | string 102 | AppBskyLabelerDefs.LabelerView 103 | AppBskyLabelerDefs.LabelerViewDetailed, 104 modOpts: ModerationOpts, 105) { 106 labeler = typeof labeler === 'string' ? labeler : labeler.creator.did 107 if (isAppLabeler(labeler)) { 108 return true 109 } 110 return modOpts.prefs.labelers.find(l => l.did === labeler) 111} 112 113export type Subject = 114 | { 115 uri: string 116 cid: string 117 } 118 | { 119 did: string 120 } 121 122export function useLabelSubject({label}: {label: ComAtprotoLabelDefs.Label}): { 123 subject: Subject 124} { 125 return useMemo(() => { 126 const {cid, uri} = label 127 if (cid) { 128 return { 129 subject: { 130 uri, 131 cid, 132 }, 133 } 134 } else { 135 return { 136 subject: { 137 did: uri, 138 }, 139 } 140 } 141 }, [label]) 142} 143 144export function unique( 145 value: ModerationCause, 146 index: number, 147 array: ModerationCause[], 148) { 149 return ( 150 array.findIndex( 151 item => getModerationCauseKey(item) === getModerationCauseKey(value), 152 ) === index 153 ) 154}