lofi icon indicating copy to clipboard operation
lofi copied to clipboard

Lyrics Window Popup

Open stamoun opened this issue 2 years ago • 11 comments

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

stamoun avatar Jan 25 '23 16:01 stamoun

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();
    });
})();

stamoun avatar Jan 25 '23 19:01 stamoun

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)

Twelve0001 avatar Jan 25 '23 20:01 Twelve0001

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.

mukulhase avatar Jul 31 '23 23:07 mukulhase

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:

image

alient12 avatar Feb 15 '24 10:02 alient12

@alient12 Why not use the above fetch instead of the cookie?

stamoun avatar Feb 19 '24 13:02 stamoun

@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.

alient12 avatar Feb 19 '24 14:02 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?

stamoun avatar Feb 19 '24 14:02 stamoun

@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:

  1. Fetching the access token using the cookie.
  2. 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.

alient12 avatar Feb 19 '24 15:02 alient12

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 avatar Feb 19 '24 15:02 stamoun

@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:

  1. Fetching the access token using the cookie.
  2. 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 avatar Mar 03 '24 14:03 stamoun

@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).

alient12 avatar Mar 06 '24 09:03 alient12