~james/core

core/web/src/lib/api/moderation.ts -rw-r--r-- 2.5 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import { ClientResponseError, ok } from "@atcute/client";
import { mainSchema as createReportSchema } from "@atcute/atproto/types/moderation/createReport";
import type * as ComAtprotoModerationCreateReport from "@atcute/atproto/types/moderation/createReport";
import type * as ComAtprotoModerationDefs from "@atcute/atproto/types/moderation/defs";
import type { AtprotoAudience, Cid, Did, ResourceUri } from "@atcute/lexicons/syntax";
import type { OAuthUserAgent } from "@atcute/oauth-browser-client";
import { createClient } from "#lib/auth/agent.ts";

export type ReasonType = ComAtprotoModerationDefs.ReasonType;
export type ReportOutput = ComAtprotoModerationCreateReport.$output;

type KnownOf<T> = T extends string ? (string extends T ? never : T) : never;
export type KnownReasonType = KnownOf<ReasonType>;

export type ReportSubject =
	{ kind: "account"; did: string } | { kind: "record"; uri: string; cid: string };

export interface ReportInput {
	subject: ReportSubject;
	reasonType: ReasonType;
	reason?: string;
}

type SubjectRef = ComAtprotoModerationCreateReport.$input["subject"];

const subjectRef = (subject: ReportSubject): SubjectRef =>
	subject.kind === "account"
		? { $type: "com.atproto.admin.defs#repoRef", did: subject.did as Did }
		: {
				$type: "com.atproto.repo.strongRef",
				uri: subject.uri as ResourceUri,
				cid: subject.cid as Cid
			};

export const createReport = async (
	agent: OAuthUserAgent,
	labelerDid: string,
	{ subject, reasonType, reason }: ReportInput
): Promise<ReportOutput> => {
	const rpc = createClient(agent, `${labelerDid}#atproto_labeler` as AtprotoAudience);
	return ok(
		rpc.call(createReportSchema, {
			input: {
				reasonType,
				reason: reason?.trim() || undefined,
				subject: subjectRef(subject),
				modTool: { name: "tangled-web" }
			}
		})
	);
};

const isMissingScope = (cause: ClientResponseError): boolean => {
	if (cause.status !== 401 && cause.status !== 403) return false;
	const text = `${cause.error} ${cause.description ?? ""}`.toLowerCase();
	return text.includes("scope") || text.includes("invalidtoken") || text.includes("permission");
};

export const reportErrorMessage = (cause: unknown): string => {
	if (cause instanceof ClientResponseError) {
		if (isMissingScope(cause)) {
			return "Reporting was added after you signed in. Sign out and back in to enable it.";
		}
		return cause.description || cause.error || "Failed to send the report.";
	}
	return cause instanceof Error ? cause.message : "Failed to send the report.";
};