feat: remove lyrics-genius

This commit is contained in:
JellyBrick
2025-07-10 16:40:33 +09:00
parent 61e19cfa9f
commit 3f442a97c5
7 changed files with 25 additions and 441 deletions

View File

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

View File

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

View File

@ -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,
});

View File

@ -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);
},
},
});
};

View File

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

View File

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

View File

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