From d6e628099c6e513177106600e7eafa04e3b7d443 Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Sun, 11 Jun 2023 19:45:50 +0000 Subject: [PATCH] Add LrcLib Fetcher (#136) * lrclib, do not show search/clear buttons if no fetchers configured --- src/main/features/core/lyrics/genius.ts | 2 +- src/main/features/core/lyrics/index.ts | 16 ++- src/main/features/core/lyrics/lrclib.ts | 119 ++++++++++++++++++ src/renderer/api/types.ts | 3 + .../features/lyrics/lyrics-actions.tsx | 6 +- 5 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 src/main/features/core/lyrics/lrclib.ts diff --git a/src/main/features/core/lyrics/genius.ts b/src/main/features/core/lyrics/genius.ts index a2823a80..7ed0f38e 100644 --- a/src/main/features/core/lyrics/genius.ts +++ b/src/main/features/core/lyrics/genius.ts @@ -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, diff --git a/src/main/features/core/lyrics/index.ts b/src/main/features/core/lyrics/index.ts index 5b0d272c..c1fb710e 100644 --- a/src/main/features/core/lyrics/index.ts +++ b/src/main/features/core/lyrics/index.ts @@ -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; const FETCHERS: Record = { [LyricSource.GENIUS]: queryGenius, + [LyricSource.LRCLIB]: queryLrclib, [LyricSource.NETEASE]: queryNetease, }; const SEARCH_FETCHERS: Record = { [LyricSource.GENIUS]: searchGenius, + [LyricSource.LRCLIB]: searchLrcLib, [LyricSource.NETEASE]: searchNetease, }; const GET_FETCHERS: Record = { [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.GENIUS]: [], + [LyricSource.LRCLIB]: [], [LyricSource.NETEASE]: [], }; diff --git a/src/main/features/core/lyrics/lrclib.ts b/src/main/features/core/lyrics/lrclib.ts new file mode 100644 index 00000000..157a3231 --- /dev/null +++ b/src/main/features/core/lyrics/lrclib.ts @@ -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 { + let result: AxiosResponse; + + if (!params.name) { + return null; + } + + try { + result = await axios.get(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 { + let result: AxiosResponse; + + try { + result = await axios.get(`${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 { + let result: AxiosResponse; + + try { + result = await axios.get(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, + }; +} diff --git a/src/renderer/api/types.ts b/src/renderer/api/types.ts index b72e6344..06b7c353 100644 --- a/src/renderer/api/types.ts +++ b/src/renderer/api/types.ts @@ -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', } diff --git a/src/renderer/features/lyrics/lyrics-actions.tsx b/src/renderer/features/lyrics/lyrics-actions.tsx index beebadf7..5b28397c 100644 --- a/src/renderer/features/lyrics/lyrics-actions.tsx +++ b/src/renderer/features/lyrics/lyrics-actions.tsx @@ -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 ? ( - {isDesktop ? ( + {isDesktop && sources.length ? (