Add LrcLib Fetcher (#136)

* lrclib, do not show search/clear buttons if no fetchers configured
This commit is contained in:
Kendall Garner 2023-06-11 19:45:50 +00:00 committed by GitHub
parent d7ca25525c
commit d6e628099c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 141 additions and 5 deletions

View file

@ -121,7 +121,7 @@ export async function getSearchResults(
if (!rawSongsResult) return null; if (!rawSongsResult) return null;
const songResults: InternetProviderLyricSearchResponse[] = rawSongsResult.map((song: any) => { const songResults: InternetProviderLyricSearchResponse[] = rawSongsResult.map((song) => {
return { return {
artist: song.artist_names, artist: song.artist_names,
id: song.url, id: song.url,

View file

@ -13,6 +13,11 @@ import {
getSearchResults as searchGenius, getSearchResults as searchGenius,
getLyricsBySongId as getGenius, getLyricsBySongId as getGenius,
} from './genius'; } from './genius';
import {
query as queryLrclib,
getSearchResults as searchLrcLib,
getLyricsBySongId as getLrcLib,
} from './lrclib';
import { import {
query as queryNetease, query as queryNetease,
getSearchResults as searchNetease, getSearchResults as searchNetease,
@ -29,16 +34,19 @@ type CachedLyrics = Record<LyricSource, InternetProviderLyricResponse>;
const FETCHERS: Record<LyricSource, SongFetcher> = { const FETCHERS: Record<LyricSource, SongFetcher> = {
[LyricSource.GENIUS]: queryGenius, [LyricSource.GENIUS]: queryGenius,
[LyricSource.LRCLIB]: queryLrclib,
[LyricSource.NETEASE]: queryNetease, [LyricSource.NETEASE]: queryNetease,
}; };
const SEARCH_FETCHERS: Record<LyricSource, SearchFetcher> = { const SEARCH_FETCHERS: Record<LyricSource, SearchFetcher> = {
[LyricSource.GENIUS]: searchGenius, [LyricSource.GENIUS]: searchGenius,
[LyricSource.LRCLIB]: searchLrcLib,
[LyricSource.NETEASE]: searchNetease, [LyricSource.NETEASE]: searchNetease,
}; };
const GET_FETCHERS: Record<LyricSource, GetFetcher> = { const GET_FETCHERS: Record<LyricSource, GetFetcher> = {
[LyricSource.GENIUS]: getGenius, [LyricSource.GENIUS]: getGenius,
[LyricSource.LRCLIB]: getLrcLib,
[LyricSource.NETEASE]: getNetease, [LyricSource.NETEASE]: getNetease,
}; };
@ -61,7 +69,12 @@ const getRemoteLyrics = async (song: QueueSong) => {
let lyricsFromSource = null; let lyricsFromSource = null;
for (const source of sources) { 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); const response = await FETCHERS[source](params);
if (response) { if (response) {
@ -92,6 +105,7 @@ const searchRemoteLyrics = async (params: LyricSearchQuery) => {
const results: Record<LyricSource, InternetProviderLyricSearchResponse[]> = { const results: Record<LyricSource, InternetProviderLyricSearchResponse[]> = {
[LyricSource.GENIUS]: [], [LyricSource.GENIUS]: [],
[LyricSource.LRCLIB]: [],
[LyricSource.NETEASE]: [], [LyricSource.NETEASE]: [],
}; };

View 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,
};
}

View file

@ -1064,7 +1064,9 @@ export const instanceOfCancellationError = (error: any) => {
}; };
export type LyricSearchQuery = { export type LyricSearchQuery = {
album?: string;
artist?: string; artist?: string;
duration?: number;
name?: string; name?: string;
}; };
@ -1075,6 +1077,7 @@ export type LyricGetQuery = {
export enum LyricSource { export enum LyricSource {
GENIUS = 'Genius', GENIUS = 'Genius',
LRCLIB = 'lrclib.net',
NETEASE = 'NetEase', NETEASE = 'NetEase',
} }

View file

@ -18,7 +18,7 @@ interface LyricsActionsProps {
export const LyricsActions = ({ onRemoveLyric, onSearchOverride }: LyricsActionsProps) => { export const LyricsActions = ({ onRemoveLyric, onSearchOverride }: LyricsActionsProps) => {
const currentSong = useCurrentSong(); const currentSong = useCurrentSong();
const { setSettings } = useSettingsStoreActions(); const { setSettings } = useSettingsStoreActions();
const { delayMs } = useLyricsSettings(); const { delayMs, sources } = useLyricsSettings();
const handleLyricOffset = (e: number) => { const handleLyricOffset = (e: number) => {
setSettings({ setSettings({
@ -34,7 +34,7 @@ export const LyricsActions = ({ onRemoveLyric, onSearchOverride }: LyricsActions
return ( return (
<> <>
{isDesktop ? ( {isDesktop && sources.length ? (
<Button <Button
uppercase uppercase
disabled={isActionsDisabled} disabled={isActionsDisabled}
@ -76,7 +76,7 @@ export const LyricsActions = ({ onRemoveLyric, onSearchOverride }: LyricsActions
> >
<RiAddFill /> <RiAddFill />
</Button> </Button>
{isDesktop ? ( {isDesktop && sources.length ? (
<Button <Button
uppercase uppercase
disabled={isActionsDisabled} disabled={isActionsDisabled}