Something went wrong. Try again.
🌿 Collaborative wiki on ATProto
Something went wrong. Try again.
4.1 kB · 153 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154#!/usr/bin/env bun
// Appview moderation CLI — hides content from our reads without touching any PDS.// Runs against the DB at DB_PATH (prod: /var/lib/lichen/lichen.db). See USAGE below.
import { resolveHandleToDid } from "../src/lib/profile.ts";import { banDid, getWiki, hideWiki, listModeration, unbanDid, unhideWiki,} from "../src/server/db/queries/index.ts";
const USAGE = `Usage: bun run moderate hide <@handle/slug | did:…/slug> [--reason=…] [--by=…] bun run moderate hide <@handle | did:…> --user [--reason=…] [--by=…] bun run moderate unhide <@handle/slug | did:…/slug> bun run moderate unhide <@handle | did:…> --user bun run moderate list`;
interface ParsedArgs { command: "hide" | "unhide" | "list"; target?: string; user: boolean; reason: string | null; by: string;}
function parseArgs(argv: string[]): ParsedArgs { const command = argv[0]; if (command !== "hide" && command !== "unhide" && command !== "list") { throw new Error(`unknown command: ${command ?? "(none)"}`); }
let target: string | undefined; let user = false; let reason: string | null = null; let by = process.env["USER"] ?? "cli";
for (let i = 1; i < argv.length; i++) { const arg = argv[i] ?? ""; if (arg === "--user") { user = true; } else if (arg.startsWith("--reason=")) { reason = arg.slice("--reason=".length); } else if (arg === "--reason") { reason = argv[++i] ?? null; } else if (arg.startsWith("--by=")) { by = arg.slice("--by=".length); } else if (arg === "--by") { by = argv[++i] ?? by; } else if (arg.startsWith("--")) { throw new Error(`unknown flag: ${arg}`); } else if (target === undefined) { target = arg; } else { throw new Error(`unexpected argument: ${arg}`); } }
return { command, ...(target ? { target } : {}), user, reason, by };}
// "@handle/slug", "did:…/slug", "@handle", or a raw "did:…" → owner + optional slug.function splitTarget(target: string): { owner: string; slug?: string } { const slash = target.indexOf("/"); if (slash === -1) return { owner: target }; return { owner: target.slice(0, slash), slug: target.slice(slash + 1) };}
async function resolveDid(owner: string): Promise<string> { const did = await resolveHandleToDid(owner); if (!did) throw new Error(`could not resolve "${owner}" to a DID`); return did;}
async function run(args: ParsedArgs): Promise<void> { if (args.command === "list") { const rows = listModeration(); if (rows.length === 0) { console.log("No moderation actions."); return; } for (const r of rows) { const meta = [ r.moderator && `by ${r.moderator}`, r.reason && `"${r.reason}"`, ] .filter(Boolean) .join(" "); console.log( `${r.created_at} ${r.subject_type.padEnd(4)} ${r.subject}${meta ? ` ${meta}` : ""}`, ); } return; }
if (!args.target) throw new Error(`${args.command} requires a target`); const { owner, slug } = splitTarget(args.target); const did = await resolveDid(owner);
if (args.user) { if (args.command === "hide") { banDid(did, args.reason, args.by); console.log( `Banned DID ${did} (all wikis hidden, future writes dropped).`, ); } else { unbanDid(did); console.log(`Lifted DID ban for ${did}.`); } return; }
if (!slug) { throw new Error( `${args.command} needs a wiki slug (got "${args.target}"). Use --user to act on the whole account.`, ); } // Construct the at_uri when absent so a not-yet-seen wiki can be pre-hidden (rkey = slug). const atUri = getWiki(did, slug)?.at_uri ?? `at://${did}/wiki.lichen.wiki/${slug}`;
if (args.command === "hide") { hideWiki(atUri, args.reason, args.by); console.log(`Hidden wiki ${atUri}.`); } else { unhideWiki(atUri); console.log(`Un-hidden wiki ${atUri}.`); }}
async function main(): Promise<number> { let args: ParsedArgs; try { args = parseArgs(process.argv.slice(2)); } catch (err) { console.error(`${String(err)}\n\n${USAGE}`); return 2; }
try { await run(args); return 0; } catch (err) { console.error(String(err)); return 1; }}
process.exit(await main());