import WebSocket from "ws"; import { connect } from "mqtt"; import secrets from "./secrets.mjs"; let ws; const wsConnect = () => { ws = new WebSocket(secrets.wsUrl); ws.on("close", () => { ws = undefined; setTimeout(wsConnect, 10000); // Retry after 10 seconds }); ws.on("error", () => { ws = undefined; setTimeout(wsConnect, 10000); // Retry after 10 seconds }); } do { wsConnect(); } while(!ws); const client = connect(secrets.mqttUrl, { username: secrets.mqttUser, password: secrets.mqttPass, protocol: "ws", reconnectPeriod: 10000, // Retry after 10 seconds }); function metaFromMetadata(metadata) { const meta = { title: metadata.name, album: metadata.album.name, artist: metadata.artist.map(artist => artist.name) }; const cover = metadata.album.coverGroup.image.find(image => image.size.toLowerCase() === "default"); if (cover) { meta.cover = `https://i.scdn.co/image/${cover.fileId.toLowerCase()}`; } return meta; } ws.on("message", (data) => { if (!client.connected) return; const parsed = JSON.parse(data); let message = {}; switch (parsed.event) { case "trackChanged": case "playbackResumed": message.playbackState = "PLAYING"; case "metadataAvailable": if (parsed.track) message = {...message, ...metaFromMetadata(parsed.track)}; break; case "playbackPaused": message.playbackState = "PAUSED"; break; case "sessionCleared": case "playbackEnded": case "inactiveSession": case "connectionDropped": default: message.playbackState = "STOPPED"; break; } client.publish(secrets.mqttTopic, JSON.stringify(message)); });