126 lines
3.4 KiB
TypeScript
126 lines
3.4 KiB
TypeScript
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<SongInfo> {
|
|
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;
|
|
}
|