import {parseHTML} from "linkedom" import { ndjsonToJson, tripleBool, tripleBoolToString } from "./lib"; import * as fs from "fs/promises" import { existsSync } from "fs"; 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 { document } = parseHTML(txt); const table_body = document.querySelector(".waffle > tbody"); if(!table_body) throw new Error("Missing table body..") //@ts-expect-error .children can be spread-operator'd const rows = [...table_body.children] let ndjson = ""; for (let i = 4; i < rows.length; i++) { if(!rows[i].children[1]) { break; } let trackerName = rows[i].children[1].innerText; if (!trackerName) continue; const urlElement = rows[i].children[1].querySelector("a"); if (!urlElement) continue; const trackerUrl = new URL(urlElement.href).searchParams.get("q"); const credits = rows[i].children[2].innerText; const updated = tripleBool(rows[i].children[3].innerText); const links_work = tripleBool(rows[i].children[4].innerText); const best = trackerName.startsWith("⭐"); trackerName = trackerName .replace(/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g, '') .trim(); ndjson += JSON.stringify({ name: trackerName, url: trackerUrl, credits, updated, links_work, best }) + "\n"; } return ndjson; } async function runComparison() { console.log("Comparing..") const data = await getTH(); const old = (await fs.readFile("./th_artists.ndjson")).toString("utf8"); if(Bun.hash(old) !== Bun.hash(data)) { const oldJson = ndjsonToJson(old); const newJson = ndjsonToJson(data); const oldMap = Object.fromEntries(oldJson.map(item => [item.name, item])); const newMap = Object.fromEntries(newJson.map(item => [item.name, item])); let message = "## TH Change Detection\n\n"; for (const name in oldMap) { const oldItem = oldMap[name]; const newItem = newMap[name]; if (!newItem) { message += `**DELETED**: \`${name}\`\n`; continue; } if (oldItem.url !== newItem.url) message += `**CHANGED URL** for \`${name}\`\n`; if (oldItem.credits !== newItem.credits) message += `**CHANGED CREDITS** for \`${name}\`\n`; if (oldItem.links_work !== newItem.links_work) message += `**CHANGED WORKING LINKS STATUS** for \`${name}\`, from ${tripleBoolToString(oldItem.links_work)} to ${tripleBoolToString(newItem.links_work)}\n`; if (oldItem.updated !== newItem.updated) message += `**CHANGED UPDATED** for \`${name}\`, from ${tripleBoolToString(oldItem.updated)} to ${tripleBoolToString(newItem.updated)}\n`; if (oldItem.best !== newItem.best) message += `**CHANGED BEST STATUS** for \`${name}\`, from ${oldItem.best ? "Yes" : "No"} to ${newItem.best ? "Yes" : "No"}\n`; } for (const name in newMap) { if (!oldMap[name]) { message += `**NEW**: \`${name}\`\n`; } } if (message.trim() !== "## Change Detection") { await fetch(process.env.WEBHOOK_URL!, { method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content: message }) }); } await fs.writeFile("./th_artists.ndjson", data); } } if (!existsSync("./th_artists.ndjson")) { console.log("Assuming first run. Downloading TH info and waiting 50s.") await fs.writeFile("./th_artists.ndjson", await getTH()); } else { await runComparison(); } setInterval(async () => { await runComparison(); }, 50000) Bun.serve({ routes: { "/": () => new Response("Sheets v2"), "/artists.ndjson": async () => new Response(await fs.readFile("artists.ndjson")), "/th_artists.ndjson": async () => new Response(await fs.readFile("th_artists.ndjson")), }, fetch() { return new Response("Unmatched route"); }, hostname: "0.0.0.0", port: 5000 });