lofi
lofi copied to clipboard
Lyrics Window Popup
Add the ability to display lyrics in a separate window (kind of like the track-info
window)
Suggestion from Discord
Not sure if the Spotify API allows that though, to be investigated
example retrieval of lyrics:
let playbackState = {
trackName: null,
trackAuthor: null,
trackId: null,
oldTrackId: null,
trackDuration: 0,
trackProgress: 0,
lyrics: [],
hasLyrics: false,
isPlaying: false
},
errorCount = 0;
function updatePlaybackState() {
return $.ajax({
url: "https://api.spotify.com/v1/me/player",
method: "GET",
dataType: "json",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${accessToken}`
},
statusCode: {
200: d => {
if(playbackState.trackId !== d.item.id) {
playbackState.trackName = d.item.name;
playbackState.trackAuthor = d.item.artists[0].name;
playbackState.oldTrackId = playbackState.trackId;
playbackState.trackId = d.item.id;
playbackState.trackDuration = d.item.duration_ms;
playbackState.lyrics = [];
$.ajax({
url: `https://spclient.wg.spotify.com/lyrics/v1/track/${playbackState.trackId}`,
method: "GET",
dataType: "json",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${accessToken}`
},
statusCode: {
200: d => {
// if spotify does not provide sync'd song lyrics, the script will change your status with the next line every (songLength/amountOfLines) seconds
if(d.lines[0].time == undefined) {
let timePerLyric = Math.round(playbackState.trackDuration / d.lines.length);
d.lines.reduce((p, c, i, a) => {
playbackState.lyrics.push({
time: p,
words: c.words[0].string
});
return p + timePerLyric;
}, timePerLyric);
} else {
for (let line of d.lines) {
playbackState.lyrics.push({
time: line.time,
words: line.words[0].string,
});
}
}
playbackState.hasLyrics = true;
},
404: () => {
playbackState.hasLyrics = false;
addLog(`Spotify didn't have any lyrics available for the song (${playbackState.trackName}), your status will not change.`, "Warning");
changeStatusRequest(settings.token, "");
}
}
});
}
playbackState.trackProgress = d.progress_ms;
playbackState.isPlaying = d.is_playing;
},
}
});
}
(async function playbackStateUpdater() {
let start = Date.now();
updatePlaybackState().always(async () => {
await sleep(1500 - (Date.now() - start));
playbackStateUpdater();
});
})();
example retrieval of lyrics:
let playbackState = { trackName: null, trackAuthor: null, trackId: null, oldTrackId: null, trackDuration: 0, trackProgress: 0, lyrics: [], hasLyrics: false, isPlaying: false }, errorCount = 0; function updatePlaybackState() { return $.ajax({ url: "https://api.spotify.com/v1/me/player", method: "GET", dataType: "json", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${accessToken}` }, statusCode: { 200: d => { if(playbackState.trackId !== d.item.id) { playbackState.trackName = d.item.name; playbackState.trackAuthor = d.item.artists[0].name; playbackState.oldTrackId = playbackState.trackId; playbackState.trackId = d.item.id; playbackState.trackDuration = d.item.duration_ms; playbackState.lyrics = []; $.ajax({ url: `https://spclient.wg.spotify.com/lyrics/v1/track/${playbackState.trackId}`, method: "GET", dataType: "json", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${accessToken}` }, statusCode: { 200: d => { // if spotify does not provide sync'd song lyrics, the script will change your status with the next line every (songLength/amountOfLines) seconds if(d.lines[0].time == undefined) { let timePerLyric = Math.round(playbackState.trackDuration / d.lines.length); d.lines.reduce((p, c, i, a) => { playbackState.lyrics.push({ time: p, words: c.words[0].string }); return p + timePerLyric; }, timePerLyric); } else { for (let line of d.lines) { playbackState.lyrics.push({ time: line.time, words: line.words[0].string, }); } } playbackState.hasLyrics = true; }, 404: () => { playbackState.hasLyrics = false; addLog(`Spotify didn't have any lyrics available for the song (${playbackState.trackName}), your status will not change.`, "Warning"); changeStatusRequest(settings.token, ""); } } }); } playbackState.trackProgress = d.progress_ms; playbackState.isPlaying = d.is_playing; }, } }); } (async function playbackStateUpdater() { let start = Date.now(); updatePlaybackState().always(async () => { await sleep(1500 - (Date.now() - start)); playbackStateUpdater(); }); })();
give clout (im the person who made the funny code)
Are these the same musicmatch lyrics which spotify sends to its own clients? I thought these aren't accessible through 3rd party api token and require the user's login token.
I have added this feature to my fork. It uses sp_dc
cookie, an idea I got from this repo. For now, the sp_dc
is hardcoded and the code is a bit messy. Once I finish the UI settings for the lyrics, I'll submit a PR. I would appreciate it if any experienced frontend developers could help make my code cleaner and better.
It looks like this:
@alient12 Why not use the above fetch instead of the cookie?
@stamoun When I was checking this repo, they said:
This project is probably against Spotify TOS. Use at your own risks.
So, I thought it would be safer for premium accounts to use a separate access token to prevent the risk of getting banned. It gets the access token using sp_dc
cookie.
@stamoun When I was checking this repo, they said:
This project is probably against Spotify TOS. Use at your own risks.
So, I thought it would be safer for premium accounts to use a separate access token to prevent the risk of getting banned. It gets the access token using
sp_dc
cookie.
That repo you just linked uses sp_dc
too no? How is your implementation different?
@stamoun When I was checking this repo, they said:
This project is probably against Spotify TOS. Use at your own risks.
So, I thought it would be safer for premium accounts to use a separate access token to prevent the risk of getting banned. It gets the access token using
sp_dc
cookie.That repo you just linked uses
sp_dc
too no? How is your implementation different?
It's not different; I simply rewrote their Python code in TypeScript. My implementation consists of two parts:
- Fetching the access token using the cookie.
- Fetching the lyrics with that token. This part performs the same function as the aforementioned code. If I had seen it sooner, I would have used it.
Ok, all in all I think I'd rather try the code posted in this issue instead of something that might break ToS.
That said, thanks a ton for this (non-trivial) contribution!!
As I said, I'm a bit short on time and I want to give this a proper review (even maybe branch off of your branch for testing/modification).
Very happy that you put in the effort to work on this, thanks @alient12 ❤️
@stamoun When I was checking this repo, they said:
This project is probably against Spotify TOS. Use at your own risks.
So, I thought it would be safer for premium accounts to use a separate access token to prevent the risk of getting banned. It gets the access token using
sp_dc
cookie.That repo you just linked uses
sp_dc
too no? How is your implementation different?It's not different; I simply rewrote their Python code in TypeScript. My implementation consists of two parts:
- Fetching the access token using the cookie.
- Fetching the lyrics with that token. This part performs the same function as the aforementioned code. If I had seen it sooner, I would have used it.
Haven't had a chance to dig in this PR, but I'm wondering... Can't we just reuse the access token stored in the config? Why use the cookie at all?
@stamoun Sorry for late reply. We can reuse it. However, providing users with the flexibility to choose between a default access token and a separate access token for lyrics, whether through cookies or by opening a browser (similar to default authentication), seems like an even better solution. I couldn’t locate an official API specifically for lyrics. As a result, both the cookie-based code and your script could potentially expose users to violations of the terms of service (TOS).