// Manage webhook subscriptions on the Remanso API. Resolves your PDS from // your AT Protocol handle, logs in with an app password, then registers or // deletes webhooks against the Remanso API. // // deno task webhooks register --url https://your-receiver --verb bulk-create // deno task webhooks delete-all // // Inputs (env or flag, env preferred): // ATPROTO_HANDLE / --handle e.g. alice.eurosky.social // ATPROTO_APP_PASSWORD / --app-password app password (NOT your account password) // REMANSO_API / --api default: https://api.remanso.space const HELP = ` Usage: deno task webhooks register \\ --url \\ [--verb create|delete|bulk-create] \\ [--method POST] \\ [--token ] deno task webhooks list deno task webhooks delete --id deno task webhooks delete-all Inputs (env or flag, env preferred): ATPROTO_HANDLE / --handle your AT Protocol handle ATPROTO_APP_PASSWORD / --app-password app password (NOT your account password) REMANSO_API / --api default: https://api.remanso.space Your PDS is resolved automatically from the handle. `; import { createSession, parseArgs, resolveDidToPds, resolveHandleToDid, } from "./_atproto-session.ts"; const die = (msg: string): never => { console.error(`error: ${msg}`); console.error(HELP); Deno.exit(1); }; const main = async () => { const [command, ...rest] = Deno.args; if (!command || command === "--help" || command === "-h") { console.log(HELP); Deno.exit(command ? 0 : 1); } const flags = parseArgs(rest); const handle = flags.handle ?? Deno.env.get("ATPROTO_HANDLE"); const password = flags["app-password"] ?? Deno.env.get("ATPROTO_APP_PASSWORD"); const api = flags.api ?? Deno.env.get("REMANSO_API") ?? "https://api.remanso.space"; if (!handle) die("ATPROTO_HANDLE (or --handle) is required"); if (!password) die("ATPROTO_APP_PASSWORD (or --app-password) is required"); const did = await resolveHandleToDid(handle); const pds = await resolveDidToPds(did); console.log(`[resolve] ${handle} → ${did} via ${pds}`); const session = await createSession(pds, handle, password); if (command === "register") { const url = flags.url ?? die("--url is required for register"); const body: Record = { method: flags.method ?? "POST", url, }; if (flags.verb) body.verb = flags.verb; if (flags.token) body.token = flags.token; const res = await fetch(`${api}/${session.did}/webhooks`, { method: "POST", headers: { "Authorization": `Bearer ${session.accessJwt}`, "Content-Type": "application/json", }, body: JSON.stringify(body), }); if (!res.ok) { console.error(`register failed (${res.status}): ${await res.text()}`); Deno.exit(1); } console.log(JSON.stringify(await res.json(), null, 2)); return; } if (command === "list") { const res = await fetch(`${api}/${session.did}/webhooks`, { headers: { "Authorization": `Bearer ${session.accessJwt}` }, }); if (!res.ok) { console.error(`list failed (${res.status}): ${await res.text()}`); Deno.exit(1); } console.log(JSON.stringify(await res.json(), null, 2)); return; } if (command === "delete") { const id = flags.id ?? die("--id is required for delete"); const res = await fetch(`${api}/${session.did}/webhooks/${id}`, { method: "DELETE", headers: { "Authorization": `Bearer ${session.accessJwt}` }, }); if (!res.ok) { console.error(`delete failed (${res.status}): ${await res.text()}`); Deno.exit(1); } console.log(`[done] webhook ${id} deleted`); return; } if (command === "delete-all") { const res = await fetch(`${api}/${session.did}/webhooks`, { method: "DELETE", headers: { "Authorization": `Bearer ${session.accessJwt}` }, }); if (!res.ok) { console.error(`delete failed (${res.status}): ${await res.text()}`); Deno.exit(1); } console.log(`[done] all webhooks for ${session.did} deleted`); return; } die(`unknown command: ${command}`); }; await main();