Lint all files

This commit is contained in:
jeffvli 2023-07-01 19:10:05 -07:00
parent 22af76b4d6
commit 30e52ebb54
334 changed files with 76519 additions and 75932 deletions

View file

@ -1,10 +1,10 @@
import axios, { AxiosResponse } from 'axios';
import { load } from 'cheerio';
import {
LyricSource,
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
LyricSource,
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
} from '../../../../renderer/api/types';
import { orderSearchResults } from './shared';
@ -13,197 +13,197 @@ const SEARCH_URL = 'https://genius.com/api/search/song';
// Adapted from https://github.com/NyaomiDEV/Sunamu/blob/master/src/main/lyricproviders/genius.ts
export interface GeniusResponse {
meta: Meta;
response: Response;
meta: Meta;
response: Response;
}
export interface Meta {
status: number;
status: number;
}
export interface Response {
next_page: number;
sections: Section[];
next_page: number;
sections: Section[];
}
export interface Section {
hits: Hit[];
type: string;
hits: Hit[];
type: string;
}
export interface Hit {
highlights: any[];
index: string;
result: Result;
type: string;
highlights: any[];
index: string;
result: Result;
type: string;
}
export interface Result {
_type: string;
annotation_count: number;
api_path: string;
artist_names: string;
featured_artists: any[];
full_title: string;
header_image_thumbnail_url: string;
header_image_url: string;
id: number;
instrumental: boolean;
language: string;
lyrics_owner_id: number;
lyrics_state: string;
lyrics_updated_at: number;
path: string;
primary_artist: PrimaryArtist;
pyongs_count: null;
relationships_index_url: string;
release_date_components: ReleaseDateComponents;
release_date_for_display: string;
release_date_with_abbreviated_month_for_display: string;
song_art_image_thumbnail_url: string;
song_art_image_url: string;
stats: Stats;
title: string;
title_with_featured: string;
updated_by_human_at: number;
url: string;
_type: string;
annotation_count: number;
api_path: string;
artist_names: string;
featured_artists: any[];
full_title: string;
header_image_thumbnail_url: string;
header_image_url: string;
id: number;
instrumental: boolean;
language: string;
lyrics_owner_id: number;
lyrics_state: string;
lyrics_updated_at: number;
path: string;
primary_artist: PrimaryArtist;
pyongs_count: null;
relationships_index_url: string;
release_date_components: ReleaseDateComponents;
release_date_for_display: string;
release_date_with_abbreviated_month_for_display: string;
song_art_image_thumbnail_url: string;
song_art_image_url: string;
stats: Stats;
title: string;
title_with_featured: string;
updated_by_human_at: number;
url: string;
}
export interface PrimaryArtist {
_type: string;
api_path: string;
header_image_url: string;
id: number;
image_url: string;
index_character: string;
is_meme_verified: boolean;
is_verified: boolean;
name: string;
slug: string;
url: string;
_type: string;
api_path: string;
header_image_url: string;
id: number;
image_url: string;
index_character: string;
is_meme_verified: boolean;
is_verified: boolean;
name: string;
slug: string;
url: string;
}
export interface ReleaseDateComponents {
day: number;
month: number;
year: number;
day: number;
month: number;
year: number;
}
export interface Stats {
hot: boolean;
unreviewed_annotations: number;
hot: boolean;
unreviewed_annotations: number;
}
export async function getSearchResults(
params: LyricSearchQuery,
params: LyricSearchQuery,
): Promise<InternetProviderLyricSearchResponse[] | null> {
let result: AxiosResponse<GeniusResponse>;
let result: AxiosResponse<GeniusResponse>;
const searchQuery = [params.artist, params.name].join(' ');
const searchQuery = [params.artist, params.name].join(' ');
if (!searchQuery) {
return null;
}
if (!searchQuery) {
return null;
}
try {
result = await axios.get(SEARCH_URL, {
params: {
per_page: '5',
q: searchQuery,
},
try {
result = await axios.get(SEARCH_URL, {
params: {
per_page: '5',
q: searchQuery,
},
});
} catch (e) {
console.error('Genius search request got an error!', e);
return null;
}
const rawSongsResult = result.data.response?.sections?.[0]?.hits?.map((hit) => hit.result);
if (!rawSongsResult) return null;
const songResults: InternetProviderLyricSearchResponse[] = rawSongsResult.map((song) => {
return {
artist: song.artist_names,
id: song.url,
name: song.full_title,
source: LyricSource.GENIUS,
};
});
} catch (e) {
console.error('Genius search request got an error!', e);
return null;
}
const rawSongsResult = result.data.response?.sections?.[0]?.hits?.map((hit) => hit.result);
if (!rawSongsResult) return null;
const songResults: InternetProviderLyricSearchResponse[] = rawSongsResult.map((song) => {
return {
artist: song.artist_names,
id: song.url,
name: song.full_title,
source: LyricSource.GENIUS,
};
});
return orderSearchResults({ params, results: songResults });
return orderSearchResults({ params, results: songResults });
}
async function getSongId(
params: LyricSearchQuery,
params: LyricSearchQuery,
): Promise<Omit<InternetProviderLyricResponse, 'lyrics'> | null> {
let result: AxiosResponse<GeniusResponse>;
try {
result = await axios.get(SEARCH_URL, {
params: {
per_page: '1',
q: `${params.artist} ${params.name}`,
},
});
} catch (e) {
console.error('Genius search request got an error!', e);
return null;
}
let result: AxiosResponse<GeniusResponse>;
try {
result = await axios.get(SEARCH_URL, {
params: {
per_page: '1',
q: `${params.artist} ${params.name}`,
},
});
} catch (e) {
console.error('Genius search request got an error!', e);
return null;
}
const hit = result.data.response?.sections?.[0]?.hits?.[0]?.result;
const hit = result.data.response?.sections?.[0]?.hits?.[0]?.result;
if (!hit) {
return null;
}
if (!hit) {
return null;
}
return {
artist: hit.artist_names,
id: hit.url,
name: hit.full_title,
source: LyricSource.GENIUS,
};
return {
artist: hit.artist_names,
id: hit.url,
name: hit.full_title,
source: LyricSource.GENIUS,
};
}
export async function getLyricsBySongId(url: string): Promise<string | null> {
let result: AxiosResponse<string, any>;
try {
result = await axios.get<string>(url, { responseType: 'text' });
} catch (e) {
console.error('Genius lyrics request got an error!', e);
return null;
}
let result: AxiosResponse<string, any>;
try {
result = await axios.get<string>(url, { responseType: 'text' });
} catch (e) {
console.error('Genius lyrics request got an error!', e);
return null;
}
const $ = load(result.data.split('<br/>').join('\n'));
const lyricsDiv = $('div.lyrics');
const $ = load(result.data.split('<br/>').join('\n'));
const lyricsDiv = $('div.lyrics');
if (lyricsDiv.length > 0) return lyricsDiv.text().trim();
if (lyricsDiv.length > 0) return lyricsDiv.text().trim();
const lyricSections = $('div[class^=Lyrics__Container]')
.map((_, e) => $(e).text())
.toArray()
.join('\n');
return lyricSections;
const lyricSections = $('div[class^=Lyrics__Container]')
.map((_, e) => $(e).text())
.toArray()
.join('\n');
return lyricSections;
}
export async function query(
params: LyricSearchQuery,
params: LyricSearchQuery,
): Promise<InternetProviderLyricResponse | null> {
const response = await getSongId(params);
if (!response) {
console.error('Could not find the song on Genius!');
return null;
}
const response = await getSongId(params);
if (!response) {
console.error('Could not find the song on Genius!');
return null;
}
const lyrics = await getLyricsBySongId(response.id);
if (!lyrics) {
console.error('Could not get lyrics on Genius!');
return null;
}
const lyrics = await getLyricsBySongId(response.id);
if (!lyrics) {
console.error('Could not get lyrics on Genius!');
return null;
}
return {
artist: response.artist,
id: response.id,
lyrics,
name: response.name,
source: LyricSource.GENIUS,
};
return {
artist: response.artist,
id: response.id,
lyrics,
name: response.name,
source: LyricSource.GENIUS,
};
}

View file

@ -1,53 +1,53 @@
import { ipcMain } from 'electron';
import {
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
QueueSong,
LyricGetQuery,
LyricSource,
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
QueueSong,
LyricGetQuery,
LyricSource,
} from '../../../../renderer/api/types';
import { store } from '../settings/index';
import {
query as queryGenius,
getSearchResults as searchGenius,
getLyricsBySongId as getGenius,
query as queryGenius,
getSearchResults as searchGenius,
getLyricsBySongId as getGenius,
} from './genius';
import {
query as queryLrclib,
getSearchResults as searchLrcLib,
getLyricsBySongId as getLrcLib,
query as queryLrclib,
getSearchResults as searchLrcLib,
getLyricsBySongId as getLrcLib,
} from './lrclib';
import {
query as queryNetease,
getSearchResults as searchNetease,
getLyricsBySongId as getNetease,
query as queryNetease,
getSearchResults as searchNetease,
getLyricsBySongId as getNetease,
} from './netease';
type SongFetcher = (params: LyricSearchQuery) => Promise<InternetProviderLyricResponse | null>;
type SearchFetcher = (
params: LyricSearchQuery,
params: LyricSearchQuery,
) => Promise<InternetProviderLyricSearchResponse[] | null>;
type GetFetcher = (id: string) => Promise<string | null>;
type CachedLyrics = Record<LyricSource, InternetProviderLyricResponse>;
const FETCHERS: Record<LyricSource, SongFetcher> = {
[LyricSource.GENIUS]: queryGenius,
[LyricSource.LRCLIB]: queryLrclib,
[LyricSource.NETEASE]: queryNetease,
[LyricSource.GENIUS]: queryGenius,
[LyricSource.LRCLIB]: queryLrclib,
[LyricSource.NETEASE]: queryNetease,
};
const SEARCH_FETCHERS: Record<LyricSource, SearchFetcher> = {
[LyricSource.GENIUS]: searchGenius,
[LyricSource.LRCLIB]: searchLrcLib,
[LyricSource.NETEASE]: searchNetease,
[LyricSource.GENIUS]: searchGenius,
[LyricSource.LRCLIB]: searchLrcLib,
[LyricSource.NETEASE]: searchNetease,
};
const GET_FETCHERS: Record<LyricSource, GetFetcher> = {
[LyricSource.GENIUS]: getGenius,
[LyricSource.LRCLIB]: getLrcLib,
[LyricSource.NETEASE]: getNetease,
[LyricSource.GENIUS]: getGenius,
[LyricSource.LRCLIB]: getLrcLib,
[LyricSource.NETEASE]: getNetease,
};
const MAX_CACHED_ITEMS = 10;
@ -55,95 +55,95 @@ const MAX_CACHED_ITEMS = 10;
const lyricCache = new Map<string, CachedLyrics>();
const getRemoteLyrics = async (song: QueueSong) => {
const sources = store.get('lyrics', []) as LyricSource[];
const sources = store.get('lyrics', []) as LyricSource[];
const cached = lyricCache.get(song.id);
const cached = lyricCache.get(song.id);
if (cached) {
for (const source of sources) {
const data = cached[source];
if (data) return data;
}
}
let lyricsFromSource = null;
if (cached) {
for (const source of sources) {
const data = cached[source];
if (data) return data;
const params = {
album: song.album || song.name,
artist: song.artistName,
duration: song.duration,
name: song.name,
};
const response = await FETCHERS[source](params);
if (response) {
const newResult = cached
? {
...cached,
[source]: response,
}
: ({ [source]: response } as CachedLyrics);
if (lyricCache.size === MAX_CACHED_ITEMS && cached === undefined) {
const toRemove = lyricCache.keys().next().value;
lyricCache.delete(toRemove);
}
lyricCache.set(song.id, newResult);
lyricsFromSource = response;
break;
}
}
}
let lyricsFromSource = null;
for (const source of sources) {
const params = {
album: song.album || song.name,
artist: song.artistName,
duration: song.duration,
name: song.name,
};
const response = await FETCHERS[source](params);
if (response) {
const newResult = cached
? {
...cached,
[source]: response,
}
: ({ [source]: response } as CachedLyrics);
if (lyricCache.size === MAX_CACHED_ITEMS && cached === undefined) {
const toRemove = lyricCache.keys().next().value;
lyricCache.delete(toRemove);
}
lyricCache.set(song.id, newResult);
lyricsFromSource = response;
break;
}
}
return lyricsFromSource;
return lyricsFromSource;
};
const searchRemoteLyrics = async (params: LyricSearchQuery) => {
const sources = store.get('lyrics', []) as LyricSource[];
const sources = store.get('lyrics', []) as LyricSource[];
const results: Record<LyricSource, InternetProviderLyricSearchResponse[]> = {
[LyricSource.GENIUS]: [],
[LyricSource.LRCLIB]: [],
[LyricSource.NETEASE]: [],
};
const results: Record<LyricSource, InternetProviderLyricSearchResponse[]> = {
[LyricSource.GENIUS]: [],
[LyricSource.LRCLIB]: [],
[LyricSource.NETEASE]: [],
};
for (const source of sources) {
const response = await SEARCH_FETCHERS[source](params);
for (const source of sources) {
const response = await SEARCH_FETCHERS[source](params);
if (response) {
response.forEach((result) => {
results[source].push(result);
});
if (response) {
response.forEach((result) => {
results[source].push(result);
});
}
}
}
return results;
return results;
};
const getRemoteLyricsById = async (params: LyricGetQuery): Promise<string | null> => {
const { remoteSongId, remoteSource } = params;
const response = await GET_FETCHERS[remoteSource](remoteSongId);
const { remoteSongId, remoteSource } = params;
const response = await GET_FETCHERS[remoteSource](remoteSongId);
if (!response) {
return null;
}
if (!response) {
return null;
}
return response;
return response;
};
ipcMain.handle('lyric-by-song', async (_event, song: QueueSong) => {
const lyric = await getRemoteLyrics(song);
return lyric;
const lyric = await getRemoteLyrics(song);
return lyric;
});
ipcMain.handle('lyric-search', async (_event, params: LyricSearchQuery) => {
const lyricResults = await searchRemoteLyrics(params);
return lyricResults;
const lyricResults = await searchRemoteLyrics(params);
return lyricResults;
});
ipcMain.handle('lyric-by-remote-id', async (_event, params: LyricGetQuery) => {
const lyricResults = await getRemoteLyricsById(params);
return lyricResults;
const lyricResults = await getRemoteLyricsById(params);
return lyricResults;
});

View file

@ -1,10 +1,10 @@
// Credits to https://github.com/tranxuanthang/lrcget for API implementation
import axios, { AxiosResponse } from 'axios';
import {
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
LyricSource,
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
LyricSource,
} from '../../../../renderer/api/types';
import { orderSearchResults } from './shared';
@ -14,106 +14,106 @@ const SEEARCH_URL = 'https://lrclib.net/api/search';
const TIMEOUT_MS = 5000;
export interface LrcLibSearchResponse {
albumName: string;
artistName: string;
id: number;
name: string;
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;
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,
params: LyricSearchQuery,
): Promise<InternetProviderLyricSearchResponse[] | null> {
let result: AxiosResponse<LrcLibSearchResponse[]>;
let result: AxiosResponse<LrcLibSearchResponse[]>;
if (!params.name) {
return null;
}
if (!params.name) {
return null;
}
try {
result = await axios.get<LrcLibSearchResponse[]>(SEEARCH_URL, {
params: {
q: params.name,
},
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,
};
});
} 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 });
return orderSearchResults({ params, results: songResults });
}
export async function getLyricsBySongId(songId: string): Promise<string | null> {
let result: AxiosResponse<LrcLibTrackResponse, any>;
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;
}
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;
return result.data.syncedLyrics || result.data.plainLyrics || null;
}
export async function query(
params: LyricSearchQuery,
params: LyricSearchQuery,
): Promise<InternetProviderLyricResponse | null> {
let result: AxiosResponse<LrcLibTrackResponse, any>;
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;
}
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;
const lyrics = result.data.syncedLyrics || result.data.plainLyrics || null;
if (!lyrics) {
console.error(`Could not get lyrics on LrcLib!`);
return 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,
};
return {
artist: result.data.artistName,
id: String(result.data.id),
lyrics,
name: result.data.name,
source: LyricSource.LRCLIB,
};
}

