import { AtpAgent, RichText } from "@atproto/api"; import express from "express"; import { timingSafeEqual } from "node:crypto"; import { Buffer } from "node:buffer"; const usermap: Record = JSON.parse( await Deno.readTextFile(new URL("./usermap.json", import.meta.url)), ); const WEBHOOK_SECRET = Deno.env.get("WEBHOOK_SECRET")!; const agent = new AtpAgent({ service: Deno.env.get("PDS")!, }); const app = express(); await agent.login({ identifier: Deno.env.get("IDENTIFIER")!, password: Deno.env.get("PASSWORD")!, }); async function verifySignature( payload: string, signature: string, ): Promise { const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(WEBHOOK_SECRET), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const sig = await crypto.subtle.sign( "HMAC", key, new TextEncoder().encode(payload), ); const expected = `sha256=${Array.from(new Uint8Array(sig)) .map((b) => b.toString(16).padStart(2, "0")) .join("")}`; try { return timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); } catch { return false; } } app.post( "/webhook", express.text({ type: "application/json" }), async (req: any, res: any) => { const signature = req.headers["x-hub-signature-256"]; if (!signature || !(await verifySignature(req.body, signature))) { return res.status(401).send("Unauthorized"); } const payload = JSON.parse(req.body); res.status(202).send("Accepted"); const githubEvent = req.headers["x-github-event"]; if (githubEvent === "push") { const commits = payload.commits; for (const commit of commits) { if ( commit.author.name === "github-actions[bot]" || commit.author.name === "github-actions" ) { continue; } const bskyHandle = usermap[commit.author.username.toLowerCase()]; const author = bskyHandle ? `@${bskyHandle}` : commit.author.name; const title = commit.message.split("\n")[0]; const message = `New change made!\n\n${author} - ${title}\n${commit.url}`; const rt = new RichText({ text: message }); await rt.detectFacets(agent); await agent.post({ text: rt.text, facets: rt.facets, }); } } }, ); const PORT = Deno.env.get("PORT") || 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });