import { isCanonicalResourceUri, isDid, type CanonicalResourceUri, type Did, type Nsid } from "@atcute/lexicons/syntax"; import type { BobbinContext } from "#lib/api/client.ts"; import { enrich, labelsOf, target, TYPE_LABELS, type Enriched, type Label, type LabelsDescriptor, type RecordPath, type Sidecar } from "#lib/api/enrich.ts"; import { PROFILE_COLLECTION } from "#lib/api/profile.ts"; import type { ProfileRecord, RecordView } from "#lib/api/records.ts"; export type LabeledProfile = Enriched>; const SEVERITY = ["hide", "warn"] as const; export type ModerationLevel = (typeof SEVERITY)[number]; export type ModerationTarget = Did | CanonicalResourceUri; export interface Moderation { level: ModerationLevel; target: ModerationTarget; } export const isAccountModeration = ( moderation: Moderation ): moderation is Moderation & { target: Did } => isDid(moderation.target); export const ACCOUNT_MODERATION: LabelsDescriptor = { source: `${PROFILE_COLLECTION}:.repo`, type: TYPE_LABELS }; export const recordModeration = (collection: Nsid): LabelsDescriptor => ({ source: `${collection}:.`, type: TYPE_LABELS }); export const PROFILE_MODERATION = recordModeration(PROFILE_COLLECTION); export const moderationTargets = ( collection: Nsid, paths: { records: RecordPath[]; authors: RecordPath[] } ): LabelsDescriptor[] => [ target(ACCOUNT_MODERATION, paths.authors), target(recordModeration(collection), paths.records) ]; const labeledAs = (labels: Label[], level: ModerationLevel): Moderation | undefined => { const uri = labels.find(({ val }) => val === `!${level}`)?.uri; return uri && (isDid(uri) || isCanonicalResourceUri(uri)) ? { level, target: uri } : undefined; }; const severityOf = (moderation: Moderation): number => SEVERITY.indexOf(moderation.level); export const strongestModeration = ( ...candidates: (Moderation | undefined)[] ): Moderation | undefined => candidates .filter((candidate): candidate is Moderation => candidate !== undefined) .sort((a, b) => severityOf(a) - severityOf(b))[0]; export const moderationOf = (labels: Label[]): Moderation | undefined => strongestModeration(...SEVERITY.map((level) => labeledAs(labels, level))); export const accountModerationOf = (data: Sidecar, did: string): Moderation | undefined => moderationOf(labelsOf(data, did, ACCOUNT_MODERATION)); export const recordModerationOf = ( data: Sidecar, collection: Nsid, uri: string ): Moderation | undefined => moderationOf(labelsOf(data, uri, recordModeration(collection))); export const subjectModeration = ( data: Sidecar, collection: Nsid, subject: { uri: string; author: string } ): Moderation | undefined => strongestModeration( accountModerationOf(data, subject.author), recordModerationOf(data, collection, subject.uri) ); export const getLabeledProfile = (ctx: BobbinContext, did: string): Promise => enrich>(ctx, { xrpc: "org.tangled.actor.getProfile", params: { actor: `at://${did}/${PROFILE_COLLECTION}/self` }, enrich: [target(PROFILE_MODERATION, ["uri"])] }); export const profileModeration = ( account?: Moderation, profile?: LabeledProfile ): Moderation | undefined => strongestModeration( account, profile && recordModerationOf(profile.data, PROFILE_COLLECTION, profile.output.uri) );