View file

@ -2,9 +2,9 @@ import axios, { AxiosResponse } from 'axios';
import { LyricSource } from '../../../../renderer/api/types';
import { orderSearchResults } from './shared';
import type {
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
} from '/@/renderer/api/types';
const SEARCH_URL = 'https://music.163.com/api/search/get';
@ -13,155 +13,155 @@ const LYRICS_URL = 'https://music.163.com/api/song/lyric';
// Adapted from https://github.com/NyaomiDEV/Sunamu/blob/master/src/main/lyricproviders/netease.ts
export interface NetEaseResponse {
code: number;
result: Result;
code: number;
result: Result;
}
export interface Result {
hasMore: boolean;
songCount: number;
songs: Song[];
hasMore: boolean;
songCount: number;
songs: Song[];
}
export interface Song {
album: Album;
alias: string[];
artists: Artist[];
copyrightId: number;
duration: number;
fee: number;
ftype: number;
id: number;
mark: number;
mvid: number;
name: string;
rUrl: null;
rtype: number;
status: number;
transNames?: string[];
album: Album;
alias: string[];
artists: Artist[];
copyrightId: number;
duration: number;
fee: number;
ftype: number;
id: number;
mark: number;
mvid: number;
name: string;
rUrl: null;
rtype: number;
status: number;
transNames?: string[];
}
export interface Album {
artist: Artist;
copyrightId: number;
id: number;
mark: number;
name: string;
picId: number;
publishTime: number;
size: number;
status: number;
transNames?: string[];
artist: Artist;
copyrightId: number;
id: number;
mark: number;
name: string;
picId: number;
publishTime: number;
size: number;
status: number;
transNames?: string[];
}
export interface Artist {
albumSize: number;
alias: any[];
fansGroup: null;
id: number;
img1v1: number;
img1v1Url: string;
name: string;
picId: number;
picUrl: null;
trans: null;
albumSize: number;
alias: any[];
fansGroup: null;
id: number;
img1v1: number;
img1v1Url: string;
name: string;
picId: number;
picUrl: null;
trans: null;
}
export async function getSearchResults(
params: LyricSearchQuery,
params: LyricSearchQuery,
): Promise<InternetProviderLyricSearchResponse[] | null> {
let result: AxiosResponse<NetEaseResponse>;
let result: AxiosResponse<NetEaseResponse>;
const searchQuery = [params.artist, params.name].join(' ');
const searchQuery = [params.artist, params.name].join(' ');
if (!searchQuery) {
return null;
}
if (!searchQuery) {
return null;
}
try {
result = await axios.get(SEARCH_URL, {
params: {
limit: 5,
offset: 0,
s: searchQuery,
type: '1',
},
try {
result = await axios.get(SEARCH_URL, {
params: {
limit: 5,
offset: 0,
s: searchQuery,
type: '1',
},
});
} catch (e) {
console.error('NetEase search request got an error!', e);
return null;
}
const rawSongsResult = result?.data.result?.songs;
if (!rawSongsResult) return null;
const songResults: InternetProviderLyricSearchResponse[] = rawSongsResult.map((song) => {
const artist = song.artists ? song.artists.map((artist) => artist.name).join(', ') : '';
return {
artist,
id: String(song.id),
name: song.name,
source: LyricSource.NETEASE,
};
});
} catch (e) {
console.error('NetEase search request got an error!', e);
return null;
}
const rawSongsResult = result?.data.result?.songs;
if (!rawSongsResult) return null;
const songResults: InternetProviderLyricSearchResponse[] = rawSongsResult.map((song) => {
const artist = song.artists ? song.artists.map((artist) => artist.name).join(', ') : '';
return {
artist,
id: String(song.id),
name: song.name,
source: LyricSource.NETEASE,
};
});
return orderSearchResults({ params, results: songResults });
return orderSearchResults({ params, results: songResults });
}
async function getMatchedLyrics(
params: LyricSearchQuery,
params: LyricSearchQuery,
): Promise<Omit<InternetProviderLyricResponse, 'lyrics'> | null> {
const results = await getSearchResults(params);
const results = await getSearchResults(params);
const firstMatch = results?.[0];
const firstMatch = results?.[0];
if (!firstMatch || (firstMatch?.score && firstMatch.score > 0.5)) {
return null;
}
if (!firstMatch || (firstMatch?.score && firstMatch.score > 0.5)) {
return null;
}
return firstMatch;
return firstMatch;
}
export async function getLyricsBySongId(songId: string): Promise<string | null> {
let result: AxiosResponse<any, any>;
try {
result = await axios.get(LYRICS_URL, {
params: {
id: songId,
kv: '-1',
lv: '-1',
},
});
} catch (e) {
console.error('NetEase lyrics request got an error!', e);
return null;
}
let result: AxiosResponse<any, any>;
try {
result = await axios.get(LYRICS_URL, {
params: {
id: songId,
kv: '-1',
lv: '-1',
},
});
} catch (e) {
console.error('NetEase lyrics request got an error!', e);
return null;
}
return result.data.klyric?.lyric || result.data.lrc?.lyric;
return result.data.klyric?.lyric || result.data.lrc?.lyric;
}
export async function query(
params: LyricSearchQuery,
params: LyricSearchQuery,
): Promise<InternetProviderLyricResponse | null> {
const lyricsMatch = await getMatchedLyrics(params);
if (!lyricsMatch) {
console.error('Could not find the song on NetEase!');
return null;
}
const lyricsMatch = await getMatchedLyrics(params);
if (!lyricsMatch) {
console.error('Could not find the song on NetEase!');
return null;
}
const lyrics = await getLyricsBySongId(lyricsMatch.id);
if (!lyrics) {
console.error('Could not get lyrics on NetEase!');
return null;
}
const lyrics = await getLyricsBySongId(lyricsMatch.id);
if (!lyrics) {
console.error('Could not get lyrics on NetEase!');
return null;
}
return {
artist: lyricsMatch.artist,
id: lyricsMatch.id,
lyrics,
name: lyricsMatch.name,
source: LyricSource.NETEASE,
};
return {
artist: lyricsMatch.artist,
id: lyricsMatch.id,
lyrics,
name: lyricsMatch.name,
source: LyricSource.NETEASE,
};
}

View file

@ -1,34 +1,34 @@
import Fuse from 'fuse.js';
import {
InternetProviderLyricSearchResponse,
LyricSearchQuery,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
} from '../../../../renderer/api/types';
export const orderSearchResults = (args: {
params: LyricSearchQuery;
results: InternetProviderLyricSearchResponse[];
params: LyricSearchQuery;
results: InternetProviderLyricSearchResponse[];
}) => {
const { params, results } = args;
const { params, results } = args;
const options: Fuse.IFuseOptions<InternetProviderLyricSearchResponse> = {
fieldNormWeight: 1,
includeScore: true,
keys: [
{ getFn: (song) => song.name, name: 'name', weight: 3 },
{ getFn: (song) => song.artist, name: 'artist' },
],
threshold: 1.0,
};
const options: Fuse.IFuseOptions<InternetProviderLyricSearchResponse> = {
fieldNormWeight: 1,
includeScore: true,
keys: [
{ getFn: (song) => song.name, name: 'name', weight: 3 },
{ getFn: (song) => song.artist, name: 'artist' },
],
threshold: 1.0,
};
const fuse = new Fuse(results, options);
const fuse = new Fuse(results, options);
const searchResults = fuse.search<InternetProviderLyricSearchResponse>({
...(params.artist && { artist: params.artist }),
...(params.name && { name: params.name }),
});
const searchResults = fuse.search<InternetProviderLyricSearchResponse>({
...(params.artist && { artist: params.artist }),
...(params.name && { name: params.name }),
});
return searchResults.map((result) => ({
...result.item,
score: result.score,
}));
return searchResults.map((result) => ({
...result.item,
score: result.score,
}));
};

View file

@ -6,215 +6,215 @@ import { PlayerData } from '/@/renderer/store';
declare module 'node-mpv';
function wait(timeout: number) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('resolved');
}, timeout);
});
return new Promise((resolve) => {
setTimeout(() => {
resolve('resolved');
}, timeout);
});
}
ipcMain.handle('player-is-running', async () => {
return getMpvInstance()?.isRunning();
return getMpvInstance()?.isRunning();
});
ipcMain.handle('player-clean-up', async () => {
getMpvInstance()?.stop();
getMpvInstance()?.clearPlaylist();
getMpvInstance()?.stop();
getMpvInstance()?.clearPlaylist();
});
ipcMain.on('player-start', async () => {
await getMpvInstance()
?.play()
.catch((err) => {
console.log('MPV failed to play', err);
});
await getMpvInstance()
?.play()
.catch((err) => {
console.log('MPV failed to play', err);
});
});
// Starts the player
ipcMain.on('player-play', async () => {
await getMpvInstance()
?.play()
.catch((err) => {
console.log('MPV failed to play', err);
});
await getMpvInstance()
?.play()
.catch((err) => {
console.log('MPV failed to play', err);
});
});
// Pauses the player
ipcMain.on('player-pause', async () => {
await getMpvInstance()
?.pause()
.catch((err) => {
console.log('MPV failed to pause', err);
});
await getMpvInstance()
?.pause()
.catch((err) => {
console.log('MPV failed to pause', err);
});
});
// Stops the player
ipcMain.on('player-stop', async () => {
await getMpvInstance()
?.stop()
.catch((err) => {
console.log('MPV failed to stop', err);
});
await getMpvInstance()
?.stop()
.catch((err) => {
console.log('MPV failed to stop', err);
});
});
// Goes to the next track in the playlist
ipcMain.on('player-next', async () => {
await getMpvInstance()
?.next()
.catch((err) => {
console.log('MPV failed to go to next', err);
});
await getMpvInstance()
?.next()
.catch((err) => {
console.log('MPV failed to go to next', err);
});
});
// Goes to the previous track in the playlist
ipcMain.on('player-previous', async () => {
await getMpvInstance()
?.prev()
.catch((err) => {
console.log('MPV failed to go to previous', err);
});
await getMpvInstance()
?.prev()
.catch((err) => {
console.log('MPV failed to go to previous', err);
});
});
// Seeks forward or backward by the given amount of seconds
ipcMain.on('player-seek', async (_event, time: number) => {
await getMpvInstance()
?.seek(time)
.catch((err) => {
console.log('MPV failed to seek', err);
});
await getMpvInstance()
?.seek(time)
.catch((err) => {
console.log('MPV failed to seek', err);
});
});
// Seeks to the given time in seconds
ipcMain.on('player-seek-to', async (_event, time: number) => {
await getMpvInstance()
?.goToPosition(time)
.catch((err) => {
console.log(`MPV failed to seek to ${time}`, err);
});
await getMpvInstance()
?.goToPosition(time)
.catch((err) => {
console.log(`MPV failed to seek to ${time}`, err);
});
});
// Sets the queue in position 0 and 1 to the given data. Used when manually starting a song or using the next/prev buttons
ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean) => {
if (!data.queue.current && !data.queue.next) {
await getMpvInstance()
?.clearPlaylist()
.catch((err) => {
console.log('MPV failed to clear playlist', err);
});
await getMpvInstance()
?.pause()
.catch((err) => {
console.log('MPV failed to pause', err);
});
return;
}
let complete = false;
let tryAttempts = 0;
while (!complete) {
if (tryAttempts > 3) {
getMainWindow()?.webContents.send('renderer-player-error', 'Failed to load song');
complete = true;
} else {
try {
if (data.queue.current) {
await getMpvInstance()
?.load(data.queue.current.streamUrl, 'replace')
if (!data.queue.current && !data.queue.next) {
await getMpvInstance()
?.clearPlaylist()
.catch((err) => {
console.log('MPV failed to load song', err);
console.log('MPV failed to clear playlist', err);
});
}
if (data.queue.next) {
await getMpvInstance()
?.load(data.queue.next.streamUrl, 'append')
await getMpvInstance()
?.pause()
.catch((err) => {
console.log('MPV failed to load next song', err);
console.log('MPV failed to pause', err);
});
}
complete = true;
} catch (err) {
console.error(err);
tryAttempts += 1;
await wait(500);
}
return;
}
}
if (pause) {
await getMpvInstance()?.pause();
}
let complete = false;
let tryAttempts = 0;
while (!complete) {
if (tryAttempts > 3) {
getMainWindow()?.webContents.send('renderer-player-error', 'Failed to load song');
complete = true;
} else {
try {
if (data.queue.current) {
await getMpvInstance()
?.load(data.queue.current.streamUrl, 'replace')
.catch((err) => {
console.log('MPV failed to load song', err);
});
}
if (data.queue.next) {
await getMpvInstance()
?.load(data.queue.next.streamUrl, 'append')
.catch((err) => {
console.log('MPV failed to load next song', err);
});
}
complete = true;
} catch (err) {
console.error(err);
tryAttempts += 1;
await wait(500);
}
}
}
if (pause) {
await getMpvInstance()?.pause();
}
});
// Replaces the queue in position 1 to the given data
ipcMain.on('player-set-queue-next', async (_event, data: PlayerData) => {
const size = await getMpvInstance()
?.getPlaylistSize()
.catch((err) => {
console.log('MPV failed to get playlist size', err);
});
const size = await getMpvInstance()
?.getPlaylistSize()
.catch((err) => {
console.log('MPV failed to get playlist size', err);
});
if (!size) {
return;
}
if (!size) {
return;
}
if (size > 1) {
await getMpvInstance()
?.playlistRemove(1)
.catch((err) => {
console.log('MPV failed to remove song from playlist', err);
});
}
if (size > 1) {
await getMpvInstance()
?.playlistRemove(1)
.catch((err) => {
console.log('MPV failed to remove song from playlist', err);
});
}
if (data.queue.next) {
await getMpvInstance()
?.load(data.queue.next.streamUrl, 'append')
.catch((err) => {
console.log('MPV failed to load next song', err);
});
}
if (data.queue.next) {
await getMpvInstance()
?.load(data.queue.next.streamUrl, 'append')
.catch((err) => {
console.log('MPV failed to load next song', err);
});
}
});
// Sets the next song in the queue when reaching the end of the queue
ipcMain.on('player-auto-next', async (_event, data: PlayerData) => {
// Always keep the current song as position 0 in the mpv queue
// This allows us to easily set update the next song in the queue without
// disturbing the currently playing song
await getMpvInstance()
?.playlistRemove(0)
.catch((err) => {
console.log('MPV failed to remove song from playlist', err);
});
if (data.queue.next) {
// Always keep the current song as position 0 in the mpv queue
// This allows us to easily set update the next song in the queue without
// disturbing the currently playing song
await getMpvInstance()
?.load(data.queue.next.streamUrl, 'append')
.catch((err) => {
console.log('MPV failed to load next song', err);
});
}
?.playlistRemove(0)
.catch((err) => {
console.log('MPV failed to remove song from playlist', err);
});
if (data.queue.next) {
await getMpvInstance()
?.load(data.queue.next.streamUrl, 'append')
.catch((err) => {
console.log('MPV failed to load next song', err);
});
}
});
// Sets the volume to the given value (0-100)
ipcMain.on('player-volume', async (_event, value: number) => {
await getMpvInstance()
?.volume(value)
.catch((err) => {
console.log('MPV failed to set volume', err);
});
await getMpvInstance()
?.volume(value)
.catch((err) => {
console.log('MPV failed to set volume', err);
});
});
// Toggles the mute status
ipcMain.on('player-mute', async () => {
await getMpvInstance()
?.mute()
.catch((err) => {
console.log('MPV failed to toggle mute', err);
});
await getMpvInstance()
?.mute()
.catch((err) => {
console.log('MPV failed to toggle mute', err);
});
});
ipcMain.handle('player-get-time', async (): Promise<number | undefined> => {
return getMpvInstance()?.getTimePosition();
return getMpvInstance()?.getTimePosition();
});

