Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 154 lines 3.7 kB view raw
1import React 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 React.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}