Basic functionality is here with a crappy ai generated frontend.

This commit is contained in:
Jurn Wubben 2026-02-01 17:07:39 +01:00
commit e9fdc05c2d
14 changed files with 1600 additions and 0 deletions

82
src/spotify.ts Normal file
View file

@ -0,0 +1,82 @@
import { buildCommand, type Command, type BaseCommand, parseCommand } from "./commandBuilder.ts";
type GetSong = () => string | null;
export type Song = {
name: string;
uri: string;
artists: string[];
album: {
name: string;
coverUrl: string | undefined;
};
};
export class SpotifyWS {
public ws: WebSocket;
private queryList: {
[x: string]: (value: Song[] | PromiseLike<Song[]>) => void;
} = {};
constructor(ws: WebSocket) {
this.ws = ws;
this.onMessage = this.onMessage.bind(this);
this.onClose = this.onClose.bind(this);
this.ws.addEventListener("message", this.onMessage);
this.ws.addEventListener("close", this.onClose);
}
public search(query: string): Promise<Song[]> {
return new Promise<Song[]>((resolve, reject) => {
if (this.ws.readyState != this.ws.OPEN) reject("WS Isn't connected.");
const id = crypto.randomUUID();
this.send(buildCommand("search", { query, id }));
this.queryList[id] = resolve;
setTimeout(() => {
reject("Timeout.");
delete this.queryList[id];
}, 3000);
});
}
public sendSong(uri?: string) {
const song = uri ?? playerManager.getNext()?.uri;
if (!song) return;
this.send(buildCommand("next_song", { song }));
}
private send(data: string) {
if (this.ws.readyState != this.ws.OPEN) return;
this.ws.send(data);
}
private onClose() {
globalThis.spotify = undefined;
}
private onMessage(msg: MessageEvent<string>) {
const text = msg.data;
const parsed = parseCommand(text);
if (parsed === null) return;
switch (parsed.c) {
case "next_song":
this.sendSong();
break;
case "search": {
if (!parsed.d || !parsed.d.id || !parsed.d.results) break;
const cmd = parsed as Command<{id: string, results: Song[]}>
const { id, results } = cmd.d;
if (!(id in this.queryList)) break;
this.queryList[id](results);
break;
}
}
}
}