chore(scripts): add deno task webhooks for register and delete-all
Wraps the createSession + Authorization: Bearer flow so callers don't need to assemble curl by hand. Reads BSKY_IDENTIFIER / BSKY_PASSWORD / BSKY_PDS / REMANSO_API from env (or matching flags). Defaults to bsky.social so non-bsky-hosted accounts must set BSKY_PDS explicitly, e.g. BSKY_PDS=https://eurosky.social.
This commit is contained in:
@@ -4,7 +4,8 @@
|
|||||||
"jetstream": "deno run --watch --allow-net --allow-read --allow-write --allow-env --allow-ffi --unstable-ffi jetstream.ts",
|
"jetstream": "deno run --watch --allow-net --allow-read --allow-write --allow-env --allow-ffi --unstable-ffi jetstream.ts",
|
||||||
"server": "deno run --watch --allow-net --allow-read --allow-write --allow-env --allow-ffi --unstable-ffi server.ts",
|
"server": "deno run --watch --allow-net --allow-read --allow-write --allow-env --allow-ffi --unstable-ffi server.ts",
|
||||||
"server:prod": "deno run --allow-net --allow-read --allow-write --allow-env --allow-ffi --unstable-ffi server.ts",
|
"server:prod": "deno run --allow-net --allow-read --allow-write --allow-env --allow-ffi --unstable-ffi server.ts",
|
||||||
"migrate": "deno run --allow-net --allow-read --allow-write --allow-env --allow-ffi --unstable-ffi src/migrations/init.ts"
|
"migrate": "deno run --allow-net --allow-read --allow-write --allow-env --allow-ffi --unstable-ffi src/migrations/init.ts",
|
||||||
|
"webhooks": "deno run --allow-net --allow-env scripts/manage-webhooks.ts"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"@db/sqlite": "jsr:@db/sqlite@^0.13.0",
|
"@db/sqlite": "jsr:@db/sqlite@^0.13.0",
|
||||||
|
|||||||
143
scripts/manage-webhooks.ts
Normal file
143
scripts/manage-webhooks.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
// 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 <receiver-url> \\
|
||||||
|
[--verb create|delete|bulk-create] \\
|
||||||
|
[--method POST] \\
|
||||||
|
[--token <outbound-bearer>]
|
||||||
|
|
||||||
|
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<string, string> => {
|
||||||
|
const out: Record<string, string> = {};
|
||||||
|
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<CreateSessionResponse> => {
|
||||||
|
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<string, unknown> = {
|
||||||
|
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();
|
||||||
Reference in New Issue
Block a user