107 lines
2.8 KiB
TypeScript
107 lines
2.8 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("/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 () => {
|
||
|
const songinfo = await getVlcSongInfo();
|
||
|
const data = {
|
||
|
blocks: [
|
||
|
{ text: topic, color: "#ffffff" },
|
||
|
{ text: `♪ ${songinfo.title} - ${songinfo.artist}`, color: "#ffffff" },
|
||
|
],
|
||
|
};
|
||
|
for (const ws of wsClients) {
|
||
|
// ws.send(
|
||
|
// `${topic} | ♪ ${songinfo.title} - ${songinfo.artist} | `,
|
||
|
// );
|
||
|
//
|
||
|
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 = {};
|
||
|
|
||
|
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"]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|