mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 18:41:47 +00:00
feat: migration to TypeScript part 3
Co-authored-by: Su-Yong <simssy2205@gmail.com>
This commit is contained in:
@ -1,36 +1,43 @@
|
||||
const { join } = require('node:path');
|
||||
import { join } from 'node:path';
|
||||
|
||||
const { ipcMain, net } = require('electron');
|
||||
const is = require('electron-is');
|
||||
const { convert } = require('html-to-text');
|
||||
import { BrowserWindow, ipcMain, net } from 'electron';
|
||||
import is from 'electron-is';
|
||||
import { convert } from 'html-to-text';
|
||||
|
||||
const { cleanupName } = require('../../providers/song-info');
|
||||
const { injectCSS } = require('../utils');
|
||||
import { GetGeniusLyric } from './types';
|
||||
|
||||
import { cleanupName, SongInfo } from '../../providers/song-info';
|
||||
|
||||
import { injectCSS } from '../utils';
|
||||
import config from '../../config';
|
||||
|
||||
const eastAsianChars = /\p{Script=Katakana}|\p{Script=Hiragana}|\p{Script=Hangul}|\p{Script=Han}/u;
|
||||
let revRomanized = false;
|
||||
|
||||
module.exports = async (win, options) => {
|
||||
const LyricGeniusTypeObj = config.get('plugins.lyric-genius');
|
||||
export type LyricGeniusType = typeof LyricGeniusTypeObj;
|
||||
|
||||
export default (win: BrowserWindow, options: LyricGeniusType) => {
|
||||
if (options.romanizedLyrics) {
|
||||
revRomanized = true;
|
||||
}
|
||||
|
||||
injectCSS(win.webContents, join(__dirname, 'style.css'));
|
||||
|
||||
ipcMain.on('search-genius-lyrics', async (event, extractedSongInfo) => {
|
||||
const metadata = JSON.parse(extractedSongInfo);
|
||||
event.returnValue = await fetchFromGenius(metadata);
|
||||
ipcMain.handle('search-genius-lyrics', async (_, extractedSongInfo: string) => {
|
||||
const metadata = JSON.parse(extractedSongInfo) as SongInfo;
|
||||
return await fetchFromGenius(metadata);
|
||||
});
|
||||
};
|
||||
|
||||
const toggleRomanized = () => {
|
||||
export const toggleRomanized = () => {
|
||||
revRomanized = !revRomanized;
|
||||
};
|
||||
|
||||
const fetchFromGenius = async (metadata) => {
|
||||
export const fetchFromGenius = async (metadata: SongInfo) => {
|
||||
const songTitle = `${cleanupName(metadata.title)}`;
|
||||
const songArtist = `${cleanupName(metadata.artist)}`;
|
||||
let lyrics;
|
||||
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.
|
||||
@ -46,7 +53,7 @@ const fetchFromGenius = async (metadata) => {
|
||||
/* 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 && eastAsianChars.test(lyrics)) {
|
||||
if (revRomanized && !hasAsianChars && lyrics && eastAsianChars.test(lyrics)) {
|
||||
lyrics = await getLyricsList(`${songArtist} ${songTitle} Romanized`);
|
||||
}
|
||||
|
||||
@ -58,7 +65,7 @@ const fetchFromGenius = async (metadata) => {
|
||||
* @param {*} queryString
|
||||
* @returns The lyrics of the first song found using the Genius-Lyrics API
|
||||
*/
|
||||
const getLyricsList = async (queryString) => {
|
||||
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)}`,
|
||||
);
|
||||
@ -69,17 +76,17 @@ const getLyricsList = async (queryString) => {
|
||||
/* 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();
|
||||
let url = '';
|
||||
try {
|
||||
url = info.response.sections.find((section) => section.type === 'song')
|
||||
.hits[0].result.url;
|
||||
} catch {
|
||||
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;
|
||||
}
|
||||
|
||||
const lyrics = await getLyrics(url);
|
||||
return lyrics;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -87,7 +94,7 @@ const getLyricsList = async (queryString) => {
|
||||
* @param {*} url
|
||||
* @returns The lyrics of the song URL provided, null if none
|
||||
*/
|
||||
const getLyrics = async (url) => {
|
||||
const getLyrics = async (url: string): Promise<string | null> => {
|
||||
const response = await net.fetch(url);
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
@ -116,6 +123,3 @@ const getLyrics = async (url) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.toggleRomanized = toggleRomanized;
|
||||
module.exports.fetchFromGenius = fetchFromGenius;
|
||||
@ -1,8 +1,8 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
const is = require('electron-is');
|
||||
import { ipcRenderer } from 'electron';
|
||||
import is from 'electron-is';
|
||||
|
||||
module.exports = () => {
|
||||
ipcRenderer.on('update-song-info', (_, extractedSongInfo) => setTimeout(() => {
|
||||
export default () => {
|
||||
ipcRenderer.on('update-song-info', (_, extractedSongInfo: string) => setTimeout(async () => {
|
||||
const tabList = document.querySelectorAll('tp-yt-paper-tab');
|
||||
const tabs = {
|
||||
upNext: tabList[0],
|
||||
@ -17,10 +17,10 @@ module.exports = () => {
|
||||
|
||||
let hasLyrics = true;
|
||||
|
||||
const lyrics = ipcRenderer.sendSync(
|
||||
const lyrics = await ipcRenderer.invoke(
|
||||
'search-genius-lyrics',
|
||||
extractedSongInfo,
|
||||
);
|
||||
) as string;
|
||||
if (!lyrics) {
|
||||
// Delete previous lyrics if tab is open and couldn't get new lyrics
|
||||
checkLyricsContainer(() => {
|
||||
@ -40,17 +40,21 @@ module.exports = () => {
|
||||
|
||||
checkLyricsContainer();
|
||||
|
||||
tabs.lyrics.addEventListener('click', () => {
|
||||
const lyricsTabHandler = () => {
|
||||
const tabContainer = document.querySelector('ytmusic-tab-renderer');
|
||||
const observer = new MutationObserver((_, observer) => {
|
||||
checkLyricsContainer(() => observer.disconnect());
|
||||
});
|
||||
observer.observe(tabContainer, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
});
|
||||
if (tabContainer) {
|
||||
const observer = new MutationObserver((_, observer) => {
|
||||
checkLyricsContainer(() => observer.disconnect());
|
||||
});
|
||||
observer.observe(tabContainer, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
tabs.lyrics.addEventListener('click', lyricsTabHandler);
|
||||
|
||||
function checkLyricsContainer(callback = () => {
|
||||
}) {
|
||||
@ -63,7 +67,7 @@ module.exports = () => {
|
||||
}
|
||||
}
|
||||
|
||||
function setLyrics(lyricsContainer) {
|
||||
function setLyrics(lyricsContainer: Element) {
|
||||
lyricsContainer.innerHTML = `<div id="contents" class="style-scope ytmusic-section-list-renderer description ytmusic-description-shelf-renderer genius-lyrics">
|
||||
${
|
||||
hasLyrics
|
||||
@ -74,15 +78,20 @@ module.exports = () => {
|
||||
</div>
|
||||
<yt-formatted-string class="footer style-scope ytmusic-description-shelf-renderer" style="align-self: baseline"></yt-formatted-string>`;
|
||||
if (hasLyrics) {
|
||||
lyricsContainer.querySelector('.footer').textContent = 'Source: Genius';
|
||||
enableLyricsTab();
|
||||
const footer = lyricsContainer.querySelector('.footer');
|
||||
if (footer) {
|
||||
footer.textContent = 'Source: Genius';
|
||||
enableLyricsTab();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTabsOnclick(callback) {
|
||||
const defaultHandler = () => {};
|
||||
|
||||
function setTabsOnclick(callback: EventListenerOrEventListenerObject | undefined) {
|
||||
for (const tab of [tabs.upNext, tabs.discover]) {
|
||||
if (tab) {
|
||||
tab.addEventListener('click', callback);
|
||||
tab.addEventListener('click', callback ?? defaultHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
const { toggleRomanized } = require('./back');
|
||||
|
||||
const { setOptions } = require('../../config/plugins');
|
||||
|
||||
module.exports = (win, options) => [
|
||||
{
|
||||
label: 'Romanized Lyrics',
|
||||
type: 'checkbox',
|
||||
checked: options.romanizedLyrics,
|
||||
click(item) {
|
||||
options.romanizedLyrics = item.checked;
|
||||
setOptions('lyrics-genius', options);
|
||||
toggleRomanized();
|
||||
},
|
||||
},
|
||||
];
|
||||
18
plugins/lyrics-genius/menu.ts
Normal file
18
plugins/lyrics-genius/menu.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { BrowserWindow, MenuItem } from 'electron';
|
||||
|
||||
import { LyricGeniusType, toggleRomanized } from './back';
|
||||
|
||||
import { setOptions } from '../../config/plugins';
|
||||
|
||||
module.exports = (win: BrowserWindow, options: LyricGeniusType) => [
|
||||
{
|
||||
label: 'Romanized Lyrics',
|
||||
type: 'checkbox',
|
||||
checked: options.romanizedLyrics,
|
||||
click(item: MenuItem) {
|
||||
options.romanizedLyrics = item.checked;
|
||||
setOptions('lyrics-genius', options);
|
||||
toggleRomanized();
|
||||
},
|
||||
},
|
||||
];
|
||||
121
plugins/lyrics-genius/types.ts
Normal file
121
plugins/lyrics-genius/types.ts
Normal file
@ -0,0 +1,121 @@
|
||||
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: Index;
|
||||
type: Index;
|
||||
result: Result;
|
||||
}
|
||||
|
||||
export interface Highlight {
|
||||
property: string;
|
||||
value: string;
|
||||
snippet: boolean;
|
||||
ranges: Range[];
|
||||
}
|
||||
|
||||
export interface Range {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
export enum Index {
|
||||
Album = 'album',
|
||||
Lyric = 'lyric',
|
||||
Song = 'song',
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
_type: Index;
|
||||
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