Add LrcLib Fetcher (#136)
* lrclib, do not show search/clear buttons if no fetchers configured
This commit is contained in:
parent
d7ca25525c
commit
d6e628099c
5 changed files with 141 additions and 5 deletions
|
@ -121,7 +121,7 @@ export async function getSearchResults(
|
|||
|
||||
if (!rawSongsResult) return null;
|
||||
|
||||
const songResults: InternetProviderLyricSearchResponse[] = rawSongsResult.map((song: any) => {
|
||||
const songResults: InternetProviderLyricSearchResponse[] = rawSongsResult.map((song) => {
|
||||
return {
|
||||
artist: song.artist_names,
|
||||
id: song.url,
|
||||
|
|
|
@ -13,6 +13,11 @@ import {
|
|||
getSearchResults as searchGenius,
|
||||
getLyricsBySongId as getGenius,
|
||||
} from './genius';
|
||||
import {
|
||||
query as queryLrclib,
|
||||
getSearchResults as searchLrcLib,
|
||||
getLyricsBySongId as getLrcLib,
|
||||
} from './lrclib';
|
||||
import {
|
||||
query as queryNetease,
|
||||
getSearchResults as searchNetease,
|
||||
|
@ -29,16 +34,19 @@ type CachedLyrics = Record<LyricSource, InternetProviderLyricResponse>;
|
|||
|
||||
const FETCHERS: Record<LyricSource, SongFetcher> = {
|
||||
[LyricSource.GENIUS]: queryGenius,
|
||||
[LyricSource.LRCLIB]: queryLrclib,
|
||||
[LyricSource.NETEASE]: queryNetease,
|
||||
};
|
||||
|
||||
const SEARCH_FETCHERS: Record<LyricSource, SearchFetcher> = {
|
||||
[LyricSource.GENIUS]: searchGenius,
|
||||
[LyricSource.LRCLIB]: searchLrcLib,
|
||||
[LyricSource.NETEASE]: searchNetease,
|
||||
};
|
||||
|
||||
const GET_FETCHERS: Record<LyricSource, GetFetcher> = {
|
||||
[LyricSource.GENIUS]: getGenius,
|
||||
[LyricSource.LRCLIB]: getLrcLib,
|
||||
[LyricSource.NETEASE]: getNetease,
|
||||
};
|
||||
|
||||
|
@ -61,7 +69,12 @@ const getRemoteLyrics = async (song: QueueSong) => {
|
|||
let lyricsFromSource = null;
|
||||
|
||||
for (const source of sources) {
|
||||
const params = { artist: song.artistName, name: song.name };
|
||||
const params = {
|
||||
album: song.album || song.name,
|
||||
artist: song.artistName,
|
||||
duration: song.duration,
|
||||
name: song.name,
|
||||
};
|
||||
const response = await FETCHERS[source](params);
|
||||
|
||||
if (response) {
|
||||
|
@ -92,6 +105,7 @@ const searchRemoteLyrics = async (params: LyricSearchQuery) => {
|
|||
|
||||
const results: Record<LyricSource, InternetProviderLyricSearchResponse[]> = {
|
||||
[LyricSource.GENIUS]: [],
|
||||
[LyricSource.LRCLIB]: [],
|
||||
[LyricSource.NETEASE]: [],
|
||||
};
|
||||
|
||||
|
|
119
src/main/features/core/lyrics/lrclib.ts
Normal file
119
src/main/features/core/lyrics/lrclib.ts
Normal file
|
@ -0,0 +1,119 @@
|
|||
// Credits to https://github.com/tranxuanthang/lrcget for API implementation
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import {
|
||||
InternetProviderLyricResponse,
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricSearchQuery,
|
||||
LyricSource,
|
||||
} from '../../../../renderer/api/types';
|
||||
import { orderSearchResults } from './shared';
|
||||
|
||||
const FETCH_URL = 'https://lrclib.net/api/get';
|
||||
const SEEARCH_URL = 'https://lrclib.net/api/search';
|
||||
|
||||
const TIMEOUT_MS = 5000;
|
||||
|
||||
export interface LrcLibSearchResponse {
|
||||
albumName: string;
|
||||
artistName: string;
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface LrcLibTrackResponse {
|
||||
albumName: string;
|
||||
artistName: string;
|
||||
duration: number;
|
||||
id: number;
|
||||
instrumental: boolean;
|
||||
isrc: string;
|
||||
lang: string;
|
||||
name: string;
|
||||
plainLyrics: string | null;
|
||||
releaseDate: string;
|
||||
spotifyId: string;
|
||||
syncedLyrics: string | null;
|
||||
}
|
||||
|
||||
export async function getSearchResults(
|
||||
params: LyricSearchQuery,
|
||||
): Promise<InternetProviderLyricSearchResponse[] | null> {
|
||||
let result: AxiosResponse<LrcLibSearchResponse[]>;
|
||||
|
||||
if (!params.name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
result = await axios.get<LrcLibSearchResponse[]>(SEEARCH_URL, {
|
||||
params: {
|
||||
q: params.name,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('LrcLib search request got an error!', e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!result.data) return null;
|
||||
|
||||
const songResults: InternetProviderLyricSearchResponse[] = result.data.map((song) => {
|
||||
return {
|
||||
artist: song.artistName,
|
||||
id: String(song.id),
|
||||
name: song.name,
|
||||
source: LyricSource.LRCLIB,
|
||||
};
|
||||
});
|
||||
|
||||
return orderSearchResults({ params, results: songResults });
|
||||
}
|
||||
|
||||
export async function getLyricsBySongId(songId: string): Promise<string | null> {
|
||||
let result: AxiosResponse<LrcLibTrackResponse, any>;
|
||||
|
||||
try {
|
||||
result = await axios.get<LrcLibTrackResponse>(`${FETCH_URL}/${songId}`);
|
||||
} catch (e) {
|
||||
console.error('LrcLib lyrics request got an error!', e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.data.syncedLyrics || result.data.plainLyrics || null;
|
||||
}
|
||||
|
||||
export async function query(
|
||||
params: LyricSearchQuery,
|
||||
): Promise<InternetProviderLyricResponse | null> {
|
||||
let result: AxiosResponse<LrcLibTrackResponse, any>;
|
||||
|
||||
try {
|
||||
result = await axios.get<LrcLibTrackResponse>(FETCH_URL, {
|
||||
params: {
|
||||
album_name: params.album,
|
||||
artist_name: params.artist,
|
||||
duration: params.duration,
|
||||
track_name: params.name,
|
||||
},
|
||||
timeout: TIMEOUT_MS,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('LrcLib search request got an error!', e);
|
||||
return null;
|
||||
}
|
||||
|
||||
const lyrics = result.data.syncedLyrics || result.data.plainLyrics || null;
|
||||
|
||||
if (!lyrics) {
|
||||
console.error(`Could not get lyrics on LrcLib!`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
artist: result.data.artistName,
|
||||
id: String(result.data.id),
|
||||
lyrics,
|
||||
name: result.data.name,
|
||||
source: LyricSource.LRCLIB,
|
||||
};
|
||||
}
|
|
@ -1064,7 +1064,9 @@ export const instanceOfCancellationError = (error: any) => {
|
|||
};
|
||||
|
||||
export type LyricSearchQuery = {
|
||||
album?: string;
|
||||
artist?: string;
|
||||
duration?: number;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
|
@ -1075,6 +1077,7 @@ export type LyricGetQuery = {
|
|||
|
||||
export enum LyricSource {
|
||||
GENIUS = 'Genius',
|
||||
LRCLIB = 'lrclib.net',
|
||||
NETEASE = 'NetEase',
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ interface LyricsActionsProps {
|
|||
export const LyricsActions = ({ onRemoveLyric, onSearchOverride }: LyricsActionsProps) => {
|
||||
const currentSong = useCurrentSong();
|
||||
const { setSettings } = useSettingsStoreActions();
|
||||
const { delayMs } = useLyricsSettings();
|
||||
const { delayMs, sources } = useLyricsSettings();
|
||||
|
||||
const handleLyricOffset = (e: number) => {
|
||||
setSettings({
|
||||
|
@ -34,7 +34,7 @@ export const LyricsActions = ({ onRemoveLyric, onSearchOverride }: LyricsActions
|
|||
|
||||
return (
|
||||
<>
|
||||
{isDesktop ? (
|
||||
{isDesktop && sources.length ? (
|
||||
<Button
|
||||
uppercase
|
||||
disabled={isActionsDisabled}
|
||||
|
@ -76,7 +76,7 @@ export const LyricsActions = ({ onRemoveLyric, onSearchOverride }: LyricsActions
|
|||
>
|
||||
<RiAddFill />
|
||||
</Button>
|
||||
{isDesktop ? (
|
||||
{isDesktop && sources.length ? (
|
||||
<Button
|
||||
uppercase
|
||||
disabled={isActionsDisabled}
|
||||
|
|
Reference in a new issue