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;
const songResults: InternetProviderLyricSearchResponse[] = rawSongsResult.map((song: any) => {
const songResults: InternetProviderLyricSearchResponse[] = rawSongsResult.map((song) => {
return {
artist: song.artist_names,
id: song.url,

View file

@ -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]: [],
};

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 = {
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',
}

View file

@ -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}