From 474c3846f7c3925e5467a91bad7be7b53c89f925 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Fri, 6 Oct 2023 14:32:04 +0900 Subject: [PATCH 01/21] feat: enable context-isolation --- config/store.ts | 2 +- index.ts | 2 +- plugins/downloader/back.ts | 13 +++++-------- plugins/downloader/front.ts | 10 +++------- plugins/picture-in-picture/front.ts | 5 ++--- preload.ts | 5 ++--- rollup.main.config.ts | 1 + 7 files changed, 15 insertions(+), 23 deletions(-) diff --git a/config/store.ts b/config/store.ts index f5fdac9ddf..709cd44269 100644 --- a/config/store.ts +++ b/config/store.ts @@ -57,7 +57,7 @@ const migrations = { }[] | Record>; let updated = false; for (const optionType of ['global', 'local']) { - if (Array.isArray(options[optionType])) { + if (Array.isArray(options?.[optionType])) { const optionsArray = options[optionType] as { action: string; shortcut: unknown; diff --git a/index.ts b/index.ts index 1617eb6891..6b71256e8e 100644 --- a/index.ts +++ b/index.ts @@ -197,7 +197,7 @@ function createMainWindow() { webPreferences: { // TODO: re-enable contextIsolation once it can work with FFMpeg.wasm // Possible bundling? https://github.com/ffmpegwasm/ffmpeg.wasm/issues/126 - contextIsolation: false, + contextIsolation: true, preload: path.join(__dirname, 'preload.js'), nodeIntegrationInSubFrames: true, ...(isTesting() diff --git a/plugins/downloader/back.ts b/plugins/downloader/back.ts index 9d02f114dc..12efeff0fc 100644 --- a/plugins/downloader/back.ts +++ b/plugins/downloader/back.ts @@ -2,7 +2,7 @@ import { createWriteStream, existsSync, mkdirSync, writeFileSync, } from 'node:f import { join } from 'node:path'; import { randomBytes } from 'node:crypto'; -import { app, BrowserWindow, dialog, ipcMain } from 'electron'; +import { app, BrowserWindow, dialog, ipcMain, net } from 'electron'; import { ClientType, Innertube, UniversalCache, Utils } from 'youtubei.js'; import is from 'electron-is'; import ytpl from 'ytpl'; @@ -80,6 +80,9 @@ export default async (win_: BrowserWindow) => { cache: new UniversalCache(false), cookie, generate_session_locally: true, + fetch: async (input: RequestInfo | URL, init?: RequestInit) => { + return net.fetch(new Request(input, init)); + } }); ipcMain.on('download-song', (_, url: string) => downloadSong(url)); ipcMain.on('video-src-changed', (_, data: GetPlayerResponse) => { @@ -107,7 +110,6 @@ export async function downloadSong( increasePlaylistProgress, ); } catch (error: unknown) { - console.log('maybe?????', error); sendError(error as Error, resolvedName || url); } } @@ -117,8 +119,7 @@ async function downloadSongUnsafe( setName: (name: string) => void, playlistFolder: string | undefined = undefined, trackId: string | undefined = undefined, - increasePlaylistProgress: (value: number) => void = () => { - }, + increasePlaylistProgress: (value: number) => void = () => {}, ) { const sendFeedback = (message: unknown, progress?: number) => { if (!playlistFolder) { @@ -320,7 +321,6 @@ async function iterableStreamToMP3( ffmpeg.FS('unlink', `${safeVideoName}.mp3`); } } catch (error: unknown) { - console.log('maybe?', error); sendError(error as Error, safeVideoName); } finally { releaseFFmpegMutex(); @@ -373,7 +373,6 @@ async function writeID3(buffer: Buffer, metadata: CustomSongInfo, sendFeedback: return NodeID3.write(tags, buffer); } catch (error: unknown) { - console.log('fallback', error); sendError(error as Error, `${metadata.artist} - ${metadata.title}`); return null; } @@ -488,7 +487,6 @@ export async function downloadPlaylist(givenUrl?: string | URL) { counter++; } } catch (error: unknown) { - console.log('also?', error); sendError(error as Error); } finally { win.setProgressBar(-1); // Close progress bar @@ -514,7 +512,6 @@ async function ffmpegWriteTags(filePath: string, metadata: CustomSongInfo, prese filePath, ); } catch (error: unknown) { - console.log('ffmpeg?', error); sendError(error as Error); } finally { releaseFFmpegMutex(); diff --git a/plugins/downloader/front.ts b/plugins/downloader/front.ts index 2a72eec615..ebf7313a49 100644 --- a/plugins/downloader/front.ts +++ b/plugins/downloader/front.ts @@ -1,4 +1,4 @@ -import { ipcRenderer } from 'electron'; +import { contextBridge, ipcRenderer } from 'electron'; import downloadHTML from './templates/download.html'; @@ -40,11 +40,7 @@ const menuObserver = new MutationObserver(() => { setTimeout(() => doneFirstLoad ||= true, 500); }); -// TODO: re-enable once contextIsolation is set to true -// contextBridge.exposeInMainWorld("downloader", { -// download: () => { -// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access -(global as any).download = () => { +contextBridge.exposeInMainWorld('download', () => { let videoUrl = getSongMenu() // Selector of first button which is always "Start Radio" ?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="0"] #navigation-endpoint') @@ -63,7 +59,7 @@ const menuObserver = new MutationObserver(() => { } ipcRenderer.send('download-song', videoUrl); -}; +}); export default () => { document.addEventListener('apiLoaded', () => { diff --git a/plugins/picture-in-picture/front.ts b/plugins/picture-in-picture/front.ts index 96236eba51..04161c5223 100644 --- a/plugins/picture-in-picture/front.ts +++ b/plugins/picture-in-picture/front.ts @@ -1,4 +1,4 @@ -import { ipcRenderer } from 'electron'; +import { contextBridge, ipcRenderer } from 'electron'; import { toKeyEvent } from 'keyboardevent-from-electron-accelerator'; import keyEventAreEqual from 'keyboardevents-areequal'; @@ -89,8 +89,7 @@ const togglePictureInPicture = async () => { return false; }; // For UI (HTML) -// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access -(global as any).togglePictureInPicture = togglePictureInPicture; +contextBridge.exposeInMainWorld('togglePictureInPicture', togglePictureInPicture); const listenForToggle = () => { const originalExitButton = $('.exit-fullscreen-button'); diff --git a/preload.ts b/preload.ts index 1886958bc3..258199dd52 100644 --- a/preload.ts +++ b/preload.ts @@ -1,4 +1,4 @@ -import { ipcRenderer } from 'electron'; +import { contextBridge, ipcRenderer } from 'electron'; import is from 'electron-is'; import config from './config'; @@ -111,8 +111,7 @@ document.addEventListener('DOMContentLoaded', () => { setupSongControls(); // Add action for reloading - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any - (global as any).reload = () => ipcRenderer.send('reload'); + contextBridge.exposeInMainWorld('reload', () => ipcRenderer.send('reload')); // Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min setInterval(() => window._lact = Date.now(), 900_000); diff --git a/rollup.main.config.ts b/rollup.main.config.ts index 8fc9d9b889..767c89a190 100644 --- a/rollup.main.config.ts +++ b/rollup.main.config.ts @@ -34,6 +34,7 @@ export default defineConfig({ css(), copy({ targets: [ + { src: 'error.html', dest: 'dist/' }, { src: 'assets', dest: 'dist/' }, ], }), From a4cbfe2b200fdec3c47712665a73d29ab52cf031 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Fri, 6 Oct 2023 23:19:11 +0900 Subject: [PATCH 02/21] fix: fix the plugins to use an isolated environment --- config/dynamic-renderer.ts | 190 +++++++++++++++++++ config/dynamic.ts | 91 ++------- config/plugins.ts | 1 - index.ts | 7 + package.json | 3 +- plugins/adblocker/back.ts | 4 +- plugins/adblocker/config.ts | 3 +- plugins/adblocker/preload.ts | 6 +- plugins/captions-selector/config-renderer.ts | 4 + plugins/captions-selector/front.ts | 26 +-- plugins/crossfade/config-renderer.ts | 4 + plugins/crossfade/front.ts | 20 +- plugins/downloader/back.ts | 23 ++- plugins/downloader/front.ts | 14 +- plugins/in-app-menu/back.ts | 17 +- plugins/in-app-menu/front.ts | 13 +- plugins/in-app-menu/menu/panel.ts | 26 +-- plugins/lyrics-genius/front.ts | 13 +- plugins/navigation/front.ts | 6 +- plugins/picture-in-picture/front.ts | 9 +- plugins/playback-speed/front.ts | 2 +- plugins/precise-volume/front.ts | 15 +- plugins/quality-changer/front.ts | 6 +- plugins/sponsorblock/front.ts | 7 +- plugins/utils-renderer.ts | 8 + plugins/utils.ts | 30 +-- plugins/video-toggle/front.ts | 7 +- preload.ts | 186 ++---------------- providers/song-controls-front.ts | 6 +- providers/song-info-front.ts | 35 ++-- renderer.ts | 171 +++++++++++++++++ reset.d.ts | 10 + rollup.main.config.ts | 6 +- rollup.preload.config.ts | 1 - rollup.renderer.config.ts | 57 ++++++ 35 files changed, 611 insertions(+), 416 deletions(-) create mode 100644 config/dynamic-renderer.ts create mode 100644 plugins/captions-selector/config-renderer.ts create mode 100644 plugins/crossfade/config-renderer.ts create mode 100644 plugins/utils-renderer.ts create mode 100644 renderer.ts create mode 100644 rollup.renderer.config.ts diff --git a/config/dynamic-renderer.ts b/config/dynamic-renderer.ts new file mode 100644 index 0000000000..91c85cd531 --- /dev/null +++ b/config/dynamic-renderer.ts @@ -0,0 +1,190 @@ +import defaultConfig from './defaults'; + +import { Entries } from '../utils/type-utils'; + +import type { OneOfDefaultConfigKey, OneOfDefaultConfig, ConfigType } from './dynamic'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const activePlugins: { [key in OneOfDefaultConfigKey]?: PluginConfig } = {}; + +export const getActivePlugins + = async () => await window.ipcRenderer.invoke('get-active-plugins') as Promise; + +export const isActive + = async (plugin: string) => plugin in (await window.ipcRenderer.invoke('get-active-plugins')); + +interface PluginConfigOptions { + enableFront: boolean; + initialOptions?: OneOfDefaultConfig; +} + +/** + * This class is used to create a dynamic synced config for plugins. + * + * [!IMPORTANT!] + * The methods are **sync** in the main process and **async** in the renderer process. + * + * @param {string} name - The name of the plugin. + * @param {boolean} [options.enableFront] - Whether the config should be available in front.js. Default: false. + * @param {object} [options.initialOptions] - The initial options for the plugin. Default: loaded from store. + * + * @example + * const { PluginConfig } = require("../../config/dynamic"); + * const config = new PluginConfig("plugin-name", { enableFront: true }); + * module.exports = { ...config }; + * + * // or + * + * module.exports = (win, options) => { + * const config = new PluginConfig("plugin-name", { + * enableFront: true, + * initialOptions: options, + * }); + * setupMyPlugin(win, config); + * }; + */ +type ValueOf = T[keyof T]; +type Mode = Mode extends 'r' ? Promise : T; +export class PluginConfig { + private readonly name: string; + private readonly config: ConfigType; + private readonly defaultConfig: ConfigType; + private readonly enableFront: boolean; + + private subscribers: { [key in keyof ConfigType]?: (config: ConfigType) => void } = {}; + private allSubscribers: ((config: ConfigType) => void)[] = []; + + constructor( + name: T, + options: PluginConfigOptions = { + enableFront: false, + }, + ) { + const pluginDefaultConfig = defaultConfig.plugins[name] ?? {}; + const pluginConfig = options.initialOptions || window.mainConfig.plugins.getOptions(name) || {}; + + this.name = name; + this.enableFront = options.enableFront; + this.defaultConfig = pluginDefaultConfig; + this.config = { ...pluginDefaultConfig, ...pluginConfig }; + + if (this.enableFront) { + this.setupFront(); + } + + activePlugins[name] = this; + } + + get = keyof ConfigType>(key: Key): ConfigType[Key] { + return this.config?.[key]; + } + + set(key: keyof ConfigType, value: ValueOf>) { + this.config[key] = value; + this.onChange(key); + this.save(); + } + + getAll(): ConfigType { + return { ...this.config }; + } + + setAll(options: Partial>) { + if (!options || typeof options !== 'object') { + throw new Error('Options must be an object.'); + } + + let changed = false; + for (const [key, value] of Object.entries(options) as Entries) { + if (this.config[key] !== value) { + if (value !== undefined) this.config[key] = value; + this.onChange(key, false); + changed = true; + } + } + + if (changed) { + for (const fn of this.allSubscribers) { + fn(this.config); + } + } + + this.save(); + } + + getDefaultConfig() { + return this.defaultConfig; + } + + /** + * Use this method to set an option and restart the app if `appConfig.restartOnConfigChange === true` + * + * Used for options that require a restart to take effect. + */ + setAndMaybeRestart(key: keyof ConfigType, value: ValueOf>) { + this.config[key] = value; + window.mainConfig.plugins.setMenuOptions(this.name, this.config); + this.onChange(key); + } + + subscribe(valueName: keyof ConfigType, fn: (config: ConfigType) => void) { + this.subscribers[valueName] = fn; + } + + subscribeAll(fn: (config: ConfigType) => void) { + this.allSubscribers.push(fn); + } + + /** Called only from back */ + private save() { + window.mainConfig.plugins.setOptions(this.name, this.config); + } + + private onChange(valueName: keyof ConfigType, single: boolean = true) { + this.subscribers[valueName]?.(this.config[valueName] as ConfigType); + if (single) { + for (const fn of this.allSubscribers) { + fn(this.config); + } + } + } + + private setupFront() { + const ignoredMethods = ['subscribe', 'subscribeAll']; + + for (const [fnName, fn] of Object.entries(this) as Entries) { + if (typeof fn !== 'function' || fn.name in ignoredMethods) { + return; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-return + this[fnName] = (async (...args: any) => await window.ipcRenderer.invoke( + `${this.name}-config-${String(fnName)}`, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + ...args, + )) as typeof this[keyof this]; + + this.subscribe = (valueName, fn: (config: ConfigType) => void) => { + if (valueName in this.subscribers) { + console.error(`Already subscribed to ${String(valueName)}`); + } + + this.subscribers[valueName] = fn; + window.ipcRenderer.on( + `${this.name}-config-changed-${String(valueName)}`, + (_, value: ConfigType) => { + fn(value); + }, + ); + window.ipcRenderer.send(`${this.name}-config-subscribe`, valueName); + }; + + this.subscribeAll = (fn: (config: ConfigType) => void) => { + window.ipcRenderer.on(`${this.name}-config-changed`, (_, value: ConfigType) => { + fn(value); + }); + window.ipcRenderer.send(`${this.name}-config-subscribe-all`); + }; + } + } +} diff --git a/config/dynamic.ts b/config/dynamic.ts index e9381bca72..fa05cb7d96 100644 --- a/config/dynamic.ts +++ b/config/dynamic.ts @@ -1,12 +1,9 @@ -/* eslint-disable @typescript-eslint/require-await */ - -import { ipcMain, ipcRenderer } from 'electron'; +import { ipcMain } from 'electron'; import defaultConfig from './defaults'; import { getOptions, setMenuOptions, setOptions } from './plugins'; - import { sendToFront } from '../providers/app-controls'; import { Entries } from '../utils/type-utils'; @@ -17,28 +14,13 @@ export type OneOfDefaultConfig = typeof defaultConfig.plugins[OneOfDefaultConfig // eslint-disable-next-line @typescript-eslint/no-explicit-any const activePlugins: { [key in OneOfDefaultConfigKey]?: PluginConfig } = {}; -/** - * [!IMPORTANT!] - * The method is **sync** in the main process and **async** in the renderer process. - */ -export const getActivePlugins - = process.type === 'renderer' - ? async () => ipcRenderer.invoke('get-active-plugins') - : () => activePlugins; +export const getActivePlugins = () => activePlugins; if (process.type === 'browser') { ipcMain.handle('get-active-plugins', getActivePlugins); } -/** - * [!IMPORTANT!] - * The method is **sync** in the main process and **async** in the renderer process. - */ -export const isActive - = process.type === 'renderer' - ? async (plugin: string) => - plugin in (await ipcRenderer.invoke('get-active-plugins')) - : (plugin: string): boolean => plugin in activePlugins; +export const isActive = (plugin: string): boolean => plugin in activePlugins; interface PluginConfigOptions { enableFront: boolean; @@ -180,62 +162,25 @@ export class PluginConfig { private setupFront() { const ignoredMethods = ['subscribe', 'subscribeAll']; - if (process.type === 'renderer') { - for (const [fnName, fn] of Object.entries(this) as Entries) { - if (typeof fn !== 'function' || fn.name in ignoredMethods) { - return; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-return - this[fnName] = (async (...args: any) => await ipcRenderer.invoke( - `${this.name}-config-${String(fnName)}`, - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - ...args, - )) as typeof this[keyof this]; - - this.subscribe = (valueName, fn: (config: ConfigType) => void) => { - if (valueName in this.subscribers) { - console.error(`Already subscribed to ${String(valueName)}`); - } - - this.subscribers[valueName] = fn; - ipcRenderer.on( - `${this.name}-config-changed-${String(valueName)}`, - (_, value: ConfigType) => { - fn(value); - }, - ); - ipcRenderer.send(`${this.name}-config-subscribe`, valueName); - }; - - this.subscribeAll = (fn: (config: ConfigType) => void) => { - ipcRenderer.on(`${this.name}-config-changed`, (_, value: ConfigType) => { - fn(value); - }); - ipcRenderer.send(`${this.name}-config-subscribe-all`); - }; - } - } else if (process.type === 'browser') { - for (const [fnName, fn] of Object.entries(this) as Entries) { - if (typeof fn !== 'function' || fn.name in ignoredMethods) { - return; - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-return - ipcMain.handle(`${this.name}-config-${String(fnName)}`, (_, ...args) => fn(...args)); + for (const [fnName, fn] of Object.entries(this) as Entries) { + if (typeof fn !== 'function' || fn.name in ignoredMethods) { + return; } - ipcMain.on(`${this.name}-config-subscribe`, (_, valueName: keyof ConfigType) => { - this.subscribe(valueName, (value) => { - sendToFront(`${this.name}-config-changed-${String(valueName)}`, value); - }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-return + ipcMain.handle(`${this.name}-config-${String(fnName)}`, (_, ...args) => fn(...args)); + } + + ipcMain.on(`${this.name}-config-subscribe`, (_, valueName: keyof ConfigType) => { + this.subscribe(valueName, (value) => { + sendToFront(`${this.name}-config-changed-${String(valueName)}`, value); }); + }); - ipcMain.on(`${this.name}-config-subscribe-all`, () => { - this.subscribeAll((value) => { - sendToFront(`${this.name}-config-changed`, value); - }); + ipcMain.on(`${this.name}-config-subscribe-all`, () => { + this.subscribeAll((value) => { + sendToFront(`${this.name}-config-changed`, value); }); - } + }); } } diff --git a/config/plugins.ts b/config/plugins.ts index f9ed8d6422..c8b17ad72b 100644 --- a/config/plugins.ts +++ b/config/plugins.ts @@ -51,7 +51,6 @@ export function enable(plugin: string) { export function disable(plugin: string) { setMenuOptions(plugin, { enabled: false }); } - export default { isEnabled, getEnabled, diff --git a/index.ts b/index.ts index 6b71256e8e..327585e58f 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,7 @@ import path from 'node:path'; +import fs from 'node:fs'; + import { BrowserWindow, app, screen, globalShortcut, session, shell, dialog, ipcMain } from 'electron'; import enhanceWebRequest from 'electron-better-web-request'; import is from 'electron-is'; @@ -257,6 +259,11 @@ function createMainWindow() { ? config.get('url') : config.defaultConfig.url; win.webContents.loadURL(urlToLoad); + + win.webContents.once('dom-ready', () => { + win.webContents.executeJavaScript(fs.readFileSync(path.join(__dirname, 'renderer.js'), 'utf-8') + ';0', true); + }); + win.on('closed', onClosed); type PiPOptions = typeof config.defaultConfig.plugins['picture-in-picture']; diff --git a/package.json b/package.json index 976cfd362b..46ef3e230e 100644 --- a/package.json +++ b/package.json @@ -104,9 +104,10 @@ "scripts": { "test": "npm run build && playwright test", "test:debug": "DEBUG=pw:browser* npm run build && playwright test", + "rollup:renderer": "rollup -c rollup.renderer.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs", "rollup:preload": "rollup -c rollup.preload.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs", "rollup:main": "rollup -c rollup.main.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs", - "build": "npm run rollup:preload && npm run rollup:main", + "build": "npm run rollup:renderer && npm run rollup:preload && npm run rollup:main", "start": "npm run build && electron ./dist/index.js", "start:debug": "ELECTRON_ENABLE_LOGGING=1 npm run start", "generate:package": "node utils/generate-package-json.js", diff --git a/plugins/adblocker/back.ts b/plugins/adblocker/back.ts index d33771ae80..bf6c10bef2 100644 --- a/plugins/adblocker/back.ts +++ b/plugins/adblocker/back.ts @@ -7,8 +7,8 @@ import type { ConfigType } from '../../config/dynamic'; type AdBlockOptions = ConfigType<'adblocker'>; -export default async (win: BrowserWindow, options: AdBlockOptions) => { - if (await shouldUseBlocklists()) { +export default (win: BrowserWindow, options: AdBlockOptions) => { + if (shouldUseBlocklists()) { loadAdBlockerEngine( win.webContents.session, options.cache, diff --git a/plugins/adblocker/config.ts b/plugins/adblocker/config.ts index e8bad5a508..6db517481e 100644 --- a/plugins/adblocker/config.ts +++ b/plugins/adblocker/config.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/await-thenable */ /* renderer */ import { PluginConfig } from '../../config/dynamic'; @@ -10,7 +9,7 @@ export const blockers = { InPlayer: 'In player', }; -export const shouldUseBlocklists = async () => await config.get('blocker') !== blockers.InPlayer; +export const shouldUseBlocklists = () => config.get('blocker') !== blockers.InPlayer; export default Object.assign(config, { shouldUseBlocklists, diff --git a/plugins/adblocker/preload.ts b/plugins/adblocker/preload.ts index bb210b0b6f..36020003ec 100644 --- a/plugins/adblocker/preload.ts +++ b/plugins/adblocker/preload.ts @@ -2,12 +2,12 @@ import config, { blockers } from './config'; import inject from './inject'; import injectCliqzPreload from './inject-cliqz-preload'; -export default async () => { - if (await config.shouldUseBlocklists()) { +export default () => { + if (config.shouldUseBlocklists()) { // Preload adblocker to inject scripts/styles injectCliqzPreload(); // eslint-disable-next-line @typescript-eslint/await-thenable - } else if ((await config.get('blocker')) === blockers.InPlayer) { + } else if ((config.get('blocker')) === blockers.InPlayer) { inject(); } }; diff --git a/plugins/captions-selector/config-renderer.ts b/plugins/captions-selector/config-renderer.ts new file mode 100644 index 0000000000..867ab9dc74 --- /dev/null +++ b/plugins/captions-selector/config-renderer.ts @@ -0,0 +1,4 @@ +import { PluginConfig } from '../../config/dynamic-renderer'; + +const configRenderer = new PluginConfig('captions-selector', { enableFront: true }); +export default configRenderer; diff --git a/plugins/captions-selector/front.ts b/plugins/captions-selector/front.ts index 113e3ef873..95d973ba6f 100644 --- a/plugins/captions-selector/front.ts +++ b/plugins/captions-selector/front.ts @@ -1,13 +1,8 @@ -/* eslint-disable @typescript-eslint/await-thenable */ -/* renderer */ - -import { ipcRenderer } from 'electron'; - -import configProvider from './config'; +import configProvider from './config-renderer'; import CaptionsSettingsButtonHTML from './templates/captions-settings-template.html'; -import { ElementFromHtml } from '../utils'; +import { ElementFromHtml } from '../utils-renderer'; import { YoutubePlayer } from '../../types/youtube-player'; import type { ConfigType } from '../../config/dynamic'; @@ -25,18 +20,17 @@ interface LanguageOptions { vss_id: string; } -let config: ConfigType<'captions-selector'>; +let captionsSelectorConfig: ConfigType<'captions-selector'>; const $ = (selector: string): Element => document.querySelector(selector)!; const captionsSettingsButton = ElementFromHtml(CaptionsSettingsButtonHTML); -export default async () => { - // RENDERER - config = await configProvider.getAll(); +export default () => { + captionsSelectorConfig = configProvider.getAll(); configProvider.subscribeAll((newConfig) => { - config = newConfig; + captionsSelectorConfig = newConfig; }); document.addEventListener('apiLoaded', (event) => setup(event.detail), { once: true, passive: true }); }; @@ -47,7 +41,7 @@ function setup(api: YoutubePlayer) { let captionTrackList = api.getOption('captions', 'tracklist') ?? []; $('video').addEventListener('srcChanged', () => { - if (config.disableCaptions) { + if (captionsSelectorConfig.disableCaptions) { setTimeout(() => api.unloadModule('captions'), 100); captionsSettingsButton.style.display = 'none'; return; @@ -58,9 +52,9 @@ function setup(api: YoutubePlayer) { setTimeout(() => { captionTrackList = api.getOption('captions', 'tracklist') ?? []; - if (config.autoload && config.lastCaptionsCode) { + if (captionsSelectorConfig.autoload && captionsSelectorConfig.lastCaptionsCode) { api.setOption('captions', 'track', { - languageCode: config.lastCaptionsCode, + languageCode: captionsSelectorConfig.lastCaptionsCode, }); } @@ -82,7 +76,7 @@ function setup(api: YoutubePlayer) { 'None', ]; - currentIndex = await ipcRenderer.invoke('captionsSelector', captionLabels, currentIndex) as number; + currentIndex = await window.ipcRenderer.invoke('captionsSelector', captionLabels, currentIndex) as number; if (currentIndex === null) { return; } diff --git a/plugins/crossfade/config-renderer.ts b/plugins/crossfade/config-renderer.ts new file mode 100644 index 0000000000..d9c1af2783 --- /dev/null +++ b/plugins/crossfade/config-renderer.ts @@ -0,0 +1,4 @@ +import { PluginConfig } from '../../config/dynamic-renderer'; + +const config = new PluginConfig('crossfade', { enableFront: true }); +export default config; diff --git a/plugins/crossfade/front.ts b/plugins/crossfade/front.ts index a38cd713c7..cc96d4e619 100644 --- a/plugins/crossfade/front.ts +++ b/plugins/crossfade/front.ts @@ -1,13 +1,9 @@ -/* eslint-disable @typescript-eslint/await-thenable */ -/* renderer */ - -import { ipcRenderer } from 'electron'; import { Howl } from 'howler'; // Extracted from https://github.com/bitfasching/VolumeFader import { VolumeFader } from './fader'; -import configProvider from './config'; +import configProvider from './config-renderer'; import defaultConfigs from '../../config/defaults'; @@ -19,11 +15,11 @@ let waitForTransition: Promise; const defaultConfig = defaultConfigs.plugins.crossfade; -let config: ConfigType<'crossfade'>; +let crossfadeConfig: ConfigType<'crossfade'>; -const configGetNumber = (key: keyof ConfigType<'crossfade'>): number => Number(config[key]) || (defaultConfig[key] as number); +const configGetNumber = (key: keyof ConfigType<'crossfade'>): number => Number(crossfadeConfig[key]) || (defaultConfig[key] as number); -const getStreamURL = async (videoID: string) => ipcRenderer.invoke('audio-url', videoID) as Promise; +const getStreamURL = async (videoID: string) => window.ipcRenderer.invoke('audio-url', videoID) as Promise; const getVideoIDFromURL = (url: string) => new URLSearchParams(url.split('?')?.at(-1)).get('v'); @@ -119,7 +115,7 @@ const onApiLoaded = () => { return; } - await createAudioForCrossfade(url); + createAudioForCrossfade(url); }); }; @@ -150,11 +146,11 @@ const crossfade = (cb: () => void) => { }); }; -export default async () => { - config = await configProvider.getAll(); +export default () => { + crossfadeConfig = configProvider.getAll(); configProvider.subscribeAll((newConfig) => { - config = newConfig; + crossfadeConfig = newConfig; }); document.addEventListener('apiLoaded', onApiLoaded, { diff --git a/plugins/downloader/back.ts b/plugins/downloader/back.ts index 12efeff0fc..2a8f2f2855 100644 --- a/plugins/downloader/back.ts +++ b/plugins/downloader/back.ts @@ -81,7 +81,22 @@ export default async (win_: BrowserWindow) => { cookie, generate_session_locally: true, fetch: async (input: RequestInfo | URL, init?: RequestInit) => { - return net.fetch(new Request(input, init)); + const url = + typeof input === 'string' ? + new URL(input) : + input instanceof URL ? + input : new URL(input.url); + + if (init?.body && !init.method) { + init.method = 'POST'; + } + + const request = new Request( + url, + input instanceof Request ? input : undefined, + ); + + return net.fetch(request, init); } }); ipcMain.on('download-song', (_, url: string) => downloadSong(url)); @@ -545,11 +560,7 @@ const getPlaylistID = (aURL: URL) => { }; const getVideoId = (url: URL | string): string | null => { - if (typeof url === 'string') { - url = new URL(url); - } - - return url.searchParams.get('v'); + return (new URL(url)).searchParams.get('v'); }; const getMetadata = (info: TrackInfo): CustomSongInfo => ({ diff --git a/plugins/downloader/front.ts b/plugins/downloader/front.ts index ebf7313a49..186f81888d 100644 --- a/plugins/downloader/front.ts +++ b/plugins/downloader/front.ts @@ -1,10 +1,8 @@ -import { contextBridge, ipcRenderer } from 'electron'; - import downloadHTML from './templates/download.html'; import defaultConfig from '../../config/defaults'; import { getSongMenu } from '../../providers/dom-elements'; -import { ElementFromHtml } from '../utils'; +import { ElementFromHtml } from '../utils-renderer'; import { getSongInfo } from '../../providers/song-info-front'; let menu: Element | null = null; @@ -40,7 +38,7 @@ const menuObserver = new MutationObserver(() => { setTimeout(() => doneFirstLoad ||= true, 500); }); -contextBridge.exposeInMainWorld('download', () => { +window.download = () => { let videoUrl = getSongMenu() // Selector of first button which is always "Start Radio" ?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="0"] #navigation-endpoint') @@ -51,15 +49,15 @@ contextBridge.exposeInMainWorld('download', () => { } if (videoUrl.includes('?playlist=')) { - ipcRenderer.send('download-playlist-request', videoUrl); + window.ipcRenderer.send('download-playlist-request', videoUrl); return; } } else { videoUrl = getSongInfo().url || window.location.href; } - ipcRenderer.send('download-song', videoUrl); -}); + window.ipcRenderer.send('download-song', videoUrl); +}; export default () => { document.addEventListener('apiLoaded', () => { @@ -69,7 +67,7 @@ export default () => { }); }, { once: true, passive: true }); - ipcRenderer.on('downloader-feedback', (_, feedback: string) => { + window.ipcRenderer.on('downloader-feedback', (_, feedback: string) => { if (progress) { progress.innerHTML = feedback || 'Download'; } else { diff --git a/plugins/in-app-menu/back.ts b/plugins/in-app-menu/back.ts index 74896df36a..6bb63f27b3 100644 --- a/plugins/in-app-menu/back.ts +++ b/plugins/in-app-menu/back.ts @@ -1,12 +1,10 @@ -import path from 'node:path'; +import {register} from 'electron-localshortcut'; -import { register } from 'electron-localshortcut'; - -import { BrowserWindow, Menu, MenuItem, ipcMain } from 'electron'; +import {BrowserWindow, ipcMain, Menu, MenuItem, nativeImage} from 'electron'; import titlebarStyle from './titlebar.css'; -import { injectCSS } from '../utils'; +import {injectCSS} from '../utils'; // Tracks menu visibility export default (win: BrowserWindow) => { @@ -25,7 +23,7 @@ export default (win: BrowserWindow) => { (key: string, value: unknown) => (key !== 'commandsMap' && key !== 'menu') ? value : undefined), ), ); - + const getMenuItemById = (commandId: number): MenuItem | null => { const menu = Menu.getApplicationMenu(); @@ -40,7 +38,7 @@ export default (win: BrowserWindow) => { break; } } - + return target; }; @@ -57,4 +55,9 @@ export default (win: BrowserWindow) => { (key: string, value: unknown) => (key !== 'commandsMap' && key !== 'menu') ? value : undefined), ); }); + + ipcMain.handle('image-path-to-data-url', (_, imagePath: string) => { + const nativeImageIcon = nativeImage.createFromPath(imagePath); + return nativeImageIcon?.toDataURL(); + }); }; diff --git a/plugins/in-app-menu/front.ts b/plugins/in-app-menu/front.ts index 84f02d8ee4..18511ea274 100644 --- a/plugins/in-app-menu/front.ts +++ b/plugins/in-app-menu/front.ts @@ -1,9 +1,8 @@ -import { ipcRenderer, Menu } from 'electron'; - import { createPanel } from './menu/panel'; import logo from '../../assets/youtube-music.svg'; -import { isEnabled } from '../../config/plugins'; + +import type { Menu } from 'electron'; function $(selector: string) { return document.querySelector(selector); @@ -38,7 +37,7 @@ export default () => { if (child !== logo) child.remove(); }); - const menu = await ipcRenderer.invoke('get-menu') as Menu | null; + const menu = await window.ipcRenderer.invoke('get-menu') as Menu | null; if (!menu) return; menu.items.forEach((menuItem) => { @@ -53,12 +52,12 @@ export default () => { document.title = 'Youtube Music'; - ipcRenderer.on('refreshMenu', () => { + window.ipcRenderer.on('refreshMenu', () => { updateMenu(); }); - if (isEnabled('picture-in-picture')) { - ipcRenderer.on('pip-toggle', () => { + if (window.mainConfig.plugins.isEnabled('picture-in-picture')) { + window.ipcRenderer.on('pip-toggle', () => { updateMenu(); }); } diff --git a/plugins/in-app-menu/menu/panel.ts b/plugins/in-app-menu/menu/panel.ts index feffb33b43..1e2aa07921 100644 --- a/plugins/in-app-menu/menu/panel.ts +++ b/plugins/in-app-menu/menu/panel.ts @@ -1,8 +1,8 @@ -import { nativeImage, type MenuItem, ipcRenderer, Menu } from 'electron'; - import Icons from './icons'; -import { ElementFromHtml } from '../../utils'; +import { ElementFromHtml } from '../../utils-renderer'; + +import type { MenuItem } from 'electron'; interface PanelOptions { placement?: 'bottom' | 'right'; @@ -19,7 +19,7 @@ export const createPanel = ( const panel = document.createElement('menu-panel'); panel.style.zIndex = `${options.order}`; - const updateIconState = (iconWrapper: HTMLElement, item: MenuItem) => { + const updateIconState = async (iconWrapper: HTMLElement, item: MenuItem) => { if (item.type === 'checkbox') { if (item.checked) iconWrapper.innerHTML = Icons.checkbox; else iconWrapper.innerHTML = ''; @@ -27,9 +27,9 @@ export const createPanel = ( if (item.checked) iconWrapper.innerHTML = Icons.radio.checked; else iconWrapper.innerHTML = Icons.radio.unchecked; } else { - const nativeImageIcon = typeof item.icon === 'string' ? nativeImage.createFromPath(item.icon) : item.icon; - const iconURL = nativeImageIcon?.toDataURL(); - + const iconURL = typeof item.icon === 'string' ? + await window.ipcRenderer.invoke('image-path-to-data-url') as string : item.icon?.toDataURL(); + if (iconURL) iconWrapper.style.background = `url(${iconURL})`; } }; @@ -37,7 +37,7 @@ export const createPanel = ( const radioGroups: [MenuItem, HTMLElement][] = []; items.map((item) => { if (item.type === 'separator') return panel.appendChild(document.createElement('menu-separator')); - + const menu = document.createElement('menu-item'); const iconWrapper = document.createElement('menu-icon'); @@ -46,9 +46,9 @@ export const createPanel = ( menu.append(item.label); menu.addEventListener('click', async () => { - await ipcRenderer.invoke('menu-event', item.commandId); - const menuItem = await ipcRenderer.invoke('get-menu-by-id', item.commandId) as MenuItem | null; - + await window.ipcRenderer.invoke('menu-event', item.commandId); + const menuItem = await window.ipcRenderer.invoke('get-menu-by-id', item.commandId) as MenuItem | null; + if (menuItem) { updateIconState(iconWrapper, menuItem); @@ -56,7 +56,7 @@ export const createPanel = ( await Promise.all( radioGroups.map(async ([item, iconWrapper]) => { if (item.commandId === menuItem.commandId) return; - const newItem = await ipcRenderer.invoke('get-menu-by-id', item.commandId) as MenuItem | null; + const newItem = await window.ipcRenderer.invoke('get-menu-by-id', item.commandId) as MenuItem | null; if (newItem) updateIconState(iconWrapper, newItem); }) @@ -64,7 +64,7 @@ export const createPanel = ( } } }); - + if (item.type === 'radio') { radioGroups.push([item, iconWrapper]); } diff --git a/plugins/lyrics-genius/front.ts b/plugins/lyrics-genius/front.ts index ebfc00714d..c86b0ddd62 100644 --- a/plugins/lyrics-genius/front.ts +++ b/plugins/lyrics-genius/front.ts @@ -1,6 +1,3 @@ -import { ipcRenderer } from 'electron'; -import is from 'electron-is'; - import type { SongInfo } from '../../providers/song-info'; export default () => { @@ -22,9 +19,9 @@ export default () => { } }; - let unregister: (() => void) | null = null; + let unregister: (() => void) | null = null; - ipcRenderer.on('update-song-info', (_, extractedSongInfo: SongInfo) => { + window.ipcRenderer.on('update-song-info', (_, extractedSongInfo: SongInfo) => { unregister?.(); setTimeout(async () => { @@ -38,7 +35,7 @@ export default () => { // Check if disabled if (!tabs.lyrics?.hasAttribute('disabled')) return; - const lyrics = await ipcRenderer.invoke( + const lyrics = await window.ipcRenderer.invoke( 'search-genius-lyrics', extractedSongInfo, ) as string | null; @@ -50,7 +47,7 @@ export default () => { return; } - if (is.dev()) { + if (window.electronIs.dev()) { console.log('Fetched lyrics from Genius'); } @@ -58,7 +55,7 @@ export default () => { const lyricsContainer = document.querySelector( '[page-type="MUSIC_PAGE_TYPE_TRACK_LYRICS"] > ytmusic-message-renderer', ); - + if (lyricsContainer) { callback?.(); diff --git a/plugins/navigation/front.ts b/plugins/navigation/front.ts index 61972b9f9c..cc03a98be5 100644 --- a/plugins/navigation/front.ts +++ b/plugins/navigation/front.ts @@ -1,12 +1,10 @@ -import { ipcRenderer } from 'electron'; - import forwardHTML from './templates/forward.html'; import backHTML from './templates/back.html'; -import { ElementFromHtml } from '../utils'; +import { ElementFromHtml } from '../utils-renderer'; export function run() { - ipcRenderer.on('navigation-css-ready', () => { + window.ipcRenderer.on('navigation-css-ready', () => { const forwardButton = ElementFromHtml(forwardHTML); const backButton = ElementFromHtml(backHTML); const menu = document.querySelector('#right-content'); diff --git a/plugins/picture-in-picture/front.ts b/plugins/picture-in-picture/front.ts index 04161c5223..406137a332 100644 --- a/plugins/picture-in-picture/front.ts +++ b/plugins/picture-in-picture/front.ts @@ -1,4 +1,3 @@ -import { contextBridge, ipcRenderer } from 'electron'; import { toKeyEvent } from 'keyboardevent-from-electron-accelerator'; import keyEventAreEqual from 'keyboardevents-areequal'; @@ -6,7 +5,7 @@ import pipHTML from './templates/picture-in-picture.html'; import { getSongMenu } from '../../providers/dom-elements'; -import { ElementFromHtml } from '../utils'; +import { ElementFromHtml } from '../utils-renderer'; import type { ConfigType } from '../../config/dynamic'; @@ -85,11 +84,11 @@ const togglePictureInPicture = async () => { } } - ipcRenderer.send('picture-in-picture'); + window.ipcRenderer.send('picture-in-picture'); return false; }; // For UI (HTML) -contextBridge.exposeInMainWorld('togglePictureInPicture', togglePictureInPicture); +window.togglePictureInPicture = togglePictureInPicture; const listenForToggle = () => { const originalExitButton = $('.exit-fullscreen-button'); @@ -105,7 +104,7 @@ const listenForToggle = () => { const titlebar = $('.cet-titlebar'); - ipcRenderer.on('pip-toggle', (_, isPip: boolean) => { + window.ipcRenderer.on('pip-toggle', (_, isPip: boolean) => { if (originalExitButton && player) { if (isPip) { replaceButton('.exit-fullscreen-button', originalExitButton)?.addEventListener('click', () => togglePictureInPicture()); diff --git a/plugins/playback-speed/front.ts b/plugins/playback-speed/front.ts index 5c491a0337..b8a0864e06 100644 --- a/plugins/playback-speed/front.ts +++ b/plugins/playback-speed/front.ts @@ -1,7 +1,7 @@ import sliderHTML from './templates/slider.html'; import { getSongMenu } from '../../providers/dom-elements'; -import { ElementFromHtml } from '../utils'; +import { ElementFromHtml } from '../utils-renderer'; import { singleton } from '../../providers/decorators'; diff --git a/plugins/precise-volume/front.ts b/plugins/precise-volume/front.ts index e5bd14935d..d4c9bfb413 100644 --- a/plugins/precise-volume/front.ts +++ b/plugins/precise-volume/front.ts @@ -1,6 +1,3 @@ -import { ipcRenderer } from 'electron'; - -import { setOptions, setMenuOptions, isEnabled } from '../../config/plugins'; import { debounce } from '../../providers/decorators'; import { YoutubePlayer } from '../../types/youtube-player'; @@ -18,15 +15,15 @@ export default (_options: ConfigType<'precise-volume'>) => { options = _options; document.addEventListener('apiLoaded', (e) => { api = e.detail; - ipcRenderer.on('changeVolume', (_, toIncrease: boolean) => changeVolume(toIncrease)); - ipcRenderer.on('setVolume', (_, value: number) => setVolume(value)); + window.ipcRenderer.on('changeVolume', (_, toIncrease: boolean) => changeVolume(toIncrease)); + window.ipcRenderer.on('setVolume', (_, value: number) => setVolume(value)); firstRun(); }, { once: true, passive: true }); }; // Without this function it would rewrite config 20 time when volume change by 20 const writeOptions = debounce(() => { - setOptions('precise-volume', options); + window.mainConfig.plugins.setOptions('precise-volume', options); }, 1000); export const moveVolumeHud = debounce((showVideo: boolean) => { @@ -68,7 +65,7 @@ function firstRun() { injectVolumeHud(noVid); if (!noVid) { setupVideoPlayerOnwheel(); - if (!isEnabled('video-toggle')) { + if (!window.mainConfig.plugins.isEnabled('video-toggle')) { // Video-toggle handles hud positioning on its own const videoMode = () => api.getPlayerResponse().videoDetails?.musicVideoType !== 'MUSIC_VIDEO_TYPE_ATV'; $('video')?.addEventListener('srcChanged', () => moveVolumeHud(videoMode())); @@ -76,9 +73,9 @@ function firstRun() { } // Change options from renderer to keep sync - ipcRenderer.on('setOptions', (_event, newOptions = {}) => { + window.ipcRenderer.on('setOptions', (_event, newOptions = {}) => { Object.assign(options, newOptions); - setMenuOptions('precise-volume', options); + window.mainConfig.plugins.setMenuOptions('precise-volume', options); }); } diff --git a/plugins/quality-changer/front.ts b/plugins/quality-changer/front.ts index 38514113c8..404447dc2a 100644 --- a/plugins/quality-changer/front.ts +++ b/plugins/quality-changer/front.ts @@ -1,8 +1,6 @@ -import { ipcRenderer } from 'electron'; - import qualitySettingsTemplate from './templates/qualitySettingsTemplate.html'; -import { ElementFromHtml } from '../utils'; +import { ElementFromHtml } from '../utils-renderer'; import { YoutubePlayer } from '../../types/youtube-player'; function $(selector: string): HTMLElement | null { @@ -23,7 +21,7 @@ function setup(event: CustomEvent) { const currentIndex = qualityLevels.indexOf(api.getPlaybackQuality()); - ipcRenderer.invoke('qualityChanger', api.getAvailableQualityLabels(), currentIndex).then((promise: { response: number }) => { + window.ipcRenderer.invoke('qualityChanger', api.getAvailableQualityLabels(), currentIndex).then((promise: { response: number }) => { if (promise.response === -1) { return; } diff --git a/plugins/sponsorblock/front.ts b/plugins/sponsorblock/front.ts index ccb2f808d4..022ff950bb 100644 --- a/plugins/sponsorblock/front.ts +++ b/plugins/sponsorblock/front.ts @@ -1,12 +1,9 @@ -import { ipcRenderer } from 'electron'; -import is from 'electron-is'; - import { Segment } from './types'; let currentSegments: Segment[] = []; export default () => { - ipcRenderer.on('sponsorblock-skip', (_, segments: Segment[]) => { + window.ipcRenderer.on('sponsorblock-skip', (_, segments: Segment[]) => { currentSegments = segments; }); @@ -24,7 +21,7 @@ export default () => { && target.currentTime < segment[1] ) { target.currentTime = segment[1]; - if (is.dev()) { + if (window.electronIs.dev()) { console.log('SponsorBlock: skipping segment', segment); } } diff --git a/plugins/utils-renderer.ts b/plugins/utils-renderer.ts new file mode 100644 index 0000000000..9b9e85ae25 --- /dev/null +++ b/plugins/utils-renderer.ts @@ -0,0 +1,8 @@ +// Creates a DOM element from an HTML string +export const ElementFromHtml = (html: string): HTMLElement => { + const template = document.createElement('template'); + html = html.trim(); // Never return a text node of whitespace as the result + template.innerHTML = html; + + return template.content.firstElementChild as HTMLElement; +}; diff --git a/plugins/utils.ts b/plugins/utils.ts index 44beb152c2..bbad875525 100644 --- a/plugins/utils.ts +++ b/plugins/utils.ts @@ -1,11 +1,10 @@ import fs from 'node:fs'; import path from 'node:path'; -import { app, ipcMain, ipcRenderer } from 'electron'; - import is from 'electron-is'; -import { ValueOf } from '../utils/type-utils'; +import { app } from 'electron'; + import defaultConfig from '../config/defaults'; export const getAssetsDirectoryLocation = () => path.resolve(__dirname, 'assets'); @@ -15,31 +14,6 @@ export const getMediaIconLocation = () => ? path.resolve(app.getPath('userData'), 'icons') : path.resolve(getAssetsDirectoryLocation(), 'media-icons-black'); -// Creates a DOM element from an HTML string -export const ElementFromHtml = (html: string): HTMLElement => { - const template = document.createElement('template'); - html = html.trim(); // Never return a text node of whitespace as the result - template.innerHTML = html; - - return template.content.firstElementChild as HTMLElement; -}; - -// Creates a DOM element from a HTML file -export const ElementFromFile = (filepath: fs.PathOrFileDescriptor) => ElementFromHtml(fs.readFileSync(filepath, 'utf8')); - -export const templatePath = (pluginPath: string, name: string) => path.join(pluginPath, 'templates', name); - -export const Actions = { - NEXT: 'next', - BACK: 'back', -}; - -export const triggerAction = (channel: string, action: ValueOf, ...args: Parameters) => ipcRenderer.send(channel, action, ...args); - -export const triggerActionSync = (channel: string, action: ValueOf, ...args: Parameters): unknown => ipcRenderer.sendSync(channel, action, ...args); - -export const listenAction = (channel: string, callback: (event: Electron.IpcMainEvent, action: string) => void) => ipcMain.on(channel, callback); - export const fileExists = ( path: fs.PathLike, callbackIfExists: { (): void; (): void; (): void; }, diff --git a/plugins/video-toggle/front.ts b/plugins/video-toggle/front.ts index 39e7f6e10d..6dd3a381b0 100644 --- a/plugins/video-toggle/front.ts +++ b/plugins/video-toggle/front.ts @@ -1,7 +1,6 @@ import buttonTemplate from './templates/button_template.html'; -import { ElementFromHtml } from '../utils'; -import { setOptions, isEnabled } from '../../config/plugins'; +import { ElementFromHtml } from '../utils-renderer'; import { moveVolumeHud as preciseVolumeMoveVolumeHud } from '../precise-volume/front'; @@ -10,7 +9,7 @@ import { ThumbnailElement } from '../../types/get-player-response'; import type { ConfigType } from '../../config/dynamic'; -const moveVolumeHud = isEnabled('precise-volume') ? preciseVolumeMoveVolumeHud : () => {}; +const moveVolumeHud = window.mainConfig.plugins.isEnabled('precise-volume') ? preciseVolumeMoveVolumeHud : () => {}; function $(selector: string): E | null { return document.querySelector(selector); @@ -99,7 +98,7 @@ function setup(e: CustomEvent) { function setVideoState(showVideo: boolean) { options.hideVideo = !showVideo; - setOptions('video-toggle', options); + window.mainConfig.plugins.setOptions('video-toggle', options); const checkbox = $('.video-switch-button-checkbox'); // custom mode if (checkbox) checkbox.checked = !options.hideVideo; diff --git a/preload.ts b/preload.ts index 258199dd52..c33afe6937 100644 --- a/preload.ts +++ b/preload.ts @@ -1,40 +1,14 @@ -import { contextBridge, ipcRenderer } from 'electron'; +import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; import is from 'electron-is'; import config from './config'; -import setupSongInfo from './providers/song-info-front'; -import { setupSongControls } from './providers/song-controls-front'; -import { startingPages } from './providers/extracted-data'; - -import albumColorThemeRenderer from './plugins/album-color-theme/front'; -import ambientModeRenderer from './plugins/ambient-mode/front'; -import audioCompressorRenderer from './plugins/audio-compressor/front'; -import bypassAgeRestrictionsRenderer from './plugins/bypass-age-restrictions/front'; -import captionsSelectorRenderer from './plugins/captions-selector/front'; -import compactSidebarRenderer from './plugins/compact-sidebar/front'; -import crossfadeRenderer from './plugins/crossfade/front'; -import disableAutoplayRenderer from './plugins/disable-autoplay/front'; -import downloaderRenderer from './plugins/downloader/front'; -import exponentialVolumeRenderer from './plugins/exponential-volume/front'; -import inAppMenuRenderer from './plugins/in-app-menu/front'; -import lyricsGeniusRenderer from './plugins/lyrics-genius/front'; -import navigationRenderer from './plugins/navigation/front'; -import noGoogleLogin from './plugins/no-google-login/front'; -import pictureInPictureRenderer from './plugins/picture-in-picture/front'; -import playbackSpeedRenderer from './plugins/playback-speed/front'; -import preciseVolumeRenderer from './plugins/precise-volume/front'; -import qualityChangerRenderer from './plugins/quality-changer/front'; -import skipSilencesRenderer from './plugins/skip-silences/front'; -import sponsorblockRenderer from './plugins/sponsorblock/front'; -import videoToggleRenderer from './plugins/video-toggle/front'; -import visualizerRenderer from './plugins/visualizer/front'; import adblockerPreload from './plugins/adblocker/preload'; import preciseVolumePreload from './plugins/precise-volume/preload'; import type { ConfigType, OneOfDefaultConfigKey } from './config/dynamic'; -type PluginMapper = { +export type PluginMapper = { [Key in OneOfDefaultConfigKey]?: ( Type extends 'renderer' ? (options: ConfigType) => (Promise | void) : Type extends 'preload' ? () => (Promise | void) : @@ -42,31 +16,6 @@ type PluginMapper = { ) }; -const rendererPlugins: PluginMapper<'renderer'> = { - 'album-color-theme': albumColorThemeRenderer, - 'ambient-mode': ambientModeRenderer, - 'audio-compressor': audioCompressorRenderer, - 'bypass-age-restrictions': bypassAgeRestrictionsRenderer, - 'captions-selector': captionsSelectorRenderer, - 'compact-sidebar': compactSidebarRenderer, - 'crossfade': crossfadeRenderer, - 'disable-autoplay': disableAutoplayRenderer, - 'downloader': downloaderRenderer, - 'exponential-volume': exponentialVolumeRenderer, - 'in-app-menu': inAppMenuRenderer, - 'lyrics-genius': lyricsGeniusRenderer, - 'navigation': navigationRenderer, - 'no-google-login': noGoogleLogin, - 'picture-in-picture': pictureInPictureRenderer, - 'playback-speed': playbackSpeedRenderer, - 'precise-volume': preciseVolumeRenderer, - 'quality-changer': qualityChangerRenderer, - 'skip-silences': skipSilencesRenderer, - 'sponsorblock': sponsorblockRenderer, - 'video-toggle': videoToggleRenderer, - 'visualizer': visualizerRenderer, -}; - const preloadPlugins: PluginMapper<'preload'> = { 'adblocker': adblockerPreload, 'precise-volume': preciseVolumePreload, @@ -74,11 +23,7 @@ const preloadPlugins: PluginMapper<'preload'> = { const enabledPluginNameAndOptions = config.plugins.getEnabled(); -const $ = document.querySelector.bind(document); - -let api: Element | null = null; - -enabledPluginNameAndOptions.forEach(async ([plugin, options]) => { +enabledPluginNameAndOptions.forEach(async ([plugin]) => { if (Object.hasOwn(preloadPlugins, plugin)) { const handler = preloadPlugins[plugin]; try { @@ -89,119 +34,20 @@ enabledPluginNameAndOptions.forEach(async ([plugin, options]) => { } }); -document.addEventListener('DOMContentLoaded', () => { - enabledPluginNameAndOptions.forEach(async ([pluginName, options]) => { - if (Object.hasOwn(rendererPlugins, pluginName)) { - const handler = rendererPlugins[pluginName]; - try { - await handler?.(options as never); - } catch (error) { - console.error(`Error in plugin "${pluginName}": ${String(error)}`); - } - } - }); - - // Wait for complete load of YouTube api - listenForApiLoad(); - - // Inject song-info provider - setupSongInfo(); - - // Inject song-controls - setupSongControls(); +contextBridge.exposeInMainWorld('mainConfig', config); +contextBridge.exposeInMainWorld('electronIs', is); +contextBridge.exposeInMainWorld('ipcRenderer', { + on: (channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void) => ipcRenderer.on(channel, listener), + once: (channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void) => ipcRenderer.once(channel, listener), + send: (channel: string, ...args: unknown[]) => ipcRenderer.send(channel, args), + removeListener: (channel: string, listener: (...args: unknown[]) => void) => ipcRenderer.removeListener(channel, listener), + removeAllListeners: (channel: string) => ipcRenderer.removeAllListeners(channel), + invoke: async (channel: string, ...args: unknown[]): Promise => ipcRenderer.invoke(channel, ...args), + sendSync: (channel: string, ...args: unknown[]): unknown => ipcRenderer.sendSync(channel, ...args), + sendToHost: (channel: string, ...args: unknown[]) => ipcRenderer.sendToHost(channel, ...args), +}); +document.addEventListener('DOMContentLoaded', () => { // Add action for reloading contextBridge.exposeInMainWorld('reload', () => ipcRenderer.send('reload')); - - // Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min - setInterval(() => window._lact = Date.now(), 900_000); - - // Setup back to front logger - if (is.dev()) { - ipcRenderer.on('log', (_event, log: string) => { - console.log(JSON.parse(log)); - }); - } }); - -function listenForApiLoad() { - api = $('#movie_player'); - if (api) { - onApiLoaded(); - return; - } - - const observer = new MutationObserver(() => { - api = $('#movie_player'); - if (api) { - observer.disconnect(); - onApiLoaded(); - } - }); - - observer.observe(document.documentElement, { childList: true, subtree: true }); -} - -interface YouTubeMusicAppElement extends HTMLElement { - navigate_(page: string): void; -} - -function onApiLoaded() { - const video = $('video')!; - const audioContext = new AudioContext(); - const audioSource = audioContext.createMediaElementSource(video); - audioSource.connect(audioContext.destination); - - video.addEventListener( - 'loadstart', - () => { - // Emit "audioCanPlay" for each video - video.addEventListener( - 'canplaythrough', - () => { - document.dispatchEvent( - new CustomEvent('audioCanPlay', { - detail: { - audioContext, - audioSource, - }, - }), - ); - }, - { once: true }, - ); - }, - { passive: true }, - );! - - document.dispatchEvent(new CustomEvent('apiLoaded', { detail: api })); - ipcRenderer.send('apiLoaded'); - - // Navigate to "Starting page" - const startingPage: string = config.get('options.startingPage'); - if (startingPage && startingPages[startingPage]) { - $('ytmusic-app')?.navigate_(startingPages[startingPage]); - } - - // Remove upgrade button - if (config.get('options.removeUpgradeButton')) { - const styles = document.createElement('style'); - styles.innerHTML = `ytmusic-guide-section-renderer #items ytmusic-guide-entry-renderer:last-child { - display: none; - }`; - document.head.appendChild(styles); - } - - // Hide / Force show like buttons - const likeButtonsOptions: string = config.get('options.likeButtons'); - if (likeButtonsOptions) { - const likeButtons: HTMLElement | null = $('ytmusic-like-button-renderer'); - if (likeButtons) { - likeButtons.style.display - = { - hide: 'none', - force: 'inherit', - }[likeButtonsOptions] || ''; - } - } -} diff --git a/providers/song-controls-front.ts b/providers/song-controls-front.ts index 012a75b4ed..be080c81e9 100644 --- a/providers/song-controls-front.ts +++ b/providers/song-controls-front.ts @@ -1,8 +1,6 @@ -import { ipcRenderer } from 'electron'; - export const setupSongControls = () => { document.addEventListener('apiLoaded', (event) => { - ipcRenderer.on('seekTo', (_, t: number) => event.detail.seekTo(t)); - ipcRenderer.on('seekBy', (_, t: number) => event.detail.seekBy(t)); + window.ipcRenderer.on('seekTo', (_, t: number) => event.detail.seekTo(t)); + window.ipcRenderer.on('seekBy', (_, t: number) => event.detail.seekBy(t)); }, { once: true, passive: true }); }; diff --git a/providers/song-info-front.ts b/providers/song-info-front.ts index f5607869ae..a45d574a5a 100644 --- a/providers/song-info-front.ts +++ b/providers/song-info-front.ts @@ -1,10 +1,8 @@ -import { ipcRenderer } from 'electron'; - import { singleton } from './decorators'; -import { getImage, SongInfo } from './song-info'; -import { YoutubePlayer } from '../types/youtube-player'; -import { GetState } from '../types/datahost-get-state'; +import type { YoutubePlayer } from '../types/youtube-player'; +import type { GetState } from '../types/datahost-get-state'; +import type { SongInfo } from './song-info'; let songInfo: SongInfo = {} as SongInfo; export const getSongInfo = () => songInfo; @@ -12,9 +10,8 @@ export const getSongInfo = () => songInfo; const $ = (s: string): E | null => document.querySelector(s); const $$ = (s: string): NodeListOf => document.querySelectorAll(s); -ipcRenderer.on('update-song-info', async (_, extractedSongInfo: SongInfo) => { +window.ipcRenderer.on('update-song-info', (_, extractedSongInfo: SongInfo) => { songInfo = extractedSongInfo; - if (songInfo.imageSrc) songInfo.image = await getImage(songInfo.imageSrc); }); // Used because 'loadeddata' or 'loadedmetadata' weren't firing on song start for some users (https://github.com/th-ch/youtube-music/issues/473) @@ -23,7 +20,7 @@ const srcChangedEvent = new CustomEvent('srcChanged'); export const setupSeekedListener = singleton(() => { $('video')?.addEventListener('seeked', (v) => { if (v.target instanceof HTMLVideoElement) { - ipcRenderer.send('seeked', v.target.currentTime); + window.ipcRenderer.send('seeked', v.target.currentTime); } }); }); @@ -32,7 +29,7 @@ export const setupTimeChangedListener = singleton(() => { const progressObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { const target = mutation.target as Node & { value: string }; - ipcRenderer.send('timeChanged', target.value); + window.ipcRenderer.send('timeChanged', target.value); songInfo.elapsedSeconds = Number(target.value); } }); @@ -46,7 +43,7 @@ export const setupRepeatChangedListener = singleton(() => { const repeatObserver = new MutationObserver((mutations) => { // provided by YouTube Music - ipcRenderer.send( + window.ipcRenderer.send( 'repeatChanged', (mutations[0].target as Node & { __dataHost: { @@ -59,7 +56,7 @@ export const setupRepeatChangedListener = singleton(() => { // Emit the initial value as well; as it's persistent between launches. // provided by YouTube Music - ipcRenderer.send( + window.ipcRenderer.send( 'repeatChanged', $ GetState; @@ -69,33 +66,33 @@ export const setupRepeatChangedListener = singleton(() => { export const setupVolumeChangedListener = singleton((api: YoutubePlayer) => { $('video')?.addEventListener('volumechange', () => { - ipcRenderer.send('volumeChanged', api.getVolume()); + window.ipcRenderer.send('volumeChanged', api.getVolume()); }); // Emit the initial value as well; as it's persistent between launches. - ipcRenderer.send('volumeChanged', api.getVolume()); + window.ipcRenderer.send('volumeChanged', api.getVolume()); }); export default () => { document.addEventListener('apiLoaded', (apiEvent) => { - ipcRenderer.on('setupTimeChangedListener', () => { + window.ipcRenderer.on('setupTimeChangedListener', () => { setupTimeChangedListener(); }); - ipcRenderer.on('setupRepeatChangedListener', () => { + window.ipcRenderer.on('setupRepeatChangedListener', () => { setupRepeatChangedListener(); }); - ipcRenderer.on('setupVolumeChangedListener', () => { + window.ipcRenderer.on('setupVolumeChangedListener', () => { setupVolumeChangedListener(apiEvent.detail); }); - ipcRenderer.on('setupSeekedListener', () => { + window.ipcRenderer.on('setupSeekedListener', () => { setupSeekedListener(); }); const playPausedHandler = (e: Event, status: string) => { if (e.target instanceof HTMLVideoElement && Math.round(e.target.currentTime) > 0) { - ipcRenderer.send('playPaused', { + window.ipcRenderer.send('playPaused', { isPaused: status === 'pause', elapsedSeconds: Math.floor(e.target.currentTime), }); @@ -138,7 +135,7 @@ export default () => { data.videoDetails.elapsedSeconds = 0; data.videoDetails.isPaused = false; - ipcRenderer.send('video-src-changed', data); + window.ipcRenderer.send('video-src-changed', data); } }, { once: true, passive: true }); }; diff --git a/renderer.ts b/renderer.ts new file mode 100644 index 0000000000..33b672e7d4 --- /dev/null +++ b/renderer.ts @@ -0,0 +1,171 @@ +import setupSongInfo from './providers/song-info-front'; +import { setupSongControls } from './providers/song-controls-front'; +import { startingPages } from './providers/extracted-data'; + +import albumColorThemeRenderer from './plugins/album-color-theme/front'; +import ambientModeRenderer from './plugins/ambient-mode/front'; +import audioCompressorRenderer from './plugins/audio-compressor/front'; +import bypassAgeRestrictionsRenderer from './plugins/bypass-age-restrictions/front'; +import captionsSelectorRenderer from './plugins/captions-selector/front'; +import compactSidebarRenderer from './plugins/compact-sidebar/front'; +import crossfadeRenderer from './plugins/crossfade/front'; +import disableAutoplayRenderer from './plugins/disable-autoplay/front'; +import downloaderRenderer from './plugins/downloader/front'; +import exponentialVolumeRenderer from './plugins/exponential-volume/front'; +import inAppMenuRenderer from './plugins/in-app-menu/front'; +import lyricsGeniusRenderer from './plugins/lyrics-genius/front'; +import navigationRenderer from './plugins/navigation/front'; +import noGoogleLogin from './plugins/no-google-login/front'; +import pictureInPictureRenderer from './plugins/picture-in-picture/front'; +import playbackSpeedRenderer from './plugins/playback-speed/front'; +import preciseVolumeRenderer from './plugins/precise-volume/front'; +import qualityChangerRenderer from './plugins/quality-changer/front'; +import skipSilencesRenderer from './plugins/skip-silences/front'; +import sponsorblockRenderer from './plugins/sponsorblock/front'; +import videoToggleRenderer from './plugins/video-toggle/front'; +import visualizerRenderer from './plugins/visualizer/front'; + +import type { PluginMapper } from './preload'; + +const rendererPlugins: PluginMapper<'renderer'> = { + 'album-color-theme': albumColorThemeRenderer, + 'ambient-mode': ambientModeRenderer, + 'audio-compressor': audioCompressorRenderer, + 'bypass-age-restrictions': bypassAgeRestrictionsRenderer, + 'captions-selector': captionsSelectorRenderer, + 'compact-sidebar': compactSidebarRenderer, + 'crossfade': crossfadeRenderer, + 'disable-autoplay': disableAutoplayRenderer, + 'downloader': downloaderRenderer, + 'exponential-volume': exponentialVolumeRenderer, + 'in-app-menu': inAppMenuRenderer, + 'lyrics-genius': lyricsGeniusRenderer, + 'navigation': navigationRenderer, + 'no-google-login': noGoogleLogin, + 'picture-in-picture': pictureInPictureRenderer, + 'playback-speed': playbackSpeedRenderer, + 'precise-volume': preciseVolumeRenderer, + 'quality-changer': qualityChangerRenderer, + 'skip-silences': skipSilencesRenderer, + 'sponsorblock': sponsorblockRenderer, + 'video-toggle': videoToggleRenderer, + 'visualizer': visualizerRenderer, +}; + +const enabledPluginNameAndOptions = window.mainConfig.plugins.getEnabled(); + +let api: Element | null = null; + +function listenForApiLoad() { + api = document.querySelector('#movie_player'); + if (api) { + onApiLoaded(); + return; + } + + const observer = new MutationObserver(() => { + api = document.querySelector('#movie_player'); + if (api) { + observer.disconnect(); + onApiLoaded(); + } + }); + + observer.observe(document.documentElement, { childList: true, subtree: true }); +} + +interface YouTubeMusicAppElement extends HTMLElement { + navigate_(page: string): void; +} + +function onApiLoaded() { + const video = document.querySelector('video')!; + const audioContext = new AudioContext(); + const audioSource = audioContext.createMediaElementSource(video); + audioSource.connect(audioContext.destination); + + video.addEventListener( + 'loadstart', + () => { + // Emit "audioCanPlay" for each video + video.addEventListener( + 'canplaythrough', + () => { + document.dispatchEvent( + new CustomEvent('audioCanPlay', { + detail: { + audioContext, + audioSource, + }, + }), + ); + }, + { once: true }, + ); + }, + { passive: true }, + );! + + document.dispatchEvent(new CustomEvent('apiLoaded', { detail: api })); + window.ipcRenderer.send('apiLoaded'); + + // Navigate to "Starting page" + const startingPage: string = window.mainConfig.get('options.startingPage'); + if (startingPage && startingPages[startingPage]) { + document.querySelector('ytmusic-app')?.navigate_(startingPages[startingPage]); + } + + // Remove upgrade button + if (window.mainConfig.get('options.removeUpgradeButton')) { + const styles = document.createElement('style'); + styles.innerHTML = `ytmusic-guide-section-renderer #items ytmusic-guide-entry-renderer:last-child { + display: none; + }`; + document.head.appendChild(styles); + } + + // Hide / Force show like buttons + const likeButtonsOptions: string = window.mainConfig.get('options.likeButtons'); + if (likeButtonsOptions) { + const likeButtons: HTMLElement | null = document.querySelector('ytmusic-like-button-renderer'); + if (likeButtons) { + likeButtons.style.display + = { + hide: 'none', + force: 'inherit', + }[likeButtonsOptions] || ''; + } + } +} + +(() => { + enabledPluginNameAndOptions.forEach(async ([pluginName, options]) => { + if (Object.hasOwn(rendererPlugins, pluginName)) { + const handler = rendererPlugins[pluginName]; + try { + await handler?.(options as never); + } catch (error) { + console.error(`Error in plugin "${pluginName}": ${String(error)}`); + } + } + }); + + // Wait for complete load of YouTube api + listenForApiLoad(); + + // Inject song-info provider + setupSongInfo(); + + // Inject song-controls + setupSongControls(); + + // Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min + setInterval(() => window._lact = Date.now(), 900_000); + + // Setup back to front logger + if (window.electronIs.dev()) { + window.ipcRenderer.on('log', (_event, log: string) => { + console.log(JSON.parse(log)); + }); + } +})(); diff --git a/reset.d.ts b/reset.d.ts index b3b3d2907c..1eefc18966 100644 --- a/reset.d.ts +++ b/reset.d.ts @@ -1,4 +1,9 @@ import '@total-typescript/ts-reset'; + +import { ipcRenderer as electronIpcRenderer } from 'electron'; +import is from 'electron-is'; + +import config from './config'; import { YoutubePlayer } from './types/youtube-player'; declare global { @@ -13,11 +18,16 @@ declare global { } interface Window { + ipcRenderer: typeof electronIpcRenderer; + mainConfig: typeof config; + electronIs: typeof is; /** * YouTube Music internal variable (Last interaction time) */ _lact: number; navigation: Navigation; + download: () => void; + togglePictureInPicture: () => Promise; } } diff --git a/rollup.main.config.ts b/rollup.main.config.ts index 767c89a190..650faf6982 100644 --- a/rollup.main.config.ts +++ b/rollup.main.config.ts @@ -38,9 +38,9 @@ export default defineConfig({ { src: 'assets', dest: 'dist/' }, ], }), - terser({ - ecma: 2020, - }), + //terser({ + // ecma: 2020, + //}), { closeBundle() { if (!process.env.ROLLUP_WATCH) { diff --git a/rollup.preload.config.ts b/rollup.preload.config.ts index 86114b6770..a2919aa9fb 100644 --- a/rollup.preload.config.ts +++ b/rollup.preload.config.ts @@ -52,7 +52,6 @@ export default defineConfig({ }, external: [ 'electron', - 'custom-electron-prompt', ...builtinModules, ], }); diff --git a/rollup.renderer.config.ts b/rollup.renderer.config.ts new file mode 100644 index 0000000000..66c5a0acaa --- /dev/null +++ b/rollup.renderer.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'rollup'; +import builtinModules from 'builtin-modules'; +import typescript from '@rollup/plugin-typescript'; +import commonjs from '@rollup/plugin-commonjs'; +import nodeResolvePlugin from '@rollup/plugin-node-resolve'; +import json from '@rollup/plugin-json'; +import terser from '@rollup/plugin-terser'; +import { string } from 'rollup-plugin-string'; +import css from 'rollup-plugin-import-css'; +import wasmPlugin from '@rollup/plugin-wasm'; +import image from '@rollup/plugin-image'; + +export default defineConfig({ + plugins: [ + typescript({ + module: 'ESNext', + }), + nodeResolvePlugin({ + browser: false, + preferBuiltins: true, + }), + commonjs({ + ignoreDynamicRequires: true, + }), + json(), + string({ + include: '**/*.html', + }), + css(), + wasmPlugin({ + maxFileSize: 0, + targetEnv: 'browser', + }), + image({ dom: true }), + terser({ + ecma: 2020, + }), + { + closeBundle() { + if (!process.env.ROLLUP_WATCH) { + setTimeout(() => process.exit(0)); + } + }, + name: 'force-close' + }, + ], + input: './renderer.ts', + output: { + format: 'cjs', + name: '[name].js', + dir: './dist', + }, + external: [ + 'electron', + ...builtinModules, + ], +}); From a9b15f74e249fe8a6169d5798bc9d0a2e78635b8 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Fri, 6 Oct 2023 23:39:41 +0900 Subject: [PATCH 03/21] chore: remove TODO --- index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/index.ts b/index.ts index 327585e58f..c1eb9db983 100644 --- a/index.ts +++ b/index.ts @@ -197,8 +197,6 @@ function createMainWindow() { backgroundColor: '#000', show: false, webPreferences: { - // TODO: re-enable contextIsolation once it can work with FFMpeg.wasm - // Possible bundling? https://github.com/ffmpegwasm/ffmpeg.wasm/issues/126 contextIsolation: true, preload: path.join(__dirname, 'preload.js'), nodeIntegrationInSubFrames: true, From 23ab088054f419201094c416dff6a06b7dd21f68 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Fri, 6 Oct 2023 23:43:09 +0900 Subject: [PATCH 04/21] fix(dynamic): fix documents --- config/dynamic-renderer.ts | 3 --- config/dynamic.ts | 3 --- 2 files changed, 6 deletions(-) diff --git a/config/dynamic-renderer.ts b/config/dynamic-renderer.ts index 91c85cd531..3aeece5fba 100644 --- a/config/dynamic-renderer.ts +++ b/config/dynamic-renderer.ts @@ -21,9 +21,6 @@ interface PluginConfigOptions { /** * This class is used to create a dynamic synced config for plugins. * - * [!IMPORTANT!] - * The methods are **sync** in the main process and **async** in the renderer process. - * * @param {string} name - The name of the plugin. * @param {boolean} [options.enableFront] - Whether the config should be available in front.js. Default: false. * @param {object} [options.initialOptions] - The initial options for the plugin. Default: loaded from store. diff --git a/config/dynamic.ts b/config/dynamic.ts index fa05cb7d96..3c7eddcd78 100644 --- a/config/dynamic.ts +++ b/config/dynamic.ts @@ -30,9 +30,6 @@ interface PluginConfigOptions { /** * This class is used to create a dynamic synced config for plugins. * - * [!IMPORTANT!] - * The methods are **sync** in the main process and **async** in the renderer process. - * * @param {string} name - The name of the plugin. * @param {boolean} [options.enableFront] - Whether the config should be available in front.js. Default: false. * @param {object} [options.initialOptions] - The initial options for the plugin. Default: loaded from store. From f3c9cb26d64846dde0edb077725390afe12ccab9 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Fri, 6 Oct 2023 23:44:24 +0900 Subject: [PATCH 05/21] feat(dynamic): update types --- config/dynamic-renderer.ts | 9 ++------- config/dynamic.ts | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/config/dynamic-renderer.ts b/config/dynamic-renderer.ts index 3aeece5fba..4458a178cb 100644 --- a/config/dynamic-renderer.ts +++ b/config/dynamic-renderer.ts @@ -2,7 +2,7 @@ import defaultConfig from './defaults'; import { Entries } from '../utils/type-utils'; -import type { OneOfDefaultConfigKey, OneOfDefaultConfig, ConfigType } from './dynamic'; +import type { OneOfDefaultConfigKey, ConfigType, PluginConfigOptions } from './dynamic'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const activePlugins: { [key in OneOfDefaultConfigKey]?: PluginConfig } = {}; @@ -13,11 +13,6 @@ export const getActivePlugins export const isActive = async (plugin: string) => plugin in (await window.ipcRenderer.invoke('get-active-plugins')); -interface PluginConfigOptions { - enableFront: boolean; - initialOptions?: OneOfDefaultConfig; -} - /** * This class is used to create a dynamic synced config for plugins. * @@ -41,7 +36,7 @@ interface PluginConfigOptions { * }; */ type ValueOf = T[keyof T]; -type Mode = Mode extends 'r' ? Promise : T; + export class PluginConfig { private readonly name: string; private readonly config: ConfigType; diff --git a/config/dynamic.ts b/config/dynamic.ts index 3c7eddcd78..12d34f1970 100644 --- a/config/dynamic.ts +++ b/config/dynamic.ts @@ -22,7 +22,7 @@ if (process.type === 'browser') { export const isActive = (plugin: string): boolean => plugin in activePlugins; -interface PluginConfigOptions { +export interface PluginConfigOptions { enableFront: boolean; initialOptions?: OneOfDefaultConfig; } @@ -51,7 +51,7 @@ interface PluginConfigOptions { */ export type ConfigType = typeof defaultConfig.plugins[T]; type ValueOf = T[keyof T]; -type Mode = Mode extends 'r' ? Promise : T; + export class PluginConfig { private readonly name: string; private readonly config: ConfigType; From ffe61b44c50d35f9d1f23020206ca3d58132e38a Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 7 Oct 2023 00:01:16 +0900 Subject: [PATCH 06/21] fix: enable terser for main script --- rollup.main.config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rollup.main.config.ts b/rollup.main.config.ts index 650faf6982..767c89a190 100644 --- a/rollup.main.config.ts +++ b/rollup.main.config.ts @@ -38,9 +38,9 @@ export default defineConfig({ { src: 'assets', dest: 'dist/' }, ], }), - //terser({ - // ecma: 2020, - //}), + terser({ + ecma: 2020, + }), { closeBundle() { if (!process.env.ROLLUP_WATCH) { From f8275d377a7d8f757d44403867e6bd7f72651b32 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 7 Oct 2023 00:32:05 +0900 Subject: [PATCH 07/21] fix: plugins for renderers should be reloaded --- index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.ts b/index.ts index c1eb9db983..f79eb78767 100644 --- a/index.ts +++ b/index.ts @@ -258,7 +258,7 @@ function createMainWindow() { : config.defaultConfig.url; win.webContents.loadURL(urlToLoad); - win.webContents.once('dom-ready', () => { + win.webContents.on('dom-ready', () => { win.webContents.executeJavaScript(fs.readFileSync(path.join(__dirname, 'renderer.js'), 'utf-8') + ';0', true); }); From d7741a43cb4955414a744beb2953527f5b50117a Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Thu, 2 Nov 2023 13:31:12 +0900 Subject: [PATCH 08/21] move to src --- {config => src/config}/defaults.ts | 0 {config => src/config}/dynamic-renderer.ts | 0 {config => src/config}/dynamic.ts | 0 {config => src/config}/index.ts | 0 {config => src/config}/plugins.ts | 0 {config => src/config}/store.ts | 0 custom-electron-prompt.d.ts => src/custom-electron-prompt.d.ts | 0 error.html => src/error.html | 0 index.ts => src/index.ts | 0 menu.ts => src/menu.ts | 0 navigation.d.ts => src/navigation.d.ts | 0 {plugins => src/plugins}/adblocker/.gitignore | 0 {plugins => src/plugins}/adblocker/back.ts | 0 {plugins => src/plugins}/adblocker/blocker.ts | 0 {plugins => src/plugins}/adblocker/config.ts | 0 {plugins => src/plugins}/adblocker/inject-cliqz-preload.ts | 0 {plugins => src/plugins}/adblocker/inject.d.ts | 0 {plugins => src/plugins}/adblocker/inject.js | 0 {plugins => src/plugins}/adblocker/menu.ts | 0 {plugins => src/plugins}/adblocker/preload.ts | 0 {plugins => src/plugins}/album-color-theme/back.ts | 0 {plugins => src/plugins}/album-color-theme/front.ts | 0 {plugins => src/plugins}/album-color-theme/style.css | 0 {plugins => src/plugins}/ambient-mode/back.ts | 0 {plugins => src/plugins}/ambient-mode/front.ts | 0 {plugins => src/plugins}/ambient-mode/style.css | 0 {plugins => src/plugins}/audio-compressor/front.ts | 0 {plugins => src/plugins}/blur-nav-bar/back.ts | 0 {plugins => src/plugins}/blur-nav-bar/style.css | 0 {plugins => src/plugins}/bypass-age-restrictions/front.ts | 0 {plugins => src/plugins}/captions-selector/back.ts | 0 {plugins => src/plugins}/captions-selector/config-renderer.ts | 0 {plugins => src/plugins}/captions-selector/config.ts | 0 {plugins => src/plugins}/captions-selector/front.ts | 0 {plugins => src/plugins}/captions-selector/menu.ts | 0 .../captions-selector/templates/captions-settings-template.html | 0 {plugins => src/plugins}/compact-sidebar/front.ts | 0 {plugins => src/plugins}/crossfade/back.ts | 0 {plugins => src/plugins}/crossfade/config-renderer.ts | 0 {plugins => src/plugins}/crossfade/config.ts | 0 {plugins => src/plugins}/crossfade/fader.ts | 0 {plugins => src/plugins}/crossfade/front.ts | 0 {plugins => src/plugins}/crossfade/menu.ts | 0 {plugins => src/plugins}/disable-autoplay/front.ts | 0 {plugins => src/plugins}/disable-autoplay/menu.ts | 0 {plugins => src/plugins}/discord/back.ts | 0 {plugins => src/plugins}/discord/menu.ts | 0 {plugins => src/plugins}/downloader/back.ts | 0 {plugins => src/plugins}/downloader/config.ts | 0 {plugins => src/plugins}/downloader/front.ts | 0 {plugins => src/plugins}/downloader/menu.ts | 0 {plugins => src/plugins}/downloader/style.css | 0 {plugins => src/plugins}/downloader/templates/download.html | 0 {plugins => src/plugins}/downloader/utils.ts | 0 {plugins => src/plugins}/exponential-volume/front.ts | 0 {plugins => src/plugins}/in-app-menu/back.ts | 0 {plugins => src/plugins}/in-app-menu/front.ts | 0 {plugins => src/plugins}/in-app-menu/menu/icons.ts | 0 {plugins => src/plugins}/in-app-menu/menu/panel.ts | 0 {plugins => src/plugins}/in-app-menu/titlebar.css | 0 {plugins => src/plugins}/last-fm/back.ts | 0 {plugins => src/plugins}/lyrics-genius/back.ts | 0 {plugins => src/plugins}/lyrics-genius/front.ts | 0 {plugins => src/plugins}/lyrics-genius/menu.ts | 0 {plugins => src/plugins}/lyrics-genius/style.css | 0 {plugins => src/plugins}/lyrics-genius/types.ts | 0 {plugins => src/plugins}/navigation/back.ts | 0 {plugins => src/plugins}/navigation/front.ts | 0 {plugins => src/plugins}/navigation/style.css | 0 {plugins => src/plugins}/navigation/templates/back.html | 0 {plugins => src/plugins}/navigation/templates/forward.html | 0 {plugins => src/plugins}/no-google-login/back.ts | 0 {plugins => src/plugins}/no-google-login/front.ts | 0 {plugins => src/plugins}/no-google-login/style.css | 0 {plugins => src/plugins}/notifications/back.ts | 0 {plugins => src/plugins}/notifications/config.ts | 0 {plugins => src/plugins}/notifications/interactive.ts | 0 {plugins => src/plugins}/notifications/menu.ts | 0 {plugins => src/plugins}/notifications/utils.ts | 0 {plugins => src/plugins}/picture-in-picture/back.ts | 0 {plugins => src/plugins}/picture-in-picture/front.ts | 0 .../keyboardevent-from-electron-accelerator.d.ts | 0 .../plugins}/picture-in-picture/keyboardevents-areequal.d.ts | 0 {plugins => src/plugins}/picture-in-picture/menu.ts | 0 {plugins => src/plugins}/picture-in-picture/style.css | 0 .../plugins}/picture-in-picture/templates/picture-in-picture.html | 0 {plugins => src/plugins}/playback-speed/front.ts | 0 {plugins => src/plugins}/playback-speed/templates/slider.html | 0 {plugins => src/plugins}/precise-volume/back.ts | 0 {plugins => src/plugins}/precise-volume/front.ts | 0 {plugins => src/plugins}/precise-volume/menu.ts | 0 {plugins => src/plugins}/precise-volume/preload.ts | 0 {plugins => src/plugins}/precise-volume/volume-hud.css | 0 {plugins => src/plugins}/quality-changer/back.ts | 0 {plugins => src/plugins}/quality-changer/front.ts | 0 .../quality-changer/templates/qualitySettingsTemplate.html | 0 {plugins => src/plugins}/shortcuts/back.ts | 0 {plugins => src/plugins}/shortcuts/menu.ts | 0 {plugins => src/plugins}/shortcuts/mpris-service.d.ts | 0 {plugins => src/plugins}/shortcuts/mpris.ts | 0 {plugins => src/plugins}/skip-silences/front.ts | 0 {plugins => src/plugins}/sponsorblock/back.ts | 0 {plugins => src/plugins}/sponsorblock/front.ts | 0 {plugins => src/plugins}/sponsorblock/segments.ts | 0 {plugins => src/plugins}/sponsorblock/tests/segments.test.js | 0 {plugins => src/plugins}/sponsorblock/types.ts | 0 {plugins => src/plugins}/taskbar-mediacontrol/back.ts | 0 {plugins => src/plugins}/touchbar/back.ts | 0 {plugins => src/plugins}/tuna-obs/back.ts | 0 {plugins => src/plugins}/utils-renderer.ts | 0 {plugins => src/plugins}/utils.ts | 0 {plugins => src/plugins}/video-toggle/back.ts | 0 {plugins => src/plugins}/video-toggle/button-switcher.css | 0 {plugins => src/plugins}/video-toggle/force-hide.css | 0 {plugins => src/plugins}/video-toggle/front.ts | 0 {plugins => src/plugins}/video-toggle/menu.ts | 0 .../plugins}/video-toggle/templates/button_template.html | 0 {plugins => src/plugins}/visualizer/back.ts | 0 {plugins => src/plugins}/visualizer/butterchurn.d.ts | 0 {plugins => src/plugins}/visualizer/empty-player.css | 0 {plugins => src/plugins}/visualizer/front.ts | 0 {plugins => src/plugins}/visualizer/menu.ts | 0 {plugins => src/plugins}/visualizer/visualizers/butterchurn.ts | 0 {plugins => src/plugins}/visualizer/visualizers/index.ts | 0 {plugins => src/plugins}/visualizer/visualizers/visualizer.ts | 0 {plugins => src/plugins}/visualizer/visualizers/vudio.ts | 0 {plugins => src/plugins}/visualizer/visualizers/wave.ts | 0 {plugins => src/plugins}/visualizer/vudio.d.ts | 0 preload.ts => src/preload.ts | 0 {providers => src/providers}/app-controls.ts | 0 {providers => src/providers}/decorators.ts | 0 {providers => src/providers}/dom-elements.ts | 0 {providers => src/providers}/extracted-data.ts | 0 {providers => src/providers}/prompt-options.ts | 0 {providers => src/providers}/protocol-handler.ts | 0 {providers => src/providers}/song-controls-front.ts | 0 {providers => src/providers}/song-controls.ts | 0 {providers => src/providers}/song-info-front.ts | 0 {providers => src/providers}/song-info.ts | 0 renderer.ts => src/renderer.ts | 0 reset.d.ts => src/reset.d.ts | 0 tray.ts => src/tray.ts | 0 {types => src/types}/datahost-get-state.ts | 0 {types => src/types}/get-player-response.ts | 0 {types => src/types}/player-api-events.ts | 0 {types => src/types}/video-details.ts | 0 {types => src/types}/youtube-player.ts | 0 {utils => src/utils}/generate-package-json.js | 0 {utils => src/utils}/testing.ts | 0 {utils => src/utils}/type-utils.ts | 0 youtube-music.css => src/youtube-music.css | 0 youtube-music.d.ts => src/youtube-music.d.ts | 0 152 files changed, 0 insertions(+), 0 deletions(-) rename {config => src/config}/defaults.ts (100%) rename {config => src/config}/dynamic-renderer.ts (100%) rename {config => src/config}/dynamic.ts (100%) rename {config => src/config}/index.ts (100%) rename {config => src/config}/plugins.ts (100%) rename {config => src/config}/store.ts (100%) rename custom-electron-prompt.d.ts => src/custom-electron-prompt.d.ts (100%) rename error.html => src/error.html (100%) rename index.ts => src/index.ts (100%) rename menu.ts => src/menu.ts (100%) rename navigation.d.ts => src/navigation.d.ts (100%) rename {plugins => src/plugins}/adblocker/.gitignore (100%) rename {plugins => src/plugins}/adblocker/back.ts (100%) rename {plugins => src/plugins}/adblocker/blocker.ts (100%) rename {plugins => src/plugins}/adblocker/config.ts (100%) rename {plugins => src/plugins}/adblocker/inject-cliqz-preload.ts (100%) rename {plugins => src/plugins}/adblocker/inject.d.ts (100%) rename {plugins => src/plugins}/adblocker/inject.js (100%) rename {plugins => src/plugins}/adblocker/menu.ts (100%) rename {plugins => src/plugins}/adblocker/preload.ts (100%) rename {plugins => src/plugins}/album-color-theme/back.ts (100%) rename {plugins => src/plugins}/album-color-theme/front.ts (100%) rename {plugins => src/plugins}/album-color-theme/style.css (100%) rename {plugins => src/plugins}/ambient-mode/back.ts (100%) rename {plugins => src/plugins}/ambient-mode/front.ts (100%) rename {plugins => src/plugins}/ambient-mode/style.css (100%) rename {plugins => src/plugins}/audio-compressor/front.ts (100%) rename {plugins => src/plugins}/blur-nav-bar/back.ts (100%) rename {plugins => src/plugins}/blur-nav-bar/style.css (100%) rename {plugins => src/plugins}/bypass-age-restrictions/front.ts (100%) rename {plugins => src/plugins}/captions-selector/back.ts (100%) rename {plugins => src/plugins}/captions-selector/config-renderer.ts (100%) rename {plugins => src/plugins}/captions-selector/config.ts (100%) rename {plugins => src/plugins}/captions-selector/front.ts (100%) rename {plugins => src/plugins}/captions-selector/menu.ts (100%) rename {plugins => src/plugins}/captions-selector/templates/captions-settings-template.html (100%) rename {plugins => src/plugins}/compact-sidebar/front.ts (100%) rename {plugins => src/plugins}/crossfade/back.ts (100%) rename {plugins => src/plugins}/crossfade/config-renderer.ts (100%) rename {plugins => src/plugins}/crossfade/config.ts (100%) rename {plugins => src/plugins}/crossfade/fader.ts (100%) rename {plugins => src/plugins}/crossfade/front.ts (100%) rename {plugins => src/plugins}/crossfade/menu.ts (100%) rename {plugins => src/plugins}/disable-autoplay/front.ts (100%) rename {plugins => src/plugins}/disable-autoplay/menu.ts (100%) rename {plugins => src/plugins}/discord/back.ts (100%) rename {plugins => src/plugins}/discord/menu.ts (100%) rename {plugins => src/plugins}/downloader/back.ts (100%) rename {plugins => src/plugins}/downloader/config.ts (100%) rename {plugins => src/plugins}/downloader/front.ts (100%) rename {plugins => src/plugins}/downloader/menu.ts (100%) rename {plugins => src/plugins}/downloader/style.css (100%) rename {plugins => src/plugins}/downloader/templates/download.html (100%) rename {plugins => src/plugins}/downloader/utils.ts (100%) rename {plugins => src/plugins}/exponential-volume/front.ts (100%) rename {plugins => src/plugins}/in-app-menu/back.ts (100%) rename {plugins => src/plugins}/in-app-menu/front.ts (100%) rename {plugins => src/plugins}/in-app-menu/menu/icons.ts (100%) rename {plugins => src/plugins}/in-app-menu/menu/panel.ts (100%) rename {plugins => src/plugins}/in-app-menu/titlebar.css (100%) rename {plugins => src/plugins}/last-fm/back.ts (100%) rename {plugins => src/plugins}/lyrics-genius/back.ts (100%) rename {plugins => src/plugins}/lyrics-genius/front.ts (100%) rename {plugins => src/plugins}/lyrics-genius/menu.ts (100%) rename {plugins => src/plugins}/lyrics-genius/style.css (100%) rename {plugins => src/plugins}/lyrics-genius/types.ts (100%) rename {plugins => src/plugins}/navigation/back.ts (100%) rename {plugins => src/plugins}/navigation/front.ts (100%) rename {plugins => src/plugins}/navigation/style.css (100%) rename {plugins => src/plugins}/navigation/templates/back.html (100%) rename {plugins => src/plugins}/navigation/templates/forward.html (100%) rename {plugins => src/plugins}/no-google-login/back.ts (100%) rename {plugins => src/plugins}/no-google-login/front.ts (100%) rename {plugins => src/plugins}/no-google-login/style.css (100%) rename {plugins => src/plugins}/notifications/back.ts (100%) rename {plugins => src/plugins}/notifications/config.ts (100%) rename {plugins => src/plugins}/notifications/interactive.ts (100%) rename {plugins => src/plugins}/notifications/menu.ts (100%) rename {plugins => src/plugins}/notifications/utils.ts (100%) rename {plugins => src/plugins}/picture-in-picture/back.ts (100%) rename {plugins => src/plugins}/picture-in-picture/front.ts (100%) rename {plugins => src/plugins}/picture-in-picture/keyboardevent-from-electron-accelerator.d.ts (100%) rename {plugins => src/plugins}/picture-in-picture/keyboardevents-areequal.d.ts (100%) rename {plugins => src/plugins}/picture-in-picture/menu.ts (100%) rename {plugins => src/plugins}/picture-in-picture/style.css (100%) rename {plugins => src/plugins}/picture-in-picture/templates/picture-in-picture.html (100%) rename {plugins => src/plugins}/playback-speed/front.ts (100%) rename {plugins => src/plugins}/playback-speed/templates/slider.html (100%) rename {plugins => src/plugins}/precise-volume/back.ts (100%) rename {plugins => src/plugins}/precise-volume/front.ts (100%) rename {plugins => src/plugins}/precise-volume/menu.ts (100%) rename {plugins => src/plugins}/precise-volume/preload.ts (100%) rename {plugins => src/plugins}/precise-volume/volume-hud.css (100%) rename {plugins => src/plugins}/quality-changer/back.ts (100%) rename {plugins => src/plugins}/quality-changer/front.ts (100%) rename {plugins => src/plugins}/quality-changer/templates/qualitySettingsTemplate.html (100%) rename {plugins => src/plugins}/shortcuts/back.ts (100%) rename {plugins => src/plugins}/shortcuts/menu.ts (100%) rename {plugins => src/plugins}/shortcuts/mpris-service.d.ts (100%) rename {plugins => src/plugins}/shortcuts/mpris.ts (100%) rename {plugins => src/plugins}/skip-silences/front.ts (100%) rename {plugins => src/plugins}/sponsorblock/back.ts (100%) rename {plugins => src/plugins}/sponsorblock/front.ts (100%) rename {plugins => src/plugins}/sponsorblock/segments.ts (100%) rename {plugins => src/plugins}/sponsorblock/tests/segments.test.js (100%) rename {plugins => src/plugins}/sponsorblock/types.ts (100%) rename {plugins => src/plugins}/taskbar-mediacontrol/back.ts (100%) rename {plugins => src/plugins}/touchbar/back.ts (100%) rename {plugins => src/plugins}/tuna-obs/back.ts (100%) rename {plugins => src/plugins}/utils-renderer.ts (100%) rename {plugins => src/plugins}/utils.ts (100%) rename {plugins => src/plugins}/video-toggle/back.ts (100%) rename {plugins => src/plugins}/video-toggle/button-switcher.css (100%) rename {plugins => src/plugins}/video-toggle/force-hide.css (100%) rename {plugins => src/plugins}/video-toggle/front.ts (100%) rename {plugins => src/plugins}/video-toggle/menu.ts (100%) rename {plugins => src/plugins}/video-toggle/templates/button_template.html (100%) rename {plugins => src/plugins}/visualizer/back.ts (100%) rename {plugins => src/plugins}/visualizer/butterchurn.d.ts (100%) rename {plugins => src/plugins}/visualizer/empty-player.css (100%) rename {plugins => src/plugins}/visualizer/front.ts (100%) rename {plugins => src/plugins}/visualizer/menu.ts (100%) rename {plugins => src/plugins}/visualizer/visualizers/butterchurn.ts (100%) rename {plugins => src/plugins}/visualizer/visualizers/index.ts (100%) rename {plugins => src/plugins}/visualizer/visualizers/visualizer.ts (100%) rename {plugins => src/plugins}/visualizer/visualizers/vudio.ts (100%) rename {plugins => src/plugins}/visualizer/visualizers/wave.ts (100%) rename {plugins => src/plugins}/visualizer/vudio.d.ts (100%) rename preload.ts => src/preload.ts (100%) rename {providers => src/providers}/app-controls.ts (100%) rename {providers => src/providers}/decorators.ts (100%) rename {providers => src/providers}/dom-elements.ts (100%) rename {providers => src/providers}/extracted-data.ts (100%) rename {providers => src/providers}/prompt-options.ts (100%) rename {providers => src/providers}/protocol-handler.ts (100%) rename {providers => src/providers}/song-controls-front.ts (100%) rename {providers => src/providers}/song-controls.ts (100%) rename {providers => src/providers}/song-info-front.ts (100%) rename {providers => src/providers}/song-info.ts (100%) rename renderer.ts => src/renderer.ts (100%) rename reset.d.ts => src/reset.d.ts (100%) rename tray.ts => src/tray.ts (100%) rename {types => src/types}/datahost-get-state.ts (100%) rename {types => src/types}/get-player-response.ts (100%) rename {types => src/types}/player-api-events.ts (100%) rename {types => src/types}/video-details.ts (100%) rename {types => src/types}/youtube-player.ts (100%) rename {utils => src/utils}/generate-package-json.js (100%) mode change 100755 => 100644 rename {utils => src/utils}/testing.ts (100%) rename {utils => src/utils}/type-utils.ts (100%) rename youtube-music.css => src/youtube-music.css (100%) rename youtube-music.d.ts => src/youtube-music.d.ts (100%) diff --git a/config/defaults.ts b/src/config/defaults.ts similarity index 100% rename from config/defaults.ts rename to src/config/defaults.ts diff --git a/config/dynamic-renderer.ts b/src/config/dynamic-renderer.ts similarity index 100% rename from config/dynamic-renderer.ts rename to src/config/dynamic-renderer.ts diff --git a/config/dynamic.ts b/src/config/dynamic.ts similarity index 100% rename from config/dynamic.ts rename to src/config/dynamic.ts diff --git a/config/index.ts b/src/config/index.ts similarity index 100% rename from config/index.ts rename to src/config/index.ts diff --git a/config/plugins.ts b/src/config/plugins.ts similarity index 100% rename from config/plugins.ts rename to src/config/plugins.ts diff --git a/config/store.ts b/src/config/store.ts similarity index 100% rename from config/store.ts rename to src/config/store.ts diff --git a/custom-electron-prompt.d.ts b/src/custom-electron-prompt.d.ts similarity index 100% rename from custom-electron-prompt.d.ts rename to src/custom-electron-prompt.d.ts diff --git a/error.html b/src/error.html similarity index 100% rename from error.html rename to src/error.html diff --git a/index.ts b/src/index.ts similarity index 100% rename from index.ts rename to src/index.ts diff --git a/menu.ts b/src/menu.ts similarity index 100% rename from menu.ts rename to src/menu.ts diff --git a/navigation.d.ts b/src/navigation.d.ts similarity index 100% rename from navigation.d.ts rename to src/navigation.d.ts diff --git a/plugins/adblocker/.gitignore b/src/plugins/adblocker/.gitignore similarity index 100% rename from plugins/adblocker/.gitignore rename to src/plugins/adblocker/.gitignore diff --git a/plugins/adblocker/back.ts b/src/plugins/adblocker/back.ts similarity index 100% rename from plugins/adblocker/back.ts rename to src/plugins/adblocker/back.ts diff --git a/plugins/adblocker/blocker.ts b/src/plugins/adblocker/blocker.ts similarity index 100% rename from plugins/adblocker/blocker.ts rename to src/plugins/adblocker/blocker.ts diff --git a/plugins/adblocker/config.ts b/src/plugins/adblocker/config.ts similarity index 100% rename from plugins/adblocker/config.ts rename to src/plugins/adblocker/config.ts diff --git a/plugins/adblocker/inject-cliqz-preload.ts b/src/plugins/adblocker/inject-cliqz-preload.ts similarity index 100% rename from plugins/adblocker/inject-cliqz-preload.ts rename to src/plugins/adblocker/inject-cliqz-preload.ts diff --git a/plugins/adblocker/inject.d.ts b/src/plugins/adblocker/inject.d.ts similarity index 100% rename from plugins/adblocker/inject.d.ts rename to src/plugins/adblocker/inject.d.ts diff --git a/plugins/adblocker/inject.js b/src/plugins/adblocker/inject.js similarity index 100% rename from plugins/adblocker/inject.js rename to src/plugins/adblocker/inject.js diff --git a/plugins/adblocker/menu.ts b/src/plugins/adblocker/menu.ts similarity index 100% rename from plugins/adblocker/menu.ts rename to src/plugins/adblocker/menu.ts diff --git a/plugins/adblocker/preload.ts b/src/plugins/adblocker/preload.ts similarity index 100% rename from plugins/adblocker/preload.ts rename to src/plugins/adblocker/preload.ts diff --git a/plugins/album-color-theme/back.ts b/src/plugins/album-color-theme/back.ts similarity index 100% rename from plugins/album-color-theme/back.ts rename to src/plugins/album-color-theme/back.ts diff --git a/plugins/album-color-theme/front.ts b/src/plugins/album-color-theme/front.ts similarity index 100% rename from plugins/album-color-theme/front.ts rename to src/plugins/album-color-theme/front.ts diff --git a/plugins/album-color-theme/style.css b/src/plugins/album-color-theme/style.css similarity index 100% rename from plugins/album-color-theme/style.css rename to src/plugins/album-color-theme/style.css diff --git a/plugins/ambient-mode/back.ts b/src/plugins/ambient-mode/back.ts similarity index 100% rename from plugins/ambient-mode/back.ts rename to src/plugins/ambient-mode/back.ts diff --git a/plugins/ambient-mode/front.ts b/src/plugins/ambient-mode/front.ts similarity index 100% rename from plugins/ambient-mode/front.ts rename to src/plugins/ambient-mode/front.ts diff --git a/plugins/ambient-mode/style.css b/src/plugins/ambient-mode/style.css similarity index 100% rename from plugins/ambient-mode/style.css rename to src/plugins/ambient-mode/style.css diff --git a/plugins/audio-compressor/front.ts b/src/plugins/audio-compressor/front.ts similarity index 100% rename from plugins/audio-compressor/front.ts rename to src/plugins/audio-compressor/front.ts diff --git a/plugins/blur-nav-bar/back.ts b/src/plugins/blur-nav-bar/back.ts similarity index 100% rename from plugins/blur-nav-bar/back.ts rename to src/plugins/blur-nav-bar/back.ts diff --git a/plugins/blur-nav-bar/style.css b/src/plugins/blur-nav-bar/style.css similarity index 100% rename from plugins/blur-nav-bar/style.css rename to src/plugins/blur-nav-bar/style.css diff --git a/plugins/bypass-age-restrictions/front.ts b/src/plugins/bypass-age-restrictions/front.ts similarity index 100% rename from plugins/bypass-age-restrictions/front.ts rename to src/plugins/bypass-age-restrictions/front.ts diff --git a/plugins/captions-selector/back.ts b/src/plugins/captions-selector/back.ts similarity index 100% rename from plugins/captions-selector/back.ts rename to src/plugins/captions-selector/back.ts diff --git a/plugins/captions-selector/config-renderer.ts b/src/plugins/captions-selector/config-renderer.ts similarity index 100% rename from plugins/captions-selector/config-renderer.ts rename to src/plugins/captions-selector/config-renderer.ts diff --git a/plugins/captions-selector/config.ts b/src/plugins/captions-selector/config.ts similarity index 100% rename from plugins/captions-selector/config.ts rename to src/plugins/captions-selector/config.ts diff --git a/plugins/captions-selector/front.ts b/src/plugins/captions-selector/front.ts similarity index 100% rename from plugins/captions-selector/front.ts rename to src/plugins/captions-selector/front.ts diff --git a/plugins/captions-selector/menu.ts b/src/plugins/captions-selector/menu.ts similarity index 100% rename from plugins/captions-selector/menu.ts rename to src/plugins/captions-selector/menu.ts diff --git a/plugins/captions-selector/templates/captions-settings-template.html b/src/plugins/captions-selector/templates/captions-settings-template.html similarity index 100% rename from plugins/captions-selector/templates/captions-settings-template.html rename to src/plugins/captions-selector/templates/captions-settings-template.html diff --git a/plugins/compact-sidebar/front.ts b/src/plugins/compact-sidebar/front.ts similarity index 100% rename from plugins/compact-sidebar/front.ts rename to src/plugins/compact-sidebar/front.ts diff --git a/plugins/crossfade/back.ts b/src/plugins/crossfade/back.ts similarity index 100% rename from plugins/crossfade/back.ts rename to src/plugins/crossfade/back.ts diff --git a/plugins/crossfade/config-renderer.ts b/src/plugins/crossfade/config-renderer.ts similarity index 100% rename from plugins/crossfade/config-renderer.ts rename to src/plugins/crossfade/config-renderer.ts diff --git a/plugins/crossfade/config.ts b/src/plugins/crossfade/config.ts similarity index 100% rename from plugins/crossfade/config.ts rename to src/plugins/crossfade/config.ts diff --git a/plugins/crossfade/fader.ts b/src/plugins/crossfade/fader.ts similarity index 100% rename from plugins/crossfade/fader.ts rename to src/plugins/crossfade/fader.ts diff --git a/plugins/crossfade/front.ts b/src/plugins/crossfade/front.ts similarity index 100% rename from plugins/crossfade/front.ts rename to src/plugins/crossfade/front.ts diff --git a/plugins/crossfade/menu.ts b/src/plugins/crossfade/menu.ts similarity index 100% rename from plugins/crossfade/menu.ts rename to src/plugins/crossfade/menu.ts diff --git a/plugins/disable-autoplay/front.ts b/src/plugins/disable-autoplay/front.ts similarity index 100% rename from plugins/disable-autoplay/front.ts rename to src/plugins/disable-autoplay/front.ts diff --git a/plugins/disable-autoplay/menu.ts b/src/plugins/disable-autoplay/menu.ts similarity index 100% rename from plugins/disable-autoplay/menu.ts rename to src/plugins/disable-autoplay/menu.ts diff --git a/plugins/discord/back.ts b/src/plugins/discord/back.ts similarity index 100% rename from plugins/discord/back.ts rename to src/plugins/discord/back.ts diff --git a/plugins/discord/menu.ts b/src/plugins/discord/menu.ts similarity index 100% rename from plugins/discord/menu.ts rename to src/plugins/discord/menu.ts diff --git a/plugins/downloader/back.ts b/src/plugins/downloader/back.ts similarity index 100% rename from plugins/downloader/back.ts rename to src/plugins/downloader/back.ts diff --git a/plugins/downloader/config.ts b/src/plugins/downloader/config.ts similarity index 100% rename from plugins/downloader/config.ts rename to src/plugins/downloader/config.ts diff --git a/plugins/downloader/front.ts b/src/plugins/downloader/front.ts similarity index 100% rename from plugins/downloader/front.ts rename to src/plugins/downloader/front.ts diff --git a/plugins/downloader/menu.ts b/src/plugins/downloader/menu.ts similarity index 100% rename from plugins/downloader/menu.ts rename to src/plugins/downloader/menu.ts diff --git a/plugins/downloader/style.css b/src/plugins/downloader/style.css similarity index 100% rename from plugins/downloader/style.css rename to src/plugins/downloader/style.css diff --git a/plugins/downloader/templates/download.html b/src/plugins/downloader/templates/download.html similarity index 100% rename from plugins/downloader/templates/download.html rename to src/plugins/downloader/templates/download.html diff --git a/plugins/downloader/utils.ts b/src/plugins/downloader/utils.ts similarity index 100% rename from plugins/downloader/utils.ts rename to src/plugins/downloader/utils.ts diff --git a/plugins/exponential-volume/front.ts b/src/plugins/exponential-volume/front.ts similarity index 100% rename from plugins/exponential-volume/front.ts rename to src/plugins/exponential-volume/front.ts diff --git a/plugins/in-app-menu/back.ts b/src/plugins/in-app-menu/back.ts similarity index 100% rename from plugins/in-app-menu/back.ts rename to src/plugins/in-app-menu/back.ts diff --git a/plugins/in-app-menu/front.ts b/src/plugins/in-app-menu/front.ts similarity index 100% rename from plugins/in-app-menu/front.ts rename to src/plugins/in-app-menu/front.ts diff --git a/plugins/in-app-menu/menu/icons.ts b/src/plugins/in-app-menu/menu/icons.ts similarity index 100% rename from plugins/in-app-menu/menu/icons.ts rename to src/plugins/in-app-menu/menu/icons.ts diff --git a/plugins/in-app-menu/menu/panel.ts b/src/plugins/in-app-menu/menu/panel.ts similarity index 100% rename from plugins/in-app-menu/menu/panel.ts rename to src/plugins/in-app-menu/menu/panel.ts diff --git a/plugins/in-app-menu/titlebar.css b/src/plugins/in-app-menu/titlebar.css similarity index 100% rename from plugins/in-app-menu/titlebar.css rename to src/plugins/in-app-menu/titlebar.css diff --git a/plugins/last-fm/back.ts b/src/plugins/last-fm/back.ts similarity index 100% rename from plugins/last-fm/back.ts rename to src/plugins/last-fm/back.ts diff --git a/plugins/lyrics-genius/back.ts b/src/plugins/lyrics-genius/back.ts similarity index 100% rename from plugins/lyrics-genius/back.ts rename to src/plugins/lyrics-genius/back.ts diff --git a/plugins/lyrics-genius/front.ts b/src/plugins/lyrics-genius/front.ts similarity index 100% rename from plugins/lyrics-genius/front.ts rename to src/plugins/lyrics-genius/front.ts diff --git a/plugins/lyrics-genius/menu.ts b/src/plugins/lyrics-genius/menu.ts similarity index 100% rename from plugins/lyrics-genius/menu.ts rename to src/plugins/lyrics-genius/menu.ts diff --git a/plugins/lyrics-genius/style.css b/src/plugins/lyrics-genius/style.css similarity index 100% rename from plugins/lyrics-genius/style.css rename to src/plugins/lyrics-genius/style.css diff --git a/plugins/lyrics-genius/types.ts b/src/plugins/lyrics-genius/types.ts similarity index 100% rename from plugins/lyrics-genius/types.ts rename to src/plugins/lyrics-genius/types.ts diff --git a/plugins/navigation/back.ts b/src/plugins/navigation/back.ts similarity index 100% rename from plugins/navigation/back.ts rename to src/plugins/navigation/back.ts diff --git a/plugins/navigation/front.ts b/src/plugins/navigation/front.ts similarity index 100% rename from plugins/navigation/front.ts rename to src/plugins/navigation/front.ts diff --git a/plugins/navigation/style.css b/src/plugins/navigation/style.css similarity index 100% rename from plugins/navigation/style.css rename to src/plugins/navigation/style.css diff --git a/plugins/navigation/templates/back.html b/src/plugins/navigation/templates/back.html similarity index 100% rename from plugins/navigation/templates/back.html rename to src/plugins/navigation/templates/back.html diff --git a/plugins/navigation/templates/forward.html b/src/plugins/navigation/templates/forward.html similarity index 100% rename from plugins/navigation/templates/forward.html rename to src/plugins/navigation/templates/forward.html diff --git a/plugins/no-google-login/back.ts b/src/plugins/no-google-login/back.ts similarity index 100% rename from plugins/no-google-login/back.ts rename to src/plugins/no-google-login/back.ts diff --git a/plugins/no-google-login/front.ts b/src/plugins/no-google-login/front.ts similarity index 100% rename from plugins/no-google-login/front.ts rename to src/plugins/no-google-login/front.ts diff --git a/plugins/no-google-login/style.css b/src/plugins/no-google-login/style.css similarity index 100% rename from plugins/no-google-login/style.css rename to src/plugins/no-google-login/style.css diff --git a/plugins/notifications/back.ts b/src/plugins/notifications/back.ts similarity index 100% rename from plugins/notifications/back.ts rename to src/plugins/notifications/back.ts diff --git a/plugins/notifications/config.ts b/src/plugins/notifications/config.ts similarity index 100% rename from plugins/notifications/config.ts rename to src/plugins/notifications/config.ts diff --git a/plugins/notifications/interactive.ts b/src/plugins/notifications/interactive.ts similarity index 100% rename from plugins/notifications/interactive.ts rename to src/plugins/notifications/interactive.ts diff --git a/plugins/notifications/menu.ts b/src/plugins/notifications/menu.ts similarity index 100% rename from plugins/notifications/menu.ts rename to src/plugins/notifications/menu.ts diff --git a/plugins/notifications/utils.ts b/src/plugins/notifications/utils.ts similarity index 100% rename from plugins/notifications/utils.ts rename to src/plugins/notifications/utils.ts diff --git a/plugins/picture-in-picture/back.ts b/src/plugins/picture-in-picture/back.ts similarity index 100% rename from plugins/picture-in-picture/back.ts rename to src/plugins/picture-in-picture/back.ts diff --git a/plugins/picture-in-picture/front.ts b/src/plugins/picture-in-picture/front.ts similarity index 100% rename from plugins/picture-in-picture/front.ts rename to src/plugins/picture-in-picture/front.ts diff --git a/plugins/picture-in-picture/keyboardevent-from-electron-accelerator.d.ts b/src/plugins/picture-in-picture/keyboardevent-from-electron-accelerator.d.ts similarity index 100% rename from plugins/picture-in-picture/keyboardevent-from-electron-accelerator.d.ts rename to src/plugins/picture-in-picture/keyboardevent-from-electron-accelerator.d.ts diff --git a/plugins/picture-in-picture/keyboardevents-areequal.d.ts b/src/plugins/picture-in-picture/keyboardevents-areequal.d.ts similarity index 100% rename from plugins/picture-in-picture/keyboardevents-areequal.d.ts rename to src/plugins/picture-in-picture/keyboardevents-areequal.d.ts diff --git a/plugins/picture-in-picture/menu.ts b/src/plugins/picture-in-picture/menu.ts similarity index 100% rename from plugins/picture-in-picture/menu.ts rename to src/plugins/picture-in-picture/menu.ts diff --git a/plugins/picture-in-picture/style.css b/src/plugins/picture-in-picture/style.css similarity index 100% rename from plugins/picture-in-picture/style.css rename to src/plugins/picture-in-picture/style.css diff --git a/plugins/picture-in-picture/templates/picture-in-picture.html b/src/plugins/picture-in-picture/templates/picture-in-picture.html similarity index 100% rename from plugins/picture-in-picture/templates/picture-in-picture.html rename to src/plugins/picture-in-picture/templates/picture-in-picture.html diff --git a/plugins/playback-speed/front.ts b/src/plugins/playback-speed/front.ts similarity index 100% rename from plugins/playback-speed/front.ts rename to src/plugins/playback-speed/front.ts diff --git a/plugins/playback-speed/templates/slider.html b/src/plugins/playback-speed/templates/slider.html similarity index 100% rename from plugins/playback-speed/templates/slider.html rename to src/plugins/playback-speed/templates/slider.html diff --git a/plugins/precise-volume/back.ts b/src/plugins/precise-volume/back.ts similarity index 100% rename from plugins/precise-volume/back.ts rename to src/plugins/precise-volume/back.ts diff --git a/plugins/precise-volume/front.ts b/src/plugins/precise-volume/front.ts similarity index 100% rename from plugins/precise-volume/front.ts rename to src/plugins/precise-volume/front.ts diff --git a/plugins/precise-volume/menu.ts b/src/plugins/precise-volume/menu.ts similarity index 100% rename from plugins/precise-volume/menu.ts rename to src/plugins/precise-volume/menu.ts diff --git a/plugins/precise-volume/preload.ts b/src/plugins/precise-volume/preload.ts similarity index 100% rename from plugins/precise-volume/preload.ts rename to src/plugins/precise-volume/preload.ts diff --git a/plugins/precise-volume/volume-hud.css b/src/plugins/precise-volume/volume-hud.css similarity index 100% rename from plugins/precise-volume/volume-hud.css rename to src/plugins/precise-volume/volume-hud.css diff --git a/plugins/quality-changer/back.ts b/src/plugins/quality-changer/back.ts similarity index 100% rename from plugins/quality-changer/back.ts rename to src/plugins/quality-changer/back.ts diff --git a/plugins/quality-changer/front.ts b/src/plugins/quality-changer/front.ts similarity index 100% rename from plugins/quality-changer/front.ts rename to src/plugins/quality-changer/front.ts diff --git a/plugins/quality-changer/templates/qualitySettingsTemplate.html b/src/plugins/quality-changer/templates/qualitySettingsTemplate.html similarity index 100% rename from plugins/quality-changer/templates/qualitySettingsTemplate.html rename to src/plugins/quality-changer/templates/qualitySettingsTemplate.html diff --git a/plugins/shortcuts/back.ts b/src/plugins/shortcuts/back.ts similarity index 100% rename from plugins/shortcuts/back.ts rename to src/plugins/shortcuts/back.ts diff --git a/plugins/shortcuts/menu.ts b/src/plugins/shortcuts/menu.ts similarity index 100% rename from plugins/shortcuts/menu.ts rename to src/plugins/shortcuts/menu.ts diff --git a/plugins/shortcuts/mpris-service.d.ts b/src/plugins/shortcuts/mpris-service.d.ts similarity index 100% rename from plugins/shortcuts/mpris-service.d.ts rename to src/plugins/shortcuts/mpris-service.d.ts diff --git a/plugins/shortcuts/mpris.ts b/src/plugins/shortcuts/mpris.ts similarity index 100% rename from plugins/shortcuts/mpris.ts rename to src/plugins/shortcuts/mpris.ts diff --git a/plugins/skip-silences/front.ts b/src/plugins/skip-silences/front.ts similarity index 100% rename from plugins/skip-silences/front.ts rename to src/plugins/skip-silences/front.ts diff --git a/plugins/sponsorblock/back.ts b/src/plugins/sponsorblock/back.ts similarity index 100% rename from plugins/sponsorblock/back.ts rename to src/plugins/sponsorblock/back.ts diff --git a/plugins/sponsorblock/front.ts b/src/plugins/sponsorblock/front.ts similarity index 100% rename from plugins/sponsorblock/front.ts rename to src/plugins/sponsorblock/front.ts diff --git a/plugins/sponsorblock/segments.ts b/src/plugins/sponsorblock/segments.ts similarity index 100% rename from plugins/sponsorblock/segments.ts rename to src/plugins/sponsorblock/segments.ts diff --git a/plugins/sponsorblock/tests/segments.test.js b/src/plugins/sponsorblock/tests/segments.test.js similarity index 100% rename from plugins/sponsorblock/tests/segments.test.js rename to src/plugins/sponsorblock/tests/segments.test.js diff --git a/plugins/sponsorblock/types.ts b/src/plugins/sponsorblock/types.ts similarity index 100% rename from plugins/sponsorblock/types.ts rename to src/plugins/sponsorblock/types.ts diff --git a/plugins/taskbar-mediacontrol/back.ts b/src/plugins/taskbar-mediacontrol/back.ts similarity index 100% rename from plugins/taskbar-mediacontrol/back.ts rename to src/plugins/taskbar-mediacontrol/back.ts diff --git a/plugins/touchbar/back.ts b/src/plugins/touchbar/back.ts similarity index 100% rename from plugins/touchbar/back.ts rename to src/plugins/touchbar/back.ts diff --git a/plugins/tuna-obs/back.ts b/src/plugins/tuna-obs/back.ts similarity index 100% rename from plugins/tuna-obs/back.ts rename to src/plugins/tuna-obs/back.ts diff --git a/plugins/utils-renderer.ts b/src/plugins/utils-renderer.ts similarity index 100% rename from plugins/utils-renderer.ts rename to src/plugins/utils-renderer.ts diff --git a/plugins/utils.ts b/src/plugins/utils.ts similarity index 100% rename from plugins/utils.ts rename to src/plugins/utils.ts diff --git a/plugins/video-toggle/back.ts b/src/plugins/video-toggle/back.ts similarity index 100% rename from plugins/video-toggle/back.ts rename to src/plugins/video-toggle/back.ts diff --git a/plugins/video-toggle/button-switcher.css b/src/plugins/video-toggle/button-switcher.css similarity index 100% rename from plugins/video-toggle/button-switcher.css rename to src/plugins/video-toggle/button-switcher.css diff --git a/plugins/video-toggle/force-hide.css b/src/plugins/video-toggle/force-hide.css similarity index 100% rename from plugins/video-toggle/force-hide.css rename to src/plugins/video-toggle/force-hide.css diff --git a/plugins/video-toggle/front.ts b/src/plugins/video-toggle/front.ts similarity index 100% rename from plugins/video-toggle/front.ts rename to src/plugins/video-toggle/front.ts diff --git a/plugins/video-toggle/menu.ts b/src/plugins/video-toggle/menu.ts similarity index 100% rename from plugins/video-toggle/menu.ts rename to src/plugins/video-toggle/menu.ts diff --git a/plugins/video-toggle/templates/button_template.html b/src/plugins/video-toggle/templates/button_template.html similarity index 100% rename from plugins/video-toggle/templates/button_template.html rename to src/plugins/video-toggle/templates/button_template.html diff --git a/plugins/visualizer/back.ts b/src/plugins/visualizer/back.ts similarity index 100% rename from plugins/visualizer/back.ts rename to src/plugins/visualizer/back.ts diff --git a/plugins/visualizer/butterchurn.d.ts b/src/plugins/visualizer/butterchurn.d.ts similarity index 100% rename from plugins/visualizer/butterchurn.d.ts rename to src/plugins/visualizer/butterchurn.d.ts diff --git a/plugins/visualizer/empty-player.css b/src/plugins/visualizer/empty-player.css similarity index 100% rename from plugins/visualizer/empty-player.css rename to src/plugins/visualizer/empty-player.css diff --git a/plugins/visualizer/front.ts b/src/plugins/visualizer/front.ts similarity index 100% rename from plugins/visualizer/front.ts rename to src/plugins/visualizer/front.ts diff --git a/plugins/visualizer/menu.ts b/src/plugins/visualizer/menu.ts similarity index 100% rename from plugins/visualizer/menu.ts rename to src/plugins/visualizer/menu.ts diff --git a/plugins/visualizer/visualizers/butterchurn.ts b/src/plugins/visualizer/visualizers/butterchurn.ts similarity index 100% rename from plugins/visualizer/visualizers/butterchurn.ts rename to src/plugins/visualizer/visualizers/butterchurn.ts diff --git a/plugins/visualizer/visualizers/index.ts b/src/plugins/visualizer/visualizers/index.ts similarity index 100% rename from plugins/visualizer/visualizers/index.ts rename to src/plugins/visualizer/visualizers/index.ts diff --git a/plugins/visualizer/visualizers/visualizer.ts b/src/plugins/visualizer/visualizers/visualizer.ts similarity index 100% rename from plugins/visualizer/visualizers/visualizer.ts rename to src/plugins/visualizer/visualizers/visualizer.ts diff --git a/plugins/visualizer/visualizers/vudio.ts b/src/plugins/visualizer/visualizers/vudio.ts similarity index 100% rename from plugins/visualizer/visualizers/vudio.ts rename to src/plugins/visualizer/visualizers/vudio.ts diff --git a/plugins/visualizer/visualizers/wave.ts b/src/plugins/visualizer/visualizers/wave.ts similarity index 100% rename from plugins/visualizer/visualizers/wave.ts rename to src/plugins/visualizer/visualizers/wave.ts diff --git a/plugins/visualizer/vudio.d.ts b/src/plugins/visualizer/vudio.d.ts similarity index 100% rename from plugins/visualizer/vudio.d.ts rename to src/plugins/visualizer/vudio.d.ts diff --git a/preload.ts b/src/preload.ts similarity index 100% rename from preload.ts rename to src/preload.ts diff --git a/providers/app-controls.ts b/src/providers/app-controls.ts similarity index 100% rename from providers/app-controls.ts rename to src/providers/app-controls.ts diff --git a/providers/decorators.ts b/src/providers/decorators.ts similarity index 100% rename from providers/decorators.ts rename to src/providers/decorators.ts diff --git a/providers/dom-elements.ts b/src/providers/dom-elements.ts similarity index 100% rename from providers/dom-elements.ts rename to src/providers/dom-elements.ts diff --git a/providers/extracted-data.ts b/src/providers/extracted-data.ts similarity index 100% rename from providers/extracted-data.ts rename to src/providers/extracted-data.ts diff --git a/providers/prompt-options.ts b/src/providers/prompt-options.ts similarity index 100% rename from providers/prompt-options.ts rename to src/providers/prompt-options.ts diff --git a/providers/protocol-handler.ts b/src/providers/protocol-handler.ts similarity index 100% rename from providers/protocol-handler.ts rename to src/providers/protocol-handler.ts diff --git a/providers/song-controls-front.ts b/src/providers/song-controls-front.ts similarity index 100% rename from providers/song-controls-front.ts rename to src/providers/song-controls-front.ts diff --git a/providers/song-controls.ts b/src/providers/song-controls.ts similarity index 100% rename from providers/song-controls.ts rename to src/providers/song-controls.ts diff --git a/providers/song-info-front.ts b/src/providers/song-info-front.ts similarity index 100% rename from providers/song-info-front.ts rename to src/providers/song-info-front.ts diff --git a/providers/song-info.ts b/src/providers/song-info.ts similarity index 100% rename from providers/song-info.ts rename to src/providers/song-info.ts diff --git a/renderer.ts b/src/renderer.ts similarity index 100% rename from renderer.ts rename to src/renderer.ts diff --git a/reset.d.ts b/src/reset.d.ts similarity index 100% rename from reset.d.ts rename to src/reset.d.ts diff --git a/tray.ts b/src/tray.ts similarity index 100% rename from tray.ts rename to src/tray.ts diff --git a/types/datahost-get-state.ts b/src/types/datahost-get-state.ts similarity index 100% rename from types/datahost-get-state.ts rename to src/types/datahost-get-state.ts diff --git a/types/get-player-response.ts b/src/types/get-player-response.ts similarity index 100% rename from types/get-player-response.ts rename to src/types/get-player-response.ts diff --git a/types/player-api-events.ts b/src/types/player-api-events.ts similarity index 100% rename from types/player-api-events.ts rename to src/types/player-api-events.ts diff --git a/types/video-details.ts b/src/types/video-details.ts similarity index 100% rename from types/video-details.ts rename to src/types/video-details.ts diff --git a/types/youtube-player.ts b/src/types/youtube-player.ts similarity index 100% rename from types/youtube-player.ts rename to src/types/youtube-player.ts diff --git a/utils/generate-package-json.js b/src/utils/generate-package-json.js old mode 100755 new mode 100644 similarity index 100% rename from utils/generate-package-json.js rename to src/utils/generate-package-json.js diff --git a/utils/testing.ts b/src/utils/testing.ts similarity index 100% rename from utils/testing.ts rename to src/utils/testing.ts diff --git a/utils/type-utils.ts b/src/utils/type-utils.ts similarity index 100% rename from utils/type-utils.ts rename to src/utils/type-utils.ts diff --git a/youtube-music.css b/src/youtube-music.css similarity index 100% rename from youtube-music.css rename to src/youtube-music.css diff --git a/youtube-music.d.ts b/src/youtube-music.d.ts similarity index 100% rename from youtube-music.d.ts rename to src/youtube-music.d.ts From 6dabfaa9bae2f3b2ea7ede2aac2c39becec73e82 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 10:30:39 +0900 Subject: [PATCH 09/21] fix: use `window` instead of `(global as any)` --- src/plugins/downloader/front.ts | 6 +----- src/plugins/picture-in-picture/front.ts | 3 +-- src/preload.ts | 3 +-- src/reset.d.ts | 3 +++ 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/plugins/downloader/front.ts b/src/plugins/downloader/front.ts index 1519366b57..06d6cfbb4a 100644 --- a/src/plugins/downloader/front.ts +++ b/src/plugins/downloader/front.ts @@ -40,11 +40,7 @@ const menuObserver = new MutationObserver(() => { setTimeout(() => doneFirstLoad ||= true, 500); }); -// TODO: re-enable once contextIsolation is set to true -// contextBridge.exposeInMainWorld("downloader", { -// download: () => { -// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access -(global as any).download = () => { +window.download = () => { let videoUrl = getSongMenu() // Selector of first button which is always "Start Radio" ?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="-1"] #navigation-endpoint') diff --git a/src/plugins/picture-in-picture/front.ts b/src/plugins/picture-in-picture/front.ts index 96236eba51..28dd2df05b 100644 --- a/src/plugins/picture-in-picture/front.ts +++ b/src/plugins/picture-in-picture/front.ts @@ -89,8 +89,7 @@ const togglePictureInPicture = async () => { return false; }; // For UI (HTML) -// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access -(global as any).togglePictureInPicture = togglePictureInPicture; +window.togglePictureInPicture = togglePictureInPicture; const listenForToggle = () => { const originalExitButton = $('.exit-fullscreen-button'); diff --git a/src/preload.ts b/src/preload.ts index bfa1d4678b..8cd32504e5 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -111,8 +111,7 @@ document.addEventListener('DOMContentLoaded', () => { setupSongControls(); // Add action for reloading - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any - (global as any).reload = () => ipcRenderer.send('reload'); + window.reload = () => ipcRenderer.send('reload'); // Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min setInterval(() => window._lact = Date.now(), 900_000); diff --git a/src/reset.d.ts b/src/reset.d.ts index b3b3d2907c..718c3de9a9 100644 --- a/src/reset.d.ts +++ b/src/reset.d.ts @@ -18,6 +18,9 @@ declare global { */ _lact: number; navigation: Navigation; + download: () => void; + togglePictureInPicture: () => void; + reload: () => void; } } From 6712fced6df0353856c06c19fcf3c67e6f8e8c7a Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 10:46:49 +0900 Subject: [PATCH 10/21] fix: fix broken menu-layout (#1360) --- src/plugins/downloader/style.css | 6 +++--- src/plugins/downloader/templates/download.html | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/downloader/style.css b/src/plugins/downloader/style.css index ea721f300e..052838ca1f 100644 --- a/src/plugins/downloader/style.css +++ b/src/plugins/downloader/style.css @@ -1,4 +1,4 @@ -.menu-item { +.ytmd-menu-item { display: var(--ytmusic-menu-item_-_display); height: var(--ytmusic-menu-item_-_height); align-items: var(--ytmusic-menu-item_-_align-items); @@ -6,11 +6,11 @@ cursor: pointer; } -.menu-item > .yt-simple-endpoint:hover { +.ytmd-menu-item > .yt-simple-endpoint:hover { background-color: var(--ytmusic-menu-item-hover-background-color); } -.menu-icon { +.ytmd-menu-item { flex: var(--ytmusic-menu-item-icon_-_flex); margin: var(--ytmusic-menu-item-icon_-_margin); fill: var(--ytmusic-menu-item-icon_-_fill); diff --git a/src/plugins/downloader/templates/download.html b/src/plugins/downloader/templates/download.html index 4079a4f51a..8c50dcbbf4 100644 --- a/src/plugins/downloader/templates/download.html +++ b/src/plugins/downloader/templates/download.html @@ -6,13 +6,13 @@ role="option" tabindex="-1" > - + From 1707261f4971db9c03652f9b973da95da492a5cf Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 10:58:14 +0900 Subject: [PATCH 11/21] chore(deps): bump deps --- package.json | 12 +-- pnpm-lock.yaml | 282 ++++++++++++++++++++++++------------------------- 2 files changed, 147 insertions(+), 147 deletions(-) diff --git a/package.json b/package.json index 50e8a50129..1512deea68 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ }, "pnpm": { "overrides": { - "rollup": "4.2.0", + "rollup": "4.3.0", "node-gyp": "10.0.1", "xml2js": "0.6.2", "node-fetch": "3.3.2", @@ -122,7 +122,7 @@ } }, "overrides": { - "rollup": "4.2.0", + "rollup": "4.3.0", "node-gyp": "10.0.1", "xml2js": "0.6.2", "node-fetch": "3.3.2", @@ -130,8 +130,8 @@ "@babel/runtime": "7.23.2" }, "dependencies": { - "@cliqz/adblocker-electron": "1.26.10", - "@cliqz/adblocker-electron-preload": "1.26.10", + "@cliqz/adblocker-electron": "1.26.11", + "@cliqz/adblocker-electron-preload": "1.26.11", "@ffmpeg.wasm/core-mt": "0.12.0", "@ffmpeg.wasm/main": "0.12.0", "@foobar404/wave": "2.0.4", @@ -181,12 +181,12 @@ "electron": "27.0.3", "electron-builder": "24.6.4", "electron-devtools-installer": "3.2.0", - "eslint": "8.52.0", + "eslint": "8.53.0", "eslint-plugin-import": "2.29.0", "eslint-plugin-prettier": "5.0.1", "node-gyp": "10.0.1", "playwright": "1.39.0", - "rollup": "4.2.0", + "rollup": "4.3.0", "rollup-plugin-copy": "3.5.0", "rollup-plugin-import-css": "3.3.5", "rollup-plugin-string": "3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac1a33f824..ec07d8ef2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - rollup: 4.2.0 + rollup: 4.3.0 node-gyp: 10.0.1 xml2js: 0.6.2 node-fetch: 3.3.2 @@ -14,11 +14,11 @@ overrides: dependencies: '@cliqz/adblocker-electron': - specifier: 1.26.10 - version: 1.26.10(electron@27.0.3) + specifier: 1.26.11 + version: 1.26.11(electron@27.0.3) '@cliqz/adblocker-electron-preload': - specifier: 1.26.10 - version: 1.26.10(electron@27.0.3) + specifier: 1.26.11 + version: 1.26.11(electron@27.0.3) '@ffmpeg.wasm/core-mt': specifier: 0.12.0 version: 0.12.0 @@ -110,25 +110,25 @@ devDependencies: version: 1.39.0 '@rollup/plugin-commonjs': specifier: 25.0.7 - version: 25.0.7(rollup@4.2.0) + version: 25.0.7(rollup@4.3.0) '@rollup/plugin-image': specifier: 3.0.3 - version: 3.0.3(rollup@4.2.0) + version: 3.0.3(rollup@4.3.0) '@rollup/plugin-json': specifier: 6.0.1 - version: 6.0.1(rollup@4.2.0) + version: 6.0.1(rollup@4.3.0) '@rollup/plugin-node-resolve': specifier: 15.2.3 - version: 15.2.3(rollup@4.2.0) + version: 15.2.3(rollup@4.3.0) '@rollup/plugin-terser': specifier: 0.4.4 - version: 0.4.4(rollup@4.2.0) + version: 0.4.4(rollup@4.3.0) '@rollup/plugin-typescript': specifier: 11.1.5 - version: 11.1.5(rollup@4.2.0)(typescript@5.2.2) + version: 11.1.5(rollup@4.3.0)(typescript@5.2.2) '@rollup/plugin-wasm': specifier: 6.2.2 - version: 6.2.2(rollup@4.2.0) + version: 6.2.2(rollup@4.3.0) '@total-typescript/ts-reset': specifier: 0.5.1 version: 0.5.1 @@ -143,7 +143,7 @@ devDependencies: version: 9.0.3 '@typescript-eslint/eslint-plugin': specifier: 6.9.1 - version: 6.9.1(@typescript-eslint/parser@6.7.5)(eslint@8.52.0)(typescript@5.2.2) + version: 6.9.1(@typescript-eslint/parser@6.7.5)(eslint@8.53.0)(typescript@5.2.2) builtin-modules: specifier: ^3.3.0 version: 3.3.0 @@ -163,14 +163,14 @@ devDependencies: specifier: 3.2.0 version: 3.2.0 eslint: - specifier: 8.52.0 - version: 8.52.0 + specifier: 8.53.0 + version: 8.53.0 eslint-plugin-import: specifier: 2.29.0 - version: 2.29.0(@typescript-eslint/parser@6.7.5)(eslint@8.52.0) + version: 2.29.0(@typescript-eslint/parser@6.7.5)(eslint@8.53.0) eslint-plugin-prettier: specifier: 5.0.1 - version: 5.0.1(eslint@8.52.0)(prettier@3.0.3) + version: 5.0.1(eslint@8.53.0)(prettier@3.0.3) node-gyp: specifier: 10.0.1 version: 10.0.1 @@ -178,14 +178,14 @@ devDependencies: specifier: 1.39.0 version: 1.39.0 rollup: - specifier: 4.2.0 - version: 4.2.0 + specifier: 4.3.0 + version: 4.3.0 rollup-plugin-copy: specifier: 3.5.0 version: 3.5.0 rollup-plugin-import-css: specifier: 3.3.5 - version: 3.3.5(rollup@4.2.0) + version: 3.3.5(rollup@4.3.0) rollup-plugin-string: specifier: 3.0.0 version: 3.0.0 @@ -240,41 +240,41 @@ packages: regenerator-runtime: 0.14.0 dev: false - /@cliqz/adblocker-content@1.26.10: - resolution: {integrity: sha512-tbN5c+V7Z1XJp27zIhxPBXqSiOSQdzj1B6OJ/sUoGcPOg7GJ6W8Uz6NquI6CkNWJkRhNyLwGWaT+JsQVvdgTqQ==} + /@cliqz/adblocker-content@1.26.11: + resolution: {integrity: sha512-2p4lDiyoadVjUQDe5Pao4C0agFE5HHjXvsWzXEMG9sDP37Ji3krxsBEUzTkQPC8CuWmOjbzsrUu9rYjKOGupaA==} dependencies: - '@cliqz/adblocker-extended-selectors': 1.26.10 + '@cliqz/adblocker-extended-selectors': 1.26.11 dev: false - /@cliqz/adblocker-electron-preload@1.26.10(electron@27.0.3): - resolution: {integrity: sha512-KeescoTX9nH4fAnAIg6rO/3tU90nYJkh591Ilt2fnX+ECK7yO+BlTIogBaJD7BSQxj2EZJJl/7d63Fld84JCrg==} + /@cliqz/adblocker-electron-preload@1.26.11(electron@27.0.3): + resolution: {integrity: sha512-sqv/dsFztDdnis+GOhZdsOxoBggHgFuEOgsd5ht5jbEXWBSjtw4z6dHJP9p1XBsBn1CgOh9aIKHtieoMAe+mYg==} peerDependencies: electron: '>11' dependencies: - '@cliqz/adblocker-content': 1.26.10 + '@cliqz/adblocker-content': 1.26.11 electron: 27.0.3 dev: false - /@cliqz/adblocker-electron@1.26.10(electron@27.0.3): - resolution: {integrity: sha512-FgXKI2JKw2E4/2LOrFfGkjm8YVucqGWpUNwV4io7WdOftxa2WKkJvc5MJIFXYw6xfJHw9XIuYoKXIA9nswlJ3g==} + /@cliqz/adblocker-electron@1.26.11(electron@27.0.3): + resolution: {integrity: sha512-ekb9YIq6tcXsmiiuy7wgm6VtSc2jCCV0dOWw/1KRg1VzVPd6a1+X0b1ziSYVs0wEiWfFyvPQSZaUu9ATfGcyDA==} peerDependencies: electron: '>11' dependencies: - '@cliqz/adblocker': 1.26.10 - '@cliqz/adblocker-electron-preload': 1.26.10(electron@27.0.3) + '@cliqz/adblocker': 1.26.11 + '@cliqz/adblocker-electron-preload': 1.26.11(electron@27.0.3) electron: 27.0.3 tldts-experimental: 6.0.16 dev: false - /@cliqz/adblocker-extended-selectors@1.26.10: - resolution: {integrity: sha512-ElSkgMM8z83AZQJQcyAAaJ/MojkAcHEYKuZTEA5KbjSNM/7hYoQMwvaMuc/KscNbDuED85+GKwmY4LrpwDAYQA==} + /@cliqz/adblocker-extended-selectors@1.26.11: + resolution: {integrity: sha512-jSnvM0LlPKPiksyUGYYZzMlC2wewUnG8/yw+WHHVqHdiaYBNz1x4wn1xfaCIh1Ee9cqGRbIEppFIO97raTxwFQ==} dev: false - /@cliqz/adblocker@1.26.10: - resolution: {integrity: sha512-aZjo0BYMDwU0OzU7Jeo42RNeYL9ocn8/xxC1UYiSZ71sQwMZG2i3YmL/monLJIVo9qP0GdTSwvthvpHHhJyHQQ==} + /@cliqz/adblocker@1.26.11: + resolution: {integrity: sha512-rrMgT+F0Wf1Rdo7RINVzWY111swOe9PqT625n5EcS95smN/E8JKIAjL3xaucjJ/gai+VoHkxJTKKRQAzmr612A==} dependencies: - '@cliqz/adblocker-content': 1.26.10 - '@cliqz/adblocker-extended-selectors': 1.26.10 + '@cliqz/adblocker-content': 1.26.11 + '@cliqz/adblocker-extended-selectors': 1.26.11 '@remusao/guess-url-type': 1.2.1 '@remusao/small': 1.2.1 '@remusao/smaz': 1.9.1 @@ -358,13 +358,13 @@ packages: - supports-color dev: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.52.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.53.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.52.0 + eslint: 8.53.0 eslint-visitor-keys: 3.4.3 dev: true @@ -373,8 +373,8 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc@2.1.2: - resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + /@eslint/eslintrc@2.1.3: + resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 @@ -390,8 +390,8 @@ packages: - supports-color dev: true - /@eslint/js@8.52.0: - resolution: {integrity: sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==} + /@eslint/js@8.53.0: + resolution: {integrity: sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -620,89 +620,89 @@ packages: resolution: {integrity: sha512-yvwa+aCyYI/UjeD39BnpMypG8N06l86wIDW1/PAc6ihBRnodIfZDwccxQN3n1t74wduzaz74m4ZMHZnB06567Q==} dev: false - /@rollup/plugin-commonjs@25.0.7(rollup@4.2.0): + /@rollup/plugin-commonjs@25.0.7(rollup@4.3.0): resolution: {integrity: sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.2.0 + rollup: 4.3.0 peerDependenciesMeta: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.5(rollup@4.2.0) + '@rollup/pluginutils': 5.0.5(rollup@4.3.0) commondir: 1.0.1 estree-walker: 2.0.2 glob: 8.1.0 is-reference: 1.2.1 magic-string: 0.30.5 - rollup: 4.2.0 + rollup: 4.3.0 dev: true - /@rollup/plugin-image@3.0.3(rollup@4.2.0): + /@rollup/plugin-image@3.0.3(rollup@4.3.0): resolution: {integrity: sha512-qXWQwsXpvD4trSb8PeFPFajp8JLpRtqqOeNYRUKnEQNHm7e5UP7fuSRcbjQAJ7wDZBbnJvSdY5ujNBQd9B1iFg==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.2.0 + rollup: 4.3.0 peerDependenciesMeta: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.5(rollup@4.2.0) + '@rollup/pluginutils': 5.0.5(rollup@4.3.0) mini-svg-data-uri: 1.4.4 - rollup: 4.2.0 + rollup: 4.3.0 dev: true - /@rollup/plugin-json@6.0.1(rollup@4.2.0): + /@rollup/plugin-json@6.0.1(rollup@4.3.0): resolution: {integrity: sha512-RgVfl5hWMkxN1h/uZj8FVESvPuBJ/uf6ly6GTj0GONnkfoBN5KC0MSz+PN2OLDgYXMhtG0mWpTrkiOjoxAIevw==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.2.0 + rollup: 4.3.0 peerDependenciesMeta: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.5(rollup@4.2.0) - rollup: 4.2.0 + '@rollup/pluginutils': 5.0.5(rollup@4.3.0) + rollup: 4.3.0 dev: true - /@rollup/plugin-node-resolve@15.2.3(rollup@4.2.0): + /@rollup/plugin-node-resolve@15.2.3(rollup@4.3.0): resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.2.0 + rollup: 4.3.0 peerDependenciesMeta: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.5(rollup@4.2.0) + '@rollup/pluginutils': 5.0.5(rollup@4.3.0) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-builtin-module: 3.2.1 is-module: 1.0.0 resolve: 1.22.8 - rollup: 4.2.0 + rollup: 4.3.0 dev: true - /@rollup/plugin-terser@0.4.4(rollup@4.2.0): + /@rollup/plugin-terser@0.4.4(rollup@4.3.0): resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.2.0 + rollup: 4.3.0 peerDependenciesMeta: rollup: optional: true dependencies: - rollup: 4.2.0 + rollup: 4.3.0 serialize-javascript: 6.0.1 smob: 1.4.1 terser: 5.21.0 dev: true - /@rollup/plugin-typescript@11.1.5(rollup@4.2.0)(typescript@5.2.2): + /@rollup/plugin-typescript@11.1.5(rollup@4.3.0)(typescript@5.2.2): resolution: {integrity: sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.2.0 + rollup: 4.3.0 tslib: '*' typescript: '>=3.7.0' peerDependenciesMeta: @@ -711,30 +711,30 @@ packages: tslib: optional: true dependencies: - '@rollup/pluginutils': 5.0.5(rollup@4.2.0) + '@rollup/pluginutils': 5.0.5(rollup@4.3.0) resolve: 1.22.8 - rollup: 4.2.0 + rollup: 4.3.0 typescript: 5.2.2 dev: true - /@rollup/plugin-wasm@6.2.2(rollup@4.2.0): + /@rollup/plugin-wasm@6.2.2(rollup@4.3.0): resolution: {integrity: sha512-gpC4R1G9Ni92ZIRTexqbhX7U+9estZrbhP+9SRb0DW9xpB9g7j34r+J2hqrcW/lRI7dJaU84MxZM0Rt82tqYPQ==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.2.0 + rollup: 4.3.0 peerDependenciesMeta: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.5(rollup@4.2.0) - rollup: 4.2.0 + '@rollup/pluginutils': 5.0.5(rollup@4.3.0) + rollup: 4.3.0 dev: true - /@rollup/pluginutils@5.0.5(rollup@4.2.0): + /@rollup/pluginutils@5.0.5(rollup@4.3.0): resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: 4.2.0 + rollup: 4.3.0 peerDependenciesMeta: rollup: optional: true @@ -742,99 +742,99 @@ packages: '@types/estree': 1.0.2 estree-walker: 2.0.2 picomatch: 2.3.1 - rollup: 4.2.0 + rollup: 4.3.0 dev: true - /@rollup/rollup-android-arm-eabi@4.2.0: - resolution: {integrity: sha512-8PlggAxGxavr+pkCNeV1TM2wTb2o+cUWDg9M1cm9nR27Dsn287uZtSLYXoQqQcmq+sYfF7lHfd3sWJJinH9GmA==} + /@rollup/rollup-android-arm-eabi@4.3.0: + resolution: {integrity: sha512-/4pns6BYi8MXdwnXM44yoGAcFYVHL/BYlB2q1HXZ6AzH++LaiEVWFpBWQ/glXhbMbv3E3o09igrHFbP/snhAvA==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.2.0: - resolution: {integrity: sha512-+71T85hbMFrJI+zKQULNmSYBeIhru55PYoF/u75MyeN2FcxE4HSPw20319b+FcZ4lWx2Nx/Ql9tN+hoaD3GH/A==} + /@rollup/rollup-android-arm64@4.3.0: + resolution: {integrity: sha512-nLO/JsL9idr416vzi3lHm3Xm+QZh4qHij8k3Er13kZr5YhL7/+kBAx84kDmPc7HMexLmwisjDCeDIKNFp8mDlQ==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.2.0: - resolution: {integrity: sha512-IIIQLuG43QIElT1JZqUP/zqIdiJl4t9U/boa0GZnQTw9m1X0k3mlBuysbgYXeloLT1RozdL7bgw4lpSaI8GOXw==} + /@rollup/rollup-darwin-arm64@4.3.0: + resolution: {integrity: sha512-dGhVBlllt4iHwTGy21IEoMOTN5wZoid19zEIxsdY29xcEiOEHqzDa7Sqrkh5OE7LKCowL61eFJXxYe/+pYa7ZQ==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.2.0: - resolution: {integrity: sha512-BXcXvnLaea1Xz900omrGJhxHFJfH9jZ0CpJuVsbjjhpniJ6qiLXz3xA8Lekaa4MuhFcJd4f0r+Ky1G4VFbYhWw==} + /@rollup/rollup-darwin-x64@4.3.0: + resolution: {integrity: sha512-h8wRfHeLEbU3NzaP1Oku7BYXCJQiTRr+8U0lklyOQXxXiEpHLL8tk1hFl+tezoRKLcPJD7joKaK74ASsqt3Ekg==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.2.0: - resolution: {integrity: sha512-f4K3MKw9Y4AKi4ANGnmPIglr+S+8tO858YrGVuqAHXxJdVghBmz9CPU9kDpOnGvT4g4vg5uNyIFpOOFvffXyMA==} + /@rollup/rollup-linux-arm-gnueabihf@4.3.0: + resolution: {integrity: sha512-wP4VgR/gfV18sylTuym3sxRTkAgUR2vh6YLeX/GEznk5jCYcYSlx585XlcUcl0c8UffIZlRJ09raWSX3JDb4GA==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.2.0: - resolution: {integrity: sha512-bNsTYQBgp4H7w6cT7FZhesxpcUPahsSIy4NgdZjH1ZwEoZHxi4XKglj+CsSEkhsKi+x6toVvMylhjRKhEMYfnA==} + /@rollup/rollup-linux-arm64-gnu@4.3.0: + resolution: {integrity: sha512-v/14JCYVkqRSJeQbxFx4oUkwVQQw6lFMN7bd4vuARBc3X2lmomkxBsc+BFiIDL/BK+CTx5AOh/k9XmqDnKWRVg==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.2.0: - resolution: {integrity: sha512-Jp1NxBJpGLuxRU2ihrQk4IZ+ia5nffobG6sOFUPW5PMYkF0kQtxEbeDuCa69Xif211vUOcxlOnf5IOEIpTEySA==} + /@rollup/rollup-linux-arm64-musl@4.3.0: + resolution: {integrity: sha512-tNhfYqFH5OxtRzfkTOKdgFYlPSZnlDLNW4+leNEvQZhwTJxoTwsZAAhR97l3qVry/kkLyJPBK+Q8EAJLPinDIg==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.2.0: - resolution: {integrity: sha512-3p3iRtQmv2aXw+vtKNyZMLOQ+LSRsqArXjKAh2Oj9cqwfIRe7OXvdkOzWfZOIp1F/x5KJzVAxGxnniF4cMbnsQ==} + /@rollup/rollup-linux-x64-gnu@4.3.0: + resolution: {integrity: sha512-pw77m8QywdsoFdFOgmc8roF1inBI0rciqzO8ffRUgLoq7+ee9o5eFqtEcS6hHOOplgifAUUisP8cAnwl9nUYPw==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.2.0: - resolution: {integrity: sha512-atih7IF/reUZe4LBLC5Izd44hth2tfDIG8LaPp4/cQXdHh9jabcZEvIeRPrpDq0i/Uu487Qu5gl5KwyAnWajnw==} + /@rollup/rollup-linux-x64-musl@4.3.0: + resolution: {integrity: sha512-tJs7v2MnV2F8w6X1UpPHl/43OfxjUy9SuJ2ZPoxn79v9vYteChVYO/ueLHCpRMmyTUIVML3N9z4azl9ENH8Xxg==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.2.0: - resolution: {integrity: sha512-vYxF3tKJeUE4ceYzpNe2p84RXk/fGK30I8frpRfv/MyPStej/mRlojztkN7Jtd1014HHVeq/tYaMBz/3IxkxZw==} + /@rollup/rollup-win32-arm64-msvc@4.3.0: + resolution: {integrity: sha512-OKGxp6kATQdTyI2DF+e9s+hB3/QZB45b6e+dzcfW1SUqiF6CviWyevhmT4USsMEdP3mlpC9zxLz3Oh+WaTMOSw==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.2.0: - resolution: {integrity: sha512-1LZJ6zpl93SaPQvas618bMFarVwufWTaczH4ESAbFcwiC4OtznA6Ym+hFPyIGaJaGEB8uMWWac0uXGPXOg5FGA==} + /@rollup/rollup-win32-ia32-msvc@4.3.0: + resolution: {integrity: sha512-DDZ5AH68JJ2ClQFEA1aNnfA7Ybqyeh0644rGbrLOdNehTmzfICHiWSn0OprzYi9HAshTPQvlwrM+bi2kuaIOjQ==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.2.0: - resolution: {integrity: sha512-dgQfFdHCNg08nM5zBmqxqc9vrm0DVzhWotpavbPa0j4//MAOKZEB75yGAfzQE9fUJ+4pvM1239Y4IhL8f6sSog==} + /@rollup/rollup-win32-x64-msvc@4.3.0: + resolution: {integrity: sha512-dMvGV8p92GQ8jhNlGIKpyhVZPzJlT258pPrM5q2F8lKcc9Iv9BbfdnhX1OfinYWnb9ms5zLw6MlaMnqLfUkKnQ==} cpu: [x64] os: [win32] requiresBuild: true @@ -1020,7 +1020,7 @@ packages: '@types/node': 20.8.6 optional: true - /@typescript-eslint/eslint-plugin@6.9.1(@typescript-eslint/parser@6.7.5)(eslint@8.52.0)(typescript@5.2.2): + /@typescript-eslint/eslint-plugin@6.9.1(@typescript-eslint/parser@6.7.5)(eslint@8.53.0)(typescript@5.2.2): resolution: {integrity: sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -1032,13 +1032,13 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.9.1 - '@typescript-eslint/parser': 6.7.5(eslint@8.52.0)(typescript@5.2.2) + '@typescript-eslint/parser': 6.7.5(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/scope-manager': 6.9.1 - '@typescript-eslint/type-utils': 6.9.1(eslint@8.52.0)(typescript@5.2.2) - '@typescript-eslint/utils': 6.9.1(eslint@8.52.0)(typescript@5.2.2) + '@typescript-eslint/type-utils': 6.9.1(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.9.1(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/visitor-keys': 6.9.1 debug: 4.3.4 - eslint: 8.52.0 + eslint: 8.53.0 graphemer: 1.4.0 ignore: 5.2.4 natural-compare: 1.4.0 @@ -1049,7 +1049,7 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.7.5(eslint@8.52.0)(typescript@5.2.2): + /@typescript-eslint/parser@6.7.5(eslint@8.53.0)(typescript@5.2.2): resolution: {integrity: sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -1064,7 +1064,7 @@ packages: '@typescript-eslint/typescript-estree': 6.7.5(typescript@5.2.2) '@typescript-eslint/visitor-keys': 6.7.5 debug: 4.3.4 - eslint: 8.52.0 + eslint: 8.53.0 typescript: 5.2.2 transitivePeerDependencies: - supports-color @@ -1086,7 +1086,7 @@ packages: '@typescript-eslint/visitor-keys': 6.9.1 dev: true - /@typescript-eslint/type-utils@6.9.1(eslint@8.52.0)(typescript@5.2.2): + /@typescript-eslint/type-utils@6.9.1(eslint@8.53.0)(typescript@5.2.2): resolution: {integrity: sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -1097,9 +1097,9 @@ packages: optional: true dependencies: '@typescript-eslint/typescript-estree': 6.9.1(typescript@5.2.2) - '@typescript-eslint/utils': 6.9.1(eslint@8.52.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.9.1(eslint@8.53.0)(typescript@5.2.2) debug: 4.3.4 - eslint: 8.52.0 + eslint: 8.53.0 ts-api-utils: 1.0.3(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: @@ -1158,19 +1158,19 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@6.9.1(eslint@8.52.0)(typescript@5.2.2): + /@typescript-eslint/utils@6.9.1(eslint@8.53.0)(typescript@5.2.2): resolution: {integrity: sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) '@types/json-schema': 7.0.13 '@types/semver': 7.5.3 '@typescript-eslint/scope-manager': 6.9.1 '@typescript-eslint/types': 6.9.1 '@typescript-eslint/typescript-estree': 6.9.1(typescript@5.2.2) - eslint: 8.52.0 + eslint: 8.53.0 semver: 7.5.4 transitivePeerDependencies: - supports-color @@ -2484,7 +2484,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint@8.52.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint@8.53.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -2505,15 +2505,15 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.7.5(eslint@8.52.0)(typescript@5.2.2) + '@typescript-eslint/parser': 6.7.5(eslint@8.53.0)(typescript@5.2.2) debug: 3.2.7 - eslint: 8.52.0 + eslint: 8.53.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.7.5)(eslint@8.52.0): + /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.7.5)(eslint@8.53.0): resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==} engines: {node: '>=4'} peerDependencies: @@ -2523,16 +2523,16 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 6.7.5(eslint@8.52.0)(typescript@5.2.2) + '@typescript-eslint/parser': 6.7.5(eslint@8.53.0)(typescript@5.2.2) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.52.0 + eslint: 8.53.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint@8.52.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.9)(eslint@8.53.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -2548,7 +2548,7 @@ packages: - supports-color dev: true - /eslint-plugin-prettier@5.0.1(eslint@8.52.0)(prettier@3.0.3): + /eslint-plugin-prettier@5.0.1(eslint@8.53.0)(prettier@3.0.3): resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -2562,7 +2562,7 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 8.52.0 + eslint: 8.53.0 prettier: 3.0.3 prettier-linter-helpers: 1.0.0 synckit: 0.8.5 @@ -2581,15 +2581,15 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.52.0: - resolution: {integrity: sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==} + /eslint@8.53.0: + resolution: {integrity: sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) '@eslint-community/regexpp': 4.9.1 - '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.52.0 + '@eslint/eslintrc': 2.1.3 + '@eslint/js': 8.53.0 '@humanwhocodes/config-array': 0.11.13 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -4624,14 +4624,14 @@ packages: is-plain-object: 3.0.1 dev: true - /rollup-plugin-import-css@3.3.5(rollup@4.2.0): + /rollup-plugin-import-css@3.3.5(rollup@4.3.0): resolution: {integrity: sha512-wSfzveEzvUDlVevo70kmVD5Mk785UN55NG4C7VVnrmdE0qZ8apcVVFajyCPfFYSNxq5YkccOcrGUT2T/2HnEcQ==} engines: {node: '>=16'} peerDependencies: - rollup: 4.2.0 + rollup: 4.3.0 dependencies: - '@rollup/pluginutils': 5.0.5(rollup@4.2.0) - rollup: 4.2.0 + '@rollup/pluginutils': 5.0.5(rollup@4.3.0) + rollup: 4.3.0 dev: true /rollup-plugin-string@3.0.0: @@ -4646,23 +4646,23 @@ packages: estree-walker: 0.6.1 dev: true - /rollup@4.2.0: - resolution: {integrity: sha512-deaMa9Z+jPVeBD2dKXv+h7EbdKte9++V2potc/ADqvVgEr6DEJ3ia9u0joarjC2lX/ubaCRYz3QVx0TzuVqAJA==} + /rollup@4.3.0: + resolution: {integrity: sha512-scIi1NrKLDIYSPK66jjECtII7vIgdAMFmFo8h6qm++I6nN9qDSV35Ku6erzGVqYjx+lj+j5wkusRMr++8SyDZg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.2.0 - '@rollup/rollup-android-arm64': 4.2.0 - '@rollup/rollup-darwin-arm64': 4.2.0 - '@rollup/rollup-darwin-x64': 4.2.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.2.0 - '@rollup/rollup-linux-arm64-gnu': 4.2.0 - '@rollup/rollup-linux-arm64-musl': 4.2.0 - '@rollup/rollup-linux-x64-gnu': 4.2.0 - '@rollup/rollup-linux-x64-musl': 4.2.0 - '@rollup/rollup-win32-arm64-msvc': 4.2.0 - '@rollup/rollup-win32-ia32-msvc': 4.2.0 - '@rollup/rollup-win32-x64-msvc': 4.2.0 + '@rollup/rollup-android-arm-eabi': 4.3.0 + '@rollup/rollup-android-arm64': 4.3.0 + '@rollup/rollup-darwin-arm64': 4.3.0 + '@rollup/rollup-darwin-x64': 4.3.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.3.0 + '@rollup/rollup-linux-arm64-gnu': 4.3.0 + '@rollup/rollup-linux-arm64-musl': 4.3.0 + '@rollup/rollup-linux-x64-gnu': 4.3.0 + '@rollup/rollup-linux-x64-musl': 4.3.0 + '@rollup/rollup-win32-arm64-msvc': 4.3.0 + '@rollup/rollup-win32-ia32-msvc': 4.3.0 + '@rollup/rollup-win32-x64-msvc': 4.3.0 fsevents: 2.3.3 dev: true From fdd6d9929fce3a1d3f77f5df7ddf5127a201071c Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 10:58:32 +0900 Subject: [PATCH 12/21] fix: temporary workaround for #1356 --- src/config/defaults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 6b49c36aad..04946e6fc0 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -172,7 +172,7 @@ const defaultConfig = { 'pip-position': [10, 10], 'pip-size': [450, 275], 'isInPiP': false, - 'useNativePiP': false, + 'useNativePiP': true, }, 'playback-speed': {}, 'precise-volume': { From e505c4261b6eac532fe3ff58fac97d90f48d959b Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 12:41:19 +0900 Subject: [PATCH 13/21] fix: fix issues - plugin config default value - script error --- .eslintignore | 1 + package.json | 2 + pnpm-lock.yaml | 3 + rollup.renderer.config.ts | 6 +- src/config/plugins.ts | 4 +- src/index.ts | 642 ++++++++++++++++++ src/plugins/album-color-theme/front.ts | 2 +- src/plugins/ambient-mode/front.ts | 22 +- src/plugins/downloader/front.ts | 2 +- src/plugins/in-app-menu/back.ts | 2 +- src/plugins/in-app-menu/front.ts | 33 +- src/plugins/in-app-menu/menu/panel.ts | 2 +- .../visualizer/visualizers/butterchurn.ts | 2 +- src/preload.ts | 187 +---- 14 files changed, 699 insertions(+), 211 deletions(-) diff --git a/.eslintignore b/.eslintignore index 97bae8d4f0..c91110d8ae 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ .eslintrc.js rollup.main.config.ts rollup.preload.config.ts +rollup.renderer.config.ts diff --git a/package.json b/package.json index f5536c9b67..7cb686e152 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "scripts": { "test": "playwright test", "test:debug": "cross-env DEBUG=pw:*,-pw:test:protocol playwright test", + "rollup:renderer": "rollup -c rollup.renderer.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs", "rollup:preload": "rollup -c rollup.preload.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs", "rollup:main": "rollup -c rollup.main.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs", "build": "yarpm-pnpm run rollup:renderer && yarpm-pnpm run rollup:preload && yarpm-pnpm run rollup:main", @@ -143,6 +144,7 @@ "butterchurn-presets": "3.0.0-beta.4", "conf": "10.2.0", "custom-electron-prompt": "1.5.7", + "deepmerge": "^4.3.1", "electron-debug": "3.2.0", "electron-is": "3.0.0", "electron-localshortcut": "3.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec07d8ef2b..cd2305e9f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,6 +52,9 @@ dependencies: custom-electron-prompt: specifier: 1.5.7 version: 1.5.7(electron@27.0.3) + deepmerge: + specifier: ^4.3.1 + version: 4.3.1 electron-debug: specifier: 3.2.0 version: 3.2.0 diff --git a/rollup.renderer.config.ts b/rollup.renderer.config.ts index 66c5a0acaa..08ceda604a 100644 --- a/rollup.renderer.config.ts +++ b/rollup.renderer.config.ts @@ -32,9 +32,9 @@ export default defineConfig({ targetEnv: 'browser', }), image({ dom: true }), - terser({ + /*terser({ ecma: 2020, - }), + }),*/ { closeBundle() { if (!process.env.ROLLUP_WATCH) { @@ -44,7 +44,7 @@ export default defineConfig({ name: 'force-close' }, ], - input: './renderer.ts', + input: './src/renderer.ts', output: { format: 'cjs', name: '[name].js', diff --git a/src/config/plugins.ts b/src/config/plugins.ts index c2357f6ff8..64f14afc69 100644 --- a/src/config/plugins.ts +++ b/src/config/plugins.ts @@ -1,3 +1,5 @@ +import deepmerge from 'deepmerge'; + import store from './store'; import defaultConfig from './defaults'; @@ -11,7 +13,7 @@ interface Plugin { type DefaultPluginsConfig = typeof defaultConfig.plugins; export function getEnabled() { - const plugins = store.get('plugins') as DefaultPluginsConfig; + const plugins = deepmerge(defaultConfig.plugins, (store.get('plugins') as DefaultPluginsConfig)) as DefaultPluginsConfig; return (Object.entries(plugins) as Entries).filter(([plugin]) => isEnabled(plugin), ); diff --git a/src/index.ts b/src/index.ts index e69de29bb2..972ebdef7b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -0,0 +1,642 @@ +import path from 'node:path'; +import fs from 'node:fs'; + +import { BrowserWindow, app, screen, globalShortcut, session, shell, dialog, ipcMain } from 'electron'; +import enhanceWebRequest, { BetterSession } from '@jellybrick/electron-better-web-request'; +import is from 'electron-is'; +import unhandled from 'electron-unhandled'; +import { autoUpdater } from 'electron-updater'; +import electronDebug from 'electron-debug'; + +import config from './config'; +import { refreshMenu, setApplicationMenu } from './menu'; +import { fileExists, injectCSS, injectCSSAsFile } from './plugins/utils'; +import { isTesting } from './utils/testing'; +import { setUpTray } from './tray'; +import { setupSongInfo } from './providers/song-info'; +import { restart, setupAppControls } from './providers/app-controls'; +import { APP_PROTOCOL, handleProtocol, setupProtocolHandler } from './providers/protocol-handler'; + +import adblocker from './plugins/adblocker/back'; +import albumColorTheme from './plugins/album-color-theme/back'; +import ambientMode from './plugins/ambient-mode/back'; +import blurNavigationBar from './plugins/blur-nav-bar/back'; +import captionsSelector from './plugins/captions-selector/back'; +import crossfade from './plugins/crossfade/back'; +import discord from './plugins/discord/back'; +import downloader from './plugins/downloader/back'; +import inAppMenu from './plugins/in-app-menu/back'; +import lastFm from './plugins/last-fm/back'; +import lumiaStream from './plugins/lumiastream/back'; +import lyricsGenius from './plugins/lyrics-genius/back'; +import navigation from './plugins/navigation/back'; +import noGoogleLogin from './plugins/no-google-login/back'; +import notifications from './plugins/notifications/back'; +import pictureInPicture, { setOptions as pipSetOptions } from './plugins/picture-in-picture/back'; +import preciseVolume from './plugins/precise-volume/back'; +import qualityChanger from './plugins/quality-changer/back'; +import shortcuts from './plugins/shortcuts/back'; +import sponsorBlock from './plugins/sponsorblock/back'; +import taskbarMediaControl from './plugins/taskbar-mediacontrol/back'; +import touchbar from './plugins/touchbar/back'; +import tunaObs from './plugins/tuna-obs/back'; +import videoToggle from './plugins/video-toggle/back'; +import visualizer from './plugins/visualizer/back'; + +import youtubeMusicCSS from './youtube-music.css'; + +// Catch errors and log them +unhandled({ + logger: console.error, + showDialog: false, +}); + +// Disable Node options if the env var is set +process.env.NODE_OPTIONS = ''; + +// Prevent window being garbage collected +let mainWindow: Electron.BrowserWindow | null; +autoUpdater.autoDownload = false; + +const gotTheLock = app.requestSingleInstanceLock(); +if (!gotTheLock) { + app.exit(); +} + +// SharedArrayBuffer: Required for downloader (@ffmpeg/core-mt) +// OverlayScrollbar: Required for overlay scrollbars +app.commandLine.appendSwitch('enable-features', 'OverlayScrollbar,SharedArrayBuffer'); +if (config.get('options.disableHardwareAcceleration')) { + if (is.dev()) { + console.log('Disabling hardware acceleration'); + } + + app.disableHardwareAcceleration(); +} + +if (is.linux() && config.plugins.isEnabled('shortcuts')) { + // Stops chromium from launching its own MPRIS service + app.commandLine.appendSwitch('disable-features', 'MediaSessionService'); +} + +if (config.get('options.proxy')) { + app.commandLine.appendSwitch('proxy-server', config.get('options.proxy')); +} + +// Adds debug features like hotkeys for triggering dev tools and reload +electronDebug({ + showDevTools: false, // Disable automatic devTools on new window +}); + +let icon = 'assets/youtube-music.png'; +if (process.platform === 'win32') { + icon = 'assets/generated/icon.ico'; +} else if (process.platform === 'darwin') { + icon = 'assets/generated/icon.icns'; +} + +function onClosed() { + // Dereference the window + // For multiple Windows store them in an array + mainWindow = null; +} + +const mainPlugins = { + 'adblocker': adblocker, + 'album-color-theme': albumColorTheme, + 'ambient-mode': ambientMode, + 'blur-nav-bar': blurNavigationBar, + 'captions-selector': captionsSelector, + 'crossfade': crossfade, + 'discord': discord, + 'downloader': downloader, + 'in-app-menu': inAppMenu, + 'last-fm': lastFm, + 'lumiastream': lumiaStream, + 'lyrics-genius': lyricsGenius, + 'navigation': navigation, + 'no-google-login': noGoogleLogin, + 'notifications': notifications, + 'picture-in-picture': pictureInPicture, + 'precise-volume': preciseVolume, + 'quality-changer': qualityChanger, + 'shortcuts': shortcuts, + 'sponsorblock': sponsorBlock, + 'taskbar-mediacontrol': undefined as typeof taskbarMediaControl | undefined, + 'touchbar': undefined as typeof touchbar | undefined, + 'tuna-obs': tunaObs, + 'video-toggle': videoToggle, + 'visualizer': visualizer, +}; +export const mainPluginNames = Object.keys(mainPlugins); + +if (is.windows()) { + mainPlugins['taskbar-mediacontrol'] = taskbarMediaControl; + delete mainPlugins['touchbar']; +} else if (is.macOS()) { + mainPlugins['touchbar'] = touchbar; + delete mainPlugins['taskbar-mediacontrol']; +} else { + delete mainPlugins['touchbar']; + delete mainPlugins['taskbar-mediacontrol']; +} + +ipcMain.handle('get-main-plugin-names', () => Object.keys(mainPlugins)); + +async function loadPlugins(win: BrowserWindow) { + injectCSS(win.webContents, youtubeMusicCSS); + // Load user CSS + const themes: string[] = config.get('options.themes'); + if (Array.isArray(themes)) { + for (const cssFile of themes) { + fileExists( + cssFile, + () => { + injectCSSAsFile(win.webContents, cssFile); + }, + () => { + console.warn(`CSS file "${cssFile}" does not exist, ignoring`); + }, + ); + } + } + + win.webContents.once('did-finish-load', () => { + if (is.dev()) { + console.log('did finish load'); + win.webContents.openDevTools(); + } + }); + + for (const [plugin, options] of config.plugins.getEnabled()) { + try { + if (Object.hasOwn(mainPlugins, plugin)) { + console.log('Loaded plugin - ' + plugin); + const handler = mainPlugins[plugin as keyof typeof mainPlugins]; + if (handler) { + await handler(win, options as never); + } + } + } catch (e) { + console.error(`Failed to load plugin "${plugin}"`, e); + } + } +} + +async function createMainWindow() { + const windowSize = config.get('window-size'); + const windowMaximized = config.get('window-maximized'); + const windowPosition: Electron.Point = config.get('window-position'); + const useInlineMenu = config.plugins.isEnabled('in-app-menu'); + + const win = new BrowserWindow({ + icon, + width: windowSize.width, + height: windowSize.height, + backgroundColor: '#000', + show: false, + webPreferences: { + contextIsolation: true, + preload: path.join(__dirname, 'preload.js'), + nodeIntegrationInSubFrames: true, + ...(isTesting() + ? undefined + : { + // Sandbox is only enabled in tests for now + // See https://www.electronjs.org/docs/latest/tutorial/sandbox#preload-scripts + sandbox: false, + }), + }, + frame: !is.macOS() && !useInlineMenu, + titleBarOverlay: { + color: '#00000000', + symbolColor: '#ffffff', + height: 36, + }, + titleBarStyle: useInlineMenu + ? 'hidden' + : (is.macOS() + ? 'hiddenInset' + : 'default'), + autoHideMenuBar: config.get('options.hideMenu'), + }); + await loadPlugins(win); + + if (windowPosition) { + const { x: windowX, y: windowY } = windowPosition; + const winSize = win.getSize(); + const displaySize + = screen.getDisplayNearestPoint(windowPosition).bounds; + if ( + windowX + winSize[0] < displaySize.x - 8 + || windowX - winSize[0] > displaySize.x + displaySize.width + || windowY < displaySize.y - 8 + || windowY > displaySize.y + displaySize.height + ) { + // Window is offscreen + if (is.dev()) { + console.log( + `Window tried to render offscreen, windowSize=${String(winSize)}, displaySize=${String(displaySize)}, position=${String(windowPosition)}`, + ); + } + } else { + win.setPosition(windowX, windowY); + } + } + + if (windowMaximized) { + win.maximize(); + } + + if (config.get('options.alwaysOnTop')) { + win.setAlwaysOnTop(true); + } + + const urlToLoad = config.get('options.resumeOnStart') + ? config.get('url') + : config.defaultConfig.url; + win.on('closed', onClosed); + + type PiPOptions = typeof config.defaultConfig.plugins['picture-in-picture']; + const setPiPOptions = config.plugins.isEnabled('picture-in-picture') + // eslint-disable-next-line @typescript-eslint/no-var-requires + ? (key: string, value: unknown) => pipSetOptions({ [key]: value }) + : () => {}; + + win.on('move', () => { + if (win.isMaximized()) { + return; + } + + const position = win.getPosition(); + const isPiPEnabled: boolean + = config.plugins.isEnabled('picture-in-picture') + && config.plugins.getOptions('picture-in-picture').isInPiP; + if (!isPiPEnabled) { + + lateSave('window-position', { x: position[0], y: position[1] }); + } else if (config.plugins.getOptions('picture-in-picture').savePosition) { + lateSave('pip-position', position, setPiPOptions); + } + }); + + let winWasMaximized: boolean; + + win.on('resize', () => { + const windowSize = win.getSize(); + const isMaximized = win.isMaximized(); + + const isPiPEnabled + = config.plugins.isEnabled('picture-in-picture') + && config.plugins.getOptions('picture-in-picture').isInPiP; + + if (!isPiPEnabled && winWasMaximized !== isMaximized) { + winWasMaximized = isMaximized; + config.set('window-maximized', isMaximized); + } + + if (isMaximized) { + return; + } + + if (!isPiPEnabled) { + lateSave('window-size', { + width: windowSize[0], + height: windowSize[1], + }); + } else if (config.plugins.getOptions('picture-in-picture').saveSize) { + lateSave('pip-size', windowSize, setPiPOptions); + } + }); + + const savedTimeouts: Record = {}; + + function lateSave(key: string, value: unknown, fn: (key: string, value: unknown) => void = config.set) { + if (savedTimeouts[key]) { + clearTimeout(savedTimeouts[key]); + } + + savedTimeouts[key] = setTimeout(() => { + fn(key, value); + savedTimeouts[key] = undefined; + }, 600); + } + + app.on('render-process-gone', (event, webContents, details) => { + showUnresponsiveDialog(win, details); + }); + + win.once('ready-to-show', () => { + if (config.get('options.appVisible')) { + win.show(); + } + }); + + removeContentSecurityPolicy(); + + win.webContents.on('dom-ready', () => { + win.webContents.executeJavaScript(fs.readFileSync(path.join(__dirname, 'renderer.js'), 'utf-8') + ';0', true); + }); + + win.webContents.loadURL(urlToLoad); + + return win; +} + +app.once('browser-window-created', (event, win) => { + if (config.get('options.overrideUserAgent')) { + // User agents are from https://developers.whatismybrowser.com/useragents/explore/ + const originalUserAgent = win.webContents.userAgent; + const userAgents = { + mac: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12.1; rv:95.0) Gecko/20100101 Firefox/95.0', + windows: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0', + linux: 'Mozilla/5.0 (Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0', + }; + + const updatedUserAgent + = is.macOS() ? userAgents.mac + : (is.windows() ? userAgents.windows + : userAgents.linux); + + win.webContents.userAgent = updatedUserAgent; + app.userAgentFallback = updatedUserAgent; + + win.webContents.session.webRequest.onBeforeSendHeaders((details, cb) => { + // This will only happen if login failed, and "retry" was pressed + if (win.webContents.getURL().startsWith('https://accounts.google.com') && details.url.startsWith('https://accounts.google.com')) { + details.requestHeaders['User-Agent'] = originalUserAgent; + } + + cb({ requestHeaders: details.requestHeaders }); + }); + } + + setupSongInfo(win); + setupAppControls(); + + win.webContents.on('did-fail-load', ( + _event, + errorCode, + errorDescription, + validatedURL, + isMainFrame, + frameProcessId, + frameRoutingId, + ) => { + const log = JSON.stringify({ + error: 'did-fail-load', + errorCode, + errorDescription, + validatedURL, + isMainFrame, + frameProcessId, + frameRoutingId, + }, null, '\t'); + if (is.dev()) { + console.log(log); + } + + if (errorCode !== -3) { // -3 is a false positive + win.webContents.send('log', log); + win.webContents.loadFile(path.join(__dirname, 'error.html')); + } + }); + + win.webContents.on('will-prevent-unload', (event) => { + event.preventDefault(); + }); +}); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } + + // Unregister all shortcuts. + globalShortcut.unregisterAll(); +}); + +app.on('activate', async () => { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + mainWindow = await createMainWindow(); + } else if (!mainWindow.isVisible()) { + mainWindow.show(); + } +}); + +app.on('ready', async () => { + if (config.get('options.autoResetAppCache')) { + // Clear cache after 20s + const clearCacheTimeout = setTimeout(() => { + if (is.dev()) { + console.log('Clearing app cache.'); + } + + session.defaultSession.clearCache(); + clearTimeout(clearCacheTimeout); + }, 20_000); + } + + // Register appID on windows + if (is.windows()) { + const appID = 'com.github.th-ch.youtube-music'; + app.setAppUserModelId(appID); + const appLocation = process.execPath; + const appData = app.getPath('appData'); + // Check shortcut validity if not in dev mode / running portable app + if (!is.dev() && !appLocation.startsWith(path.join(appData, '..', 'Local', 'Temp'))) { + const shortcutPath = path.join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'YouTube Music.lnk'); + try { // Check if shortcut is registered and valid + const shortcutDetails = shell.readShortcutLink(shortcutPath); // Throw error if doesn't exist yet + if ( + shortcutDetails.target !== appLocation + || shortcutDetails.appUserModelId !== appID + ) { + throw 'needUpdate'; + } + } catch (error) { // If not valid -> Register shortcut + shell.writeShortcutLink( + shortcutPath, + error === 'needUpdate' ? 'update' : 'create', + { + target: appLocation, + cwd: path.dirname(appLocation), + description: 'YouTube Music Desktop App - including custom plugins', + appUserModelId: appID, + }, + ); + } + } + } + + mainWindow = await createMainWindow(); + setApplicationMenu(mainWindow); + refreshMenu(mainWindow); + setUpTray(app, mainWindow); + + setupProtocolHandler(mainWindow); + + app.on('second-instance', (_, commandLine) => { + const uri = `${APP_PROTOCOL}://`; + const protocolArgv = commandLine.find((arg) => arg.startsWith(uri)); + if (protocolArgv) { + const lastIndex = protocolArgv.endsWith('/') ? -1 : undefined; + const command = protocolArgv.slice(uri.length, lastIndex); + if (is.dev()) { + console.debug(`Received command over protocol: "${command}"`); + } + + handleProtocol(command); + return; + } + + if (!mainWindow) { + return; + } + + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } + + if (!mainWindow.isVisible()) { + mainWindow.show(); + } + + mainWindow.focus(); + }); + + // Autostart at login + app.setLoginItemSettings({ + openAtLogin: config.get('options.startAtLogin'), + }); + + if (!is.dev() && config.get('options.autoUpdates')) { + const updateTimeout = setTimeout(() => { + autoUpdater.checkForUpdatesAndNotify(); + clearTimeout(updateTimeout); + }, 2000); + autoUpdater.on('update-available', () => { + const downloadLink + = 'https://github.com/th-ch/youtube-music/releases/latest'; + const dialogOptions: Electron.MessageBoxOptions = { + type: 'info', + buttons: ['OK', 'Download', 'Disable updates'], + title: 'Application Update', + message: 'A new version is available', + detail: `A new version is available and can be downloaded at ${downloadLink}`, + }; + dialog.showMessageBox(dialogOptions).then((dialogOutput) => { + switch (dialogOutput.response) { + // Download + case 1: { + shell.openExternal(downloadLink); + break; + } + + // Disable updates + case 2: { + config.set('options.autoUpdates', false); + break; + } + + default: { + break; + } + } + }); + }); + } + + if (config.get('options.hideMenu') && !config.get('options.hideMenuWarned')) { + dialog.showMessageBox(mainWindow, { + type: 'info', title: 'Hide Menu Enabled', + message: "Menu is hidden, use 'Alt' to show it (or 'Escape' if using in-app-menu)", + }); + config.set('options.hideMenuWarned', true); + } + + // Optimized for Mac OS X + if (is.macOS() && !config.get('options.appVisible')) { + app.dock.hide(); + } + + let forceQuit = false; + app.on('before-quit', () => { + forceQuit = true; + }); + + if (is.macOS() || config.get('options.tray')) { + mainWindow.on('close', (event) => { + // Hide the window instead of quitting (quit is available in tray options) + if (!forceQuit) { + event.preventDefault(); + mainWindow!.hide(); + } + }); + } +}); + +function showUnresponsiveDialog(win: BrowserWindow, details: Electron.RenderProcessGoneDetails) { + if (details) { + console.log('Unresponsive Error!\n' + JSON.stringify(details, null, '\t')); + } + + dialog.showMessageBox(win, { + type: 'error', + title: 'Window Unresponsive', + message: 'The Application is Unresponsive', + detail: 'We are sorry for the inconvenience! please choose what to do:', + buttons: ['Wait', 'Relaunch', 'Quit'], + cancelId: 0, + }).then((result) => { + switch (result.response) { + case 1: { + restart(); + break; + } + + case 2: { + app.quit(); + break; + } + } + }); +} + +function removeContentSecurityPolicy( + betterSession: BetterSession = session.defaultSession as BetterSession, +) { + // Allows defining multiple "onHeadersReceived" listeners + // by enhancing the session. + // Some plugins (e.g. adblocker) also define a "onHeadersReceived" listener + enhanceWebRequest(betterSession); + + // Custom listener to tweak the content security policy + betterSession.webRequest.onHeadersReceived((details, callback) => { + details.responseHeaders ??= {}; + + // Remove the content security policy + delete details.responseHeaders['content-security-policy-report-only']; + delete details.responseHeaders['content-security-policy']; + + callback({ cancel: false, responseHeaders: details.responseHeaders }); + }); + + // When multiple listeners are defined, apply them all + betterSession.webRequest.setResolver('onHeadersReceived', async (listeners) => { + return listeners.reduce( + async (accumulator, listener) => { + const acc = await accumulator; + if (acc.cancel) { + return acc; + } + + const result = await listener.apply(); + return { ...accumulator, ...result }; + }, + Promise.resolve({ cancel: false }), + ); + }); +} diff --git a/src/plugins/album-color-theme/front.ts b/src/plugins/album-color-theme/front.ts index a068bf4705..a07d08fd24 100644 --- a/src/plugins/album-color-theme/front.ts +++ b/src/plugins/album-color-theme/front.ts @@ -1,6 +1,6 @@ import { FastAverageColor } from 'fast-average-color'; -import { ConfigType } from '../../config/dynamic'; +import type { ConfigType } from '../../config/dynamic'; function hexToHSL(H: string) { // Convert hex to RGB first diff --git a/src/plugins/ambient-mode/front.ts b/src/plugins/ambient-mode/front.ts index 1fae36406e..227686fe71 100644 --- a/src/plugins/ambient-mode/front.ts +++ b/src/plugins/ambient-mode/front.ts @@ -1,6 +1,4 @@ -import { ipcRenderer } from 'electron'; - -import { ConfigType } from '../../config/dynamic'; +import type { ConfigType } from '../../config/dynamic'; export default (config: ConfigType<'ambient-mode'>) => { let interpolationTime = config.interpolationTime; // interpolation time (ms) @@ -30,7 +28,7 @@ export default (config: ConfigType<'ambient-mode'>) => { /* effect */ let lastEffectWorkId: number | null = null; let lastImageData: ImageData | null = null; - + const onSync = () => { if (typeof lastEffectWorkId === 'number') cancelAnimationFrame(lastEffectWorkId); @@ -40,6 +38,7 @@ export default (config: ConfigType<'ambient-mode'>) => { const width = qualityRatio; let height = Math.max(Math.floor(blurCanvas.height / blurCanvas.width * width), 1); if (!Number.isFinite(height)) height = width; + if (!height) return; context.globalAlpha = 1; if (lastImageData) { @@ -50,8 +49,7 @@ export default (config: ConfigType<'ambient-mode'>) => { } context.drawImage(video, 0, 0, width, height); - const nowImageData = context.getImageData(0, 0, width, height); - lastImageData = nowImageData; + lastImageData = context.getImageData(0, 0, width, height); // current image data lastEffectWorkId = null; }); @@ -102,8 +100,8 @@ export default (config: ConfigType<'ambient-mode'>) => { applyVideoAttributes(); }; - ipcRenderer.on('ambient-mode:config-change', onConfigSync); - + window.ipcRenderer.on('ambient-mode:config-change', onConfigSync); + /* hooking */ let canvasInterval: NodeJS.Timeout | null = null; canvasInterval = setInterval(onSync, Math.max(1, Math.ceil(1000 / buffer))); @@ -135,17 +133,17 @@ export default (config: ConfigType<'ambient-mode'>) => { observer.disconnect(); resizeObserver.disconnect(); - ipcRenderer.off('ambient-mode:config-change', onConfigSync); + window.ipcRenderer.removeListener('ambient-mode:config-change', onConfigSync); window.removeEventListener('resize', applyVideoAttributes); wrapper.removeChild(blurCanvas); }; }; - + const playerPage = document.querySelector('#player-page'); const ytmusicAppLayout = document.querySelector('#layout'); - + const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'attributes') { @@ -164,4 +162,4 @@ export default (config: ConfigType<'ambient-mode'>) => { if (playerPage) { observer.observe(playerPage, { attributes: true }); } -}; \ No newline at end of file +}; diff --git a/src/plugins/downloader/front.ts b/src/plugins/downloader/front.ts index ebd7bf4794..f178ddd9c0 100644 --- a/src/plugins/downloader/front.ts +++ b/src/plugins/downloader/front.ts @@ -2,7 +2,7 @@ import downloadHTML from './templates/download.html'; import defaultConfig from '../../config/defaults'; import { getSongMenu } from '../../providers/dom-elements'; -import { ElementFromHtml } from '../utils'; +import { ElementFromHtml } from '../utils-renderer'; import { getSongInfo } from '../../providers/song-info-front'; let menu: Element | null = null; diff --git a/src/plugins/in-app-menu/back.ts b/src/plugins/in-app-menu/back.ts index d80c538a88..9f5f3f05c4 100644 --- a/src/plugins/in-app-menu/back.ts +++ b/src/plugins/in-app-menu/back.ts @@ -1,6 +1,6 @@ import { register } from 'electron-localshortcut'; -import { BrowserWindow, Menu, MenuItem, ipcMain } from 'electron'; +import { BrowserWindow, Menu, MenuItem, ipcMain, nativeImage } from 'electron'; import titlebarStyle from './titlebar.css'; diff --git a/src/plugins/in-app-menu/front.ts b/src/plugins/in-app-menu/front.ts index 8d5681ecd7..885ff4096f 100644 --- a/src/plugins/in-app-menu/front.ts +++ b/src/plugins/in-app-menu/front.ts @@ -6,9 +6,6 @@ import minimize from './assets/minimize.svg'; import maximize from './assets/maximize.svg'; import unmaximize from './assets/unmaximize.svg'; -import { isEnabled } from '../../config/plugins'; -import config from '../../config'; - import type { Menu } from 'electron'; function $(selector: string) { @@ -19,8 +16,8 @@ const isMacOS = navigator.userAgent.includes('Macintosh'); const isNotWindowsOrMacOS = !navigator.userAgent.includes('Windows') && !isMacOS; export default async () => { - const hideDOMWindowControls = config.get('plugins.in-app-menu.hideDOMWindowControls'); - let hideMenu = config.get('options.hideMenu'); + const hideDOMWindowControls = window.mainConfig.get('plugins.in-app-menu.hideDOMWindowControls'); + let hideMenu = window.mainConfig.get('options.hideMenu'); const titleBar = document.createElement('title-bar'); const navBar = document.querySelector('#nav-bar-background'); let maximizeButton: HTMLButtonElement; @@ -42,7 +39,7 @@ export default async () => { }; logo.onclick = logoClick; - ipcRenderer.on('toggleMenu', logoClick); + window.ipcRenderer.on('toggleMenu', logoClick); if (!isMacOS) titleBar.appendChild(logo); document.body.appendChild(titleBar); @@ -55,10 +52,10 @@ export default async () => { const minimizeButton = document.createElement('button'); minimizeButton.classList.add('window-control'); minimizeButton.appendChild(minimize); - minimizeButton.onclick = () => ipcRenderer.invoke('window-minimize'); + minimizeButton.onclick = () => window.ipcRenderer.invoke('window-minimize'); maximizeButton = document.createElement('button'); - if (await ipcRenderer.invoke('window-is-maximized')) { + if (await window.ipcRenderer.invoke('window-is-maximized')) { maximizeButton.classList.add('window-control'); maximizeButton.appendChild(unmaximize); } else { @@ -66,27 +63,27 @@ export default async () => { maximizeButton.appendChild(maximize); } maximizeButton.onclick = async () => { - if (await ipcRenderer.invoke('window-is-maximized')) { + if (await window.ipcRenderer.invoke('window-is-maximized')) { // change icon to maximize maximizeButton.removeChild(maximizeButton.firstChild!); maximizeButton.appendChild(maximize); // call unmaximize - await ipcRenderer.invoke('window-unmaximize'); + await window.ipcRenderer.invoke('window-unmaximize'); } else { // change icon to unmaximize maximizeButton.removeChild(maximizeButton.firstChild!); maximizeButton.appendChild(unmaximize); // call maximize - await ipcRenderer.invoke('window-maximize'); + await window.ipcRenderer.invoke('window-maximize'); } }; const closeButton = document.createElement('button'); closeButton.classList.add('window-control'); closeButton.appendChild(close); - closeButton.onclick = () => ipcRenderer.invoke('window-close'); + closeButton.onclick = () => window.ipcRenderer.invoke('window-close'); // Create a container div for the window control buttons const windowControlsContainer = document.createElement('div'); @@ -118,7 +115,7 @@ export default async () => { if (child !== logo) child.remove(); }); - const menu = await ipcRenderer.invoke('get-menu') as Menu | null; + const menu = await window.ipcRenderer.invoke('get-menu') as Menu | null; if (!menu) return; menu.items.forEach((menuItem) => { @@ -137,22 +134,22 @@ export default async () => { document.title = 'Youtube Music'; - ipcRenderer.on('refreshMenu', () => updateMenu()); - ipcRenderer.on('window-maximize', () => { + window.ipcRenderer.on('refreshMenu', () => updateMenu()); + window.ipcRenderer.on('window-maximize', () => { if (isNotWindowsOrMacOS && !hideDOMWindowControls && maximizeButton.firstChild) { maximizeButton.removeChild(maximizeButton.firstChild); maximizeButton.appendChild(unmaximize); } }); - ipcRenderer.on('window-unmaximize', () => { + window.ipcRenderer.on('window-unmaximize', () => { if (isNotWindowsOrMacOS && !hideDOMWindowControls && maximizeButton.firstChild) { maximizeButton.removeChild(maximizeButton.firstChild); maximizeButton.appendChild(unmaximize); } }); - if (isEnabled('picture-in-picture')) { - ipcRenderer.on('pip-toggle', () => { + if (window.mainConfig.plugins.isEnabled('picture-in-picture')) { + window.ipcRenderer.on('pip-toggle', () => { updateMenu(); }); } diff --git a/src/plugins/in-app-menu/menu/panel.ts b/src/plugins/in-app-menu/menu/panel.ts index 563424bcf3..18bd39f725 100644 --- a/src/plugins/in-app-menu/menu/panel.ts +++ b/src/plugins/in-app-menu/menu/panel.ts @@ -19,7 +19,7 @@ export const createPanel = ( const panel = document.createElement('menu-panel'); panel.style.zIndex = `${options.order}`; - const updateIconState = (iconWrapper: HTMLElement, item: MenuItem) => { + const updateIconState = async (iconWrapper: HTMLElement, item: MenuItem) => { if (item.type === 'checkbox') { if (item.checked) iconWrapper.innerHTML = Icons.checkbox; else iconWrapper.innerHTML = ''; diff --git a/src/plugins/visualizer/visualizers/butterchurn.ts b/src/plugins/visualizer/visualizers/butterchurn.ts index 5c44cc3326..6d3b070ea5 100644 --- a/src/plugins/visualizer/visualizers/butterchurn.ts +++ b/src/plugins/visualizer/visualizers/butterchurn.ts @@ -3,7 +3,7 @@ import ButterchurnPresets from 'butterchurn-presets'; import { Visualizer } from './visualizer'; -import { ConfigType } from '../../../config/dynamic'; +import type { ConfigType } from '../../../config/dynamic'; class ButterchurnVisualizer extends Visualizer { name = 'butterchurn'; diff --git a/src/preload.ts b/src/preload.ts index 8cd32504e5..9021c4ebfb 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,40 +1,14 @@ -import { ipcRenderer } from 'electron'; +import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; import is from 'electron-is'; import config from './config'; -import setupSongInfo from './providers/song-info-front'; -import { setupSongControls } from './providers/song-controls-front'; -import { startingPages } from './providers/extracted-data'; - -import albumColorThemeRenderer from './plugins/album-color-theme/front'; -import ambientModeRenderer from './plugins/ambient-mode/front'; -import audioCompressorRenderer from './plugins/audio-compressor/front'; -import bypassAgeRestrictionsRenderer from './plugins/bypass-age-restrictions/front'; -import captionsSelectorRenderer from './plugins/captions-selector/front'; -import compactSidebarRenderer from './plugins/compact-sidebar/front'; -import crossfadeRenderer from './plugins/crossfade/front'; -import disableAutoplayRenderer from './plugins/disable-autoplay/front'; -import downloaderRenderer from './plugins/downloader/front'; -import exponentialVolumeRenderer from './plugins/exponential-volume/front'; -import inAppMenuRenderer from './plugins/in-app-menu/front'; -import lyricsGeniusRenderer from './plugins/lyrics-genius/front'; -import navigationRenderer from './plugins/navigation/front'; -import noGoogleLogin from './plugins/no-google-login/front'; -import pictureInPictureRenderer from './plugins/picture-in-picture/front'; -import playbackSpeedRenderer from './plugins/playback-speed/front'; -import preciseVolumeRenderer from './plugins/precise-volume/front'; -import qualityChangerRenderer from './plugins/quality-changer/front'; -import skipSilencesRenderer from './plugins/skip-silences/front'; -import sponsorblockRenderer from './plugins/sponsorblock/front'; -import videoToggleRenderer from './plugins/video-toggle/front'; -import visualizerRenderer from './plugins/visualizer/front'; import adblockerPreload from './plugins/adblocker/preload'; import preciseVolumePreload from './plugins/precise-volume/preload'; import type { ConfigType, OneOfDefaultConfigKey } from './config/dynamic'; -type PluginMapper = { +export type PluginMapper = { [Key in OneOfDefaultConfigKey]?: ( Type extends 'renderer' ? (options: ConfigType) => (Promise | void) : Type extends 'preload' ? () => (Promise | void) : @@ -42,31 +16,6 @@ type PluginMapper = { ) }; -const rendererPlugins: PluginMapper<'renderer'> = { - 'album-color-theme': albumColorThemeRenderer, - 'ambient-mode': ambientModeRenderer, - 'audio-compressor': audioCompressorRenderer, - 'bypass-age-restrictions': bypassAgeRestrictionsRenderer, - 'captions-selector': captionsSelectorRenderer, - 'compact-sidebar': compactSidebarRenderer, - 'crossfade': crossfadeRenderer, - 'disable-autoplay': disableAutoplayRenderer, - 'downloader': downloaderRenderer, - 'exponential-volume': exponentialVolumeRenderer, - 'in-app-menu': inAppMenuRenderer, - 'lyrics-genius': lyricsGeniusRenderer, - 'navigation': navigationRenderer, - 'no-google-login': noGoogleLogin, - 'picture-in-picture': pictureInPictureRenderer, - 'playback-speed': playbackSpeedRenderer, - 'precise-volume': preciseVolumeRenderer, - 'quality-changer': qualityChangerRenderer, - 'skip-silences': skipSilencesRenderer, - 'sponsorblock': sponsorblockRenderer, - 'video-toggle': videoToggleRenderer, - 'visualizer': visualizerRenderer, -}; - const preloadPlugins: PluginMapper<'preload'> = { 'adblocker': adblockerPreload, 'precise-volume': preciseVolumePreload, @@ -74,10 +23,6 @@ const preloadPlugins: PluginMapper<'preload'> = { const enabledPluginNameAndOptions = config.plugins.getEnabled(); -const $ = document.querySelector.bind(document); - -let api: Element | null = null; - enabledPluginNameAndOptions.forEach(async ([plugin, options]) => { if (Object.hasOwn(preloadPlugins, plugin)) { const handler = preloadPlugins[plugin]; @@ -89,119 +34,17 @@ enabledPluginNameAndOptions.forEach(async ([plugin, options]) => { } }); -document.addEventListener('DOMContentLoaded', () => { - enabledPluginNameAndOptions.forEach(async ([pluginName, options]) => { - if (Object.hasOwn(rendererPlugins, pluginName)) { - const handler = rendererPlugins[pluginName]; - try { - await handler?.(options as never); - } catch (error) { - console.error(`Error in plugin "${pluginName}": ${String(error)}`); - } - } - }); - - // Wait for complete load of YouTube api - listenForApiLoad(); - - // Inject song-info provider - setupSongInfo(); - - // Inject song-controls - setupSongControls(); - - // Add action for reloading - window.reload = () => ipcRenderer.send('reload'); - - // Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min - setInterval(() => window._lact = Date.now(), 900_000); - - // Setup back to front logger - if (is.dev()) { - ipcRenderer.on('log', (_event, log: string) => { - console.log(JSON.parse(log)); - }); - } +contextBridge.exposeInMainWorld('mainConfig', config); +contextBridge.exposeInMainWorld('electronIs', is); +contextBridge.exposeInMainWorld('ipcRenderer', { + on: (channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void) => ipcRenderer.on(channel, listener), + off: (channel: string, listener: (...args: unknown[]) => void) => ipcRenderer.off(channel, listener), + once: (channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void) => ipcRenderer.once(channel, listener), + send: (channel: string, ...args: unknown[]) => ipcRenderer.send(channel, args), + removeListener: (channel: string, listener: (...args: unknown[]) => void) => ipcRenderer.removeListener(channel, listener), + removeAllListeners: (channel: string) => ipcRenderer.removeAllListeners(channel), + invoke: async (channel: string, ...args: unknown[]): Promise => ipcRenderer.invoke(channel, ...args), + sendSync: (channel: string, ...args: unknown[]): unknown => ipcRenderer.sendSync(channel, ...args), + sendToHost: (channel: string, ...args: unknown[]) => ipcRenderer.sendToHost(channel, ...args), }); - -function listenForApiLoad() { - api = $('#movie_player'); - if (api) { - onApiLoaded(); - return; - } - - const observer = new MutationObserver(() => { - api = $('#movie_player'); - if (api) { - observer.disconnect(); - onApiLoaded(); - } - }); - - observer.observe(document.documentElement, { childList: true, subtree: true }); -} - -interface YouTubeMusicAppElement extends HTMLElement { - navigate_(page: string): void; -} - -function onApiLoaded() { - const video = $('video')!; - const audioContext = new AudioContext(); - const audioSource = audioContext.createMediaElementSource(video); - audioSource.connect(audioContext.destination); - - video.addEventListener( - 'loadstart', - () => { - // Emit "audioCanPlay" for each video - video.addEventListener( - 'canplaythrough', - () => { - document.dispatchEvent( - new CustomEvent('audioCanPlay', { - detail: { - audioContext, - audioSource, - }, - }), - ); - }, - { once: true }, - ); - }, - { passive: true }, - );! - - document.dispatchEvent(new CustomEvent('apiLoaded', { detail: api })); - ipcRenderer.send('apiLoaded'); - - // Navigate to "Starting page" - const startingPage: string = config.get('options.startingPage'); - if (startingPage && startingPages[startingPage]) { - $('ytmusic-app')?.navigate_(startingPages[startingPage]); - } - - // Remove upgrade button - if (config.get('options.removeUpgradeButton')) { - const styles = document.createElement('style'); - styles.innerHTML = `ytmusic-guide-section-renderer #items ytmusic-guide-entry-renderer:nth-child(4) { - display: none; - }`; - document.head.appendChild(styles); - } - - // Hide / Force show like buttons - const likeButtonsOptions: string = config.get('options.likeButtons'); - if (likeButtonsOptions) { - const likeButtons: HTMLElement | null = $('ytmusic-like-button-renderer'); - if (likeButtons) { - likeButtons.style.display - = { - hide: 'none', - force: 'inherit', - }[likeButtonsOptions] || ''; - } - } -} +contextBridge.exposeInMainWorld('reload', () => ipcRenderer.send('reload')); From eaf8410df53a347c845dfe01e584387ecc58cb13 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 12:47:59 +0900 Subject: [PATCH 14/21] feat: enable terser for renderer --- rollup.renderer.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rollup.renderer.config.ts b/rollup.renderer.config.ts index 08ceda604a..c363a17e9f 100644 --- a/rollup.renderer.config.ts +++ b/rollup.renderer.config.ts @@ -32,9 +32,9 @@ export default defineConfig({ targetEnv: 'browser', }), image({ dom: true }), - /*terser({ + terser({ ecma: 2020, - }),*/ + }), { closeBundle() { if (!process.env.ROLLUP_WATCH) { From 60527c8db9fa208bf2d3ae5a3463bf868889cddd Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 13:29:27 +0900 Subject: [PATCH 15/21] fix: fix `ipcRenderer.send` --- src/preload.ts | 2 +- src/providers/song-info-front.ts | 3 +-- src/renderer.ts | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/preload.ts b/src/preload.ts index 9021c4ebfb..6b19f88432 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -40,7 +40,7 @@ contextBridge.exposeInMainWorld('ipcRenderer', { on: (channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void) => ipcRenderer.on(channel, listener), off: (channel: string, listener: (...args: unknown[]) => void) => ipcRenderer.off(channel, listener), once: (channel: string, listener: (event: IpcRendererEvent, ...args: unknown[]) => void) => ipcRenderer.once(channel, listener), - send: (channel: string, ...args: unknown[]) => ipcRenderer.send(channel, args), + send: (channel: string, ...args: unknown[]) => ipcRenderer.send(channel, ...args), removeListener: (channel: string, listener: (...args: unknown[]) => void) => ipcRenderer.removeListener(channel, listener), removeAllListeners: (channel: string) => ipcRenderer.removeAllListeners(channel), invoke: async (channel: string, ...args: unknown[]): Promise => ipcRenderer.invoke(channel, ...args), diff --git a/src/providers/song-info-front.ts b/src/providers/song-info-front.ts index 94dd6d6c6d..6fa53badba 100644 --- a/src/providers/song-info-front.ts +++ b/src/providers/song-info-front.ts @@ -10,9 +10,8 @@ let songInfo: SongInfo = {} as SongInfo; export const getSongInfo = () => songInfo; const $ = (s: string): E | null => document.querySelector(s); -const $$ = (s: string): NodeListOf => document.querySelectorAll(s); -window.ipcRenderer.on('update-song-info', async (_, extractedSongInfo: SongInfo) => { +window.ipcRenderer.on('update-song-info', (_, extractedSongInfo: SongInfo) => { songInfo = extractedSongInfo; }); diff --git a/src/renderer.ts b/src/renderer.ts index 33b672e7d4..030662258c 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -150,15 +150,15 @@ function onApiLoaded() { } }); - // Wait for complete load of YouTube api - listenForApiLoad(); - // Inject song-info provider setupSongInfo(); // Inject song-controls setupSongControls(); + // Wait for complete load of YouTube api + listenForApiLoad(); + // Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min setInterval(() => window._lact = Date.now(), 900_000); From cde08f5b2ac300722c1c3982314473cf6660403a Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 13:48:02 +0900 Subject: [PATCH 16/21] fix: download button --- src/plugins/downloader/front.ts | 74 ++++++++++++++++----------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/plugins/downloader/front.ts b/src/plugins/downloader/front.ts index f178ddd9c0..ef29862a1e 100644 --- a/src/plugins/downloader/front.ts +++ b/src/plugins/downloader/front.ts @@ -11,55 +11,55 @@ const downloadButton = ElementFromHtml(downloadHTML); let doneFirstLoad = false; -const menuObserver = new MutationObserver(() => { - if (!menu) { - menu = getSongMenu(); +export default () => { + const menuObserver = new MutationObserver(() => { if (!menu) { - return; + menu = getSongMenu(); + if (!menu) { + return; + } } - } - if (menu.contains(downloadButton)) { - return; - } + if (menu.contains(downloadButton)) { + return; + } - const menuUrl = document.querySelector('tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint')?.href; - if (!menuUrl?.includes('watch?') && doneFirstLoad) { - return; - } + const menuUrl = document.querySelector('tp-yt-paper-listbox [tabindex="-1"] #navigation-endpoint')?.href; + if (!menuUrl?.includes('watch?') && doneFirstLoad) { + return; + } - menu.prepend(downloadButton); - progress = document.querySelector('#ytmcustom-download'); + menu.prepend(downloadButton); + progress = document.querySelector('#ytmcustom-download'); - if (doneFirstLoad) { - return; - } + if (doneFirstLoad) { + return; + } - setTimeout(() => doneFirstLoad ||= true, 500); -}); + setTimeout(() => doneFirstLoad ||= true, 500); + }); -window.download = () => { - let videoUrl = getSongMenu() - // Selector of first button which is always "Start Radio" - ?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="-1"] #navigation-endpoint') - ?.getAttribute('href'); - if (videoUrl) { - if (videoUrl.startsWith('watch?')) { - videoUrl = defaultConfig.url + '/' + videoUrl; - } + window.download = () => { + let videoUrl = getSongMenu() + // Selector of first button which is always "Start Radio" + ?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="-1"] #navigation-endpoint') + ?.getAttribute('href'); + if (videoUrl) { + if (videoUrl.startsWith('watch?')) { + videoUrl = defaultConfig.url + '/' + videoUrl; + } - if (videoUrl.includes('?playlist=')) { - window.ipcRenderer.send('download-playlist-request', videoUrl); - return; + if (videoUrl.includes('?playlist=')) { + window.ipcRenderer.send('download-playlist-request', videoUrl); + return; + } + } else { + videoUrl = getSongInfo().url || window.location.href; } - } else { - videoUrl = getSongInfo().url || window.location.href; - } - window.ipcRenderer.send('download-song', videoUrl); -}; + window.ipcRenderer.send('download-song', videoUrl); + }; -export default () => { document.addEventListener('apiLoaded', () => { menuObserver.observe(document.querySelector('ytmusic-popup-container')!, { childList: true, From 47f38cc6903bb8e551a552b3144fe25f0ee675c3 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 16:56:41 +0900 Subject: [PATCH 17/21] fix: add workaround for `podcast` type video (#1362) --- src/providers/song-info-front.ts | 6 ++++++ src/types/get-player-response.ts | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/providers/song-info-front.ts b/src/providers/song-info-front.ts index 0c308c7087..f18a3a3a4d 100644 --- a/src/providers/song-info-front.ts +++ b/src/providers/song-info-front.ts @@ -137,6 +137,12 @@ export default () => { data.videoDetails.album = videoData?.Hd?.playerOverlays?.playerOverlayRenderer?.browserMediaSession?.browserMediaSessionRenderer?.album.runs?.at(0)?.text; data.videoDetails.elapsedSeconds = 0; data.videoDetails.isPaused = false; + + // HACK: This is a workaround for "podcast" type video. GREAT JOB GOOGLE. + if (data.playabilityStatus.transportControlsConfig) { + data.videoDetails.author = data.microformat.microformatDataRenderer.pageOwnerDetails.name; + } + ipcRenderer.send('video-src-changed', data); } }, { once: true, passive: true }); diff --git a/src/types/get-player-response.ts b/src/types/get-player-response.ts index 3a1955004d..54bd7d5a9f 100644 --- a/src/types/get-player-response.ts +++ b/src/types/get-player-response.ts @@ -217,6 +217,17 @@ export interface PlayabilityStatus { audioOnlyPlayability: AudioOnlyPlayability; miniplayer: Miniplayer; contextParams: string; + transportControlsConfig?: TransportControlsConfig; +} + +type ReplaceDefaultType = { + replaceDefault: boolean, +}; + +export interface TransportControlsConfig { + seekForwardStatus: ReplaceDefaultType; + seekBackwardStatus: ReplaceDefaultType; + playbackRateStatus: ReplaceDefaultType; } export interface AudioOnlyPlayability { From 78d8d66f2b79eae384b8811053b79df1bf2f6c23 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 17:11:29 +0900 Subject: [PATCH 18/21] chore(deps): pin `deepmerge` version --- package.json | 2 +- pnpm-lock.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7cb686e152..3f7ab1bcac 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "butterchurn-presets": "3.0.0-beta.4", "conf": "10.2.0", "custom-electron-prompt": "1.5.7", - "deepmerge": "^4.3.1", + "deepmerge": "4.3.1", "electron-debug": "3.2.0", "electron-is": "3.0.0", "electron-localshortcut": "3.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd2305e9f7..e4eddd539a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,7 +53,7 @@ dependencies: specifier: 1.5.7 version: 1.5.7(electron@27.0.3) deepmerge: - specifier: ^4.3.1 + specifier: 4.3.1 version: 4.3.1 electron-debug: specifier: 3.2.0 From 30f4064438fedd5bbbcbff9dbf86bda00a7b203c Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 19:42:05 +0900 Subject: [PATCH 19/21] feat: use `executeJavaScriptInIsolatedWorld` to make debug easier --- src/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 972ebdef7b..b0bbe6fd21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import path from 'node:path'; +import url from 'node:url'; import fs from 'node:fs'; import { BrowserWindow, app, screen, globalShortcut, session, shell, dialog, ipcMain } from 'electron'; @@ -335,7 +336,11 @@ async function createMainWindow() { removeContentSecurityPolicy(); win.webContents.on('dom-ready', () => { - win.webContents.executeJavaScript(fs.readFileSync(path.join(__dirname, 'renderer.js'), 'utf-8') + ';0', true); + const rendererScriptPath = path.join(__dirname, 'renderer.js'); + win.webContents.executeJavaScriptInIsolatedWorld(0, [{ + code: fs.readFileSync(rendererScriptPath, 'utf-8') + ';0', + url: url.pathToFileURL(rendererScriptPath).toString(), + }], true); }); win.webContents.loadURL(urlToLoad); From 500444d715d3b04d3a7fd497b0a3c63f3fc37eb9 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 20:29:21 +0900 Subject: [PATCH 20/21] feat: disable `nodeIntegrationInSubFrames` --- src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index b0bbe6fd21..ab88b559d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -199,7 +199,6 @@ async function createMainWindow() { webPreferences: { contextIsolation: true, preload: path.join(__dirname, 'preload.js'), - nodeIntegrationInSubFrames: true, ...(isTesting() ? undefined : { From 8cf7e955a4226fd8f111bbf02bfb2b141d7c32c3 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Sat, 4 Nov 2023 22:35:49 +0900 Subject: [PATCH 21/21] feat: little optimization --- package.json | 2 +- pnpm-lock.yaml | 10 +++++++--- src/config/plugins.ts | 9 +++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 3f7ab1bcac..59a90f8f11 100644 --- a/package.json +++ b/package.json @@ -133,6 +133,7 @@ "dependencies": { "@cliqz/adblocker-electron": "1.26.11", "@cliqz/adblocker-electron-preload": "1.26.11", + "@fastify/deepmerge": "1.3.0", "@ffmpeg.wasm/core-mt": "0.12.0", "@ffmpeg.wasm/main": "0.12.0", "@foobar404/wave": "2.0.4", @@ -144,7 +145,6 @@ "butterchurn-presets": "3.0.0-beta.4", "conf": "10.2.0", "custom-electron-prompt": "1.5.7", - "deepmerge": "4.3.1", "electron-debug": "3.2.0", "electron-is": "3.0.0", "electron-localshortcut": "3.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4eddd539a..7a58067689 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ dependencies: '@cliqz/adblocker-electron-preload': specifier: 1.26.11 version: 1.26.11(electron@27.0.3) + '@fastify/deepmerge': + specifier: 1.3.0 + version: 1.3.0 '@ffmpeg.wasm/core-mt': specifier: 0.12.0 version: 0.12.0 @@ -52,9 +55,6 @@ dependencies: custom-electron-prompt: specifier: 1.5.7 version: 1.5.7(electron@27.0.3) - deepmerge: - specifier: 4.3.1 - version: 4.3.1 electron-debug: specifier: 3.2.0 version: 3.2.0 @@ -403,6 +403,10 @@ packages: engines: {node: '>=14'} dev: false + /@fastify/deepmerge@1.3.0: + resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + dev: false + /@ffmpeg.wasm/core-mt@0.12.0: resolution: {integrity: sha512-M9pjL7JQX4AYl3WI8vGcPGPTz/O7JmhW8ac/fHA3oXTxoRAPwYSY/OsY1N9C0XahIM0+fxa1QSLN9Ekz8sBM/Q==} dev: false diff --git a/src/config/plugins.ts b/src/config/plugins.ts index 64f14afc69..3092b1d211 100644 --- a/src/config/plugins.ts +++ b/src/config/plugins.ts @@ -1,4 +1,4 @@ -import deepmerge from 'deepmerge'; +import { deepmerge } from '@fastify/deepmerge'; import store from './store'; import defaultConfig from './defaults'; @@ -11,11 +11,12 @@ interface Plugin { } type DefaultPluginsConfig = typeof defaultConfig.plugins; +const deepmergeFn = deepmerge(); export function getEnabled() { - const plugins = deepmerge(defaultConfig.plugins, (store.get('plugins') as DefaultPluginsConfig)) as DefaultPluginsConfig; - return (Object.entries(plugins) as Entries).filter(([plugin]) => - isEnabled(plugin), + const plugins = deepmergeFn(defaultConfig.plugins, (store.get('plugins') as DefaultPluginsConfig)); + return (Object.entries(plugins) as Entries).filter(([, options]) => + (options as Plugin).enabled, ); }