View file

@ -2,26 +2,26 @@
import { BrowserWindow, globalShortcut } from 'electron';
export const enableMediaKeys = (window: BrowserWindow | null) => {
globalShortcut.register('MediaStop', () => {
window?.webContents.send('renderer-player-stop');
});
globalShortcut.register('MediaStop', () => {
window?.webContents.send('renderer-player-stop');
});
globalShortcut.register('MediaPlayPause', () => {
window?.webContents.send('renderer-player-play-pause');
});
globalShortcut.register('MediaPlayPause', () => {
window?.webContents.send('renderer-player-play-pause');
});
globalShortcut.register('MediaNextTrack', () => {
window?.webContents.send('renderer-player-next');
});
globalShortcut.register('MediaNextTrack', () => {
window?.webContents.send('renderer-player-next');
});
globalShortcut.register('MediaPreviousTrack', () => {
window?.webContents.send('renderer-player-previous');
});
globalShortcut.register('MediaPreviousTrack', () => {
window?.webContents.send('renderer-player-previous');
});
};
export const disableMediaKeys = () => {
globalShortcut.unregister('MediaStop');
globalShortcut.unregister('MediaPlayPause');
globalShortcut.unregister('MediaNextTrack');
globalShortcut.unregister('MediaPreviousTrack');
globalShortcut.unregister('MediaStop');
globalShortcut.unregister('MediaPlayPause');
globalShortcut.unregister('MediaNextTrack');
globalShortcut.unregister('MediaPreviousTrack');
};

