// Manage webhook subscriptions on the Remanso API using a Bluesky session // token from your own PDS. Usage: // // deno task webhooks register --url https://your-receiver --verb bulk-create // deno task webhooks delete-all // // Required env vars (or matching --flags): // BSKY_IDENTIFIER your handle (e.g. alice.eurosky.social) or email // BSKY_PASSWORD app password (NOT your main password) // BSKY_PDS your PDS base URL (default: https://bsky.social) // REMANSO_API Remanso API base URL (default: https://api.remanso.space) // // Example for an eurosky-hosted account: // // BSKY_IDENTIFIER=alice.eurosky.social \ // BSKY_PASSWORD='xxxx-xxxx-xxxx-xxxx' \ // BSKY_PDS=https://eurosky.social \ // deno task webhooks register --url https://example.com/hook --verb bulk-create const HELP = ` Usage: deno task webhooks register \\ --url \\ [--verb create|delete|bulk-create] \\ [--method POST] \\ [--token ] deno task webhooks delete-all Auth (env or flag, env preferred): BSKY_IDENTIFIER / --identifier BSKY_PASSWORD / --password BSKY_PDS / --pds (default: https://bsky.social) REMANSO_API / --api (default: https://api.remanso.space) `; type CreateSessionResponse = { did: string; accessJwt: string; }; const parseArgs = (args: string[]): Record => { const out: Record = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (!arg.startsWith("--")) continue; const key = arg.slice(2); const next = args[i + 1]; if (next === undefined || next.startsWith("--")) { out[key] = "true"; } else { out[key] = next; i++; } } return out; }; const die = (msg: string): never => { console.error(`error: ${msg}`); console.error(HELP); Deno.exit(1); }; const createSession = async ( pds: string, identifier: string, password: string, ): Promise => { const res = await fetch(`${pds}/xrpc/com.atproto.server.createSession`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ identifier, password }), }); if (!res.ok) { throw new Error( `createSession failed (${res.status}): ${await res.text()}`, ); } return await res.json(); }; 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 identifier = flags.identifier ?? Deno.env.get("BSKY_IDENTIFIER"); const password = flags.password ?? Deno.env.get("BSKY_PASSWORD"); const pds = flags.pds ?? Deno.env.get("BSKY_PDS") ?? "https://bsky.social"; const api = flags.api ?? Deno.env.get("REMANSO_API") ?? "https://api.remanso.space"; if (!identifier) die("BSKY_IDENTIFIER (or --identifier) is required"); if (!password) die("BSKY_PASSWORD (or --password) is required"); const session = await createSession(pds, identifier, password); console.log(`[auth] verified DID: ${session.did} via ${pds}`); 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 === "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();