mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-10 10:11:46 +00:00
feat: reimplement inject css, fix types
This commit is contained in:
62
.eslintrc.js
62
.eslintrc.js
@ -1,8 +1,8 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'plugin:import/typescript',
|
||||
'eslint:recommended',
|
||||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
@ -13,51 +13,30 @@ module.exports = {
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 'latest',
|
||||
ecmaVersion: 'latest'
|
||||
},
|
||||
rules: {
|
||||
'arrow-parens': ['error', 'always'],
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
'@typescript-eslint/no-floating-promises': 'off',
|
||||
'@typescript-eslint/no-misused-promises': [
|
||||
'off',
|
||||
{ checksVoidReturn: false },
|
||||
],
|
||||
'@typescript-eslint/no-misused-promises': ['off', { checksVoidReturn: false }],
|
||||
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
'import/first': 'error',
|
||||
'import/newline-after-import': 'error',
|
||||
'import/no-default-export': 'off',
|
||||
'import/no-duplicates': 'error',
|
||||
'import/no-unresolved': [
|
||||
'error',
|
||||
{
|
||||
ignore: [
|
||||
'^virtual:',
|
||||
'\\?inline$',
|
||||
'\\?raw$',
|
||||
'\\?asset&asarUnpack',
|
||||
'^youtubei.js$',
|
||||
],
|
||||
},
|
||||
],
|
||||
'import/no-unresolved': ['error', { ignore: ['^virtual:', '\\?inline$', '\\?raw$', '\\?asset&asarUnpack'] }],
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
groups: [
|
||||
'builtin',
|
||||
'external',
|
||||
['internal', 'index', 'sibling'],
|
||||
'parent',
|
||||
'type',
|
||||
],
|
||||
'groups': ['builtin', 'external', ['internal', 'index', 'sibling'], 'parent', 'type'],
|
||||
'newlines-between': 'always-and-inside-groups',
|
||||
alphabetize: { order: 'ignore', caseInsensitive: false },
|
||||
},
|
||||
'alphabetize': {order: 'ignore', caseInsensitive: false}
|
||||
}
|
||||
],
|
||||
'import/prefer-default-export': 'off',
|
||||
camelcase: ['error', { properties: 'never' }],
|
||||
'camelcase': ['error', {properties: 'never'}],
|
||||
'class-methods-use-this': 'off',
|
||||
'lines-around-comment': [
|
||||
'error',
|
||||
@ -70,21 +49,17 @@ module.exports = {
|
||||
],
|
||||
'max-len': 'off',
|
||||
'no-mixed-operators': 'error',
|
||||
'no-multi-spaces': ['error', { ignoreEOLComments: true }],
|
||||
'no-multi-spaces': ['error', {ignoreEOLComments: true}],
|
||||
'no-tabs': 'error',
|
||||
'no-void': 'error',
|
||||
'no-empty': 'off',
|
||||
'prefer-promise-reject-errors': 'off',
|
||||
quotes: [
|
||||
'error',
|
||||
'single',
|
||||
{
|
||||
avoidEscape: true,
|
||||
allowTemplateLiterals: false,
|
||||
},
|
||||
],
|
||||
'quotes': ['error', 'single', {
|
||||
avoidEscape: true,
|
||||
allowTemplateLiterals: false,
|
||||
}],
|
||||
'quote-props': ['error', 'consistent'],
|
||||
semi: ['error', 'always'],
|
||||
'semi': ['error', 'always'],
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
@ -95,12 +70,11 @@ module.exports = {
|
||||
root: true,
|
||||
settings: {
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
||||
'@typescript-eslint/parser': ['.ts']
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
},
|
||||
typescript: {},
|
||||
exports: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -157,7 +157,6 @@
|
||||
"electron-store": "8.1.0",
|
||||
"electron-unhandled": "4.0.1",
|
||||
"electron-updater": "6.1.4",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"fast-average-color": "9.4.0",
|
||||
"fast-equals": "^5.0.1",
|
||||
"filenamify": "6.0.0",
|
||||
@ -180,7 +179,7 @@
|
||||
"@types/electron-localshortcut": "3.1.3",
|
||||
"@types/howler": "2.2.11",
|
||||
"@types/html-to-text": "9.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "6.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.12.0",
|
||||
"bufferutil": "4.0.8",
|
||||
"builtin-modules": "^3.3.0",
|
||||
"cross-env": "7.0.3",
|
||||
@ -189,7 +188,9 @@
|
||||
"electron-builder": "24.6.4",
|
||||
"electron-devtools-installer": "3.2.0",
|
||||
"electron-vite": "1.0.28",
|
||||
"eslint": "8.53.0",
|
||||
"eslint": "8.54.0",
|
||||
"eslint-import-resolver-exports": "1.0.0-beta.5",
|
||||
"eslint-import-resolver-typescript": "3.6.1",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
"eslint-plugin-prettier": "5.0.1",
|
||||
"glob": "10.3.10",
|
||||
|
||||
268
pnpm-lock.yaml
generated
268
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
|
||||
import { deepmerge } from 'deepmerge-ts';
|
||||
import { mainPlugins } from 'virtual:plugins';
|
||||
|
||||
import { PluginDef } from '@/types/plugins';
|
||||
import { PluginConfig, PluginDef } from '@/types/plugins';
|
||||
import { BackendContext } from '@/types/contexts';
|
||||
import config from '@/config';
|
||||
import { startPlugin, stopPlugin } from '@/utils';
|
||||
@ -14,35 +12,37 @@ const loadedPluginMap: Record<string, PluginDef> = {};
|
||||
|
||||
const createContext = (id: string, win: BrowserWindow): BackendContext => ({
|
||||
getConfig: () =>
|
||||
// @ts-expect-error ts dum dum
|
||||
deepmerge(
|
||||
mainPlugins[id].config,
|
||||
config.get(`plugins.${id}`) ?? { enabled: false },
|
||||
),
|
||||
) as PluginConfig,
|
||||
setConfig: (newConfig) => {
|
||||
config.setPartial(`plugins.${id}`, newConfig);
|
||||
},
|
||||
|
||||
send: (event: string, ...args: unknown[]) => {
|
||||
win.webContents.send(event, ...args);
|
||||
},
|
||||
// @ts-expect-error ts dum dum
|
||||
handle: (event: string, listener) => {
|
||||
// @ts-expect-error ts dum dum
|
||||
ipcMain.handle(event, (_, ...args) => listener(...(args as never)));
|
||||
},
|
||||
// @ts-expect-error ts dum dum
|
||||
on: (event: string, listener) => {
|
||||
// @ts-expect-error ts dum dum
|
||||
ipcMain.on(event, (_, ...args) => listener(...(args as never)));
|
||||
ipc: {
|
||||
send: (event: string, ...args: unknown[]) => {
|
||||
win.webContents.send(event, ...args);
|
||||
},
|
||||
handle: (event: string, listener: CallableFunction) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
ipcMain.handle(event, (_, ...args: unknown[]) => listener(...args));
|
||||
},
|
||||
on: (event: string, listener: CallableFunction) => {
|
||||
ipcMain.on(event, (_, ...args: unknown[]) => {
|
||||
listener(...args);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
window: win,
|
||||
});
|
||||
|
||||
export const forceUnloadMainPlugin = async (
|
||||
id: string,
|
||||
win: BrowserWindow,
|
||||
): Promise<void> => {
|
||||
const plugin = loadedPluginMap[id]!;
|
||||
const plugin = loadedPluginMap[id];
|
||||
if (!plugin) return;
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
|
||||
@ -15,6 +15,17 @@ const createContext = (id: string): RendererContext => ({
|
||||
setConfig: async (newConfig) => {
|
||||
await window.ipcRenderer.invoke('set-config', id, newConfig);
|
||||
},
|
||||
ipc: {
|
||||
send: (event: string, ...args: unknown[]) => {
|
||||
window.ipcRenderer.send(event, ...args);
|
||||
},
|
||||
invoke: (event: string, ...args: unknown[]) => window.ipcRenderer.invoke(event, ...args),
|
||||
on: (event: string, listener: CallableFunction) => {
|
||||
window.ipcRenderer.on(event, (_, ...args: unknown[]) => {
|
||||
listener(...args);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const forceUnloadRendererPlugin = (id: string) => {
|
||||
@ -27,7 +38,7 @@ export const forceUnloadRendererPlugin = (id: string) => {
|
||||
if (!plugin) return;
|
||||
|
||||
stopPlugin(id, plugin, { ctx: 'renderer', context: createContext(id) });
|
||||
if (plugin.renderer?.stylesheet)
|
||||
if (plugin?.stylesheets)
|
||||
document.querySelector(`style#plugin-${id}`)?.remove();
|
||||
|
||||
console.log('[YTMusic]', `"${id}" plugin is unloaded`);
|
||||
@ -42,14 +53,14 @@ export const forceLoadRendererPlugin = (id: string) => {
|
||||
context: createContext(id),
|
||||
});
|
||||
|
||||
if (hasEvaled || plugin.renderer?.stylesheet) {
|
||||
if (hasEvaled || plugin?.stylesheets) {
|
||||
loadedPluginMap[id] = plugin;
|
||||
|
||||
if (plugin.renderer?.stylesheet)
|
||||
if (plugin?.stylesheets)
|
||||
document.head.appendChild(
|
||||
Object.assign(document.createElement('style'), {
|
||||
id: `plugin-${id}`,
|
||||
innerHTML: plugin.renderer?.stylesheet ?? '',
|
||||
innerHTML: plugin?.stylesheets ?? '',
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@ -3,5 +3,5 @@ import style from './style.css?inline';
|
||||
|
||||
export default createPlugin({
|
||||
name: 'Blur Navigation Bar',
|
||||
renderer: { stylesheet: style },
|
||||
renderer: { stylesheets: [style] },
|
||||
});
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
import { rendererPlugins } from 'virtual:plugins';
|
||||
|
||||
import {
|
||||
PluginBaseConfig,
|
||||
PluginBuilder,
|
||||
RendererPluginFactory,
|
||||
} from '@/plugins/utils/builder';
|
||||
|
||||
import { startingPages } from './providers/extracted-data';
|
||||
@ -81,6 +77,13 @@ function onApiLoaded() {
|
||||
{ passive: true },
|
||||
);
|
||||
|
||||
Object.values(getAllLoadedRendererPlugins())
|
||||
.forEach((plugin) => {
|
||||
if (typeof plugin.renderer !== 'function') {
|
||||
plugin.renderer?.onPlayerApiReady?.(api!);
|
||||
}
|
||||
});
|
||||
|
||||
window.ipcRenderer.send('ytmd:player-api-loaded');
|
||||
|
||||
// Navigate to "Starting page"
|
||||
@ -133,6 +136,9 @@ function onApiLoaded() {
|
||||
forceLoadRendererPlugin(id);
|
||||
if (api) {
|
||||
const plugin = getLoadedRendererPlugin(id);
|
||||
if (plugin && typeof plugin.renderer !== 'function') {
|
||||
plugin.renderer?.onPlayerApiReady?.(api);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -141,6 +147,9 @@ function onApiLoaded() {
|
||||
'config-changed',
|
||||
(_event, id: string, newConfig: PluginBaseConfig) => {
|
||||
const plugin = getAllLoadedRendererPlugins()[id];
|
||||
if (plugin && typeof plugin.renderer !== 'function') {
|
||||
plugin.renderer?.onConfigChange?.(newConfig);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -1,33 +1,32 @@
|
||||
import type { IpcMain, IpcRenderer, BrowserWindow } from 'electron';
|
||||
import type { BrowserWindow } from 'electron';
|
||||
import type { PluginConfig } from '@/types/plugins';
|
||||
|
||||
export interface BackendContext {
|
||||
export interface BaseContext {
|
||||
getConfig(): PluginConfig;
|
||||
setConfig(conf: Omit<PluginConfig, 'enabled'>): void;
|
||||
}
|
||||
|
||||
export interface BackendContext extends BaseContext {
|
||||
ipc: {
|
||||
send: (event: string, ...args: unknown[]) => void;
|
||||
handle: (event: string, listener: CallableFunction) => void;
|
||||
on: (event: string, listener: CallableFunction) => void;
|
||||
};
|
||||
|
||||
win: BrowserWindow;
|
||||
electron: typeof import('electron');
|
||||
window: BrowserWindow;
|
||||
}
|
||||
|
||||
export interface MenuContext {
|
||||
getConfig(): PluginConfig;
|
||||
setConfig(conf: Omit<PluginConfig, 'enabled'>): void;
|
||||
|
||||
export interface MenuContext extends BaseContext {
|
||||
window: BrowserWindow;
|
||||
refresh: () => Promise<void> | void;
|
||||
}
|
||||
|
||||
export interface PreloadContext {
|
||||
getConfig(): PluginConfig;
|
||||
setConfig(conf: Omit<PluginConfig, 'enabled'>): void;
|
||||
}
|
||||
export interface PreloadContext extends BaseContext {}
|
||||
|
||||
export interface RendererContext {
|
||||
getConfig(): PluginConfig;
|
||||
setConfig(conf: Omit<PluginConfig, 'enabled'>): void;
|
||||
export interface RendererContext extends BaseContext {
|
||||
ipc: {
|
||||
send: (event: string, ...args: unknown[]) => void;
|
||||
invoke: (event: string, ...args: unknown[]) => Promise<unknown>;
|
||||
on: (event: string, listener: CallableFunction) => void;
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { YoutubePlayer } from '@/types/youtube-player';
|
||||
|
||||
import type {
|
||||
BackendContext,
|
||||
MenuContext,
|
||||
@ -11,15 +13,17 @@ export type PluginConfig = {
|
||||
enabled: boolean;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type PluginExtra = Record<string, any>;
|
||||
type PluginExtra = Record<string, unknown>;
|
||||
|
||||
type PluginLifecycle<T> =
|
||||
| ((ctx: T) => void | Promise<void>)
|
||||
| ({
|
||||
start?(ctx: T): void | Promise<void>;
|
||||
stop?(ctx: T): void | Promise<void>;
|
||||
} & PluginExtra);
|
||||
export type PluginLifecycleSimple<T> = (ctx: T) => void | Promise<void>;
|
||||
export type PluginLifecycleExtra<T> = {
|
||||
start?: PluginLifecycleSimple<T>;
|
||||
stop?: PluginLifecycleSimple<T>;
|
||||
onConfigChange?: (newConfig: PluginConfig) => void | Promise<void>;
|
||||
onPlayerApiReady?: (playerApi: YoutubePlayer) => void | Promise<void>;
|
||||
} & PluginExtra;
|
||||
|
||||
export type PluginLifecycle<T> = PluginLifecycleSimple<T> | PluginLifecycleExtra<T>;
|
||||
|
||||
export interface PluginDef {
|
||||
name: string;
|
||||
@ -28,11 +32,10 @@ export interface PluginDef {
|
||||
config: PluginConfig;
|
||||
|
||||
menu?: (ctx: MenuContext) => Electron.MenuItemConstructorOptions[];
|
||||
stylesheets?: string[];
|
||||
restartNeeded?: boolean;
|
||||
|
||||
backend?: PluginLifecycle<BackendContext>;
|
||||
preload?: PluginLifecycle<PreloadContext>;
|
||||
renderer?: PluginLifecycle<RendererContext> & {
|
||||
stylesheet?: string;
|
||||
};
|
||||
renderer?: PluginLifecycle<RendererContext>;
|
||||
}
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
import {
|
||||
import type {
|
||||
BackendContext,
|
||||
PreloadContext,
|
||||
RendererContext,
|
||||
} from '@/types/contexts';
|
||||
import { PluginDef, PluginConfig } from '@/types/plugins';
|
||||
|
||||
import type {
|
||||
PluginDef,
|
||||
PluginConfig,
|
||||
PluginLifecycleExtra,
|
||||
PluginLifecycleSimple,
|
||||
} from '@/types/plugins';
|
||||
|
||||
export const createPlugin = (
|
||||
def: Omit<PluginDef, 'config'> & {
|
||||
@ -17,11 +23,10 @@ type Options =
|
||||
| { ctx: 'renderer'; context: RendererContext };
|
||||
|
||||
export const startPlugin = (id: string, def: PluginDef, options: Options) => {
|
||||
const lifecycle: (ctx: (typeof options)['context']) => void =
|
||||
const lifecycle =
|
||||
typeof def[options.ctx] === 'function'
|
||||
? def[options.ctx]
|
||||
: // @ts-expect-error TS is dum dum
|
||||
def[options.ctx]?.start;
|
||||
? def[options.ctx] as PluginLifecycleSimple<Options['context']>
|
||||
: (def[options.ctx] as PluginLifecycleExtra<Options['context']>)?.start;
|
||||
|
||||
if (!lifecycle) return false;
|
||||
|
||||
@ -29,7 +34,6 @@ export const startPlugin = (id: string, def: PluginDef, options: Options) => {
|
||||
const start = performance.now();
|
||||
lifecycle(options.context);
|
||||
|
||||
// prettier-ignore
|
||||
console.log(`[YTM] Executed ${id}::${options.ctx} in ${performance.now() - start} ms`);
|
||||
|
||||
return true;
|
||||
@ -43,15 +47,13 @@ export const stopPlugin = (id: string, def: PluginDef, options: Options) => {
|
||||
if (!def[options.ctx]) return false;
|
||||
if (typeof def[options.ctx] === 'function') return false;
|
||||
|
||||
// @ts-expect-error TS is dum dum
|
||||
const stop: (ctx: typeof options.context) => void = def[options.ctx]?.stop;
|
||||
const stop = def[options.ctx] as PluginLifecycleExtra<Options['context']>['stop'];
|
||||
if (!stop) return false;
|
||||
|
||||
try {
|
||||
const start = performance.now();
|
||||
stop(options.context);
|
||||
|
||||
// prettier-ignore
|
||||
console.log(`[YTM] Executed ${id}::${options.ctx} in ${performance.now() - start} ms`);
|
||||
|
||||
return true;
|
||||
|
||||
@ -6,13 +6,13 @@ import { Project, ts, ObjectLiteralExpression } from 'ts-morph';
|
||||
|
||||
import type { PluginOption } from 'vite';
|
||||
|
||||
export default function (mode: 'backend' | 'preload' | 'renderer' | 'none') {
|
||||
export default function (mode: 'backend' | 'preload' | 'renderer' | 'none'): PluginOption {
|
||||
const pluginFilter = createFilter([
|
||||
'src/plugins/*/index.{js,ts}',
|
||||
'src/plugins/*',
|
||||
]);
|
||||
|
||||
return <PluginOption>{
|
||||
return {
|
||||
name: 'ytm-plugin-loader',
|
||||
async load(id) {
|
||||
if (!pluginFilter(id)) return null;
|
||||
@ -98,7 +98,6 @@ export default function (mode: 'backend' | 'preload' | 'renderer' | 'none') {
|
||||
|
||||
return {
|
||||
code: src.getText(),
|
||||
ast: src,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user