mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-10 18:21: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';
|
||||
|
||||
// prettier-ignore
|
||||
export type IStore = InstanceType<typeof import('conf').default<Record<string, unknown>>>;
|
||||
import type { SyncedLyricsPluginConfig } from '@/plugins/synced-lyrics/types';
|
||||
|
||||
export type IStore = InstanceType<
|
||||
typeof import('conf').default<Record<string, unknown>>
|
||||
>;
|
||||
|
||||
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) {
|
||||
const lastfmConfig = store.get('plugins.lastfm') as {
|
||||
enabled?: boolean;
|
||||
|
||||
@ -19,8 +19,6 @@ import {
|
||||
sendFeedback as sendFeedback_,
|
||||
setBadge,
|
||||
} from './utils';
|
||||
import { fetchFromGenius } from '@/plugins/lyrics-genius/main';
|
||||
import { isEnabled } from '@/config/plugins';
|
||||
import registerCallback, {
|
||||
cleanupName,
|
||||
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) {
|
||||
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