babies first atproto thingy
at main 90 lines 2.3 kB view raw
1import { AtpAgent, RichText } from "@atproto/api"; 2import express from "express"; 3import { timingSafeEqual } from "node:crypto"; 4import { Buffer } from "node:buffer"; 5 6const usermap: Record<string, string> = JSON.parse( 7 await Deno.readTextFile(new URL("./usermap.json", import.meta.url)), 8); 9 10const WEBHOOK_SECRET = Deno.env.get("WEBHOOK_SECRET")!; 11 12const agent = new AtpAgent({ 13 service: Deno.env.get("PDS")!, 14}); 15const app = express(); 16 17await agent.login({ 18 identifier: Deno.env.get("IDENTIFIER")!, 19 password: Deno.env.get("PASSWORD")!, 20}); 21 22async function verifySignature( 23 payload: string, 24 signature: string, 25): Promise<boolean> { 26 const key = await crypto.subtle.importKey( 27 "raw", 28 new TextEncoder().encode(WEBHOOK_SECRET), 29 { name: "HMAC", hash: "SHA-256" }, 30 false, 31 ["sign"], 32 ); 33 const sig = await crypto.subtle.sign( 34 "HMAC", 35 key, 36 new TextEncoder().encode(payload), 37 ); 38 const expected = `sha256=${Array.from(new Uint8Array(sig)) 39 .map((b) => b.toString(16).padStart(2, "0")) 40 .join("")}`; 41 try { 42 return timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); 43 } catch { 44 return false; 45 } 46} 47 48app.post( 49 "/webhook", 50 express.text({ type: "application/json" }), 51 async (req: any, res: any) => { 52 const signature = req.headers["x-hub-signature-256"]; 53 if (!signature || !(await verifySignature(req.body, signature))) { 54 return res.status(401).send("Unauthorized"); 55 } 56 57 const payload = JSON.parse(req.body); 58 res.status(202).send("Accepted"); 59 60 const githubEvent = req.headers["x-github-event"]; 61 62 if (githubEvent === "push") { 63 const commits = payload.commits; 64 65 for (const commit of commits) { 66 if ( 67 commit.author.name === "github-actions[bot]" || 68 commit.author.name === "github-actions" 69 ) { 70 continue; 71 } 72 const bskyHandle = usermap[commit.author.username.toLowerCase()]; 73 const author = bskyHandle ? `@${bskyHandle}` : commit.author.name; 74 const title = commit.message.split("\n")[0]; 75 const message = `New change made!\n\n${author} - ${title}\n${commit.url}`; 76 const rt = new RichText({ text: message }); 77 await rt.detectFacets(agent); 78 await agent.post({ 79 text: rt.text, 80 facets: rt.facets, 81 }); 82 } 83 } 84 }, 85); 86 87const PORT = Deno.env.get("PORT") || 3000; 88app.listen(PORT, () => { 89 console.log(`Server is running on port ${PORT}`); 90});