import { Application, Router } from "@oak/oak"; import { XMLParser } from "npm:fast-xml-parser"; const router = new Router(); let wsClients: WebSocket[] = []; router.get("/", (context) => { if (context.isUpgradable) { const ws = context.upgrade(); ws.onopen = () => wsClients.push(ws); ws.onclose = () => wsClients = wsClients.filter((client) => client != ws); } }); router .get("/statusline", async (ctx) => { ctx.response.body = await Deno.readFile("web/statusline.html"); }) .get("/statusline.js", async (ctx) => { ctx.response.body = await Deno.readFile("web/statusline.js"); }) .get("/gone", async (ctx) => { ctx.response.body = await Deno.readFile("web/gone.html"); }) .get("/gone.js", async (ctx) => { ctx.response.body = await Deno.readFile("web/gone.js"); }) .get("/style.css", async (ctx) => { ctx.response.body = await Deno.readFile("web/style.css"); }); const app = new Application(); app.use(router.routes()); app.use(router.allowedMethods()); app.addEventListener("listen", ({ hostname, port }) => { console.log(`Start listening on ${hostname}:${port}`); }); app.listen({ port: 8012 }); const topic = "Something Fun For Everyone With Streamboy!!!!!!!! git.jeevio.xyz/jeeves/streamboy"; setInterval(async () => { let songinfo: SongInfo | null = null; try { songinfo = await getVlcSongInfo(); } catch (e) { console.log(`getVlcSongInfo() error: ${e}`); // properly handling this error is by ignoring it, // since then that leaves songinfo as null, // and that is guaranteed to be handled correctly. } const data = { blocks: [{ text: topic, color: "#ffffff" }], }; if (songinfo != null) { data.blocks.push({ text: `♪ ${songinfo.title} - ${songinfo.artist}`, color: "#ffffff", }); } for (const ws of wsClients) { ws.send(JSON.stringify(data)); } }, 900); interface SongInfo { title?: string; artist?: string; album?: string; } async function getVlcSongInfo(): Promise { const parser = new XMLParser({ ignoreAttributes: false }); const password = "1234"; const res = await fetch("http://localhost:8080/requests/status.xml", { headers: { authorization: `Basic ${btoa(`:${password}`)}` }, }); const json = parser.parse(await res.text()); const songinfo: SongInfo = {}; try { for (const category of json.root.information.category) { if (category["@_name"] != "meta") continue; for (const property of category.info) { if (property["@_name"] == "title") { songinfo.title = processBadXmlString(property["#text"]); } else if (property["@_name"] == "artist") { songinfo.artist = processBadXmlString(property["#text"]); } else if (property["@_name"] == "album") { songinfo.album = processBadXmlString(property["#text"]); } } } } catch { songinfo.title = "Unknown Title"; songinfo.artist = "Unknown Artist"; songinfo.album = "Unknown Album"; } return songinfo; } function processBadXmlString(str: string): string { let newStr = str; while (true) { const amp = newStr.indexOf("&#"); if (amp > 0) { const semi = newStr.indexOf(";", amp); if (semi > 0) { const int = String.fromCharCode(parseInt(newStr.slice(amp + 2, semi))); newStr = newStr.replace(newStr.slice(amp, semi + 1), int); } else break; } else break; } return newStr; }