mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 05:20:13 +02:00
restructure files onto electron-vite boilerplate
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { load } from 'cheerio';
|
||||
import { orderSearchResults } from './shared';
|
||||
|
||||
import {
|
||||
LyricSource,
|
||||
InternetProviderLyricResponse,
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricSearchQuery,
|
||||
} from '../../../../renderer/api/types';
|
||||
LyricSource,
|
||||
} from '.';
|
||||
import { orderSearchResults } from './shared';
|
||||
|
||||
const SEARCH_URL = 'https://genius.com/api/search/song';
|
||||
|
||||
@@ -17,20 +18,6 @@ export interface GeniusResponse {
|
||||
response: Response;
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
next_page: number;
|
||||
sections: Section[];
|
||||
}
|
||||
|
||||
export interface Section {
|
||||
hits: Hit[];
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface Hit {
|
||||
highlights: any[];
|
||||
index: string;
|
||||
@@ -38,6 +25,35 @@ export interface Hit {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
status: number;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export interface ReleaseDateComponents {
|
||||
day: number;
|
||||
month: number;
|
||||
year: number;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
next_page: number;
|
||||
sections: Section[];
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
_type: string;
|
||||
annotation_count: number;
|
||||
@@ -69,24 +85,9 @@ export interface Result {
|
||||
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;
|
||||
}
|
||||
|
||||
export interface ReleaseDateComponents {
|
||||
day: number;
|
||||
month: number;
|
||||
year: number;
|
||||
export interface Section {
|
||||
hits: Hit[];
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
@@ -94,6 +95,27 @@ export interface Stats {
|
||||
unreviewed_annotations: number;
|
||||
}
|
||||
|
||||
export async function getLyricsBySongId(url: string): Promise<null | string> {
|
||||
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');
|
||||
|
||||
if (lyricsDiv.length > 0) return lyricsDiv.text().trim();
|
||||
|
||||
const lyricSections = $('div[class^=Lyrics__Container]')
|
||||
.map((_, e) => $(e).text())
|
||||
.toArray()
|
||||
.join('\n');
|
||||
return lyricSections;
|
||||
}
|
||||
|
||||
export async function getSearchResults(
|
||||
params: LyricSearchQuery,
|
||||
): Promise<InternetProviderLyricSearchResponse[] | null> {
|
||||
@@ -133,9 +155,33 @@ export async function getSearchResults(
|
||||
return orderSearchResults({ params, results: songResults });
|
||||
}
|
||||
|
||||
export async function query(
|
||||
params: LyricSearchQuery,
|
||||
): Promise<InternetProviderLyricResponse | 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;
|
||||
}
|
||||
|
||||
return {
|
||||
artist: response.artist,
|
||||
id: response.id,
|
||||
lyrics,
|
||||
name: response.name,
|
||||
source: LyricSource.GENIUS,
|
||||
};
|
||||
}
|
||||
|
||||
async function getSongId(
|
||||
params: LyricSearchQuery,
|
||||
): Promise<Omit<InternetProviderLyricResponse, 'lyrics'> | null> {
|
||||
): Promise<null | Omit<InternetProviderLyricResponse, 'lyrics'>> {
|
||||
let result: AxiosResponse<GeniusResponse>;
|
||||
try {
|
||||
result = await axios.get(SEARCH_URL, {
|
||||
@@ -162,48 +208,3 @@ async function getSongId(
|
||||
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;
|
||||
}
|
||||
|
||||
const $ = load(result.data.split('<br/>').join('\n'));
|
||||
const lyricsDiv = $('div.lyrics');
|
||||
|
||||
if (lyricsDiv.length > 0) return lyricsDiv.text().trim();
|
||||
|
||||
const lyricSections = $('div[class^=Lyrics__Container]')
|
||||
.map((_, e) => $(e).text())
|
||||
.toArray()
|
||||
.join('\n');
|
||||
return lyricSections;
|
||||
}
|
||||
|
||||
export async function query(
|
||||
params: LyricSearchQuery,
|
||||
): Promise<InternetProviderLyricResponse | 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;
|
||||
}
|
||||
|
||||
return {
|
||||
artist: response.artist,
|
||||
id: response.id,
|
||||
lyrics,
|
||||
name: response.name,
|
||||
source: LyricSource.GENIUS,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,36 +1,76 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
import { store } from '../settings/index';
|
||||
import {
|
||||
getLyricsBySongId as getGenius,
|
||||
query as queryGenius,
|
||||
getSearchResults as searchGenius,
|
||||
getLyricsBySongId as getGenius,
|
||||
} from './genius';
|
||||
import {
|
||||
getLyricsBySongId as getLrcLib,
|
||||
query as queryLrclib,
|
||||
getSearchResults as searchLrcLib,
|
||||
getLyricsBySongId as getLrcLib,
|
||||
} from './lrclib';
|
||||
import {
|
||||
getLyricsBySongId as getNetease,
|
||||
query as queryNetease,
|
||||
getSearchResults as searchNetease,
|
||||
getLyricsBySongId as getNetease,
|
||||
} from './netease';
|
||||
import {
|
||||
InternetProviderLyricResponse,
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricSearchQuery,
|
||||
QueueSong,
|
||||
LyricGetQuery,
|
||||
LyricSource,
|
||||
} from '../../../../renderer/api/types';
|
||||
import { store } from '../settings/index';
|
||||
|
||||
type SongFetcher = (params: LyricSearchQuery) => Promise<InternetProviderLyricResponse | null>;
|
||||
export enum LyricSource {
|
||||
GENIUS = 'Genius',
|
||||
LRCLIB = 'lrclib.net',
|
||||
NETEASE = 'NetEase',
|
||||
}
|
||||
|
||||
export type FullLyricsMetadata = Omit<InternetProviderLyricResponse, 'id' | 'lyrics' | 'source'> & {
|
||||
lyrics: LyricsResponse;
|
||||
remote: boolean;
|
||||
source: string;
|
||||
};
|
||||
|
||||
export type InternetProviderLyricResponse = {
|
||||
artist: string;
|
||||
id: string;
|
||||
lyrics: string;
|
||||
name: string;
|
||||
source: LyricSource;
|
||||
};
|
||||
|
||||
export type InternetProviderLyricSearchResponse = {
|
||||
artist: string;
|
||||
id: string;
|
||||
name: string;
|
||||
score?: number;
|
||||
source: LyricSource;
|
||||
};
|
||||
|
||||
export type LyricGetQuery = {
|
||||
remoteSongId: string;
|
||||
remoteSource: LyricSource;
|
||||
song: Song;
|
||||
};
|
||||
|
||||
export type LyricOverride = Omit<InternetProviderLyricResponse, 'lyrics'>;
|
||||
|
||||
export type LyricSearchQuery = {
|
||||
album?: string;
|
||||
artist?: string;
|
||||
duration?: number;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type LyricsResponse = string | SynchronizedLyricsArray;
|
||||
|
||||
export type SynchronizedLyricsArray = Array<[number, string]>;
|
||||
|
||||
type CachedLyrics = Record<LyricSource, InternetProviderLyricResponse>;
|
||||
type GetFetcher = (id: string) => Promise<null | string>;
|
||||
type SearchFetcher = (
|
||||
params: LyricSearchQuery,
|
||||
) => Promise<InternetProviderLyricSearchResponse[] | null>;
|
||||
type GetFetcher = (id: string) => Promise<string | null>;
|
||||
|
||||
type CachedLyrics = Record<LyricSource, InternetProviderLyricResponse>;
|
||||
type SongFetcher = (params: LyricSearchQuery) => Promise<InternetProviderLyricResponse | null>;
|
||||
|
||||
const FETCHERS: Record<LyricSource, SongFetcher> = {
|
||||
[LyricSource.GENIUS]: queryGenius,
|
||||
@@ -54,7 +94,7 @@ const MAX_CACHED_ITEMS = 10;
|
||||
|
||||
const lyricCache = new Map<string, CachedLyrics>();
|
||||
|
||||
const getRemoteLyrics = async (song: QueueSong) => {
|
||||
const getRemoteLyrics = async (song: any) => {
|
||||
const sources = store.get('lyrics', []) as LyricSource[];
|
||||
|
||||
const cached = lyricCache.get(song.id);
|
||||
@@ -122,7 +162,7 @@ const searchRemoteLyrics = async (params: LyricSearchQuery) => {
|
||||
return results;
|
||||
};
|
||||
|
||||
const getRemoteLyricsById = async (params: LyricGetQuery): Promise<string | null> => {
|
||||
const getRemoteLyricsById = async (params: LyricGetQuery): Promise<null | string> => {
|
||||
const { remoteSongId, remoteSource } = params;
|
||||
const response = await GET_FETCHERS[remoteSource](remoteSongId);
|
||||
|
||||
@@ -133,7 +173,7 @@ const getRemoteLyricsById = async (params: LyricGetQuery): Promise<string | null
|
||||
return response;
|
||||
};
|
||||
|
||||
ipcMain.handle('lyric-by-song', async (_event, song: QueueSong) => {
|
||||
ipcMain.handle('lyric-by-song', async (_event, song: any) => {
|
||||
const lyric = await getRemoteLyrics(song);
|
||||
return lyric;
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Credits to https://github.com/tranxuanthang/lrcget for API implementation
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { orderSearchResults } from './shared';
|
||||
|
||||
import {
|
||||
InternetProviderLyricResponse,
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricSearchQuery,
|
||||
LyricSource,
|
||||
} from '../../../../renderer/api/types';
|
||||
} from '.';
|
||||
import { orderSearchResults } from './shared';
|
||||
|
||||
const FETCH_URL = 'https://lrclib.net/api/get';
|
||||
const SEEARCH_URL = 'https://lrclib.net/api/search';
|
||||
@@ -29,10 +30,23 @@ export interface LrcLibTrackResponse {
|
||||
isrc: string;
|
||||
lang: string;
|
||||
name: string;
|
||||
plainLyrics: string | null;
|
||||
plainLyrics: null | string;
|
||||
releaseDate: string;
|
||||
spotifyId: string;
|
||||
syncedLyrics: string | null;
|
||||
syncedLyrics: null | string;
|
||||
}
|
||||
|
||||
export async function getLyricsBySongId(songId: string): Promise<null | string> {
|
||||
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 getSearchResults(
|
||||
@@ -69,19 +83,6 @@ export async function getSearchResults(
|
||||
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> {
|
||||
|
||||
@@ -1,46 +1,18 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { LyricSource } from '../../../../renderer/api/types';
|
||||
import { orderSearchResults } from './shared';
|
||||
import type {
|
||||
|
||||
import {
|
||||
InternetProviderLyricResponse,
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricSearchQuery,
|
||||
} from '/@/renderer/api/types';
|
||||
LyricSource,
|
||||
} from '.';
|
||||
import { orderSearchResults } from './shared';
|
||||
|
||||
const SEARCH_URL = 'https://music.163.com/api/search/get';
|
||||
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;
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
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[];
|
||||
}
|
||||
|
||||
export interface Album {
|
||||
artist: Artist;
|
||||
copyrightId: number;
|
||||
@@ -67,6 +39,53 @@ export interface Artist {
|
||||
trans: null;
|
||||
}
|
||||
|
||||
export interface NetEaseResponse {
|
||||
code: number;
|
||||
result: Result;
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
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;
|
||||
rtype: number;
|
||||
rUrl: null;
|
||||
status: number;
|
||||
transNames?: string[];
|
||||
}
|
||||
|
||||
export async function getLyricsBySongId(songId: string): Promise<null | string> {
|
||||
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;
|
||||
}
|
||||
|
||||
export async function getSearchResults(
|
||||
params: LyricSearchQuery,
|
||||
): Promise<InternetProviderLyricSearchResponse[] | null> {
|
||||
@@ -110,38 +129,6 @@ export async function getSearchResults(
|
||||
return orderSearchResults({ params, results: songResults });
|
||||
}
|
||||
|
||||
async function getMatchedLyrics(
|
||||
params: LyricSearchQuery,
|
||||
): Promise<Omit<InternetProviderLyricResponse, 'lyrics'> | null> {
|
||||
const results = await getSearchResults(params);
|
||||
|
||||
const firstMatch = results?.[0];
|
||||
|
||||
if (!firstMatch || (firstMatch?.score && firstMatch.score > 0.5)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return result.data.klyric?.lyric || result.data.lrc?.lyric;
|
||||
}
|
||||
|
||||
export async function query(
|
||||
params: LyricSearchQuery,
|
||||
): Promise<InternetProviderLyricResponse | null> {
|
||||
@@ -165,3 +152,17 @@ export async function query(
|
||||
source: LyricSource.NETEASE,
|
||||
};
|
||||
}
|
||||
|
||||
async function getMatchedLyrics(
|
||||
params: LyricSearchQuery,
|
||||
): Promise<null | Omit<InternetProviderLyricResponse, 'lyrics'>> {
|
||||
const results = await getSearchResults(params);
|
||||
|
||||
const firstMatch = results?.[0];
|
||||
|
||||
if (!firstMatch || (firstMatch?.score && firstMatch.score > 0.5)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return firstMatch;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import {
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricSearchQuery,
|
||||
|
||||
Reference in New Issue
Block a user