From 91514024083a41d6830a72a667bbe1cedfefdc81 Mon Sep 17 00:00:00 2001 From: fucksophie Date: Sat, 3 Jan 2026 15:27:50 +0200 Subject: [PATCH] support multiple origins --- index.ts | 310 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 190 insertions(+), 120 deletions(-) diff --git a/index.ts b/index.ts index 06d246b..16b383e 100644 --- a/index.ts +++ b/index.ts @@ -1,42 +1,52 @@ -import { parseHTML } from "linkedom" -import { jsonToCsv, ndjsonToJson, prettyStringify, requireBasicAuth, tripleBool, type Change, type Entry } from "./lib"; -import * as fs from "fs/promises" +import { parseHTML } from "linkedom"; +import { + jsonToCsv, + ndjsonToJson, + prettyStringify, + requireBasicAuth, + tripleBool, + type Change, + type Entry, +} from "./lib"; +import * as fs from "fs/promises"; import { existsSync } from "fs"; -import { $ } from "bun" +import { $, type BodyInit } from "bun"; interface ChangeWithMessage { - changes: Change[], - webhook: unknown + changes: Change[]; + webhook: unknown; } -let changelist_ids: Record = {} +let changelist_ids: Record = {}; -if(existsSync("./changelist_ids.json")) { - changelist_ids = JSON.parse((await fs.readFile("./changelist_ids.json")).toString("utf8")) +if (existsSync("./changelist_ids.json")) { + changelist_ids = JSON.parse( + (await fs.readFile("./changelist_ids.json")).toString("utf8"), + ); } async function updateChangelistIds() { await fs.writeFile("./changelist_ids.json", JSON.stringify(changelist_ids)); } -const tracker_page = "https://docs.google.com/spreadsheets/u/0/d/1Z8aANbxXbnUGoZPRvJfWL3gz6jrzPPrwVt3d0c1iJ_4/htmlview/sheet?headers=false&gid=1884837542" +const tracker_page = + "https://docs.google.com/spreadsheets/u/0/d/1Z8aANbxXbnUGoZPRvJfWL3gz6jrzPPrwVt3d0c1iJ_4/htmlview/sheet?headers=false&gid=1884837542"; async function getTH() { const req = await fetch(tracker_page); - const txt = await req.text() + const txt = await req.text(); const { document } = parseHTML(txt); const table_body = document.querySelector(".waffle > tbody"); - if(!table_body) throw new Error("Missing table body..") + if (!table_body) throw new Error("Missing table body.."); - //@ts-expect-error .children can be spread-operator'd - const rows = [...table_body.children] + const rows = [...table_body.children]; let ndjson = ""; for (let i = 4; i < rows.length; i++) { - if(!rows[i].children[1]) { + if (!rows[i].children[1]) { break; } let trackerName = rows[i].children[1].innerText; @@ -53,26 +63,34 @@ async function getTH() { const best = trackerName.startsWith("⭐"); trackerName = trackerName - .replace(/\p{Extended_Pictographic}/gu, '') - .replace(/[\uFE00-\uFE0F]/g, '') - .replace(/\u200D/g, '') + .replace(/\p{Extended_Pictographic}/gu, "") + .replace(/[\uFE00-\uFE0F]/g, "") + .replace(/\u200D/g, "") .trim(); - ndjson += JSON.stringify({ name: trackerName, url: trackerUrl, credit, updated, links_work, best }) + "\n"; + ndjson += + JSON.stringify({ + name: trackerName, + url: trackerUrl, + credit, + updated, + links_work, + best, + }) + "\n"; } return ndjson; } -async function runComparison() { +async function runComparison() { const data = await getTH(); const old = (await fs.readFile("./th_artists.ndjson")).toString("utf8"); - if(Bun.hash(old) !== Bun.hash(data)) { + if (Bun.hash(old) !== Bun.hash(data)) { const oldJson: Entry[] = ndjsonToJson(old) as Entry[]; const newJson: Entry[] = ndjsonToJson(data) as Entry[]; - const oldMap = Object.fromEntries(oldJson.map(item => [item.name, item])); - const newMap = Object.fromEntries(newJson.map(item => [item.name, item])); + const oldMap = Object.fromEntries(oldJson.map((item) => [item.name, item])); + const newMap = Object.fromEntries(newJson.map((item) => [item.name, item])); const changes: Change[] = []; @@ -89,7 +107,8 @@ async function runComparison() { if (oldItem.url !== newItem.url) delta.url = newItem.url; if (oldItem.credit !== newItem.credit) delta.credit = newItem.credit; - if (oldItem.links_work !== newItem.links_work) delta.links_work = newItem.links_work; + if (oldItem.links_work !== newItem.links_work) + delta.links_work = newItem.links_work; if (oldItem.updated !== newItem.updated) delta.updated = newItem.updated; if (oldItem.best !== newItem.best) delta.best = newItem.best; @@ -121,50 +140,54 @@ async function runComparison() { if (message) { const id = crypto.randomUUID().split("-")[0]!; - const z = await fetch(process.env.WEBHOOK_URL! + "?wait=true&with_components=true", { - method: 'POST', - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - "components": [ - { - "type": 1, - "components": [ - { - "type": 2, - "style": 5, - "label": "Accept", - "emoji": { - "name": "✅" + const z = await fetch( + process.env.WEBHOOK_URL! + "?wait=true&with_components=true", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + components: [ + { + type: 1, + components: [ + { + type: 2, + style: 5, + label: "Accept", + emoji: { + name: "✅", + }, + url: `${process.env.PUBLIC}/merge-th/${id}`, }, - "url": `${process.env.PUBLIC}/merge-th/${id}`, - }, - { - "type": 2, - "style": 5, - "url": `${process.env.PUBLIC}/ignore-th/${id}`, - "label": "Deny", - "emoji": { - "name": "❌" - } - } - ] - } - ], - "embeds": [{ - "title": "TH Tracker Changes", - "description": message, - }] - }) - }); + { + type: 2, + style: 5, + url: `${process.env.PUBLIC}/ignore-th/${id}`, + label: "Deny", + emoji: { + name: "❌", + }, + }, + ], + }, + ], + embeds: [ + { + title: "TH Tracker Changes", + description: message, + }, + ], + }), + }, + ); - const json = await z.json() + const json = await z.json(); changelist_ids[id] = { changes, - webhook: json - } + webhook: json, + }; await updateChangelistIds(); - } await fs.writeFile("./th_artists.ndjson", data); @@ -172,41 +195,70 @@ async function runComparison() { } export class ClientResponse extends Response { - constructor(body?: BodyInit, init?: ResponseInit) { + constructor(url: URL, body?: BodyInit, init?: ResponseInit) { super(body, init); - this.headers.set("Access-Control-Allow-Origin", process.env.ORIGIN!); - this.headers.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, DELETE"); + + const origins = process.env.ORIGIN!.split(","); + + if (origins.includes(url.origin)) { + this.headers.set("Access-Control-Allow-Origin", url.origin); + this.headers.set( + "Access-Control-Allow-Methods", + "GET, POST, PUT, DELETE, OPTIONS", + ); + } } - static override json(body: unknown, init?: ResponseInit): Response { - return new Response(JSON.stringify(body), { + static json_c(body: unknown, url: URL, init?: ResponseInit): Response { + const res = new Response(JSON.stringify(body), { ...init, headers: { "Content-Type": "application/json", - "Access-Control-Allow-Origin": process.env.ORIGIN!, - "Access-Control-Allow-Methods": "OPTIONS, GET, POST, PUT, DELETE", ...init?.headers, }, }); + + const origins = process.env.ORIGIN!.split(","); + + if (origins.includes(url.origin)) { + res.headers.set("Access-Control-Allow-Origin", url.origin); + res.headers.set( + "Access-Control-Allow-Methods", + "GET, POST, PUT, DELETE, OPTIONS", + ); + return res; + } else { + return res; + } } } Bun.serve({ routes: { - "/": () => new ClientResponse("Sheets v2"), - "/artists.json": async () => ClientResponse.json( - ndjsonToJson((await fs.readFile("artists.ndjson")).toString("utf8")) - ), - "/artists.csv": async () => new ClientResponse( + "/": (req) => new ClientResponse(new URL(req.url), "Sheets v2"), + "/artists.json": async (req) => + ClientResponse.json_c( + ndjsonToJson((await fs.readFile("artists.ndjson")).toString("utf8")), + new URL(req.url), + ), + "/artists.csv": async (req) => + new ClientResponse( + new URL(req.url), jsonToCsv( - ndjsonToJson((await fs.readFile("artists.ndjson")).toString("utf8") - ) - ), { - headers: { - "Content-Type": "text/csv" - } - }), - "/artists.ndjson": async () => new ClientResponse(await fs.readFile("artists.ndjson")), - "/th_artists.ndjson": async () => new ClientResponse(await fs.readFile("th_artists.ndjson")), + ndjsonToJson((await fs.readFile("artists.ndjson")).toString("utf8")), + ), + { + headers: { + "Content-Type": "text/csv", + }, + }, + ), + "/artists.ndjson": async (req) => + new ClientResponse(new URL(req.url), await fs.readFile("artists.ndjson")), + "/th_artists.ndjson": async (req) => + new ClientResponse( + new URL(req.url), + await fs.readFile("th_artists.ndjson"), + ), "/ignore-th/:id": async (req) => { const authFail = requireBasicAuth(req); if (authFail) return authFail; @@ -215,25 +267,33 @@ Bun.serve({ const changes = changelist_ids[id]; if (!changes) { - return new ClientResponse("Id is invalid.", { status: 404 }); + return new ClientResponse(new URL(req.url), "Id is invalid.", { + status: 404, + }); } - const embed = changes.webhook.embeds[0]; - embed.title = embed.title + " - Denied." + const embed = (changes.webhook as { embeds: any[] }).embeds[0]; + embed.title = embed.title + " - Denied."; - await fetch(process.env.WEBHOOK_URL! + "/messages/" + changes.webhook.id + "?with_components=true", { - method: 'PATCH', - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - embeds: [embed], - components: [] - }) - }); + await fetch( + process.env.WEBHOOK_URL! + + "/messages/" + + (changes.webhook as { id: string }).id + + "?with_components=true", + { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + embeds: [embed], + components: [], + }), + }, + ); delete changelist_ids[id]; await updateChangelistIds(); - return new ClientResponse("Ignored successfully."); + return new ClientResponse(new URL(req.url), "Ignored successfully."); }, "/merge-th/:id": async (req) => { const authFail = requireBasicAuth(req); @@ -243,15 +303,15 @@ Bun.serve({ const changes = changelist_ids[id]; if (!changes) { - return new ClientResponse("Id is invalid.", { status: 404 }); + return new ClientResponse(new URL(req.url), "Id is invalid.", { + status: 404, + }); } const artistsRaw = await fs.readFile("./artists.ndjson", "utf8"); const artists = ndjsonToJson(artistsRaw) as Entry[]; - const map = new Map( - artists.map(a => [a.name, a]) - ); + const map = new Map(artists.map((a) => [a.name, a])); for (const change of changes.changes) { if (change.op === "delete") { @@ -270,27 +330,31 @@ Bun.serve({ } const merged = - [...map.values()] - .map(v => prettyStringify(v)) - .join("\n") + "\n"; + [...map.values()].map((v) => prettyStringify(v)).join("\n") + "\n"; await fs.writeFile("./artists.ndjson", merged); await fs.writeFile( "./th_artists.ndjson", - await fs.readFile("./th_artists.ndjson") + await fs.readFile("./th_artists.ndjson"), ); - const embed = changes.webhook.embeds[0]; - embed.title = embed.title + " - Accepted!" + const embed = (changes.webhook as { embeds: any[] }).embeds[0]; + embed.title = embed.title + " - Accepted!"; - await fetch(process.env.WEBHOOK_URL! + "/messages/" + changes.webhook.id + "?with_components=true", { - method: 'PATCH', - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - embeds: [embed], - components: [] - }) - }); + await fetch( + process.env.WEBHOOK_URL! + + "/messages/" + + (changes.webhook as { id: string }).id + + "?with_components=true", + { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + embeds: [embed], + components: [], + }), + }, + ); delete changelist_ids[id]; await updateChangelistIds(); @@ -315,21 +379,27 @@ Bun.serve({ } } catch (err) { console.error("Error:", err); - return new ClientResponse("Failed to automerge to git. Error: " + err, { status: 400 }); + return new ClientResponse( + new URL(req.url), + "Failed to automerge to git. Error: " + err, + { + status: 400, + }, + ); } - return new ClientResponse("Merged successfully."); - } + return new ClientResponse(new URL(req.url), "Merged successfully."); + }, }, - fetch() { - return new ClientResponse("Unmatched route"); + fetch(req) { + return new ClientResponse(new URL(req.url), "Unmatched route"); }, hostname: process.env.HOST || "127.0.0.1", }); if (!existsSync("./th_artists.ndjson")) { - console.log("Assuming first run. Downloading TH info and waiting 50s.") + console.log("Assuming first run. Downloading TH info and waiting 50s."); await fs.writeFile("./th_artists.ndjson", await getTH()); } else { await runComparison(); @@ -337,4 +407,4 @@ if (!existsSync("./th_artists.ndjson")) { setInterval(async () => { await runComparison(); -}, 50000) +}, 50000);