support multiple origins
This commit is contained in:
parent
40d982de7b
commit
9151402408
1 changed files with 190 additions and 120 deletions
310
index.ts
310
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<string, ChangeWithMessage> = {}
|
||||
let changelist_ids: Record<string, ChangeWithMessage> = {};
|
||||
|
||||
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<string, Entry>(
|
||||
artists.map(a => [a.name, a])
|
||||
);
|
||||
const map = new Map<string, Entry>(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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue