mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 18:41:47 +00:00
@ -1,13 +0,0 @@
|
||||
const { loadAdBlockerEngine } = require('./blocker');
|
||||
const config = require('./config');
|
||||
|
||||
module.exports = async (win, options) => {
|
||||
if (await config.shouldUseBlocklists()) {
|
||||
loadAdBlockerEngine(
|
||||
win.webContents.session,
|
||||
options.cache,
|
||||
options.additionalBlockLists,
|
||||
options.disableDefaultLists,
|
||||
);
|
||||
}
|
||||
};
|
||||
20
plugins/adblocker/back.ts
Normal file
20
plugins/adblocker/back.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
import { loadAdBlockerEngine } from './blocker';
|
||||
import config from './config';
|
||||
|
||||
import pluginConfig from '../../config';
|
||||
|
||||
const AdBlockOptionsObj = pluginConfig.get('plugins.adblocker');
|
||||
type AdBlockOptions = typeof AdBlockOptionsObj;
|
||||
|
||||
export default async (win: BrowserWindow, options: AdBlockOptions) => {
|
||||
if (await config.shouldUseBlocklists()) {
|
||||
loadAdBlockerEngine(
|
||||
win.webContents.session,
|
||||
options.cache,
|
||||
options.additionalBlockLists,
|
||||
options.disableDefaultLists,
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1,7 +1,8 @@
|
||||
const { promises } = require('node:fs'); // Used for caching
|
||||
const path = require('node:path');
|
||||
// Used for caching
|
||||
import path from 'node:path';
|
||||
import { promises } from 'node:fs';
|
||||
|
||||
const { ElectronBlocker } = require('@cliqz/adblocker-electron');
|
||||
import { ElectronBlocker } from '@cliqz/adblocker-electron';
|
||||
|
||||
const SOURCES = [
|
||||
'https://raw.githubusercontent.com/kbinani/adblock-youtube-ads/master/signed.txt',
|
||||
@ -15,11 +16,11 @@ const SOURCES = [
|
||||
'https://secure.fanboy.co.nz/fanboy-annoyance_ubo.txt',
|
||||
];
|
||||
|
||||
const loadAdBlockerEngine = (
|
||||
session = undefined,
|
||||
export const loadAdBlockerEngine = (
|
||||
session: Electron.Session | undefined = undefined,
|
||||
cache = true,
|
||||
additionalBlockLists = [],
|
||||
disableDefaultLists = false,
|
||||
disableDefaultLists: boolean | string[] = false,
|
||||
) => {
|
||||
// Only use cache if no additional blocklists are passed
|
||||
const cachingOptions
|
||||
@ -56,7 +57,7 @@ const loadAdBlockerEngine = (
|
||||
.catch((error) => console.log('Error loading adBlocker engine', error));
|
||||
};
|
||||
|
||||
module.exports = { loadAdBlockerEngine };
|
||||
export default { loadAdBlockerEngine };
|
||||
if (require.main === module) {
|
||||
loadAdBlockerEngine(); // Generate the engine without enabling it
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
const { PluginConfig } = require('../../config/dynamic');
|
||||
|
||||
const config = new PluginConfig('adblocker', { enableFront: true });
|
||||
|
||||
const blockers = {
|
||||
WithBlocklists: 'With blocklists',
|
||||
InPlayer: 'In player',
|
||||
};
|
||||
|
||||
const shouldUseBlocklists = async () =>
|
||||
(await config.get('blocker')) !== blockers.InPlayer;
|
||||
|
||||
module.exports = { shouldUseBlocklists, blockers, ...config };
|
||||
17
plugins/adblocker/config.ts
Normal file
17
plugins/adblocker/config.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { PluginConfig } from '../../config/dynamic';
|
||||
|
||||
const config = new PluginConfig('adblocker', { enableFront: true });
|
||||
|
||||
export const blockers = {
|
||||
WithBlocklists: 'With blocklists',
|
||||
InPlayer: 'In player',
|
||||
};
|
||||
|
||||
export const shouldUseBlocklists = async () => await config.get('blocker') !== blockers.InPlayer;
|
||||
|
||||
export default {
|
||||
shouldUseBlocklists,
|
||||
blockers,
|
||||
get: config.get.bind(this),
|
||||
set: config.set.bind(this),
|
||||
};
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// Source: https://addons.mozilla.org/en-US/firefox/addon/adblock-for-youtube/
|
||||
// https://robwu.nl/crxviewer/?crx=https%3A%2F%2Faddons.mozilla.org%2Fen-US%2Ffirefox%2Faddon%2Fadblock-for-youtube%2F
|
||||
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
const config = require('./config');
|
||||
|
||||
module.exports = () => [
|
||||
{
|
||||
label: 'Blocker',
|
||||
submenu: Object.values(config.blockers).map((blocker) => ({
|
||||
label: blocker,
|
||||
type: 'radio',
|
||||
checked: (config.get('blocker') || config.blockers.WithBlocklists) === blocker,
|
||||
click() {
|
||||
config.set('blocker', blocker);
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
19
plugins/adblocker/menu.ts
Normal file
19
plugins/adblocker/menu.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import config from './config';
|
||||
|
||||
export default async () => {
|
||||
const blockerConfig = await config.get('blocker');
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'Blocker',
|
||||
submenu: Object.values(config.blockers).map((blocker) => ({
|
||||
label: blocker,
|
||||
type: 'radio',
|
||||
checked: (blockerConfig || config.blockers.WithBlocklists) === blocker,
|
||||
click() {
|
||||
config.set('blocker', blocker);
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,10 +1,10 @@
|
||||
const config = require('./config');
|
||||
import config from './config';
|
||||
|
||||
module.exports = async () => {
|
||||
export default async () => {
|
||||
if (await config.shouldUseBlocklists()) {
|
||||
// Preload adblocker to inject scripts/styles
|
||||
require('@cliqz/adblocker-electron-preload');
|
||||
} else if ((await config.get('blocker')) === config.blockers.InPlayer) {
|
||||
require('./inject');
|
||||
require('./inject.js');
|
||||
}
|
||||
};
|
||||
@ -1,24 +0,0 @@
|
||||
const { triggerAction } = require('../utils');
|
||||
|
||||
const CHANNEL = 'navigation';
|
||||
const ACTIONS = {
|
||||
NEXT: 'next',
|
||||
BACK: 'back',
|
||||
};
|
||||
|
||||
function goToNextPage() {
|
||||
triggerAction(CHANNEL, ACTIONS.NEXT);
|
||||
}
|
||||
|
||||
function goToPreviousPage() {
|
||||
triggerAction(CHANNEL, ACTIONS.BACK);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CHANNEL,
|
||||
ACTIONS,
|
||||
actions: {
|
||||
goToNextPage,
|
||||
goToPreviousPage,
|
||||
},
|
||||
};
|
||||
21
plugins/navigation/actions.ts
Normal file
21
plugins/navigation/actions.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Actions, triggerAction } from '../utils';
|
||||
|
||||
export const CHANNEL = 'navigation';
|
||||
export const ACTIONS = Actions;
|
||||
|
||||
export function goToNextPage() {
|
||||
triggerAction(CHANNEL, Actions.NEXT);
|
||||
}
|
||||
|
||||
export function goToPreviousPage() {
|
||||
triggerAction(CHANNEL, Actions.BACK);
|
||||
}
|
||||
|
||||
export default {
|
||||
CHANNEL,
|
||||
ACTIONS,
|
||||
actions: {
|
||||
goToNextPage,
|
||||
goToPreviousPage,
|
||||
},
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
const path = require('node:path');
|
||||
|
||||
const { ACTIONS, CHANNEL } = require('./actions.js');
|
||||
const { ACTIONS, CHANNEL } = require('./actions.ts');
|
||||
|
||||
const { injectCSS, listenAction } = require('../utils');
|
||||
|
||||
|
||||
@ -1,28 +1,35 @@
|
||||
const path = require('node:path');
|
||||
import path from 'node:path';
|
||||
|
||||
const { app, ipcMain } = require('electron');
|
||||
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||
|
||||
const { setOptions } = require('../../config/plugins');
|
||||
const { injectCSS } = require('../utils');
|
||||
import { setOptions as setPluginOptions } from '../../config/plugins';
|
||||
import { injectCSS } from '../utils';
|
||||
|
||||
import config from '../../config';
|
||||
|
||||
let isInPiP = false;
|
||||
let originalPosition;
|
||||
let originalSize;
|
||||
let originalFullScreen;
|
||||
let originalMaximized;
|
||||
let originalPosition: number[];
|
||||
let originalSize: number[];
|
||||
let originalFullScreen: boolean;
|
||||
let originalMaximized: boolean;
|
||||
|
||||
let win;
|
||||
let options;
|
||||
let win: BrowserWindow;
|
||||
|
||||
// Magic of TypeScript
|
||||
const PiPOptionsObj = config.get('plugins.picture-in-picture');
|
||||
type PiPOptions = typeof PiPOptionsObj;
|
||||
|
||||
let options: Partial<PiPOptions>;
|
||||
|
||||
const pipPosition = () => (options.savePosition && options['pip-position']) || [10, 10];
|
||||
const pipSize = () => (options.saveSize && options['pip-size']) || [450, 275];
|
||||
|
||||
const setLocalOptions = (_options) => {
|
||||
const setLocalOptions = (_options: Partial<PiPOptions>) => {
|
||||
options = { ...options, ..._options };
|
||||
setOptions('picture-in-picture', _options);
|
||||
setPluginOptions('picture-in-picture', _options);
|
||||
};
|
||||
|
||||
const togglePiP = async () => {
|
||||
const togglePiP = () => {
|
||||
isInPiP = !isInPiP;
|
||||
setLocalOptions({ isInPiP });
|
||||
|
||||
@ -82,7 +89,7 @@ const togglePiP = async () => {
|
||||
win.setWindowButtonVisibility?.(!isInPiP);
|
||||
};
|
||||
|
||||
const blockShortcutsInPiP = (event, input) => {
|
||||
const blockShortcutsInPiP = (event: Electron.Event, input: Electron.Input) => {
|
||||
const key = input.key.toLowerCase();
|
||||
|
||||
if (key === 'f') {
|
||||
@ -93,14 +100,14 @@ const blockShortcutsInPiP = (event, input) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = (_win, _options) => {
|
||||
export default (_win: BrowserWindow, _options: PiPOptions) => {
|
||||
options ??= _options;
|
||||
win ??= _win;
|
||||
setLocalOptions({ isInPiP });
|
||||
injectCSS(win.webContents, path.join(__dirname, 'style.css'));
|
||||
ipcMain.on('picture-in-picture', async () => {
|
||||
await togglePiP();
|
||||
ipcMain.on('picture-in-picture', () => {
|
||||
togglePiP();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.setOptions = setLocalOptions;
|
||||
export const setOptions = setLocalOptions;
|
||||
@ -1,6 +1,6 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
const { toKeyEvent } = require('keyboardevent-from-electron-accelerator');
|
||||
const keyEventAreEqual = require('keyboardevents-areequal');
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { toKeyEvent } from 'keyboardevent-from-electron-accelerator';
|
||||
import keyEventAreEqual from 'keyboardevents-areequal';
|
||||
|
||||
const { getSongMenu } = require('../../providers/dom-elements');
|
||||
const { ElementFromFile, templatePath } = require('../utils');
|
||||
@ -1,6 +1,6 @@
|
||||
const prompt = require('custom-electron-prompt');
|
||||
|
||||
const { setOptions } = require('./back.js');
|
||||
const { setOptions } = require('./back.ts');
|
||||
|
||||
const promptOptions = require('../../providers/prompt-options');
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const { ipcMain, ipcRenderer } = require('electron');
|
||||
|
||||
// Creates a DOM element from a HTML string
|
||||
module.exports.ElementFromHtml = (html) => {
|
||||
const template = document.createElement('template');
|
||||
html = html.trim(); // Never return a text node of whitespace as the result
|
||||
template.innerHTML = html;
|
||||
return template.content.firstChild;
|
||||
};
|
||||
|
||||
// Creates a DOM element from a HTML file
|
||||
module.exports.ElementFromFile = (filepath) => module.exports.ElementFromHtml(fs.readFileSync(filepath, 'utf8'));
|
||||
|
||||
module.exports.templatePath = (pluginPath, name) => path.join(pluginPath, 'templates', name);
|
||||
|
||||
module.exports.triggerAction = (channel, action, ...args) => ipcRenderer.send(channel, action, ...args);
|
||||
|
||||
module.exports.triggerActionSync = (channel, action, ...args) => ipcRenderer.sendSync(channel, action, ...args);
|
||||
|
||||
module.exports.listenAction = (channel, callback) => ipcMain.on(channel, callback);
|
||||
|
||||
module.exports.fileExists = (
|
||||
path,
|
||||
callbackIfExists,
|
||||
callbackIfError = undefined,
|
||||
) => {
|
||||
fs.access(path, fs.F_OK, (error) => {
|
||||
if (error) {
|
||||
if (callbackIfError) {
|
||||
callbackIfError();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
callbackIfExists();
|
||||
});
|
||||
};
|
||||
|
||||
const cssToInject = new Map();
|
||||
module.exports.injectCSS = (webContents, filepath, cb = undefined) => {
|
||||
if (cssToInject.size === 0) {
|
||||
setupCssInjection(webContents);
|
||||
}
|
||||
|
||||
cssToInject.set(filepath, cb);
|
||||
};
|
||||
|
||||
const setupCssInjection = (webContents) => {
|
||||
webContents.on('did-finish-load', () => {
|
||||
cssToInject.forEach(async (cb, filepath) => {
|
||||
await webContents.insertCSS(fs.readFileSync(filepath, 'utf8'));
|
||||
cb?.();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.getAllPlugins = () => {
|
||||
const isDirectory = (source) => fs.lstatSync(source).isDirectory();
|
||||
return fs
|
||||
.readdirSync(__dirname)
|
||||
.map((name) => path.join(__dirname, name))
|
||||
.filter(isDirectory)
|
||||
.map((name) => path.basename(name));
|
||||
};
|
||||
74
plugins/utils.ts
Normal file
74
plugins/utils.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { ipcMain, ipcRenderer } from 'electron';
|
||||
|
||||
import { ValueOf } from '../utils/type-utils';
|
||||
|
||||
|
||||
// Creates a DOM element from an HTML string
|
||||
export const ElementFromHtml = (html: string) => {
|
||||
const template = document.createElement('template');
|
||||
html = html.trim(); // Never return a text node of whitespace as the result
|
||||
template.innerHTML = html;
|
||||
return template.content.firstChild;
|
||||
};
|
||||
|
||||
// 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 = <Parameters extends unknown[]>(channel: string, action: ValueOf<typeof Actions>, ...args: Parameters) => ipcRenderer.send(channel, action, ...args);
|
||||
|
||||
export const triggerActionSync = <Parameters extends unknown[]>(channel: string, action: ValueOf<typeof Actions>, ...args: Parameters): unknown => ipcRenderer.sendSync(channel, action, ...args);
|
||||
|
||||
export const listenAction = (channel: string, callback: <Parameters extends unknown[]>(event: Electron.IpcMainEvent, ...args: Parameters) => void) => ipcMain.on(channel, callback);
|
||||
|
||||
export const fileExists = (
|
||||
path: fs.PathLike,
|
||||
callbackIfExists: { (): void; (): void; (): void; },
|
||||
callbackIfError: (() => void) | undefined = undefined,
|
||||
) => {
|
||||
fs.access(path, fs.constants.F_OK, (error) => {
|
||||
if (error) {
|
||||
callbackIfError?.();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
callbackIfExists();
|
||||
});
|
||||
};
|
||||
|
||||
const cssToInject = new Map();
|
||||
export const injectCSS = (webContents: Electron.WebContents, filepath: unknown, cb = undefined) => {
|
||||
if (cssToInject.size === 0) {
|
||||
setupCssInjection(webContents);
|
||||
}
|
||||
|
||||
cssToInject.set(filepath, cb);
|
||||
};
|
||||
|
||||
const setupCssInjection = (webContents: Electron.WebContents) => {
|
||||
webContents.on('did-finish-load', () => {
|
||||
cssToInject.forEach(async (callback: () => void | undefined, filepath: fs.PathOrFileDescriptor) => {
|
||||
await webContents.insertCSS(fs.readFileSync(filepath, 'utf8'));
|
||||
callback?.();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const getAllPlugins = () => {
|
||||
const isDirectory = (source: fs.PathLike) => fs.lstatSync(source).isDirectory();
|
||||
return fs
|
||||
.readdirSync(__dirname)
|
||||
.map((name) => path.join(__dirname, name))
|
||||
.filter(isDirectory)
|
||||
.map((name) => path.basename(name));
|
||||
};
|
||||
Reference in New Issue
Block a user