babies first atproto thingy
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});