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.";
};