mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 10:31:47 +00:00
feat: remove lyrics-genius
This commit is contained in:
@ -4,10 +4,33 @@ import defaults from './defaults';
|
|||||||
|
|
||||||
import { DefaultPresetList, type Preset } from '@/plugins/downloader/types';
|
import { DefaultPresetList, type Preset } from '@/plugins/downloader/types';
|
||||||
|
|
||||||
// prettier-ignore
|
import type { SyncedLyricsPluginConfig } from '@/plugins/synced-lyrics/types';
|
||||||
export type IStore = InstanceType<typeof import('conf').default<Record<string, unknown>>>;
|
|
||||||
|
export type IStore = InstanceType<
|
||||||
|
typeof import('conf').default<Record<string, unknown>>
|
||||||
|
>;
|
||||||
|
|
||||||
const migrations = {
|
const migrations = {
|
||||||
|
'>=3.4.0'(store: IStore) {
|
||||||
|
const lyricGeniusConfig = store.get('plugins.lyrics-genius') as
|
||||||
|
| {
|
||||||
|
enabled?: boolean;
|
||||||
|
romanizedLyrics?: boolean;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (lyricGeniusConfig) {
|
||||||
|
const syncedLyricsConfig = store.get('plugins.synced-lyrics') as
|
||||||
|
| SyncedLyricsPluginConfig
|
||||||
|
| undefined;
|
||||||
|
store.set('plugins.synced-lyrics', {
|
||||||
|
...syncedLyricsConfig,
|
||||||
|
enabled: lyricGeniusConfig.enabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
store.delete('plugins.lyrics-genius');
|
||||||
|
}
|
||||||
|
},
|
||||||
'>=3.3.0'(store: IStore) {
|
'>=3.3.0'(store: IStore) {
|
||||||
const lastfmConfig = store.get('plugins.lastfm') as {
|
const lastfmConfig = store.get('plugins.lastfm') as {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
|
|||||||
@ -19,8 +19,6 @@ import {
|
|||||||
sendFeedback as sendFeedback_,
|
sendFeedback as sendFeedback_,
|
||||||
setBadge,
|
setBadge,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { fetchFromGenius } from '@/plugins/lyrics-genius/main';
|
|
||||||
import { isEnabled } from '@/config/plugins';
|
|
||||||
import registerCallback, {
|
import registerCallback, {
|
||||||
cleanupName,
|
cleanupName,
|
||||||
getImage,
|
getImage,
|
||||||
@ -598,16 +596,6 @@ async function writeID3(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEnabled('lyrics-genius')) {
|
|
||||||
const lyrics = await fetchFromGenius(metadata);
|
|
||||||
if (lyrics) {
|
|
||||||
tags.unsynchronisedLyrics = {
|
|
||||||
language: '',
|
|
||||||
text: lyrics,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metadata.trackId) {
|
if (metadata.trackId) {
|
||||||
tags.trackNumber = metadata.trackId;
|
tags.trackNumber = metadata.trackId;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
import style from './style.css?inline';
|
|
||||||
import { createPlugin } from '@/utils';
|
|
||||||
import { onConfigChange, onMainLoad } from './main';
|
|
||||||
import { onRendererLoad } from './renderer';
|
|
||||||
import { t } from '@/i18n';
|
|
||||||
|
|
||||||
export type LyricsGeniusPluginConfig = {
|
|
||||||
enabled: boolean;
|
|
||||||
romanizedLyrics: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createPlugin({
|
|
||||||
name: () => t('plugins.lyrics-genius.name'),
|
|
||||||
description: () => t('plugins.lyrics-genius.description'),
|
|
||||||
restartNeeded: true,
|
|
||||||
config: {
|
|
||||||
enabled: false,
|
|
||||||
romanizedLyrics: false,
|
|
||||||
} as LyricsGeniusPluginConfig,
|
|
||||||
stylesheets: [style],
|
|
||||||
async menu({ getConfig, setConfig }) {
|
|
||||||
const config = await getConfig();
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: t('plugins.lyrics-genius.menu.romanized-lyrics'),
|
|
||||||
type: 'checkbox',
|
|
||||||
checked: config.romanizedLyrics,
|
|
||||||
click(item) {
|
|
||||||
setConfig({
|
|
||||||
romanizedLyrics: item.checked,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
backend: {
|
|
||||||
start: onMainLoad,
|
|
||||||
onConfigChange,
|
|
||||||
},
|
|
||||||
renderer: onRendererLoad,
|
|
||||||
});
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
import { net } from 'electron';
|
|
||||||
import is from 'electron-is';
|
|
||||||
import { convert } from 'html-to-text';
|
|
||||||
|
|
||||||
import { GetGeniusLyric } from './types';
|
|
||||||
import { cleanupName, type SongInfo } from '@/providers/song-info';
|
|
||||||
|
|
||||||
import type { LyricsGeniusPluginConfig } from './index';
|
|
||||||
|
|
||||||
import type { BackendContext } from '@/types/contexts';
|
|
||||||
|
|
||||||
const eastAsianChars =
|
|
||||||
/\p{Script=Katakana}|\p{Script=Hiragana}|\p{Script=Hangul}|\p{Script=Han}/u;
|
|
||||||
let revRomanized = false;
|
|
||||||
|
|
||||||
export const onMainLoad = async ({
|
|
||||||
ipc,
|
|
||||||
getConfig,
|
|
||||||
}: BackendContext<LyricsGeniusPluginConfig>) => {
|
|
||||||
const config = await getConfig();
|
|
||||||
|
|
||||||
if (config.romanizedLyrics) {
|
|
||||||
revRomanized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ipc.handle('search-genius-lyrics', async (extractedSongInfo: SongInfo) => {
|
|
||||||
const metadata = extractedSongInfo;
|
|
||||||
return await fetchFromGenius(metadata);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const onConfigChange = (newConfig: LyricsGeniusPluginConfig) => {
|
|
||||||
revRomanized = newConfig.romanizedLyrics;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchFromGenius = async (metadata: SongInfo) => {
|
|
||||||
const songTitle = `${cleanupName(metadata.title)}`;
|
|
||||||
const songArtist = `${cleanupName(metadata.artist)}`;
|
|
||||||
let lyrics: string | null;
|
|
||||||
|
|
||||||
/* Uses Regex to test the title and artist first for said characters if romanization is enabled. Otherwise, normal
|
|
||||||
Genius Lyrics behavior is observed.
|
|
||||||
*/
|
|
||||||
let hasAsianChars = false;
|
|
||||||
if (
|
|
||||||
revRomanized &&
|
|
||||||
(eastAsianChars.test(songTitle) || eastAsianChars.test(songArtist))
|
|
||||||
) {
|
|
||||||
lyrics = await getLyricsList(`${songArtist} ${songTitle} Romanized`);
|
|
||||||
hasAsianChars = true;
|
|
||||||
} else {
|
|
||||||
lyrics = await getLyricsList(`${songArtist} ${songTitle}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the romanization toggle is on, and we did not detect any characters in the title or artist, we do a check
|
|
||||||
for characters in the lyrics themselves. If this check proves true, we search for Romanized lyrics.
|
|
||||||
*/
|
|
||||||
if (revRomanized && !hasAsianChars && lyrics && eastAsianChars.test(lyrics)) {
|
|
||||||
lyrics = await getLyricsList(`${songArtist} ${songTitle} Romanized`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lyrics;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a JSON of songs which is then parsed and passed into getLyrics to get the lyrical content of the first song
|
|
||||||
* @param {*} queryString
|
|
||||||
* @returns The lyrics of the first song found using the Genius-Lyrics API
|
|
||||||
*/
|
|
||||||
const getLyricsList = async (queryString: string): Promise<string | null> => {
|
|
||||||
const response = await net.fetch(
|
|
||||||
`https://genius.com/api/search/multi?per_page=5&q=${encodeURIComponent(
|
|
||||||
queryString,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
if (!response.ok) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fetch the first URL with the api, giving a collection of song results.
|
|
||||||
Pick the first song, parsing the json given by the API.
|
|
||||||
*/
|
|
||||||
const info = (await response.json()) as GetGeniusLyric;
|
|
||||||
const url = info?.response?.sections?.find(
|
|
||||||
(section) => section.type === 'song',
|
|
||||||
)?.hits[0]?.result?.url;
|
|
||||||
|
|
||||||
if (url) {
|
|
||||||
return await getLyrics(url);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} url
|
|
||||||
* @returns The lyrics of the song URL provided, null if none
|
|
||||||
*/
|
|
||||||
const getLyrics = async (url: string): Promise<string | null> => {
|
|
||||||
const response = await net.fetch(url);
|
|
||||||
if (!response.ok) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is.dev()) {
|
|
||||||
console.log('Fetching lyrics from Genius:', url);
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = await response.text();
|
|
||||||
return convert(html, {
|
|
||||||
baseElements: {
|
|
||||||
selectors: ['[class^="Lyrics__Container"]', '.lyrics'],
|
|
||||||
},
|
|
||||||
selectors: [
|
|
||||||
{
|
|
||||||
selector: 'a',
|
|
||||||
format: 'linkFormatter',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
formatters: {
|
|
||||||
// Remove links by keeping only the content
|
|
||||||
linkFormatter(element, walk, builder) {
|
|
||||||
walk(element.children, builder);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,127 +0,0 @@
|
|||||||
import { LoggerPrefix } from '@/utils';
|
|
||||||
|
|
||||||
import { t } from '@/i18n';
|
|
||||||
|
|
||||||
import { defaultTrustedTypePolicy } from '@/utils/trusted-types';
|
|
||||||
|
|
||||||
import type { SongInfo } from '@/providers/song-info';
|
|
||||||
import type { RendererContext } from '@/types/contexts';
|
|
||||||
import type { LyricsGeniusPluginConfig } from '@/plugins/lyrics-genius/index';
|
|
||||||
|
|
||||||
export const onRendererLoad = ({
|
|
||||||
ipc: { invoke, on },
|
|
||||||
}: RendererContext<LyricsGeniusPluginConfig>) => {
|
|
||||||
const setLyrics = (lyricsContainer: Element, lyrics: string | null) => {
|
|
||||||
const targetHtml = `
|
|
||||||
<div id="contents" class="style-scope ytmusic-section-list-renderer description ytmusic-description-shelf-renderer genius-lyrics">
|
|
||||||
${
|
|
||||||
lyrics?.replaceAll(/\r\n|\r|\n/g, '<br/>') ??
|
|
||||||
'Could not retrieve lyrics from genius'
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer" style="align-self: baseline">
|
|
||||||
</yt-formatted-string>
|
|
||||||
`;
|
|
||||||
(lyricsContainer.innerHTML as string | TrustedHTML) =
|
|
||||||
defaultTrustedTypePolicy
|
|
||||||
? defaultTrustedTypePolicy.createHTML(targetHtml)
|
|
||||||
: targetHtml;
|
|
||||||
|
|
||||||
if (lyrics) {
|
|
||||||
const footer = lyricsContainer.querySelector('.footer');
|
|
||||||
|
|
||||||
if (footer) {
|
|
||||||
footer.textContent = 'Source: Genius';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let unregister: (() => void) | null = null;
|
|
||||||
|
|
||||||
on('ytmd:update-song-info', (extractedSongInfo: SongInfo) => {
|
|
||||||
unregister?.();
|
|
||||||
|
|
||||||
setTimeout(async () => {
|
|
||||||
const tabList = document.querySelectorAll<HTMLElement>('tp-yt-paper-tab');
|
|
||||||
const tabs = {
|
|
||||||
upNext: tabList[0],
|
|
||||||
lyrics: tabList[1],
|
|
||||||
discover: tabList[2],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if disabled
|
|
||||||
if (!tabs.lyrics?.hasAttribute('disabled')) return;
|
|
||||||
|
|
||||||
const lyrics = (await invoke(
|
|
||||||
'search-genius-lyrics',
|
|
||||||
extractedSongInfo,
|
|
||||||
)) as string | null;
|
|
||||||
|
|
||||||
if (!lyrics) {
|
|
||||||
// Delete previous lyrics if tab is open and couldn't get new lyrics
|
|
||||||
tabs.upNext.click();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.electronIs.dev()) {
|
|
||||||
console.log(
|
|
||||||
LoggerPrefix,
|
|
||||||
t('plugins.lyric-genius.renderer.fetched-lyrics'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tryToInjectLyric = (callback?: () => void) => {
|
|
||||||
const lyricsContainer = document.querySelector(
|
|
||||||
'[page-type="MUSIC_PAGE_TYPE_TRACK_LYRICS"] > ytmusic-message-renderer',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (lyricsContainer) {
|
|
||||||
callback?.();
|
|
||||||
|
|
||||||
setLyrics(lyricsContainer, lyrics);
|
|
||||||
applyLyricsTabState();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyLyricsTabState = () => {
|
|
||||||
if (lyrics) {
|
|
||||||
tabs.lyrics.removeAttribute('disabled');
|
|
||||||
tabs.lyrics.removeAttribute('aria-disabled');
|
|
||||||
} else {
|
|
||||||
tabs.lyrics.setAttribute('disabled', '');
|
|
||||||
tabs.lyrics.setAttribute('aria-disabled', '');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const lyricsTabHandler = () => {
|
|
||||||
const tabContainer = document.querySelector('ytmusic-tab-renderer');
|
|
||||||
if (!tabContainer) return;
|
|
||||||
|
|
||||||
const observer = new MutationObserver((_, observer) => {
|
|
||||||
tryToInjectLyric(() => observer.disconnect());
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(tabContainer, {
|
|
||||||
attributes: true,
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
applyLyricsTabState();
|
|
||||||
|
|
||||||
tabs.discover.addEventListener('click', applyLyricsTabState);
|
|
||||||
tabs.lyrics.addEventListener('click', lyricsTabHandler);
|
|
||||||
tabs.upNext.addEventListener('click', applyLyricsTabState);
|
|
||||||
|
|
||||||
tryToInjectLyric();
|
|
||||||
|
|
||||||
unregister = () => {
|
|
||||||
tabs.discover.removeEventListener('click', applyLyricsTabState);
|
|
||||||
tabs.lyrics.removeEventListener('click', lyricsTabHandler);
|
|
||||||
tabs.upNext.removeEventListener('click', applyLyricsTabState);
|
|
||||||
};
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
/* Disable links in Genius lyrics */
|
|
||||||
.genius-lyrics a {
|
|
||||||
color: var(--ytmusic-text-primary);
|
|
||||||
display: inline-block;
|
|
||||||
pointer-events: none;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
font-size: clamp(1.4rem, 1.1vmax, 3rem) !important;
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
export interface GetGeniusLyric {
|
|
||||||
meta: Meta;
|
|
||||||
response: Response;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Meta {
|
|
||||||
status: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Response {
|
|
||||||
sections: Section[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Section {
|
|
||||||
type: string;
|
|
||||||
hits: Hit[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Hit {
|
|
||||||
highlights: Highlight[];
|
|
||||||
index: ResultType;
|
|
||||||
type: ResultType;
|
|
||||||
result: Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Highlight {
|
|
||||||
property: string;
|
|
||||||
value: string;
|
|
||||||
snippet: boolean;
|
|
||||||
ranges: Range[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Range {
|
|
||||||
start: number;
|
|
||||||
end: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ResultType = 'song' | 'album' | 'lyric';
|
|
||||||
|
|
||||||
export interface Result {
|
|
||||||
_type: ResultType;
|
|
||||||
annotation_count?: number;
|
|
||||||
api_path: string;
|
|
||||||
artist_names?: string;
|
|
||||||
full_title: string;
|
|
||||||
header_image_thumbnail_url?: string;
|
|
||||||
header_image_url?: string;
|
|
||||||
id: number;
|
|
||||||
instrumental?: boolean;
|
|
||||||
lyrics_owner_id?: number;
|
|
||||||
lyrics_state?: LyricsState;
|
|
||||||
lyrics_updated_at?: number;
|
|
||||||
path?: string;
|
|
||||||
pyongs_count?: number | 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;
|
|
||||||
featured_artists?: string[];
|
|
||||||
primary_artist?: Artist;
|
|
||||||
cover_art_thumbnail_url?: string;
|
|
||||||
cover_art_url?: string;
|
|
||||||
name?: string;
|
|
||||||
name_with_artist?: string;
|
|
||||||
artist?: Artist;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Artist {
|
|
||||||
_type: Type;
|
|
||||||
api_path: string;
|
|
||||||
header_image_url: string;
|
|
||||||
id: number;
|
|
||||||
image_url: string;
|
|
||||||
index_character: IndexCharacter;
|
|
||||||
is_meme_verified: boolean;
|
|
||||||
is_verified: boolean;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
url: string;
|
|
||||||
iq?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add more types
|
|
||||||
export enum Type {
|
|
||||||
Artist = 'artist',
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add more index characters
|
|
||||||
export enum IndexCharacter {
|
|
||||||
G = 'g',
|
|
||||||
Y = 'y',
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add more states
|
|
||||||
export enum LyricsState {
|
|
||||||
Complete = 'complete',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReleaseDateComponents {
|
|
||||||
year: number;
|
|
||||||
month: number;
|
|
||||||
day: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Stats {
|
|
||||||
unreviewed_annotations: number;
|
|
||||||
concurrents?: number;
|
|
||||||
hot: boolean;
|
|
||||||
pageviews?: number;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user