View file

@ -4,47 +4,47 @@ import Store from 'electron-store';
export const store = new Store();
ipcMain.handle('settings-get', (_event, data: { property: string }) => {
return store.get(`${data.property}`);
return store.get(`${data.property}`);
});
ipcMain.on('settings-set', (__event, data: { property: string; value: any }) => {
store.set(`${data.property}`, data.value);
store.set(`${data.property}`, data.value);
});
ipcMain.handle('password-get', (_event, server: string): string | null => {
if (safeStorage.isEncryptionAvailable()) {
const servers = store.get('server') as Record<string, string> | undefined;
if (safeStorage.isEncryptionAvailable()) {
const servers = store.get('server') as Record<string, string> | undefined;
if (!servers) {
return null;
if (!servers) {
return null;
}
const encrypted = servers[server];
if (!encrypted) return null;
const decrypted = safeStorage.decryptString(Buffer.from(encrypted, 'hex'));
return decrypted;
}
const encrypted = servers[server];
if (!encrypted) return null;
const decrypted = safeStorage.decryptString(Buffer.from(encrypted, 'hex'));
return decrypted;
}
return null;
return null;
});
ipcMain.on('password-remove', (_event, server: string) => {
const passwords = store.get('server', {}) as Record<string, string>;
if (server in passwords) {
delete passwords[server];
}
store.set({ server: passwords });
const passwords = store.get('server', {}) as Record<string, string>;
if (server in passwords) {
delete passwords[server];
}
store.set({ server: passwords });
});
ipcMain.handle('password-set', (_event, password: string, server: string) => {
if (safeStorage.isEncryptionAvailable()) {
const encrypted = safeStorage.encryptString(password);
const passwords = store.get('server', {}) as Record<string, string>;
passwords[server] = encrypted.toString('hex');
store.set({ server: passwords });
if (safeStorage.isEncryptionAvailable()) {
const encrypted = safeStorage.encryptString(password);
const passwords = store.get('server', {}) as Record<string, string>;
passwords[server] = encrypted.toString('hex');
store.set({ server: passwords });
return true;
}
return false;
return true;
}
return false;
});

View file

@ -5,164 +5,166 @@ import { getMainWindow } from '../../main';
import { PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/renderer/types';
const mprisPlayer = Player({
identity: 'Feishin',
maximumRate: 1.0,
minimumRate: 1.0,
name: 'Feishin',
rate: 1.0,
supportedInterfaces: ['player'],
supportedMimeTypes: ['audio/mpeg', 'application/ogg'],
supportedUriSchemes: ['file'],
identity: 'Feishin',
maximumRate: 1.0,
minimumRate: 1.0,
name: 'Feishin',
rate: 1.0,
supportedInterfaces: ['player'],
supportedMimeTypes: ['audio/mpeg', 'application/ogg'],
supportedUriSchemes: ['file'],
});
mprisPlayer.on('quit', () => {
process.exit();
process.exit();
});
mprisPlayer.on('stop', () => {
getMainWindow()?.webContents.send('renderer-player-stop');
mprisPlayer.playbackStatus = 'Paused';
getMainWindow()?.webContents.send('renderer-player-stop');
mprisPlayer.playbackStatus = 'Paused';
});
mprisPlayer.on('pause', () => {
getMainWindow()?.webContents.send('renderer-player-pause');
mprisPlayer.playbackStatus = 'Paused';
getMainWindow()?.webContents.send('renderer-player-pause');
mprisPlayer.playbackStatus = 'Paused';
});
mprisPlayer.on('play', () => {
getMainWindow()?.webContents.send('renderer-player-play');
mprisPlayer.playbackStatus = 'Playing';
getMainWindow()?.webContents.send('renderer-player-play');
mprisPlayer.playbackStatus = 'Playing';
});
mprisPlayer.on('playpause', () => {
getMainWindow()?.webContents.send('renderer-player-play-pause');
if (mprisPlayer.playbackStatus !== 'Playing') {
mprisPlayer.playbackStatus = 'Playing';
} else {
mprisPlayer.playbackStatus = 'Paused';
}
getMainWindow()?.webContents.send('renderer-player-play-pause');
if (mprisPlayer.playbackStatus !== 'Playing') {
mprisPlayer.playbackStatus = 'Playing';
} else {
mprisPlayer.playbackStatus = 'Paused';
}
});
mprisPlayer.on('next', () => {
getMainWindow()?.webContents.send('renderer-player-next');
getMainWindow()?.webContents.send('renderer-player-next');
if (mprisPlayer.playbackStatus !== 'Playing') {
mprisPlayer.playbackStatus = 'Playing';
}
if (mprisPlayer.playbackStatus !== 'Playing') {
mprisPlayer.playbackStatus = 'Playing';
}
});
mprisPlayer.on('previous', () => {
getMainWindow()?.webContents.send('renderer-player-previous');
getMainWindow()?.webContents.send('renderer-player-previous');
if (mprisPlayer.playbackStatus !== 'Playing') {
mprisPlayer.playbackStatus = Player.PLAYBACK_STATUS_PLAYING;
}
if (mprisPlayer.playbackStatus !== 'Playing') {
mprisPlayer.playbackStatus = Player.PLAYBACK_STATUS_PLAYING;
}
});
mprisPlayer.on('volume', (event: any) => {
getMainWindow()?.webContents.send('mpris-request-volume', {
volume: event,
});
getMainWindow()?.webContents.send('mpris-request-volume', {
volume: event,
});
});
mprisPlayer.on('shuffle', (event: boolean) => {
getMainWindow()?.webContents.send('mpris-request-toggle-shuffle', { shuffle: event });
mprisPlayer.shuffle = event;
getMainWindow()?.webContents.send('mpris-request-toggle-shuffle', { shuffle: event });
mprisPlayer.shuffle = event;
});
mprisPlayer.on('loopStatus', (event: string) => {
getMainWindow()?.webContents.send('mpris-request-toggle-repeat', { repeat: event });
mprisPlayer.loopStatus = event;
getMainWindow()?.webContents.send('mpris-request-toggle-repeat', { repeat: event });
mprisPlayer.loopStatus = event;
});
mprisPlayer.on('position', (event: any) => {
getMainWindow()?.webContents.send('mpris-request-position', {
position: event.position / 1e6,
});
getMainWindow()?.webContents.send('mpris-request-position', {
position: event.position / 1e6,
});
});
mprisPlayer.on('seek', (event: number) => {
getMainWindow()?.webContents.send('mpris-request-seek', {
offset: event / 1e6,
});
getMainWindow()?.webContents.send('mpris-request-seek', {
offset: event / 1e6,
});
});
ipcMain.on('mpris-update-position', (_event, arg) => {
mprisPlayer.getPosition = () => arg * 1e6;
mprisPlayer.getPosition = () => arg * 1e6;
});
ipcMain.on('mpris-update-seek', (_event, arg) => {
mprisPlayer.seeked(arg * 1e6);
mprisPlayer.seeked(arg * 1e6);
});
ipcMain.on('mpris-update-volume', (_event, arg) => {
mprisPlayer.volume = Number(arg);
mprisPlayer.volume = Number(arg);
});
ipcMain.on('mpris-update-repeat', (_event, arg) => {
mprisPlayer.loopStatus = arg;
mprisPlayer.loopStatus = arg;
});
ipcMain.on('mpris-update-shuffle', (_event, arg) => {
mprisPlayer.shuffle = arg;
mprisPlayer.shuffle = arg;
});
ipcMain.on(
'mpris-update-song',
(
_event,
args: {
currentTime: number;
repeat: PlayerRepeat;
shuffle: PlayerShuffle;
song: QueueSong;
status: PlayerStatus;
'mpris-update-song',
(
_event,
args: {
currentTime: number;
repeat: PlayerRepeat;
shuffle: PlayerShuffle;
song: QueueSong;
status: PlayerStatus;
},
) => {
const { song, status, repeat, shuffle } = args || {};
try {
mprisPlayer.playbackStatus = status;
if (repeat) {
mprisPlayer.loopStatus =
repeat === 'all' ? 'Playlist' : repeat === 'one' ? 'Track' : 'None';
}
if (shuffle) {
mprisPlayer.shuffle = shuffle !== 'none';
}
if (!song) return;
const upsizedImageUrl = song.imageUrl
? song.imageUrl
?.replace(/&size=\d+/, '&size=300')
.replace(/\?width=\d+/, '?width=300')
.replace(/&height=\d+/, '&height=300')
: null;
mprisPlayer.metadata = {
'mpris:artUrl': upsizedImageUrl,
'mpris:length': song.duration ? Math.round((song.duration || 0) * 1e6) : null,
'mpris:trackid': song?.id
? mprisPlayer.objectPath(`track/${song.id?.replace('-', '')}`)
: '',
'xesam:album': song.album || null,
'xesam:albumArtist': song.albumArtists?.length ? song.albumArtists[0].name : null,
'xesam:artist':
song.artists?.length !== 0
? song.artists?.map((artist: RelatedArtist) => artist.name)
: null,
'xesam:discNumber': song.discNumber ? song.discNumber : null,
'xesam:genre': song.genres?.length
? song.genres.map((genre: any) => genre.name)
: null,
'xesam:title': song.name || null,
'xesam:trackNumber': song.trackNumber ? song.trackNumber : null,
'xesam:useCount':
song.playCount !== null && song.playCount !== undefined ? song.playCount : null,
};
} catch (err) {
console.log(err);
}
},
) => {
const { song, status, repeat, shuffle } = args || {};
try {
mprisPlayer.playbackStatus = status;
if (repeat) {
mprisPlayer.loopStatus =
repeat === 'all' ? 'Playlist' : repeat === 'one' ? 'Track' : 'None';
}
if (shuffle) {
mprisPlayer.shuffle = shuffle !== 'none';
}
if (!song) return;
const upsizedImageUrl = song.imageUrl
? song.imageUrl
?.replace(/&size=\d+/, '&size=300')
.replace(/\?width=\d+/, '?width=300')
.replace(/&height=\d+/, '&height=300')
: null;
mprisPlayer.metadata = {
'mpris:artUrl': upsizedImageUrl,
'mpris:length': song.duration ? Math.round((song.duration || 0) * 1e6) : null,
'mpris:trackid': song?.id
? mprisPlayer.objectPath(`track/${song.id?.replace('-', '')}`)
: '',
'xesam:album': song.album || null,
'xesam:albumArtist': song.albumArtists?.length ? song.albumArtists[0].name : null,
'xesam:artist':
song.artists?.length !== 0
? song.artists?.map((artist: RelatedArtist) => artist.name)
: null,
'xesam:discNumber': song.discNumber ? song.discNumber : null,
'xesam:genre': song.genres?.length ? song.genres.map((genre: any) => genre.name) : null,
'xesam:title': song.name || null,
'xesam:trackNumber': song.trackNumber ? song.trackNumber : null,
'xesam:useCount':
song.playCount !== null && song.playCount !== undefined ? song.playCount : null,
};
} catch (err) {
console.log(err);
}
},
);