streamboy/src/main.ts

127 lines
3.4 KiB
TypeScript
Raw Normal View History

2025-02-18 22:03:27 -07:00
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");
})
2025-02-19 10:54:32 -07:00
.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");
})
2025-02-18 22:03:27 -07:00
.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();
2025-02-19 10:54:32 -07:00
} 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.
}
2025-02-18 22:03:27 -07:00
const data = {
blocks: [{ text: topic, color: "#ffffff" }],
2025-02-18 22:03:27 -07:00
};
if (songinfo != null) {
data.blocks.push({
text: `${songinfo.title} - ${songinfo.artist}`,
color: "#ffffff",
});
}
2025-02-18 22:03:27 -07:00
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"]);
}
2025-02-18 22:03:27 -07:00
}
}
} catch {
songinfo.title = "Unknown Title";
songinfo.artist = "Unknown Artist";
songinfo.album = "Unknown Album";
2025-02-18 22:03:27 -07:00
}
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;
}