Files
bsky-social-app/src/lib/moderation.ts
T

155 lines
3.6 KiB
TypeScript

import {useMemo} from 'react'
import {
type AppBskyLabelerDefs,
BskyAgent,
type ComAtprotoLabelDefs,
type InterpretedLabelValueDefinition,
LABELS,
type ModerationCause,
type ModerationOpts,
type ModerationUI,
} from '@atproto/api'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {type AppModerationCause} from '#/components/Pills'
export const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] as const
export const OTHER_SELF_LABELS = ['graphic-media'] as const
export const SELF_LABELS = [
...ADULT_CONTENT_LABELS,
...OTHER_SELF_LABELS,
] as const
export type AdultSelfLabel = (typeof ADULT_CONTENT_LABELS)[number]
export type OtherSelfLabel = (typeof OTHER_SELF_LABELS)[number]
export type SelfLabel = (typeof SELF_LABELS)[number]
export function getModerationCauseKey(
cause: ModerationCause | AppModerationCause,
): string {
const source =
cause.source.type === 'labeler'
? cause.source.did
: cause.source.type === 'list'
? cause.source.list.uri
: 'user'
if (cause.type === 'label') {
return `label:${cause.label.val}:${source}`
}
return `${cause.type}:${source}`
}
export function isJustAMute(modui: ModerationUI): boolean {
return modui.filters.length === 1 && modui.filters[0].type === 'muted'
}
export function moduiContainsHideableOffense(modui: ModerationUI): boolean {
const label = modui.filters.at(0)
if (label && label.type === 'label') {
return labelIsHideableOffense(label.label)
}
return false
}
export function labelIsHideableOffense(
label: ComAtprotoLabelDefs.Label,
): boolean {
return ['!hide', '!takedown'].includes(label.val)
}
export function getLabelingServiceTitle({
displayName,
handle,
}: {
displayName?: string
handle: string
}) {
return displayName
? sanitizeDisplayName(displayName)
: sanitizeHandle(handle, '@')
}
export function lookupLabelValueDefinition(
labelValue: string,
customDefs: InterpretedLabelValueDefinition[] | undefined,
): InterpretedLabelValueDefinition | undefined {
let def
if (!labelValue.startsWith('!') && customDefs) {
def = customDefs.find(d => d.identifier === labelValue)
}
if (!def) {
def = LABELS[labelValue as keyof typeof LABELS]
}
return def
}
export function isAppLabeler(
labeler:
| string
| AppBskyLabelerDefs.LabelerView
| AppBskyLabelerDefs.LabelerViewDetailed,
): boolean {
if (typeof labeler === 'string') {
return BskyAgent.appLabelers.includes(labeler)
}
return BskyAgent.appLabelers.includes(labeler.creator.did)
}
export function isLabelerSubscribed(
labeler:
| string
| AppBskyLabelerDefs.LabelerView
| AppBskyLabelerDefs.LabelerViewDetailed,
modOpts: ModerationOpts,
) {
labeler = typeof labeler === 'string' ? labeler : labeler.creator.did
if (isAppLabeler(labeler)) {
return true
}
return modOpts.prefs.labelers.find(l => l.did === labeler)
}
export type Subject =
| {
uri: string
cid: string
}
| {
did: string
}
export function useLabelSubject({label}: {label: ComAtprotoLabelDefs.Label}): {
subject: Subject
} {
return useMemo(() => {
const {cid, uri} = label
if (cid) {
return {
subject: {
uri,
cid,
},
}
} else {
return {
subject: {
did: uri,
},
}
}
}, [label])
}
export function unique(
value: ModerationCause,
index: number,
array: ModerationCause[],
) {
return (
array.findIndex(
item => getModerationCauseKey(item) === getModerationCauseKey(value),
) === index
)
}