Skip to content

Commit 32f69a8

Browse files
committed
Use LRCLIB as backup server
Closed #142 #141 #140 #139
1 parent 77e9ce4 commit 32f69a8

File tree

5 files changed

+160
-94
lines changed

5 files changed

+160
-94
lines changed

Diff for: src/page/lrclib.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { SongLyricResult, Song } from './netease';
2+
import { request } from './request';
3+
4+
const API_HOST = `https://lrclib.net/api`;
5+
6+
export const LRCLIB_ID_TOKEN = 0.1;
7+
8+
interface LrcLibLyricRes {
9+
id: number;
10+
trackName: string;
11+
artistName: string;
12+
albumName: string;
13+
duration: number;
14+
instrumental: boolean;
15+
syncedLyrics: string;
16+
}
17+
18+
export async function fetchLRCLIBSongList(s: string, fetchOptions?: RequestInit): Promise<Song[]> {
19+
const searchQuery = new URLSearchParams({ q: s });
20+
const fetchPromise: Promise<LrcLibLyricRes[]> = request(
21+
`${API_HOST}/search?${searchQuery}`,
22+
fetchOptions,
23+
);
24+
const list = await fetchPromise;
25+
return list
26+
.filter((e) => !!e.syncedLyrics)
27+
.map((e) => ({
28+
album: { name: e.albumName },
29+
artists: e.artistName.split(',').map((name) => ({ name, alias: [] })),
30+
id: e.id + LRCLIB_ID_TOKEN,
31+
name: e.trackName,
32+
duration: e.duration * 1000,
33+
}));
34+
}
35+
36+
export async function fetchLRCLIBLyric(
37+
songId: number,
38+
fetchOptions?: RequestInit,
39+
): Promise<SongLyricResult> {
40+
const { syncedLyrics = '' }: LrcLibLyricRes = await request(
41+
`${API_HOST}/get/${songId}`,
42+
fetchOptions,
43+
);
44+
return { lrc: { lyric: syncedLyrics } };
45+
}

Diff for: src/page/lyrics.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ describe('matching track', () => {
3838
{ id: 2, artists: [{ name: '张卫健', alias: [] }], name: '夜雪', album: { name: '' } },
3939
];
4040
let search = '';
41-
const fetchData = async (s: string) => {
41+
const fetchSongList = async (s: string) => {
4242
if (!search) search = s;
4343
return songs;
4444
};
4545
const fetchTransName = async () => ({});
46-
const result = await matchingLyrics(query, { fetchData, fetchTransName });
46+
const result = await matchingLyrics(query, { fetchSongList, fetchTransName });
4747
expect(search).toBe('张卫健 夜雪');
4848
expect(result.id).toBe(2);
4949
});

Diff for: src/page/lyrics.ts

+23-87
Original file line numberDiff line numberDiff line change
@@ -4,52 +4,16 @@ import { Options, isProd } from '../common/constants';
44

55
import { configPromise } from './config';
66
import { optionsPromise } from './options';
7-
import { request } from './request';
8-
import { captureException } from './utils';
7+
import { fetchNetEaseChineseName, fetchNetEaseLyric, fetchNetEaseSongList, Song } from './netease';
8+
import { fetchLRCLIBLyric, LRCLIB_ID_TOKEN } from './lrclib';
9+
10+
export { Song } from './netease';
911

1012
export interface Query {
1113
name: string;
1214
artists: string;
1315
}
1416

15-
interface Artist {
16-
name: string;
17-
alias: string[];
18-
transNames?: string[];
19-
}
20-
interface Album {
21-
name: string;
22-
}
23-
export interface Song {
24-
id: number;
25-
name: string;
26-
artists: Artist[];
27-
album: Album;
28-
/**ms */
29-
duration?: number;
30-
}
31-
32-
interface SearchSongsResult {
33-
result?: {
34-
songs?: Song[];
35-
};
36-
}
37-
38-
interface SearchArtistsResult {
39-
result?: {
40-
artists?: Artist[];
41-
};
42-
}
43-
44-
interface SongResult {
45-
lrc?: {
46-
lyric?: string;
47-
};
48-
tlyric?: {
49-
lyric?: string;
50-
};
51-
}
52-
5317
// Convert all into English punctuation marks for processing
5418
const normalize = (s: string, emptySymbol = true) => {
5519
const result = s
@@ -121,18 +85,9 @@ const buildInSingerAliasPromise = new Promise<Record<string, string>>(async (res
12185
});
12286

12387
async function fetchChineseName(s: string, fetchOptions?: RequestInit) {
124-
const { API_HOST } = await configPromise;
12588
const singerAlias: Record<string, string> = {};
126-
const searchQuery = new URLSearchParams({
127-
keywords: s,
128-
type: '100',
129-
limit: '100',
130-
});
13189
try {
132-
const { result }: SearchArtistsResult = await request(
133-
`${API_HOST}/search?${searchQuery}`,
134-
fetchOptions,
135-
);
90+
const { result } = await fetchNetEaseChineseName(s, fetchOptions);
13691
const artists = result?.artists || [];
13792
artists.forEach((artist) => {
13893
const alias = [...artist.alias, ...(artist.transNames || [])].map(simplifiedText).sort();
@@ -143,39 +98,14 @@ async function fetchChineseName(s: string, fetchOptions?: RequestInit) {
14398
}
14499
});
145100
});
146-
} catch (e) {
147-
if (e.name !== 'AbortError') {
148-
captureException(e);
149-
}
150-
}
101+
} catch {}
151102
return singerAlias;
152103
}
153104

