// NAME: Spotify Info // AUTHOR: Ricky12Awesome // DESCRIPTION: Get song information for other apps to use /// // ----- SETTINGS ----- // Change this if you want to use a custom port // make sure to also change it on the other end // // default: 19532 const port = 19532; // How often should this check for connections? // // default: 1000 const checkConnectionInterval = 1000; // How often should this send position updates in milliseconds // // can be changed by the other end // // default: 1000 let progressUpdateInterval = 1000; // -------------------- function SpotifyInfo() { if (!Spicetify.CosmosAsync || !Spicetify.Platform) { setTimeout(SpotifyInfo, 500); return; } let ws; let ws_connected; let ws_data; let storage = { uid: undefined, uri: undefined, state: undefined, duration: undefined, title: undefined, album: undefined, artist: undefined, cover: undefined, background: undefined }; async function updateStorage(data) { if (!data?.track?.metadata) { return; } const meta = data.track.metadata; const local = { uid: undefined, uri: undefined, state: undefined, duration: undefined, title: undefined, album: undefined, artist: undefined, cover: undefined, background: undefined }; local.uid = data.track.uid; local.uri = data.track.uri; local.state = data.is_paused ? 1 : 2; local.duration = meta.duration; local.title = meta.title; local.album = meta.album_title; local.artist = meta.artist_name; const cover = meta.image_xlarge_url; local.cover = cover?.indexOf("localfile") === -1 ? "https://i.scdn.co/image/" + cover.substring(cover.lastIndexOf(":") + 1) : undefined; try { const res = await Spicetify.CosmosAsync.get( `https://api-partner.spotify.com/pathfinder/v1/query?operationName=queryArtistOverview&variables=%7B%22uri%22%3A%22${meta.artist_uri}%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22433e28d1e949372d3ca3aa6c47975cff428b5dc37b12f5325d9213accadf770a%22%7D%7D` ) local.background = res.data.artist.visuals.headerImage.sources[0].url } catch (e) { local.background = undefined; } // so it doesn't spam multiple messages if (local.uid !== storage.uid) { storage = local; ws_data = [ local.uid, local.uri, local.state ?? 0, local.duration, // just for some weird edge case it messes things up local.title.replace(";", "${#{#{SEMI_COLON}#}#}$"), local.album.replace(";", "${#{#{SEMI_COLON}#}#}$"), local.artist.replace(";", "${#{#{SEMI_COLON}#}#}$"), local.cover ?? "NONE", local.background ?? "NONE" ].join(";"); if (ws_connected) { ws.send(`TRACK_CHANGED;${ws_data}`); } } else if (local.state !== storage.state) { storage.state = local.state; if (ws_connected) { ws.send(`STATE_CHANGED;${local.state ?? 0}`); if (storage.state !== 2) { ws.send(`PROGRESS_CHANGED;${Spicetify.Player.getProgressPercent()}`); } } } } Spicetify.CosmosAsync.sub("sp://player/v2/main", updateStorage); function init() { ws_connected = false; ws = new WebSocket(`ws://127.0.0.1:${port}`); ws.onopen = () => { ws_connected = true; if (ws_data) ws.send(`TRACK_CHANGED;${ws_data}`); }; ws.onclose = () => { ws_connected = false; setTimeout(init, checkConnectionInterval); }; ws.onmessage = (message) => { /** * @type {Array} */ let data = message.data.split(";"); if (data.length === 0) { return; } if (data[0] === "SET_PROGRESS_INTERVAL") { let n = Number.parseInt(data[1]); if (!isNaN(n)) { progressUpdateInterval = n; } } }; } init(); const progressInterval = () => { if (ws_connected && storage.state === 2) { ws.send(`PROGRESS_CHANGED;${Spicetify.Player.getProgressPercent()}`) } setTimeout(progressInterval, progressUpdateInterval) }; setTimeout(progressInterval, progressUpdateInterval); window.onbeforeunload = () => { ws_connected = false; ws.onclose = null; ws.close(); } } SpotifyInfo()