Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
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}