154-
async function fetchSongList(s: string, fetchOptions?: RequestInit): Promise<Song[]> {
155-
const { API_HOST } = await configPromise;
156-
const searchQuery = new URLSearchParams({
157-
keywords: s,
158-
type: '1',
159-
limit: '100',
160-
});
161-
const options = await optionsPromise;
162-
const fetchPromise: Promise<SearchSongsResult> = request(
163-
`${API_HOST}/search?${searchQuery}`,
164-
fetchOptions,
165-
);
166-
try {
167-
return (await fetchPromise).result?.songs || [];
168-
} catch (err) {
169-
if (!options['use-unreviewed-lyrics']) throw err;
170-
console.error(err);
171-
return [];
172-
}
173-
}
174-
175105
interface MatchingLyricsOptions {
176106
onlySearchName?: boolean;
177107
getDuration?: () => Promise<number>;
178-
fetchData?: (s: string, fetchOptions?: RequestInit) => Promise<Song[]>;
108+
fetchSongList?: (s: string, fetchOptions?: RequestInit) => Promise<Song[]>;
179109
fetchTransName?: (s: string, fetchOptions?: RequestInit) => Promise<Record<string, string>>;
180110
fetchOptions?: RequestInit;
181111
}
@@ -185,10 +115,10 @@ export async function matchingLyrics(
185115
): Promise<{ list: Song[]; id: number; score: number }> {
186116
const { name = '', artists = '' } = query;
187117
const {
188-
getDuration,
189118
onlySearchName = false,
190-
fetchData = fetchSongList,
191119
fetchTransName = fetchChineseName,
120+
fetchSongList = fetchNetEaseSongList,
121+
getDuration,
192122
fetchOptions,
193123
} = options;
194124

@@ -222,7 +152,7 @@ export async function matchingLyrics(
222152
const searchString = onlySearchName
223153
? removeSongFeat(name)
224154
: `${queryArtistsArr4.join()} ${removeSongFeat(name)}`;
225-
const songs = await fetchData(searchString, fetchOptions);
155+
const songs = await fetchSongList(searchString, fetchOptions);
226156
const list: Song[] = [];
227157
const listIdSet = new Set<number>();
228158

@@ -351,10 +281,10 @@ export async function matchingLyrics(
351281
list: listForMissingName,
352282
score: scoreForMissingName,
353283
} = await matchingLyrics(query, {
354-
getDuration,
355284
onlySearchName: true,
356-
fetchData,
357285
fetchTransName: async () => singerAlias,
286+
getDuration,
287+
fetchSongList,
358288
fetchOptions,
359289
});
360290
listForMissingName.forEach((song) => {
@@ -370,11 +300,17 @@ export async function matchingLyrics(
370300
}
371301

372302
export async function fetchLyric(songId: number, fetchOptions?: RequestInit) {
373-
const { API_HOST } = await configPromise;
374-
const { lrc, tlyric }: SongResult = await request(
375-
`${API_HOST}/lyric?${new URLSearchParams({ id: String(songId) })}`,
376-
fetchOptions,
377-
);
303+
const id = Math.floor(songId);
304+
const token = Math.round((songId % 1) * 10) / 10;
305+
const get = () => {
306+
switch (token) {
307+
case LRCLIB_ID_TOKEN:
308+
return fetchLRCLIBLyric(id, fetchOptions);
309+
default:
310+
return fetchNetEaseLyric(id, fetchOptions);
311+
}
312+
};
313+
const { lrc, tlyric } = await get();
378314
const options = await optionsPromise;
379315
return (options['lyrics-transform'] === 'Simplified' && tlyric?.lyric) || lrc?.lyric || '';
380316
}

Diff for: src/page/netease.ts

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { configPromise } from './config';
2+
import { fetchLRCLIBSongList } from './lrclib';
3+
import { request } from './request';
4+
5+
interface Artist {
6+
name: string;
7+
alias: string[];
8+
transNames?: string[];
9+
}
10+
11+
interface Album {
12+
name: string;
13+
}
14+
15+
export interface Song {
16+
id: number;
17+
name: string;
18+
artists: Artist[];
19+
album: Album;
20+
/**ms */
21+
duration?: number;
22+
}
23+
24+
interface SearchSongsResult {
25+
result?: {
26+
songs?: Song[];
27+
};
28+
}
29+
30+
interface SearchArtistsResult {
31+
result?: {
32+
artists?: Artist[];
33+
};
34+
}
35+
36+
export interface SongLyricResult {
37+
lrc?: {
38+
lyric?: string;
39+
};
40+
tlyric?: {
41+
lyric?: string;
42+
};
43+
}
44+
45+
export async function fetchNetEaseChineseName(
46+
s: string,
47+
fetchOptions?: RequestInit,
48+
): Promise<SearchArtistsResult> {
49+
const { API_HOST } = await configPromise;
50+
const searchQuery = new URLSearchParams({
51+
keywords: s,
52+
type: '100',
53+
limit: '100',
54+
});
55+
return request(`${API_HOST}/search?${searchQuery}`, fetchOptions);
56+
}
57+
58+
// auto swtch to lrclib
59+
let down = false;
60+
export async function fetchNetEaseSongList(s: string, fetchOptions?: RequestInit) {
61+
if (down) return fetchLRCLIBSongList(s, fetchOptions);
62+
63+
const { API_HOST } = await configPromise;
64+
const searchQuery = new URLSearchParams({
65+
keywords: s,
66+
type: '1',
67+
limit: '100',
68+
});
69+
70+
try {
71+
const res: SearchSongsResult = await request(`${API_HOST}/search?${searchQuery}`, fetchOptions);
72+
return res.result?.songs || [];
73+
} catch (err) {
74+
down = true;
75+
return fetchLRCLIBSongList(s, fetchOptions);
76+
}
77+
}
78+
79+
export async function fetchNetEaseLyric(
80+
songId: number,
81+
fetchOptions?: RequestInit,
82+
): Promise<SongLyricResult> {
83+
const { API_HOST } = await configPromise;
84+
return request(`${API_HOST}/lyric?${new URLSearchParams({ id: String(songId) })}`, fetchOptions);
85+
}

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export class SharedData {
136136
};
137137
}
138138

139-
private async _getLyricsFromNetEase(fetchOptions: RequestInit) {
139+
private async _getLyricsFromAPI(fetchOptions: RequestInit) {
140140
if (this._id === 0) {
141141
return null;
142142
}
@@ -158,7 +158,7 @@ export class SharedData {
158158
const fetchTransName = async () => ({});
159159
const { id } = await matchingLyrics(this.req, {
160160
onlySearchName: false,
161-
fetchData: fetchSongList,
161+
fetchSongList,
162162
fetchTransName,
163163
fetchOptions,
164164
});
@@ -213,7 +213,7 @@ export class SharedData {
213213
else if (isSelf && remoteData?.neteaseID) {
214214
this._id = remoteData.neteaseID;
215215
this._aId = this._id;
216-
this._lyrics = await this._getLyricsFromNetEase(fetchOptions);
216+
this._lyrics = await this._getLyricsFromAPI(fetchOptions);
217217
}
218218

219219
// 3. use other user upload lyrics
@@ -229,7 +229,7 @@ export class SharedData {
229229
this._aId = this._id;
230230
// Allow adjustment order
231231
const getLyricsList = [
232-
this._getLyricsFromNetEase.bind(this),
232+
this._getLyricsFromAPI.bind(this),
233233
this._getLyricsFromBuiltIn.bind(this),
234234
];
235235
try {
@@ -282,7 +282,7 @@ export class SharedData {
282282
await this._matching(fetchOptions);
283283
this.sendToContentScript();
284284
} else {
285-
this._lyrics = await this._getLyricsFromNetEase(fetchOptions);
285+
this._lyrics = await this._getLyricsFromAPI(fetchOptions);
286286
}
287287
} catch (e) {
288288
if (e.name !== 'AbortError') {

0 commit comments

Comments
 (0)