From 6e315b9af2d10faac1f23b98a2b7eccf7af86ad6 Mon Sep 17 00:00:00 2001 From: JellyBrick Date: Tue, 3 Oct 2023 23:42:12 +0900 Subject: [PATCH] refactor: remove dynamic require (partial of #2) Co-authored-by: Su-Yong --- config/defaults.ts | 178 ++--- config/dynamic.ts | 6 +- index.ts | 86 ++- menu.ts | 56 +- plugins/adblocker/inject-cliqz-preload.ts | 3 + plugins/adblocker/inject.d.ts | 3 + plugins/adblocker/inject.js | 771 +++++++++++----------- plugins/adblocker/menu.ts | 4 +- plugins/adblocker/preload.ts | 6 +- plugins/album-color-theme/front.ts | 4 +- plugins/crossfade/menu.ts | 4 +- plugins/discord/menu.ts | 5 +- plugins/lyrics-genius/back.ts | 2 +- plugins/lyrics-genius/menu.ts | 3 +- plugins/navigation/actions.ts | 27 - plugins/navigation/back.ts | 28 +- plugins/navigation/templates/back.html | 2 +- plugins/navigation/templates/forward.html | 2 +- plugins/notifications/menu.ts | 31 +- plugins/sponsorblock/back.ts | 9 +- plugins/taskbar-mediacontrol/back.ts | 83 ++- plugins/touchbar/back.ts | 117 ++-- plugins/utils.ts | 21 +- preload.ts | 135 ++-- 24 files changed, 841 insertions(+), 745 deletions(-) create mode 100644 plugins/adblocker/inject-cliqz-preload.ts create mode 100644 plugins/adblocker/inject.d.ts delete mode 100644 plugins/navigation/actions.ts diff --git a/config/defaults.ts b/config/defaults.ts index fa3a929d..ee2e0c3c 100644 --- a/config/defaults.ts +++ b/config/defaults.ts @@ -67,11 +67,8 @@ const defaultConfig = { overrideUserAgent: false, themes: [] as string[], }, + /** please order alphabetically */ 'plugins': { - // Enabled plugins - 'navigation': { - enabled: true, - }, 'adblocker': { enabled: true, cache: true, @@ -79,7 +76,95 @@ const defaultConfig = { additionalBlockLists: [], // Additional list of filters, e.g "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt" disableDefaultLists: [], }, - // Disabled plugins + 'album-color-theme': {}, + 'audio-compressor': {}, + 'blur-nav-bar': {}, + 'bypass-age-restrictions': {}, + 'captions-selector': { + enabled: false, + disableCaptions: false, + autoload: false, + lastCaptionsCode: '', + disabledCaptions: false, + }, + 'compact-sidebar': {}, + 'crossfade': { + enabled: false, + fadeInDuration: 1500, // Ms + fadeOutDuration: 5000, // Ms + secondsBeforeEnd: 10, // S + fadeScaling: 'linear', // 'linear', 'logarithmic' or a positive number in dB + }, + 'disable-autoplay': { + applyOnce: false, + }, + 'discord': { + enabled: false, + autoReconnect: true, // If enabled, will try to reconnect to discord every 5 seconds after disconnecting or failing to connect + activityTimoutEnabled: true, // If enabled, the discord rich presence gets cleared when music paused after the time specified below + activityTimoutTime: 10 * 60 * 1000, // 10 minutes + listenAlong: true, // Add a "listen along" button to rich presence + hideDurationLeft: false, // Hides the start and end time of the song to rich presence + }, + 'downloader': { + enabled: false, + ffmpegArgs: ['-b:a', '256k'], // E.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s + downloadFolder: undefined as string | undefined, // Custom download folder (absolute path) + preset: 'mp3', + skipExisting: false, + playlistMaxItems: undefined as number | undefined, + }, + 'exponential-volume': {}, + 'in-app-menu': {}, + 'last-fm': { + enabled: false, + token: undefined as string | undefined, // Token used for authentication + session_key: undefined as string | undefined, // Session key used for scrobbling + api_root: 'http://ws.audioscrobbler.com/2.0/', + api_key: '04d76faaac8726e60988e14c105d421a', // Api key registered by @semvis123 + secret: 'a5d2a36fdf64819290f6982481eaffa2', + }, + 'lyrics-genius': { + romanizedLyrics: false, + }, + 'navigation': { + enabled: true, + }, + 'no-google-login': {}, + 'notifications': { + enabled: false, + unpauseNotification: false, + urgency: 'normal', // Has effect only on Linux + // the following has effect only on Windows + interactive: true, + toastStyle: 1, // See plugins/notifications/utils for more info + refreshOnPlayPause: false, + trayControls: true, + hideButtonText: false, + }, + 'picture-in-picture': { + 'enabled': false, + 'alwaysOnTop': true, + 'savePosition': true, + 'saveSize': false, + 'hotkey': 'P', + 'pip-position': [10, 10], + 'pip-size': [450, 275], + 'isInPiP': false, + 'useNativePiP': false, + }, + 'playback-speed': {}, + 'precise-volume': { + enabled: false, + steps: 1, // Percentage of volume to change + arrowsShortcut: true, // Enable ArrowUp + ArrowDown local shortcuts + globalShortcuts: { + volumeUp: '', + volumeDown: '', + }, + savedVolume: undefined as number | undefined, // Plugin save volume between session here + }, + 'quality-changer': {}, 'shortcuts': { enabled: false, overrideMediaKeys: false, @@ -94,56 +179,8 @@ const defaultConfig = { next: '', } as Record, }, - 'downloader': { - enabled: false, - ffmpegArgs: ['-b:a', '256k'], // E.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s - downloadFolder: undefined as string | undefined, // Custom download folder (absolute path) - preset: 'mp3', - skipExisting: false, - playlistMaxItems: undefined as number | undefined, - }, - 'last-fm': { - enabled: false, - token: undefined as string | undefined, // Token used for authentication - session_key: undefined as string | undefined, // Session key used for scrobbling - api_root: 'http://ws.audioscrobbler.com/2.0/', - api_key: '04d76faaac8726e60988e14c105d421a', // Api key registered by @semvis123 - secret: 'a5d2a36fdf64819290f6982481eaffa2', - }, - 'lyric-genius': { - romanizedLyrics: false, - }, - 'disable-autoplay': { - applyOnce: false, - }, - 'discord': { - enabled: false, - autoReconnect: true, // If enabled, will try to reconnect to discord every 5 seconds after disconnecting or failing to connect - activityTimoutEnabled: true, // If enabled, the discord rich presence gets cleared when music paused after the time specified below - activityTimoutTime: 10 * 60 * 1000, // 10 minutes - listenAlong: true, // Add a "listen along" button to rich presence - hideDurationLeft: false, // Hides the start and end time of the song to rich presence - }, - 'notifications': { - enabled: false, - unpauseNotification: false, - urgency: 'normal', // Has effect only on Linux - // the following has effect only on Windows - interactive: true, - toastStyle: 1, // See plugins/notifications/utils for more info - refreshOnPlayPause: false, - trayControls: true, - hideButtonText: false, - }, - 'precise-volume': { - enabled: false, - steps: 1, // Percentage of volume to change - arrowsShortcut: true, // Enable ArrowUp + ArrowDown local shortcuts - globalShortcuts: { - volumeUp: '', - volumeDown: '', - }, - savedVolume: undefined as number | undefined, // Plugin save volume between session here + 'skip-silences': { + onlySkipBeginning: false, }, 'sponsorblock': { enabled: false, @@ -157,6 +194,9 @@ const defaultConfig = { 'music_offtopic', ], }, + 'taskbar-mediacontrol': {}, + 'touchbar': {}, + 'tuna-obs': {}, 'video-toggle': { enabled: false, hideVideo: false, @@ -164,34 +204,6 @@ const defaultConfig = { forceHide: false, align: '', }, - 'picture-in-picture': { - 'enabled': false, - 'alwaysOnTop': true, - 'savePosition': true, - 'saveSize': false, - 'hotkey': 'P', - 'pip-position': [10, 10], - 'pip-size': [450, 275], - 'isInPiP': false, - 'useNativePiP': false, - }, - 'captions-selector': { - enabled: false, - disableCaptions: false, - autoload: false, - lastCaptionsCode: '', - disabledCaptions: false, - }, - 'skip-silences': { - onlySkipBeginning: false, - }, - 'crossfade': { - enabled: false, - fadeInDuration: 1500, // Ms - fadeOutDuration: 5000, // Ms - secondsBeforeEnd: 10, // S - fadeScaling: 'linear', // 'linear', 'logarithmic' or a positive number in dB - }, 'visualizer': { enabled: false, type: 'butterchurn', diff --git a/config/dynamic.ts b/config/dynamic.ts index c05e49c4..e9381bca 100644 --- a/config/dynamic.ts +++ b/config/dynamic.ts @@ -10,9 +10,9 @@ import { getOptions, setMenuOptions, setOptions } from './plugins'; import { sendToFront } from '../providers/app-controls'; import { Entries } from '../utils/type-utils'; -type DefaultPluginsConfig = typeof defaultConfig.plugins; -type OneOfDefaultConfigKey = keyof DefaultPluginsConfig; -type OneOfDefaultConfig = typeof defaultConfig.plugins[OneOfDefaultConfigKey]; +export type DefaultPluginsConfig = typeof defaultConfig.plugins; +export type OneOfDefaultConfigKey = keyof DefaultPluginsConfig; +export type OneOfDefaultConfig = typeof defaultConfig.plugins[OneOfDefaultConfigKey]; // eslint-disable-next-line @typescript-eslint/no-explicit-any const activePlugins: { [key in OneOfDefaultConfigKey]?: PluginConfig } = {}; diff --git a/index.ts b/index.ts index 5d744118..cfca5fff 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,6 @@ import path from 'node:path'; -import { BrowserWindow, app, screen, globalShortcut, session, shell, dialog } from 'electron'; +import { BrowserWindow, app, screen, globalShortcut, session, shell, dialog, ipcMain } from 'electron'; import enhanceWebRequest from 'electron-better-web-request'; import is from 'electron-is'; import unhandled from 'electron-unhandled'; @@ -18,6 +18,29 @@ 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 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 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'; // Catch errors and log them unhandled({ @@ -75,6 +98,46 @@ function onClosed() { mainWindow = null; } +const mainPlugins = { + 'adblocker': adblocker, + 'album-color-theme': albumColorTheme, + 'blur-nav-bar': blurNavigationBar, + 'captions-selector': captionsSelector, + 'crossfade': crossfade, + 'discord': discord, + 'downloader': downloader, + 'in-app-menu': inAppMenu, + 'last-fm': lastFm, + '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)); + function loadPlugins(win: BrowserWindow) { injectCSS(win.webContents, path.join(__dirname, 'youtube-music.css')); // Load user CSS @@ -101,13 +164,17 @@ function loadPlugins(win: BrowserWindow) { }); for (const [plugin, options] of config.plugins.getEnabled()) { - console.log('Loaded plugin - ' + plugin); - const pluginPath = path.join(__dirname, 'plugins', plugin, 'back.js'); - fileExists(pluginPath, () => { - // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-member-access - const handle = require(pluginPath).default as (window: BrowserWindow, option: typeof options) => void; - handle(win, options); - }); + try { + if (Object.hasOwn(mainPlugins, plugin)) { + console.log('Loaded plugin - ' + plugin); + const handler = mainPlugins[plugin as keyof typeof mainPlugins]; + if (handler) { + handler(win, options as never); + } + } + } catch (e) { + console.error(`Failed to load plugin "${plugin}"`, e); + } } } @@ -191,8 +258,7 @@ function createMainWindow() { 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) => (require('./plugins/picture-in-picture/back') as typeof import('./plugins/picture-in-picture/back')) - .setOptions({ [key]: value }) + ? (key: string, value: unknown) => pipSetOptions({ [key]: value }) : () => {}; win.on('move', () => { diff --git a/menu.ts b/menu.ts index 66ac1db0..661095a2 100644 --- a/menu.ts +++ b/menu.ts @@ -1,21 +1,44 @@ -import { existsSync } from 'node:fs'; -import path from 'node:path'; - import is from 'electron-is'; import { app, BrowserWindow, clipboard, dialog, Menu } from 'electron'; import prompt from 'custom-electron-prompt'; import { restart } from './providers/app-controls'; -import { getAllPlugins } from './plugins/utils'; import config from './config'; import { startingPages } from './providers/extracted-data'; import promptOptions from './providers/prompt-options'; -export type MenuTemplate = (Electron.MenuItemConstructorOptions | Electron.MenuItem)[]; +import adblockerMenu from './plugins/adblocker/menu'; +import captionsSelectorMenu from './plugins/captions-selector/menu'; +import crossfadeMenu from './plugins/crossfade/menu'; +import discordMenu from './plugins/discord/menu'; +import downloaderMenu from './plugins/downloader/menu'; +import lyricsGeniusMenu from './plugins/lyrics-genius/menu'; +import notificationsMenu from './plugins/notifications/menu'; +import preciseVolumeMenu from './plugins/precise-volume/menu'; +import shortcutsMenu from './plugins/shortcuts/menu'; +import videoToggleMenu from './plugins/video-toggle/menu'; +import visualizerMenu from './plugins/visualizer/menu'; +import { getAvailablePluginNames } from './plugins/utils'; + +export type MenuTemplate = Electron.MenuItemConstructorOptions[]; // True only if in-app-menu was loaded on launch const inAppMenuActive = config.plugins.isEnabled('in-app-menu'); +const pluginMenus = { + 'adblocker': adblockerMenu, + 'captions-selector': captionsSelectorMenu, + 'crossfade': crossfadeMenu, + 'discord': discordMenu, + 'downloader': downloaderMenu, + 'lyrics-genius': lyricsGeniusMenu, + 'notifications': notificationsMenu, + 'precise-volume': preciseVolumeMenu, + 'shortcuts': shortcutsMenu, + 'video-toggle': videoToggleMenu, + 'visualizer': visualizerMenu, +}; + const pluginEnabledMenu = (plugin: string, label = '', hasSubmenu = false, refreshMenu: (() => void ) | undefined = undefined): Electron.MenuItemConstructorOptions => ({ label: label || plugin, type: 'checkbox', @@ -45,33 +68,30 @@ export const mainMenuTemplate = (win: BrowserWindow): MenuTemplate => { { label: 'Plugins', submenu: - getAllPlugins().map((plugin) => { - const pluginPath = path.join(__dirname, 'plugins', plugin, 'menu.js'); - if (existsSync(pluginPath)) { - let pluginLabel = plugin; + getAvailablePluginNames().map((pluginName) => { + if (Object.hasOwn(pluginMenus, pluginName)) { + const getPluginMenu = pluginMenus[pluginName as keyof typeof pluginMenus]; + + let pluginLabel = pluginName; if (pluginLabel === 'crossfade') { pluginLabel = 'crossfade [beta]'; } - if (!config.plugins.isEnabled(plugin)) { - return pluginEnabledMenu(plugin, pluginLabel, true, refreshMenu); + if (!config.plugins.isEnabled(pluginName)) { + return pluginEnabledMenu(pluginName, pluginLabel, true, refreshMenu); } - type PluginType = (window: BrowserWindow, plugins: string, func: () => void) => Electron.MenuItemConstructorOptions[]; - - // eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-member-access - const getPluginMenu = require(pluginPath).default as PluginType; return { label: pluginLabel, submenu: [ - pluginEnabledMenu(plugin, 'Enabled', true, refreshMenu), + pluginEnabledMenu(pluginName, 'Enabled', true, refreshMenu), { type: 'separator' }, - ...getPluginMenu(win, config.plugins.getOptions(plugin), refreshMenu), + ...getPluginMenu(win, config.plugins.getOptions(pluginName), refreshMenu), ], } satisfies Electron.MenuItemConstructorOptions; } - return pluginEnabledMenu(plugin); + return pluginEnabledMenu(pluginName); }), }, { diff --git a/plugins/adblocker/inject-cliqz-preload.ts b/plugins/adblocker/inject-cliqz-preload.ts new file mode 100644 index 00000000..0d0d7e71 --- /dev/null +++ b/plugins/adblocker/inject-cliqz-preload.ts @@ -0,0 +1,3 @@ +export default () => { + require('@cliqz/adblocker-electron-preload'); +}; diff --git a/plugins/adblocker/inject.d.ts b/plugins/adblocker/inject.d.ts new file mode 100644 index 00000000..cda88ca4 --- /dev/null +++ b/plugins/adblocker/inject.d.ts @@ -0,0 +1,3 @@ +const inject: () => void; + +export default inject; diff --git a/plugins/adblocker/inject.js b/plugins/adblocker/inject.js index 513bc960..ccc9ea83 100644 --- a/plugins/adblocker/inject.js +++ b/plugins/adblocker/inject.js @@ -7,428 +7,429 @@ Parts of this code is derived from set-constant.js: https://github.com/gorhill/uBlock/blob/5de0ce975753b7565759ac40983d31978d1f84ca/assets/resources/scriptlets.js#L704 */ - -{ - const pruner = function (o) { - delete o.playerAds; - delete o.adPlacements; - // - if (o.playerResponse) { - delete o.playerResponse.playerAds; - delete o.playerResponse.adPlacements; - } - - // - return o; - }; - - JSON.parse = new Proxy(JSON.parse, { - apply() { - return pruner(Reflect.apply(...arguments)); - }, - }); - - Response.prototype.json = new Proxy(Response.prototype.json, { - apply() { - return Reflect.apply(...arguments).then((o) => pruner(o)); - }, - }); -} - -(function () { - let cValue = 'undefined'; - const chain = 'playerResponse.adPlacements'; - const thisScript = document.currentScript; - // - switch (cValue) { - case 'null': { - cValue = null; - break; - } - - case "''": { - cValue = ''; - break; - } - - case 'true': { - cValue = true; - break; - } - - case 'false': { - cValue = false; - break; - } - - case 'undefined': { - cValue = undefined; - break; - } - - case 'noopFunc': { - cValue = function () { - }; - - break; - } - - case 'trueFunc': { - cValue = function () { - return true; - }; - - break; - } - - case 'falseFunc': { - cValue = function () { - return false; - }; - - break; - } - - default: { - if (/^\d+$/.test(cValue)) { - cValue = Number.parseFloat(cValue); - // - if (isNaN(cValue)) { - return; - } - - if (Math.abs(cValue) > 0x7F_FF) { - return; - } - } else { - return; - } - } - } - - // - let aborted = false; - const mustAbort = function (v) { - if (aborted) { - return true; - } - - aborted - = v !== undefined - && v !== null - && cValue !== undefined - && cValue !== null - && typeof v !== typeof cValue; - return aborted; - }; - - /* - Support multiple trappers for the same property: - https://github.com/uBlockOrigin/uBlock-issues/issues/156 - */ - - const trapProp = function (owner, prop, configurable, handler) { - if (handler.init(owner[prop]) === false) { - return; - } - - // - const odesc = Object.getOwnPropertyDescriptor(owner, prop); - let previousGetter; - let previousSetter; - if (odesc instanceof Object) { - if (odesc.configurable === false) { - return; +module.exports = () => { + { + const pruner = function (o) { + delete o.playerAds; + delete o.adPlacements; + // + if (o.playerResponse) { + delete o.playerResponse.playerAds; + delete o.playerResponse.adPlacements; } - if (odesc.get instanceof Function) { - previousGetter = odesc.get; - } + // + return o; + }; - if (odesc.set instanceof Function) { - previousSetter = odesc.set; - } - } - - // - Object.defineProperty(owner, prop, { - configurable, - get() { - if (previousGetter !== undefined) { - previousGetter(); - } - - // - return handler.getter(); - }, - set(a) { - if (previousSetter !== undefined) { - previousSetter(a); - } - - // - handler.setter(a); + JSON.parse = new Proxy(JSON.parse, { + apply() { + return pruner(Reflect.apply(...arguments)); }, }); - }; - const trapChain = function (owner, chain) { - const pos = chain.indexOf('.'); - if (pos === -1) { - trapProp(owner, chain, false, { - v: undefined, - getter() { - return document.currentScript === thisScript ? this.v : cValue; - }, - setter(a) { - if (mustAbort(a) === false) { + Response.prototype.json = new Proxy(Response.prototype.json, { + apply() { + return Reflect.apply(...arguments).then((o) => pruner(o)); + }, + }); + } + + (function () { + let cValue = 'undefined'; + const chain = 'playerResponse.adPlacements'; + const thisScript = document.currentScript; + // + switch (cValue) { + case 'null': { + cValue = null; + break; + } + + case "''": { + cValue = ''; + break; + } + + case 'true': { + cValue = true; + break; + } + + case 'false': { + cValue = false; + break; + } + + case 'undefined': { + cValue = undefined; + break; + } + + case 'noopFunc': { + cValue = function () { + }; + + break; + } + + case 'trueFunc': { + cValue = function () { + return true; + }; + + break; + } + + case 'falseFunc': { + cValue = function () { + return false; + }; + + break; + } + + default: { + if (/^\d+$/.test(cValue)) { + cValue = Number.parseFloat(cValue); + // + if (isNaN(cValue)) { return; } - cValue = a; - }, - init(v) { - if (mustAbort(v)) { - return false; + if (Math.abs(cValue) > 0x7F_FF) { + return; + } + } else { + return; + } + } + } + + // + let aborted = false; + const mustAbort = function (v) { + if (aborted) { + return true; + } + + aborted + = v !== undefined + && v !== null + && cValue !== undefined + && cValue !== null + && typeof v !== typeof cValue; + return aborted; + }; + + /* + Support multiple trappers for the same property: + https://github.com/uBlockOrigin/uBlock-issues/issues/156 + */ + + const trapProp = function (owner, prop, configurable, handler) { + if (handler.init(owner[prop]) === false) { + return; + } + + // + const odesc = Object.getOwnPropertyDescriptor(owner, prop); + let previousGetter; + let previousSetter; + if (odesc instanceof Object) { + if (odesc.configurable === false) { + return; + } + + if (odesc.get instanceof Function) { + previousGetter = odesc.get; + } + + if (odesc.set instanceof Function) { + previousSetter = odesc.set; + } + } + + // + Object.defineProperty(owner, prop, { + configurable, + get() { + if (previousGetter !== undefined) { + previousGetter(); } // + return handler.getter(); + }, + set(a) { + if (previousSetter !== undefined) { + previousSetter(a); + } + + // + handler.setter(a); + }, + }); + }; + + const trapChain = function (owner, chain) { + const pos = chain.indexOf('.'); + if (pos === -1) { + trapProp(owner, chain, false, { + v: undefined, + getter() { + return document.currentScript === thisScript ? this.v : cValue; + }, + setter(a) { + if (mustAbort(a) === false) { + return; + } + + cValue = a; + }, + init(v) { + if (mustAbort(v)) { + return false; + } + + // + this.v = v; + return true; + }, + }); + // + return; + } + + // + const prop = chain.slice(0, pos); + const v = owner[prop]; + // + chain = chain.slice(pos + 1); + if (v instanceof Object || (typeof v === 'object' && v !== null)) { + trapChain(v, chain); + return; + } + + // + trapProp(owner, prop, true, { + v: undefined, + getter() { + return this.v; + }, + setter(a) { + this.v = a; + if (a instanceof Object) { + trapChain(a, chain); + } + }, + init(v) { this.v = v; return true; }, }); - // - return; - } + }; // - const prop = chain.slice(0, pos); - const v = owner[prop]; + trapChain(window, chain); + })(); + + (function () { + let cValue = 'undefined'; + const thisScript = document.currentScript; + const chain = 'ytInitialPlayerResponse.adPlacements'; // - chain = chain.slice(pos + 1); - if (v instanceof Object || (typeof v === 'object' && v !== null)) { - trapChain(v, chain); - return; - } - - // - trapProp(owner, prop, true, { - v: undefined, - getter() { - return this.v; - }, - setter(a) { - this.v = a; - if (a instanceof Object) { - trapChain(a, chain); - } - }, - init(v) { - this.v = v; - return true; - }, - }); - }; - - // - trapChain(window, chain); -})(); - -(function () { - let cValue = 'undefined'; - const thisScript = document.currentScript; - const chain = 'ytInitialPlayerResponse.adPlacements'; - // - switch (cValue) { - case 'null': { - cValue = null; - break; - } - - case "''": { - cValue = ''; - break; - } - - case 'true': { - cValue = true; - break; - } - - case 'false': { - cValue = false; - break; - } - - case 'undefined': { - cValue = undefined; - break; - } - - case 'noopFunc': { - cValue = function () { - }; - - break; - } - - case 'trueFunc': { - cValue = function () { - return true; - }; - - break; - } - - case 'falseFunc': { - cValue = function () { - return false; - }; - - break; - } - - default: { - if (/^\d+$/.test(cValue)) { - cValue = Number.parseFloat(cValue); - // - if (isNaN(cValue)) { - return; - } - - if (Math.abs(cValue) > 0x7F_FF) { - return; - } - } else { - return; - } - } - } - - // - let aborted = false; - const mustAbort = function (v) { - if (aborted) { - return true; - } - - aborted - = v !== undefined - && v !== null - && cValue !== undefined - && cValue !== null - && typeof v !== typeof cValue; - return aborted; - }; - - /* - Support multiple trappers for the same property: - https://github.com/uBlockOrigin/uBlock-issues/issues/156 - */ - - const trapProp = function (owner, prop, configurable, handler) { - if (handler.init(owner[prop]) === false) { - return; - } - - // - const odesc = Object.getOwnPropertyDescriptor(owner, prop); - let previousGetter; - let previousSetter; - if (odesc instanceof Object) { - if (odesc.configurable === false) { - return; + switch (cValue) { + case 'null': { + cValue = null; + break; } - if (odesc.get instanceof Function) { - previousGetter = odesc.get; + case "''": { + cValue = ''; + break; } - if (odesc.set instanceof Function) { - previousSetter = odesc.set; + case 'true': { + cValue = true; + break; } - } - // - Object.defineProperty(owner, prop, { - configurable, - get() { - if (previousGetter !== undefined) { - previousGetter(); - } + case 'false': { + cValue = false; + break; + } - // - return handler.getter(); - }, - set(a) { - if (previousSetter !== undefined) { - previousSetter(a); - } + case 'undefined': { + cValue = undefined; + break; + } - // - handler.setter(a); - }, - }); - }; + case 'noopFunc': { + cValue = function () { + }; - const trapChain = function (owner, chain) { - const pos = chain.indexOf('.'); - if (pos === -1) { - trapProp(owner, chain, false, { - v: undefined, - getter() { - return document.currentScript === thisScript ? this.v : cValue; - }, - setter(a) { - if (mustAbort(a) === false) { + break; + } + + case 'trueFunc': { + cValue = function () { + return true; + }; + + break; + } + + case 'falseFunc': { + cValue = function () { + return false; + }; + + break; + } + + default: { + if (/^\d+$/.test(cValue)) { + cValue = Number.parseFloat(cValue); + // + if (isNaN(cValue)) { return; } - cValue = a; - }, - init(v) { - if (mustAbort(v)) { - return false; + if (Math.abs(cValue) > 0x7F_FF) { + return; + } + } else { + return; + } + } + } + + // + let aborted = false; + const mustAbort = function (v) { + if (aborted) { + return true; + } + + aborted + = v !== undefined + && v !== null + && cValue !== undefined + && cValue !== null + && typeof v !== typeof cValue; + return aborted; + }; + + /* + Support multiple trappers for the same property: + https://github.com/uBlockOrigin/uBlock-issues/issues/156 + */ + + const trapProp = function (owner, prop, configurable, handler) { + if (handler.init(owner[prop]) === false) { + return; + } + + // + const odesc = Object.getOwnPropertyDescriptor(owner, prop); + let previousGetter; + let previousSetter; + if (odesc instanceof Object) { + if (odesc.configurable === false) { + return; + } + + if (odesc.get instanceof Function) { + previousGetter = odesc.get; + } + + if (odesc.set instanceof Function) { + previousSetter = odesc.set; + } + } + + // + Object.defineProperty(owner, prop, { + configurable, + get() { + if (previousGetter !== undefined) { + previousGetter(); } // + return handler.getter(); + }, + set(a) { + if (previousSetter !== undefined) { + previousSetter(a); + } + + // + handler.setter(a); + }, + }); + }; + + const trapChain = function (owner, chain) { + const pos = chain.indexOf('.'); + if (pos === -1) { + trapProp(owner, chain, false, { + v: undefined, + getter() { + return document.currentScript === thisScript ? this.v : cValue; + }, + setter(a) { + if (mustAbort(a) === false) { + return; + } + + cValue = a; + }, + init(v) { + if (mustAbort(v)) { + return false; + } + + // + this.v = v; + return true; + }, + }); + // + return; + } + + // + const prop = chain.slice(0, pos); + const v = owner[prop]; + // + chain = chain.slice(pos + 1); + if (v instanceof Object || (typeof v === 'object' && v !== null)) { + trapChain(v, chain); + return; + } + + // + trapProp(owner, prop, true, { + v: undefined, + getter() { + return this.v; + }, + setter(a) { + this.v = a; + if (a instanceof Object) { + trapChain(a, chain); + } + }, + init(v) { this.v = v; return true; }, }); - // - return; - } + }; // - const prop = chain.slice(0, pos); - const v = owner[prop]; - // - chain = chain.slice(pos + 1); - if (v instanceof Object || (typeof v === 'object' && v !== null)) { - trapChain(v, chain); - return; - } - - // - trapProp(owner, prop, true, { - v: undefined, - getter() { - return this.v; - }, - setter(a) { - this.v = a; - if (a instanceof Object) { - trapChain(a, chain); - } - }, - init(v) { - this.v = v; - return true; - }, - }); - }; - - // - trapChain(window, chain); -})(); + trapChain(window, chain); + })(); +}; diff --git a/plugins/adblocker/menu.ts b/plugins/adblocker/menu.ts index 1e8f43e0..92651424 100644 --- a/plugins/adblocker/menu.ts +++ b/plugins/adblocker/menu.ts @@ -1,6 +1,8 @@ import config, { blockers } from './config'; -export default () => { +import { MenuTemplate } from '../../menu'; + +export default (): MenuTemplate => { return [ { label: 'Blocker', diff --git a/plugins/adblocker/preload.ts b/plugins/adblocker/preload.ts index 33cb6b14..924728ce 100644 --- a/plugins/adblocker/preload.ts +++ b/plugins/adblocker/preload.ts @@ -1,11 +1,13 @@ import config from './config'; +import inject from './inject'; +import injectCliqzPreload from './inject-cliqz-preload'; export default async () => { if (await config.shouldUseBlocklists()) { // Preload adblocker to inject scripts/styles - require('@cliqz/adblocker-electron-preload'); + injectCliqzPreload(); // eslint-disable-next-line @typescript-eslint/await-thenable } else if ((await config.get('blocker')) === config.blockers.InPlayer) { - require('./inject.js'); + inject(); } }; diff --git a/plugins/album-color-theme/front.ts b/plugins/album-color-theme/front.ts index 4c095baa..0400586a 100644 --- a/plugins/album-color-theme/front.ts +++ b/plugins/album-color-theme/front.ts @@ -1,5 +1,7 @@ import { ipcRenderer } from 'electron'; +import { ConfigType } from '../../config/dynamic'; + import type { FastAverageColorResult } from 'fast-average-color'; function hexToHSL(H: string) { @@ -71,7 +73,7 @@ function changeElementColor(element: HTMLElement | null, hue: number, saturation } } -export default () => { +export default (_: ConfigType<'album-color-theme'>) => { const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'attributes') { diff --git a/plugins/crossfade/menu.ts b/plugins/crossfade/menu.ts index fbfa4f0b..d20d9def 100644 --- a/plugins/crossfade/menu.ts +++ b/plugins/crossfade/menu.ts @@ -7,11 +7,13 @@ import config from './config'; import promptOptions from '../../providers/prompt-options'; import configOptions from '../../config/defaults'; +import { MenuTemplate } from '../../menu'; + import type { ConfigType } from '../../config/dynamic'; const defaultOptions = configOptions.plugins.crossfade; -export default (win: BrowserWindow) => [ +export default (win: BrowserWindow): MenuTemplate => [ { label: 'Advanced', async click() { diff --git a/plugins/discord/menu.ts b/plugins/discord/menu.ts index a8ea14c6..4bc6983c 100644 --- a/plugins/discord/menu.ts +++ b/plugins/discord/menu.ts @@ -7,6 +7,7 @@ import { clear, connect, isConnected, registerRefresh } from './back'; import { setMenuOptions } from '../../config/plugins'; import promptOptions from '../../providers/prompt-options'; import { singleton } from '../../providers/decorators'; +import { MenuTemplate } from '../../menu'; import type { ConfigType } from '../../config/dynamic'; @@ -16,14 +17,14 @@ const registerRefreshOnce = singleton((refreshMenu: () => void) => { type DiscordOptions = ConfigType<'discord'>; -export default (win: Electron.BrowserWindow, options: DiscordOptions, refreshMenu: () => void) => { +export default (win: Electron.BrowserWindow, options: DiscordOptions, refreshMenu: () => void): MenuTemplate => { registerRefreshOnce(refreshMenu); return [ { label: isConnected() ? 'Connected' : 'Reconnect', enabled: !isConnected(), - click: connect, + click: () => connect(), }, { label: 'Auto reconnect', diff --git a/plugins/lyrics-genius/back.ts b/plugins/lyrics-genius/back.ts index a4da0d3d..7c3c9946 100644 --- a/plugins/lyrics-genius/back.ts +++ b/plugins/lyrics-genius/back.ts @@ -15,7 +15,7 @@ import type { ConfigType } from '../../config/dynamic'; const eastAsianChars = /\p{Script=Katakana}|\p{Script=Hiragana}|\p{Script=Hangul}|\p{Script=Han}/u; let revRomanized = false; -export type LyricGeniusType = ConfigType<'lyric-genius'>; +export type LyricGeniusType = ConfigType<'lyrics-genius'>; export default (win: BrowserWindow, options: LyricGeniusType) => { if (options.romanizedLyrics) { diff --git a/plugins/lyrics-genius/menu.ts b/plugins/lyrics-genius/menu.ts index 9e5eb9dc..09664f38 100644 --- a/plugins/lyrics-genius/menu.ts +++ b/plugins/lyrics-genius/menu.ts @@ -3,8 +3,9 @@ import { BrowserWindow, MenuItem } from 'electron'; import { LyricGeniusType, toggleRomanized } from './back'; import { setOptions } from '../../config/plugins'; +import { MenuTemplate } from '../../menu'; -export default (_: BrowserWindow, options: LyricGeniusType) => [ +export default (_: BrowserWindow, options: LyricGeniusType): MenuTemplate => [ { label: 'Romanized Lyrics', type: 'checkbox', diff --git a/plugins/navigation/actions.ts b/plugins/navigation/actions.ts deleted file mode 100644 index 32813956..00000000 --- a/plugins/navigation/actions.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Actions, triggerAction } from '../utils'; - -export const CHANNEL = 'navigation'; -export const ACTIONS = Actions; - -export function goToNextPage() { - triggerAction(CHANNEL, Actions.NEXT); -} -// for HTML -// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any -(global as any).goToNextPage = goToNextPage; - -export function goToPreviousPage() { - triggerAction(CHANNEL, Actions.BACK); -} -// for HTML -// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any -(global as any).goToPreviousPage = goToPreviousPage; - -export default { - CHANNEL, - ACTIONS, - actions: { - goToNextPage, - goToPreviousPage, - }, -}; diff --git a/plugins/navigation/back.ts b/plugins/navigation/back.ts index bdf7e77d..aac7eb59 100644 --- a/plugins/navigation/back.ts +++ b/plugins/navigation/back.ts @@ -2,38 +2,12 @@ import path from 'node:path'; import { BrowserWindow } from 'electron'; -import { ACTIONS, CHANNEL } from './actions'; - -import { injectCSS, listenAction } from '../utils'; +import { injectCSS } from '../utils'; export function handle(win: BrowserWindow) { injectCSS(win.webContents, path.join(__dirname, 'style.css'), () => { win.webContents.send('navigation-css-ready'); }); - - listenAction(CHANNEL, (_, action) => { - switch (action) { - case ACTIONS.NEXT: { - if (win.webContents.canGoForward()) { - win.webContents.goForward(); - } - - break; - } - - case ACTIONS.BACK: { - if (win.webContents.canGoBack()) { - win.webContents.goBack(); - } - - break; - } - - default: { - console.log('Unknown action: ' + action); - } - } - }); } export default handle; diff --git a/plugins/navigation/templates/back.html b/plugins/navigation/templates/back.html index 083cf380..a07c46bd 100644 --- a/plugins/navigation/templates/back.html +++ b/plugins/navigation/templates/back.html @@ -1,6 +1,6 @@