UNPKG

@atproto/ozone

Version:

Backend service for moderating the Bluesky network.

234 lines (211 loc) 6.79 kB
import { InvalidRequestError } from '@atproto/xrpc-server' import { AppContext } from '../context' import { Member } from '../db/schema/member' import { ModerationEvent } from '../db/schema/moderation_event' import { ids } from '../lexicon/lexicons' import { AccountView } from '../lexicon/types/com/atproto/admin/defs' import { InputSchema as ReportInput } from '../lexicon/types/com/atproto/moderation/createReport' import { REASONAPPEAL, REASONMISLEADING, REASONOTHER, REASONRUDE, REASONSEXUAL, REASONSPAM, REASONVIOLATION, ReasonType, } from '../lexicon/types/com/atproto/moderation/defs' import { REVIEWCLOSED, REVIEWESCALATED, REVIEWOPEN, RepoView, RepoViewDetail, } from '../lexicon/types/tools/ozone/moderation/defs' import { ROLEADMIN, ROLEMODERATOR, ROLETRIAGE, ROLEVERIFIER, } from '../lexicon/types/tools/ozone/team/defs' import { ModerationSubjectStatusRow } from '../mod-service/types' export const getPdsAccountInfos = async ( ctx: AppContext, dids: string[], ): Promise<Map<string, AccountView | null>> => { const results = new Map<string, AccountView | null>() const agent = ctx.pdsAgent if (!agent || !dids.length) return results const auth = await ctx.pdsAuth(ids.ComAtprotoAdminGetAccountInfos) if (!auth) return results try { const res = await agent.com.atproto.admin.getAccountInfos({ dids }, auth) res.data.infos.forEach((info) => { results.set(info.did, info) }) return results } catch { return results } } function un$type<T extends object>(obj: T): Omit<T, '$type'> { if ('$type' in obj) { const { $type: _, ...rest } = obj return rest } return obj } export const addAccountInfoToRepoViewDetail = ( repoView: RepoView | RepoViewDetail, accountInfo: AccountView | null, includeEmail = false, ): RepoViewDetail => { if (!accountInfo) { return un$type({ ...repoView, moderation: un$type(repoView.moderation), }) } const { email, deactivatedAt, emailConfirmedAt, inviteNote, invitedBy, invites, invitesDisabled, threatSignatures, // pick some duplicate/unwanted details out $type: _accountType, did: _did, handle: _handle, indexedAt: _indexedAt, relatedRecords: _relatedRecords, ...otherAccountInfo } = accountInfo return { ...otherAccountInfo, ...un$type(repoView), moderation: un$type(repoView.moderation), email: includeEmail ? email : undefined, invitedBy, invitesDisabled, inviteNote, invites, emailConfirmedAt, deactivatedAt, threatSignatures, } } export const addAccountInfoToRepoView = ( repoView: RepoView, accountInfo: AccountView | null, includeEmail = false, ): RepoView => { if (!accountInfo) return repoView return { ...repoView, email: includeEmail ? accountInfo.email : undefined, invitedBy: accountInfo.invitedBy, invitesDisabled: accountInfo.invitesDisabled, inviteNote: accountInfo.inviteNote, deactivatedAt: accountInfo.deactivatedAt, threatSignatures: accountInfo.threatSignatures, } } export const getReasonType = (reasonType: ReportInput['reasonType']) => { if (reasonTypes.has(reasonType)) { return reasonType } throw new InvalidRequestError('Invalid reason type') } export const getEventType = (type: string) => { if (eventTypes.has(type)) { return type as ModerationEvent['action'] } throw new InvalidRequestError('Invalid event type') } export const getReviewState = (reviewState?: string) => { if (!reviewState) return undefined if (reviewStates.has(reviewState)) { return reviewState as ModerationSubjectStatusRow['reviewState'] } throw new InvalidRequestError('Invalid review state') } const reviewStates = new Set([REVIEWCLOSED, REVIEWESCALATED, REVIEWOPEN]) const reasonTypes = new Set<ReasonType>([ REASONOTHER, REASONSPAM, REASONMISLEADING, REASONRUDE, REASONSEXUAL, REASONVIOLATION, REASONAPPEAL, ]) const eventTypes = new Set([ 'tools.ozone.moderation.defs#modEventTakedown', 'tools.ozone.moderation.defs#modEventAcknowledge', 'tools.ozone.moderation.defs#modEventEscalate', 'tools.ozone.moderation.defs#modEventComment', 'tools.ozone.moderation.defs#modEventLabel', 'tools.ozone.moderation.defs#modEventReport', 'tools.ozone.moderation.defs#modEventMute', 'tools.ozone.moderation.defs#modEventUnmute', 'tools.ozone.moderation.defs#modEventMuteReporter', 'tools.ozone.moderation.defs#modEventUnmuteReporter', 'tools.ozone.moderation.defs#modEventReverseTakedown', 'tools.ozone.moderation.defs#modEventEmail', 'tools.ozone.moderation.defs#modEventResolveAppeal', 'tools.ozone.moderation.defs#modEventTag', 'tools.ozone.moderation.defs#modEventDivert', 'tools.ozone.moderation.defs#accountEvent', 'tools.ozone.moderation.defs#identityEvent', 'tools.ozone.moderation.defs#recordEvent', 'tools.ozone.moderation.defs#modEventPriorityScore', 'tools.ozone.moderation.defs#ageAssuranceEvent', 'tools.ozone.moderation.defs#ageAssuranceOverrideEvent', ]) export const getMemberRole = (role: string) => { if (memberRoles.has(role)) { return role as Member['role'] } throw new InvalidRequestError('Invalid member role') } const memberRoles = new Set([ ROLEADMIN, ROLEMODERATOR, ROLETRIAGE, ROLEVERIFIER, ]) export const getSafelinkPattern = (pattern: string): SafelinkPatternType => { if (safelinkPatterns.has(pattern)) { return pattern as SafelinkPatternType } throw new InvalidRequestError('Invalid safelink pattern type') } export const getSafelinkAction = (action: string): SafelinkActionType => { if (safelinkActions.has(action)) { return action as SafelinkActionType } throw new InvalidRequestError('Invalid safelink action type') } export const getSafelinkReason = (reason: string): SafelinkReasonType => { if (safelinkReasons.has(reason)) { return reason as SafelinkReasonType } throw new InvalidRequestError('Invalid safelink reason type') } export const getSafelinkEventType = (eventType: string): SafelinkEventType => { if (safelinkEventTypes.has(eventType)) { return eventType as SafelinkEventType } throw new InvalidRequestError('Invalid safelink event type') } export type SafelinkEventType = 'addRule' | 'updateRule' | 'removeRule' export type SafelinkPatternType = 'domain' | 'url' export type SafelinkActionType = 'block' | 'warn' | 'whitelist' export type SafelinkReasonType = 'csam' | 'spam' | 'phishing' | 'none' const safelinkPatterns = new Set(['domain', 'url']) const safelinkActions = new Set(['block', 'warn', 'whitelist']) const safelinkReasons = new Set(['csam', 'spam', 'phishing', 'none']) const safelinkEventTypes = new Set(['addRule', 'updateRule', 'removeRule'])