Skip to content

Commit 77e9ce4

Browse files
committed
Enable spotify built-in lyrics
Closed #55
1 parent dfceb61 commit 77e9ce4

File tree

4 files changed

+121
-48
lines changed

4 files changed

+121
-48
lines changed

Diff for: src/page/lyrics.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ export interface Query {
1212
artists: string;
1313
}
1414

15-
export interface Artist {
15+
interface Artist {
1616
name: string;
1717
alias: string[];
1818
transNames?: string[];
1919
}
20-
export interface Album {
20+
interface Album {
2121
name: string;
2222
}
2323
export interface Song {
@@ -159,15 +159,14 @@ async function fetchSongList(s: string, fetchOptions?: RequestInit): Promise<Son
159159
limit: '100',
160160
});
161161
const options = await optionsPromise;
162-
const fetchPromise = request(`${API_HOST}/search?${searchQuery}`, fetchOptions);
163-
if (!options['use-unreviewed-lyrics']) {
164-
const { result }: SearchSongsResult = await fetchPromise;
165-
return result?.songs || [];
166-
}
162+
const fetchPromise: Promise<SearchSongsResult> = request(
163+
`${API_HOST}/search?${searchQuery}`,
164+
fetchOptions,
165+
);
167166
try {
168-
const { result }: SearchSongsResult = await fetchPromise;
169-
return result?.songs || [];
167+
return (await fetchPromise).result?.songs || [];
170168
} catch (err) {
169+
if (!options['use-unreviewed-lyrics']) throw err;
171170
console.error(err);
172171
return [];
173172
}

Diff for: src/page/observer.ts

+35-31
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { insetLyricsBtn } from './btn';
44
import { sharedData } from './share-data';
55
import { generateCover } from './cover';
66
import { captureException, documentQueryHasSelector } from './utils';
7-
import { SpotifyTrackLyrics, SpotifyTrackMetadata } from './types';
7+
import type { SpotifyTrackMetadata, SpotifyTrackColorLyrics } from './types';
88

99
let loginResolve: (value?: unknown) => void;
1010
export const loggedPromise = new Promise((res) => (loginResolve = res));
@@ -133,40 +133,44 @@ let latestHeader = new Headers();
133133
globalThis.fetch = async (...rest) => {
134134
const res = await originFetch(...rest);
135135
const url = new URL(rest[0] instanceof Request ? rest[0].url : rest[0], location.origin);
136+
136137
const spotifyAPI = 'https://spclient.wg.spotify.com';
137138
if (url.origin === spotifyAPI && url.pathname.startsWith('/metadata/4/track/')) {
138139
latestHeader = new Headers(rest[0] instanceof Request ? rest[0].headers : rest[1]?.headers);
139140

140-
// const metadata: SpotifyTrackMetadata = await res.clone().json();
141-
// const { name = '', artist = [], duration = 0, canonical_uri, has_lyrics } = metadata || {};
142-
// const trackId = canonical_uri?.match(/spotify:track:([^:]*)/)?.[1];
143-
// // match artists element textContent
144-
// const artists = artist?.map((e) => e?.name).join(', ');
145-
// sharedData.cacheTrackAndLyrics({
146-
// name,
147-
// artists,
148-
// duration: duration / 1000,
149-
// getLyrics: has_lyrics
150-
// ? async ({ signal }) => {
151-
// const res = await fetch(`${spotifyAPI}/lyrics/v1/track/${trackId}?market=from_token`, {
152-
// headers: latestHeader,
153-
// signal,
154-
// });
155-
// if (!res.ok) return '';
156-
// const spLyrics: SpotifyTrackLyrics = await res.json();
157-
// if (spLyrics.kind !== 'LINE') return '';
158-
// return spLyrics.lines
159-
// .map(({ time, words }) =>
160-
// words.map(({ string }) => {
161-
// const sec = time / 1000;
162-
// return `[${Math.floor(sec / 60)}:${sec % 60}]\n${string}`;
163-
// }),
164-
// )
165-
// .flat()
166-
// .join('\n');
167-
// }
168-
// : undefined,
169-
// });
141+
const metadata: SpotifyTrackMetadata = await res.clone().json();
142+
const { name = '', artist = [], duration = 0, canonical_uri, has_lyrics } = metadata || {};
143+
const trackId = canonical_uri?.match(/spotify:track:([^:]*)/)?.[1];
144+
// match artists element textContent
145+
const artists = artist?.map((e) => e?.name).join(', ');
146+
sharedData.cacheTrackAndLyrics({
147+
name,
148+
artists,
149+
duration: duration / 1000,
150+
getLyrics: has_lyrics
151+
? async ({ signal }) => {
152+
// https://github.com/mantou132/Spotify-Lyrics/issues/55
153+
const res = await fetch(
154+
`${spotifyAPI}/color-lyrics/v2/track/${trackId}?format=json&vocalRemoval=false&market=from_token`,
155+
{
156+
headers: latestHeader,
157+
signal,
158+
},
159+
);
160+
if (!res.ok) return '';
161+
const { lyrics: spLyrics }: SpotifyTrackColorLyrics = await res.json();
162+
console.log(spLyrics);
163+
if (spLyrics.syncType !== 'LINE_SYNCED') return '';
164+
return spLyrics.lines
165+
.map(({ startTimeMs, words }) => {
166+
const sec = Number(startTimeMs) / 1000;
167+
return `[${Math.floor(sec / 60)}:${sec % 60}]\n${words}`;
168+
})
169+
.flat()
170+
.join('\n');
171+
}
172+
: undefined,
173+
});
170174
}
171175
return res;
172176
};

Diff for: src/page/share-data.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,8 @@ export class SharedData {
150150
}
151151

152152
private async _getLyricsFromBuiltIn(fetchOptions: RequestInit) {
153-
return parseLyrics(
154-
await getCache(this.name, this.artists).getLyrics(fetchOptions),
155-
await this._getParseLyricsOptions(),
156-
);
153+
const lrc = await getCache(this.name, this.artists).getLyrics(fetchOptions);
154+
return parseLyrics(lrc, await this._getParseLyricsOptions());
157155
}
158156

159157
private async _fetchHighlight(fetchOptions: RequestInit) {
@@ -204,22 +202,35 @@ export class SharedData {
204202
this._list = list;
205203
const reviewed = options['use-unreviewed-lyrics'] === 'on' || remoteData?.reviewed;
206204
const isSelf = remoteData?.user === options.cid;
205+
206+
// 1. use uploaded lyrics
207207
if (isSelf && remoteData?.lyric) {
208208
this._lyrics = parseLyrics(remoteData.lyric, parseLyricsOptions);
209209
sendEvent(options.cid, events.useRemoteLyrics);
210-
} else if (isSelf && remoteData?.neteaseID) {
210+
}
211+
212+
// 2. use selected lyrics
213+
else if (isSelf && remoteData?.neteaseID) {
211214
this._id = remoteData.neteaseID;
212215
this._aId = this._id;
213216
this._lyrics = await this._getLyricsFromNetEase(fetchOptions);
214-
} else if (reviewed && remoteData?.lyric) {
217+
}
218+
219+
// 3. use other user upload lyrics
220+
else if (reviewed && remoteData?.lyric) {
215221
this._lyrics = parseLyrics(remoteData.lyric, parseLyricsOptions);
216222
sendEvent(options.cid, events.useRemoteLyrics);
217-
} else {
223+
}
224+
225+
// **default behavior**
226+
// 4. use build-in lyrics or netease lyrics
227+
else {
218228
this._id = (reviewed ? remoteData?.neteaseID || id : id || remoteData?.neteaseID) || 0;
219229
this._aId = this._id;
230+
// Allow adjustment order
220231
const getLyricsList = [
221-
this._getLyricsFromBuiltIn.bind(this),
222232
this._getLyricsFromNetEase.bind(this),
233+
this._getLyricsFromBuiltIn.bind(this),
223234
];
224235
try {
225236
this._lyrics = await getLyricsList[0](fetchOptions);
@@ -240,6 +251,8 @@ export class SharedData {
240251
const ev = (performance.now() - startTime).toFixed();
241252
sendEvent(options.cid, { ev, ...events.loadLyrics }, { cd1: this.cd1 });
242253
}
254+
255+
// 5. use song highlight
243256
this._fetchHighlight(fetchOptions);
244257
}
245258

Diff for: src/page/types.ts

+57
Original file line numberDiff line numberDiff line change
@@ -419,3 +419,60 @@ const spotifyTrackLyrics = {
419419
};
420420

421421
export type SpotifyTrackLyrics = typeof spotifyTrackLyrics;
422+
423+
const spotifyTrackColorLyrics = {
424+
lyrics: {
425+
syncType: 'UNSYNCED',
426+
lines: [
427+
{
428+
startTimeMs: '0',
429+
words: 'Help me see',
430+
syllables: [],
431+
endTimeMs: '0',
432+
},
433+
{
434+
startTimeMs: '0',
435+
words: 'Maybe I should be my own thing',
436+
syllables: [],
437+
endTimeMs: '0',
438+
},
439+
{
440+
startTimeMs: '0',
441+
words: "'Cause I'm guessing",
442+
syllables: [],
443+
endTimeMs: '0',
444+
},
445+
{
446+
startTimeMs: '0',
447+
words: 'That time will pass',
448+
syllables: [],
449+
endTimeMs: '0',
450+
},
451+
{
452+
startTimeMs: '0',
453+
words: "There'll be nothing left inside me",
454+
syllables: [],
455+
endTimeMs: '0',
456+
},
457+
],
458+
provider: 'MusixMatch',
459+
providerLyricsId: '95349346',
460+
providerDisplayName: 'Musixmatch',
461+
syncLyricsUri: '',
462+
isDenseTypeface: false,
463+
alternatives: [],
464+
language: 'en',
465+
isRtlLanguage: false,
466+
showUpsell: true,
467+
capStatus: 'NONE',
468+
isSnippet: true,
469+
},
470+
colors: {
471+
background: -9013642,
472+
text: -16777216,
473+
highlightText: -1,
474+
},
475+
hasVocalRemoval: false,
476+
};
477+
478+
export type SpotifyTrackColorLyrics = typeof spotifyTrackColorLyrics;

0 commit comments

Comments
 (0)