mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-10 10:11:46 +00:00
Merge branch 'master' into remove-upgrade-button
This commit is contained in:
@ -30,6 +30,7 @@ const defaultConfig = {
|
||||
// Disabled plugins
|
||||
shortcuts: {
|
||||
enabled: false,
|
||||
overrideMediaKeys: false,
|
||||
},
|
||||
downloader: {
|
||||
enabled: false,
|
||||
@ -45,7 +46,8 @@ const defaultConfig = {
|
||||
discord: {
|
||||
enabled: false,
|
||||
activityTimoutEnabled: true, // if enabled, the discord rich presence gets cleared when music paused after the time specified below
|
||||
activityTimoutTime: 10 * 60 * 1000 // 10 minutes
|
||||
activityTimoutTime: 10 * 60 * 1000, // 10 minutes
|
||||
listenAlong: true, // add a "listen along" button to rich presence
|
||||
},
|
||||
notifications: {
|
||||
enabled: false,
|
||||
@ -58,9 +60,8 @@ const defaultConfig = {
|
||||
steps: 1, //percentage of volume to change
|
||||
arrowsShortcut: true, //enable ArrowUp + ArrowDown local shortcuts
|
||||
globalShortcuts: {
|
||||
enabled: false, // enable global shortcuts
|
||||
volumeUp: "Shift+PageUp", // Keybind default can be changed
|
||||
volumeDown: "Shift+PageDown"
|
||||
volumeUp: "",
|
||||
volumeDown: ""
|
||||
},
|
||||
savedVolume: undefined //plugin save volume between session here
|
||||
},
|
||||
|
||||
@ -3,6 +3,32 @@ const Store = require("electron-store");
|
||||
const defaults = require("./defaults");
|
||||
|
||||
const migrations = {
|
||||
">=1.13.0": (store) => {
|
||||
if (store.get("plugins.discord.listenAlong") === undefined) {
|
||||
store.set("plugins.discord.listenAlong", true);
|
||||
}
|
||||
},
|
||||
">=1.12.0": (store) => {
|
||||
const options = store.get("plugins.shortcuts")
|
||||
let updated = false;
|
||||
for (const optionType of ["global", "local"]) {
|
||||
if (Array.isArray(options[optionType])) {
|
||||
const updatedOptions = {};
|
||||
for (const optionObject of options[optionType]) {
|
||||
if (optionObject.action && optionObject.shortcut) {
|
||||
updatedOptions[optionObject.action] = optionObject.shortcut;
|
||||
}
|
||||
}
|
||||
|
||||
options[optionType] = updatedOptions;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
store.set("plugins.shortcuts", options);
|
||||
}
|
||||
},
|
||||
">=1.11.0": (store) => {
|
||||
if (store.get("options.resumeOnStart") === undefined) {
|
||||
store.set("options.resumeOnStart", true);
|
||||
|
||||
6
index.js
6
index.js
@ -39,7 +39,9 @@ if (config.get("options.proxy")) {
|
||||
}
|
||||
|
||||
// Adds debug features like hotkeys for triggering dev tools and reload
|
||||
require("electron-debug")();
|
||||
require("electron-debug")({
|
||||
showDevTools: false //disable automatic devTools on new window
|
||||
});
|
||||
|
||||
// Prevent window being garbage collected
|
||||
let mainWindow;
|
||||
@ -60,7 +62,7 @@ function onClosed() {
|
||||
|
||||
function loadPlugins(win) {
|
||||
injectCSS(win.webContents, path.join(__dirname, "youtube-music.css"));
|
||||
win.webContents.on("did-finish-load", () => {
|
||||
win.webContents.once("did-finish-load", () => {
|
||||
if (is.dev()) {
|
||||
console.log("did finish load");
|
||||
win.webContents.openDevTools();
|
||||
|
||||
33
menu.js
33
menu.js
@ -7,6 +7,9 @@ const is = require("electron-is");
|
||||
const { getAllPlugins } = require("./plugins/utils");
|
||||
const config = require("./config");
|
||||
|
||||
const prompt = require("custom-electron-prompt");
|
||||
const promptOptions = require("./providers/prompt-options");
|
||||
|
||||
// true only if in-app-menu was loaded on launch
|
||||
const inAppMenuActive = config.plugins.isEnabled("in-app-menu");
|
||||
|
||||
@ -157,6 +160,14 @@ const mainMenuTemplate = (win) => {
|
||||
{
|
||||
label: "Advanced options",
|
||||
submenu: [
|
||||
{
|
||||
label: "Proxy",
|
||||
type: "checkbox",
|
||||
checked: !!config.get("options.proxy"),
|
||||
click: (item) => {
|
||||
setProxy(item, win);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Disable hardware acceleration",
|
||||
type: "checkbox",
|
||||
@ -283,3 +294,25 @@ module.exports.setApplicationMenu = (win) => {
|
||||
const menu = Menu.buildFromTemplate(menuTemplate);
|
||||
Menu.setApplicationMenu(menu);
|
||||
};
|
||||
|
||||
async function setProxy(item, win) {
|
||||
const output = await prompt({
|
||||
title: 'Set Proxy',
|
||||
label: 'Enter Proxy Address: (leave empty to disable)',
|
||||
value: config.get("options.proxy"),
|
||||
type: 'input',
|
||||
inputAttrs: {
|
||||
type: 'url',
|
||||
placeholder: "Example: 'socks5://127.0.0.1:9999"
|
||||
},
|
||||
width: 450,
|
||||
...promptOptions()
|
||||
}, win);
|
||||
|
||||
if (typeof output === "string") {
|
||||
config.set("options.proxy", output);
|
||||
item.checked = output !== "";
|
||||
} else { //user pressed cancel
|
||||
item.checked = !item.checked; //reset checkbox
|
||||
}
|
||||
}
|
||||
|
||||
13
package.json
13
package.json
@ -52,9 +52,8 @@
|
||||
"build:mac": "yarn run clean && electron-builder --mac",
|
||||
"build:win": "yarn run clean && electron-builder --win",
|
||||
"lint": "xo",
|
||||
"plugins": "yarn run plugin:adblocker && yarn run plugin:autoconfirm",
|
||||
"plugins": "yarn run plugin:adblocker",
|
||||
"plugin:adblocker": "rimraf plugins/adblocker/ad-blocker-engine.bin && node plugins/adblocker/blocker.js",
|
||||
"plugin:autoconfirm": "yarn run generate:package YoutubeNonStop",
|
||||
"release:linux": "yarn run clean && electron-builder --linux -p always -c.snap.publish=github",
|
||||
"release:mac": "yarn run clean && electron-builder --mac -p always",
|
||||
"release:win": "yarn run clean && electron-builder --win -p always"
|
||||
@ -64,12 +63,12 @@
|
||||
"npm": "Please use yarn and not npm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cliqz/adblocker-electron": "^1.22.5",
|
||||
"@cliqz/adblocker-electron": "^1.22.6",
|
||||
"@ffmpeg/core": "^0.10.0",
|
||||
"@ffmpeg/ffmpeg": "^0.10.0",
|
||||
"YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.9.0",
|
||||
"async-mutex": "^0.3.1",
|
||||
"async-mutex": "^0.3.2",
|
||||
"browser-id3-writer": "^4.4.0",
|
||||
"custom-electron-prompt": "^1.1.0",
|
||||
"chokidar": "^3.5.2",
|
||||
"custom-electron-titlebar": "^3.2.7",
|
||||
"discord-rpc": "^3.2.0",
|
||||
@ -83,13 +82,13 @@
|
||||
"filenamify": "^4.3.0",
|
||||
"md5": "^2.3.0",
|
||||
"mpris-service": "^2.1.2",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-fetch": "^2.6.2",
|
||||
"node-notifier": "^9.0.1",
|
||||
"ytdl-core": "^4.9.1",
|
||||
"ytpl": "^2.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^12.0.8",
|
||||
"electron": "^12.1.0",
|
||||
"electron-builder": "^22.10.5",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-icon-maker": "0.0.5",
|
||||
|
||||
25
plugins/audio-compressor/front.js
Normal file
25
plugins/audio-compressor/front.js
Normal file
@ -0,0 +1,25 @@
|
||||
const applyCompressor = () => {
|
||||
const videoElement = document.querySelector("video");
|
||||
|
||||
// If video element is not loaded yet try again
|
||||
if(videoElement === null) {
|
||||
setTimeout(applyCompressor, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
const audioContext = new AudioContext();
|
||||
|
||||
let compressor = audioContext.createDynamicsCompressor();
|
||||
compressor.threshold.value = -50;
|
||||
compressor.ratio.value = 12;
|
||||
compressor.knee.value = 40;
|
||||
compressor.attack.value = 0;
|
||||
compressor.release.value = 0.25;
|
||||
|
||||
const source = audioContext.createMediaElementSource(videoElement);
|
||||
|
||||
source.connect(compressor);
|
||||
compressor.connect(audioContext.destination);
|
||||
};
|
||||
|
||||
module.exports = applyCompressor;
|
||||
@ -1,12 +0,0 @@
|
||||
// Define global chrome object to be compliant with the extension code
|
||||
global.chrome = {
|
||||
runtime: {
|
||||
getManifest: () => ({
|
||||
version: 1
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = () => {
|
||||
require("YoutubeNonStop/autoconfirm.js");
|
||||
};
|
||||
@ -1,61 +1,151 @@
|
||||
const Discord = require("discord-rpc");
|
||||
const { dev } = require("electron-is");
|
||||
const { dialog } = require("electron");
|
||||
|
||||
const registerCallback = require("../../providers/song-info");
|
||||
|
||||
const rpc = new Discord.Client({
|
||||
transport: "ipc",
|
||||
});
|
||||
|
||||
// Application ID registered by @semvis123
|
||||
const clientId = "790655993809338398";
|
||||
|
||||
let clearActivity;
|
||||
/**
|
||||
* @typedef {Object} Info
|
||||
* @property {import('discord-rpc').Client} rpc
|
||||
* @property {boolean} ready
|
||||
* @property {import('../../providers/song-info').SongInfo} lastSongInfo
|
||||
*/
|
||||
/**
|
||||
* @type {Info}
|
||||
*/
|
||||
const info = {
|
||||
rpc: null,
|
||||
ready: false,
|
||||
lastSongInfo: null,
|
||||
};
|
||||
/**
|
||||
* @type {(() => void)[]}
|
||||
*/
|
||||
const refreshCallbacks = [];
|
||||
const resetInfo = () => {
|
||||
info.rpc = null;
|
||||
info.ready = false;
|
||||
clearTimeout(clearActivity);
|
||||
if (dev()) console.log("discord disconnected");
|
||||
refreshCallbacks.forEach(cb => cb());
|
||||
};
|
||||
|
||||
module.exports = (win, {activityTimoutEnabled, activityTimoutTime}) => {
|
||||
// If the page is ready, register the callback
|
||||
win.once("ready-to-show", () => {
|
||||
rpc.once("ready", () => {
|
||||
// Register the callback
|
||||
registerCallback((songInfo) => {
|
||||
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
|
||||
return;
|
||||
}
|
||||
// Song information changed, so lets update the rich presence
|
||||
const activityInfo = {
|
||||
details: songInfo.title,
|
||||
state: songInfo.artist,
|
||||
largeImageKey: "logo",
|
||||
largeImageText: [
|
||||
songInfo.uploadDate,
|
||||
songInfo.views.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " views"
|
||||
].join(' || '),
|
||||
buttons: [
|
||||
{ label: "Listen Along", url: songInfo.url },
|
||||
]
|
||||
};
|
||||
let window;
|
||||
const connect = (showErr = false) => {
|
||||
if (info.rpc) {
|
||||
if (dev())
|
||||
console.log('Attempted to connect with active RPC object');
|
||||
return;
|
||||
}
|
||||
|
||||
if (songInfo.isPaused) {
|
||||
// Add an idle icon to show that the song is paused
|
||||
activityInfo.smallImageKey = "idle";
|
||||
activityInfo.smallImageText = "idle/paused";
|
||||
// Set start the timer so the activity gets cleared after a while if enabled
|
||||
if (activityTimoutEnabled)
|
||||
clearActivity = setTimeout(()=>rpc.clearActivity(), activityTimoutTime||10000);
|
||||
} else {
|
||||
// stop the clear activity timout
|
||||
clearTimeout(clearActivity);
|
||||
// Add the start and end time of the song
|
||||
const songStartTime = Date.now() - songInfo.elapsedSeconds * 1000;
|
||||
activityInfo.startTimestamp = songStartTime;
|
||||
activityInfo.endTimestamp =
|
||||
songStartTime + songInfo.songDuration * 1000;
|
||||
}
|
||||
info.rpc = new Discord.Client({
|
||||
transport: "ipc",
|
||||
});
|
||||
info.ready = false;
|
||||
|
||||
rpc.setActivity(activityInfo);
|
||||
});
|
||||
});
|
||||
info.rpc.once("connected", () => {
|
||||
if (dev()) console.log("discord connected");
|
||||
refreshCallbacks.forEach(cb => cb());
|
||||
});
|
||||
info.rpc.once("ready", () => {
|
||||
info.ready = true;
|
||||
if (info.lastSongInfo) updateActivity(info.lastSongInfo)
|
||||
});
|
||||
info.rpc.once("disconnected", resetInfo);
|
||||
|
||||
// Startup the rpc client
|
||||
rpc.login({ clientId }).catch(console.error);
|
||||
// Startup the rpc client
|
||||
info.rpc.login({ clientId }).catch(err => {
|
||||
resetInfo();
|
||||
if (dev()) console.error(err);
|
||||
if (showErr) dialog.showMessageBox(window, { title: 'Connection failed', message: err.message || String(err), type: 'error' });
|
||||
});
|
||||
};
|
||||
|
||||
let clearActivity;
|
||||
/**
|
||||
* @type {import('../../providers/song-info').songInfoCallback}
|
||||
*/
|
||||
let updateActivity;
|
||||
|
||||
module.exports = (win, {activityTimoutEnabled, activityTimoutTime, listenAlong}) => {
|
||||
window = win;
|
||||
// We get multiple events
|
||||
// Next song: PAUSE(n), PAUSE(n+1), PLAY(n+1)
|
||||
// Skip time: PAUSE(N), PLAY(N)
|
||||
updateActivity = songInfo => {
|
||||
if (songInfo.title.length === 0 && songInfo.artist.length === 0) {
|
||||
return;
|
||||
}
|
||||
info.lastSongInfo = songInfo;
|
||||
|
||||
// stop the clear activity timout
|
||||
clearTimeout(clearActivity);
|
||||
|
||||
// stop early if discord connection is not ready
|
||||
// do this after clearTimeout to avoid unexpected clears
|
||||
if (!info.rpc || !info.ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clear directly if timeout is 0
|
||||
if (songInfo.isPaused && activityTimoutEnabled && activityTimoutTime === 0) {
|
||||
info.rpc.clearActivity().catch(console.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Song information changed, so lets update the rich presence
|
||||
// @see https://discord.com/developers/docs/topics/gateway#activity-object
|
||||
// not all options are transfered through https://github.com/discordjs/RPC/blob/6f83d8d812c87cb7ae22064acd132600407d7d05/src/client.js#L518-530
|
||||
const activityInfo = {
|
||||
type: 2, // Listening, addressed in https://github.com/discordjs/RPC/pull/149
|
||||
details: songInfo.title,
|
||||
state: songInfo.artist,
|
||||
largeImageKey: "logo",
|
||||
largeImageText: [
|
||||
songInfo.uploadDate,
|
||||
songInfo.views.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " views",
|
||||
].join(' || '),
|
||||
buttons: listenAlong ? [
|
||||
{ label: "Listen Along", url: songInfo.url },
|
||||
] : undefined,
|
||||
};
|
||||
|
||||
if (songInfo.isPaused) {
|
||||
// Add an idle icon to show that the song is paused
|
||||
activityInfo.smallImageKey = "idle";
|
||||
activityInfo.smallImageText = "idle/paused";
|
||||
// Set start the timer so the activity gets cleared after a while if enabled
|
||||
if (activityTimoutEnabled)
|
||||
clearActivity = setTimeout(() => info.rpc.clearActivity().catch(console.error), activityTimoutTime ?? 10000);
|
||||
} else {
|
||||
// Add the start and end time of the song
|
||||
const songStartTime = Date.now() - songInfo.elapsedSeconds * 1000;
|
||||
activityInfo.startTimestamp = songStartTime;
|
||||
activityInfo.endTimestamp =
|
||||
songStartTime + songInfo.songDuration * 1000;
|
||||
}
|
||||
|
||||
info.rpc.setActivity(activityInfo).catch(console.error);
|
||||
};
|
||||
|
||||
// If the page is ready, register the callback
|
||||
win.once("ready-to-show", () => {
|
||||
registerCallback(updateActivity);
|
||||
connect();
|
||||
});
|
||||
win.on("close", () => module.exports.clear());
|
||||
};
|
||||
|
||||
module.exports.clear = () => {
|
||||
if (info.rpc) info.rpc.clearActivity();
|
||||
clearTimeout(clearActivity);
|
||||
};
|
||||
module.exports.connect = connect;
|
||||
module.exports.registerRefresh = (cb) => refreshCallbacks.push(cb);
|
||||
/**
|
||||
* @type {Info}
|
||||
*/
|
||||
module.exports.info = Object.defineProperties({}, Object.keys(info).reduce((o, k) => ({ ...o, [k]: { enumerable: true, get: () => info[k] } }), {}));
|
||||
|
||||
47
plugins/discord/menu.js
Normal file
47
plugins/discord/menu.js
Normal file
@ -0,0 +1,47 @@
|
||||
const { setOptions } = require("../../config/plugins");
|
||||
const { edit } = require("../../config");
|
||||
const { clear, info, connect, registerRefresh } = require("./back");
|
||||
|
||||
let hasRegisterred = false;
|
||||
|
||||
module.exports = (win, options, refreshMenu) => {
|
||||
if (!hasRegisterred) {
|
||||
registerRefresh(refreshMenu);
|
||||
hasRegisterred = true;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: info.rpc !== null ? "Connected" : "Reconnect",
|
||||
enabled: info.rpc === null,
|
||||
click: connect,
|
||||
},
|
||||
{
|
||||
label: "Clear activity",
|
||||
click: clear,
|
||||
},
|
||||
{
|
||||
label: "Clear activity after timeout",
|
||||
type: "checkbox",
|
||||
checked: options.activityTimoutEnabled,
|
||||
click: (item) => {
|
||||
options.activityTimoutEnabled = item.checked;
|
||||
setOptions('discord', options);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Listen Along",
|
||||
type: "checkbox",
|
||||
checked: options.listenAlong,
|
||||
click: (item) => {
|
||||
options.listenAlong = item.checked;
|
||||
setOptions('discord', options);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Set timeout time in config",
|
||||
// open config.json
|
||||
click: edit,
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,23 +1,9 @@
|
||||
const { isEnabled } = require("../../config/plugins");
|
||||
|
||||
/*
|
||||
This is used to determine if plugin is actually active
|
||||
(not if its only enabled in options)
|
||||
*/
|
||||
let enabled = false;
|
||||
|
||||
module.exports = (win) => {
|
||||
enabled = true;
|
||||
module.exports = () => enabled = true;
|
||||
|
||||
// youtube-music register some of the target listeners after DOMContentLoaded
|
||||
// did-finish-load is called after all elements finished loading, including said listeners
|
||||
// Thats the reason the timing is controlled from main
|
||||
win.webContents.once("did-finish-load", () => {
|
||||
win.webContents.send("restoreAddEventListener");
|
||||
win.webContents.send("setupVideoPlayerVolumeMousewheel", !isEnabled("hide-video-player"));
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.enabled = () => {
|
||||
return enabled;
|
||||
};
|
||||
module.exports.enabled = () => enabled;
|
||||
|
||||
@ -3,27 +3,73 @@ const { ipcRenderer, remote } = require("electron");
|
||||
const { setOptions } = require("../../config/plugins");
|
||||
|
||||
function $(selector) { return document.querySelector(selector); }
|
||||
let api;
|
||||
|
||||
module.exports = (options) => {
|
||||
document.addEventListener('apiLoaded', e => {
|
||||
api = e.detail;
|
||||
firstRun(options);
|
||||
})
|
||||
};
|
||||
|
||||
/** Restore saved volume and setup tooltip */
|
||||
function firstRun(options) {
|
||||
if (typeof options.savedVolume === "number") {
|
||||
// Set saved volume as tooltip
|
||||
setTooltip(options.savedVolume);
|
||||
|
||||
if (api.getVolume() !== options.savedVolume) {
|
||||
api.setVolume(options.savedVolume);
|
||||
}
|
||||
}
|
||||
|
||||
setupPlaybar(options);
|
||||
|
||||
setupSliderObserver(options);
|
||||
|
||||
setupLocalArrowShortcuts(options);
|
||||
|
||||
if (options.globalShortcuts?.enabled) {
|
||||
setupGlobalShortcuts(options);
|
||||
setupGlobalShortcuts(options);
|
||||
|
||||
const noVid = $("#main-panel")?.computedStyleMap().get("display").value === "none";
|
||||
injectVolumeHud(noVid);
|
||||
if (!noVid) {
|
||||
setupVideoPlayerOnwheel(options);
|
||||
}
|
||||
}
|
||||
|
||||
function injectVolumeHud(noVid) {
|
||||
if (noVid) {
|
||||
const position = "top: 18px; right: 60px; z-index: 999; position: absolute;";
|
||||
const mainStyle = "font-size: xx-large; padding: 10px; transition: opacity 1s";
|
||||
|
||||
$(".center-content.ytmusic-nav-bar").insertAdjacentHTML("beforeend",
|
||||
`<span id="volumeHud" style="${position + mainStyle}"></span>`)
|
||||
} else {
|
||||
const position = `top: 10px; left: 10px; z-index: 999; position: absolute;`;
|
||||
const mainStyle = "font-size: xxx-large; padding: 10px; transition: opacity 0.6s; webkit-text-stroke: 1px black; font-weight: 600;";
|
||||
|
||||
$("#song-video").insertAdjacentHTML('afterend',
|
||||
`<span id="volumeHud" style="${position + mainStyle}"></span>`)
|
||||
}
|
||||
}
|
||||
|
||||
let hudFadeTimeout;
|
||||
|
||||
function showVolumeHud(volume) {
|
||||
let volumeHud = $("#volumeHud");
|
||||
if (!volumeHud) return;
|
||||
|
||||
volumeHud.textContent = volume + '%';
|
||||
volumeHud.style.opacity = 1;
|
||||
|
||||
if (hudFadeTimeout) {
|
||||
clearTimeout(hudFadeTimeout);
|
||||
}
|
||||
|
||||
firstRun(options);
|
||||
|
||||
// This way the ipc listener gets cleared either way
|
||||
ipcRenderer.once("setupVideoPlayerVolumeMousewheel", (_event, toEnable) => {
|
||||
if (toEnable)
|
||||
setupVideoPlayerOnwheel(options);
|
||||
});
|
||||
};
|
||||
hudFadeTimeout = setTimeout(() => {
|
||||
volumeHud.style.opacity = 0;
|
||||
hudFadeTimeout = null;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/** Add onwheel event to video player */
|
||||
function setupVideoPlayerOnwheel(options) {
|
||||
@ -34,35 +80,20 @@ function setupVideoPlayerOnwheel(options) {
|
||||
});
|
||||
}
|
||||
|
||||
function toPercent(volume) {
|
||||
return Math.round(Number.parseFloat(volume) * 100);
|
||||
}
|
||||
|
||||
function saveVolume(volume, options) {
|
||||
options.savedVolume = volume;
|
||||
setOptions("precise-volume", options);
|
||||
writeOptions(options);
|
||||
}
|
||||
|
||||
/** Restore saved volume and setup tooltip */
|
||||
function firstRun(options) {
|
||||
const videoStream = $(".video-stream");
|
||||
const slider = $("#volume-slider");
|
||||
// Those elements load abit after DOMContentLoaded
|
||||
if (videoStream && slider) {
|
||||
// Set saved volume IF it pass checks
|
||||
if (options.savedVolume
|
||||
&& options.savedVolume >= 0 && options.savedVolume <= 100
|
||||
&& Math.abs(slider.value - options.savedVolume) < 5
|
||||
// If plugin was disabled and volume changed then diff>4
|
||||
) {
|
||||
videoStream.volume = options.savedVolume / 100;
|
||||
slider.value = options.savedVolume;
|
||||
}
|
||||
// Set current volume as tooltip
|
||||
setTooltip(toPercent(videoStream.volume));
|
||||
} else {
|
||||
setTimeout(firstRun, 500, options); // Try again in 500 milliseconds
|
||||
}
|
||||
//without this function it would rewrite config 20 time when volume change by 20
|
||||
let writeTimeout;
|
||||
function writeOptions(options) {
|
||||
if (writeTimeout) clearTimeout(writeTimeout);
|
||||
|
||||
writeTimeout = setTimeout(() => {
|
||||
setOptions("precise-volume", options);
|
||||
writeTimeout = null;
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
/** Add onwheel event to play bar and also track if play bar is hovered*/
|
||||
@ -83,32 +114,63 @@ function setupPlaybar(options) {
|
||||
playerbar.addEventListener("mouseleave", () => {
|
||||
playerbar.classList.remove("on-hover");
|
||||
});
|
||||
|
||||
setupSliderObserver(options);
|
||||
}
|
||||
|
||||
/** Save volume + Update the volume tooltip when volume-slider is manually changed */
|
||||
function setupSliderObserver(options) {
|
||||
const sliderObserver = new MutationObserver(mutations => {
|
||||
for (const mutation of mutations) {
|
||||
// This checks that volume-slider was manually set
|
||||
if (mutation.oldValue !== mutation.target.value &&
|
||||
(typeof options.savedVolume !== "number" || Math.abs(options.savedVolume - mutation.target.value) > 4)) {
|
||||
// Diff>4 means it was manually set
|
||||
setTooltip(mutation.target.value);
|
||||
saveVolume(mutation.target.value, options);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Observing only changes in 'value' of volume-slider
|
||||
sliderObserver.observe($("#volume-slider"), {
|
||||
attributeFilter: ["value"],
|
||||
attributeOldValue: true
|
||||
});
|
||||
}
|
||||
|
||||
/** if (toIncrease = false) then volume decrease */
|
||||
function changeVolume(toIncrease, options) {
|
||||
// Need to change both the actual volume and the slider
|
||||
const videoStream = $(".video-stream");
|
||||
const slider = $("#volume-slider");
|
||||
// Apply volume change if valid
|
||||
const steps = (options.steps || 1) / 100;
|
||||
videoStream.volume = toIncrease ?
|
||||
Math.min(videoStream.volume + steps, 1) :
|
||||
Math.max(videoStream.volume - steps, 0);
|
||||
const steps = (options.steps || 1);
|
||||
api.setVolume(toIncrease ?
|
||||
Math.min(api.getVolume() + steps, 100) :
|
||||
Math.max(api.getVolume() - steps, 0));
|
||||
|
||||
// Save the new volume
|
||||
saveVolume(toPercent(videoStream.volume), options);
|
||||
// Slider value automatically rounds to multiples of 5
|
||||
slider.value = options.savedVolume;
|
||||
saveVolume(api.getVolume(), options);
|
||||
|
||||
// change slider position (important)
|
||||
updateVolumeSlider(options);
|
||||
|
||||
// Change tooltips to new value
|
||||
setTooltip(options.savedVolume);
|
||||
// Show volume slider on volume change
|
||||
showVolumeSlider(slider);
|
||||
// Show volume slider
|
||||
showVolumeSlider();
|
||||
// Show volume HUD
|
||||
showVolumeHud(options.savedVolume);
|
||||
}
|
||||
|
||||
function updateVolumeSlider(options) {
|
||||
// Slider value automatically rounds to multiples of 5
|
||||
$("#volume-slider").value = options.savedVolume > 0 && options.savedVolume < 5 ?
|
||||
5 : options.savedVolume;
|
||||
}
|
||||
|
||||
let volumeHoverTimeoutID;
|
||||
|
||||
function showVolumeSlider(slider) {
|
||||
function showVolumeSlider() {
|
||||
const slider = $("#volume-slider");
|
||||
// This class display the volume slider if not in minimized mode
|
||||
slider.classList.add("on-hover");
|
||||
// Reset timeout if previous one hasn't completed
|
||||
@ -124,27 +186,6 @@ function showVolumeSlider(slider) {
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/** Save volume + Update the volume tooltip when volume-slider is manually changed */
|
||||
function setupSliderObserver(options) {
|
||||
const sliderObserver = new MutationObserver(mutations => {
|
||||
for (const mutation of mutations) {
|
||||
// This checks that volume-slider was manually set
|
||||
if (mutation.oldValue !== mutation.target.value &&
|
||||
(!options.savedVolume || Math.abs(options.savedVolume - mutation.target.value) > 4)) {
|
||||
// Diff>4 means it was manually set
|
||||
setTooltip(mutation.target.value);
|
||||
saveVolume(mutation.target.value, options);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Observing only changes in 'value' of volume-slider
|
||||
sliderObserver.observe($("#volume-slider"), {
|
||||
attributeFilter: ["value"],
|
||||
attributeOldValue: true
|
||||
});
|
||||
}
|
||||
|
||||
// Set new volume as tooltip for volume slider and icon + expanding slider (appears when window size is small)
|
||||
const tooltipTargets = [
|
||||
"#volume-slider",
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
const { enabled } = require("./back");
|
||||
const { setOptions } = require("../../config/plugins");
|
||||
const prompt = require("custom-electron-prompt");
|
||||
const promptOptions = require("../../providers/prompt-options");
|
||||
|
||||
|
||||
module.exports = (win, options) => [
|
||||
{
|
||||
label: "Arrowkeys controls",
|
||||
label: "Local Arrowkeys Controls",
|
||||
type: "checkbox",
|
||||
checked: !!options.arrowsShortcut,
|
||||
click: (item) => {
|
||||
// Dynamically change setting if plugin enabled
|
||||
click: item => {
|
||||
// Dynamically change setting if plugin is enabled
|
||||
if (enabled()) {
|
||||
win.webContents.send("setArrowsShortcut", item.checked);
|
||||
} else { // Fallback to usual method if disabled
|
||||
@ -15,5 +18,61 @@ module.exports = (win, options) => [
|
||||
setOptions("precise-volume", options);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Global Hotkeys",
|
||||
type: "checkbox",
|
||||
checked: !!options.globalShortcuts.volumeUp || !!options.globalShortcuts.volumeDown,
|
||||
click: item => promptGlobalShortcuts(win, options, item)
|
||||
},
|
||||
{
|
||||
label: "Set Custom Volume Steps",
|
||||
click: () => promptVolumeSteps(win, options)
|
||||
}
|
||||
];
|
||||
|
||||
// Helper function for globalShortcuts prompt
|
||||
const kb = (label_, value_, default_) => { return { value: value_, label: label_, default: default_ || undefined }; };
|
||||
|
||||
async function promptVolumeSteps(win, options) {
|
||||
const output = await prompt({
|
||||
title: "Volume Steps",
|
||||
label: "Choose Volume Increase/Decrease Steps",
|
||||
value: options.steps || 1,
|
||||
type: "counter",
|
||||
counterOptions: { minimum: 0, maximum: 100, multiFire: true },
|
||||
width: 380,
|
||||
...promptOptions()
|
||||
}, win)
|
||||
|
||||
if (output || output === 0) { // 0 is somewhat valid
|
||||
options.steps = output;
|
||||
setOptions("precise-volume", options);
|
||||
}
|
||||
}
|
||||
|
||||
async function promptGlobalShortcuts(win, options, item) {
|
||||
const output = await prompt({
|
||||
title: "Global Volume Keybinds",
|
||||
label: "Choose Global Volume Keybinds:",
|
||||
type: "keybind",
|
||||
keybindOptions: [
|
||||
kb("Increase Volume", "volumeUp", options.globalShortcuts?.volumeUp),
|
||||
kb("Decrease Volume", "volumeDown", options.globalShortcuts?.volumeDown)
|
||||
],
|
||||
...promptOptions()
|
||||
}, win)
|
||||
|
||||
if (output) {
|
||||
for (const { value, accelerator } of output) {
|
||||
options.globalShortcuts[value] = accelerator;
|
||||
}
|
||||
|
||||
setOptions("precise-volume", options);
|
||||
|
||||
item.checked = !!options.globalShortcuts.volumeUp || !!options.globalShortcuts.volumeDown;
|
||||
} else {
|
||||
// Reset checkbox if prompt was canceled
|
||||
item.checked = !item.checked;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,10 +24,10 @@ function overrideAddEventListener() {
|
||||
|
||||
module.exports = () => {
|
||||
overrideAddEventListener();
|
||||
// Restore original function after did-finish-load to avoid keeping Element.prototype altered
|
||||
ipcRenderer.once("restoreAddEventListener", () => { // Called from main to make sure page is completly loaded
|
||||
// Restore original function after finished loading to avoid keeping Element.prototype altered
|
||||
window.addEventListener('load', () => {
|
||||
Element.prototype.addEventListener = Element.prototype._addEventListener;
|
||||
Element.prototype._addEventListener = undefined;
|
||||
ignored = undefined;
|
||||
});
|
||||
}, { once: true });
|
||||
};
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
const { globalShortcut } = require("electron");
|
||||
const is = require("electron-is");
|
||||
const electronLocalshortcut = require("electron-localshortcut");
|
||||
|
||||
const getSongControls = require("../../providers/song-controls");
|
||||
const { setupMPRIS } = require("./mpris");
|
||||
const registerCallback = require("../../providers/song-info");
|
||||
|
||||
let player;
|
||||
|
||||
function _registerGlobalShortcut(webContents, shortcut, action) {
|
||||
globalShortcut.register(shortcut, () => {
|
||||
@ -21,47 +23,89 @@ function registerShortcuts(win, options) {
|
||||
const songControls = getSongControls(win);
|
||||
const { playPause, next, previous, search } = songControls;
|
||||
|
||||
_registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause);
|
||||
_registerGlobalShortcut(win.webContents, "MediaNextTrack", next);
|
||||
_registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previous);
|
||||
if (options.overrideMediaKeys) {
|
||||
_registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause);
|
||||
_registerGlobalShortcut(win.webContents, "MediaNextTrack", next);
|
||||
_registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previous);
|
||||
}
|
||||
|
||||
_registerLocalShortcut(win, "CommandOrControl+F", search);
|
||||
_registerLocalShortcut(win, "CommandOrControl+L", search);
|
||||
registerCallback(songInfo => {
|
||||
if (player) {
|
||||
player.metadata = {
|
||||
'mpris:length': songInfo.songDuration * 60 * 1000 * 1000, // In microseconds
|
||||
'mpris:artUrl': songInfo.imageSrc,
|
||||
'xesam:title': songInfo.title,
|
||||
'xesam:artist': songInfo.artist
|
||||
};
|
||||
if (!songInfo.isPaused) {
|
||||
player.playbackStatus = "Playing"
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (is.linux()) {
|
||||
try {
|
||||
const player = setupMPRIS();
|
||||
const MPRISPlayer = setupMPRIS();
|
||||
|
||||
player.on("raise", () => {
|
||||
MPRISPlayer.on("raise", () => {
|
||||
win.setSkipTaskbar(false);
|
||||
win.show();
|
||||
});
|
||||
player.on("playpause", playPause);
|
||||
player.on("next", next);
|
||||
player.on("previous", previous);
|
||||
MPRISPlayer.on("play", () => {
|
||||
if (MPRISPlayer.playbackStatus !== 'Playing') {
|
||||
MPRISPlayer.playbackStatus = 'Playing';
|
||||
playPause()
|
||||
}
|
||||
});
|
||||
MPRISPlayer.on("pause", () => {
|
||||
if (MPRISPlayer.playbackStatus !== 'Paused') {
|
||||
MPRISPlayer.playbackStatus = 'Paused';
|
||||
playPause()
|
||||
}
|
||||
});
|
||||
MPRISPlayer.on("next", () => {
|
||||
next()
|
||||
});
|
||||
MPRISPlayer.on("previous", () => {
|
||||
previous()
|
||||
});
|
||||
|
||||
player = MPRISPlayer
|
||||
|
||||
} catch (e) {
|
||||
console.warn("Error in MPRIS", e);
|
||||
}
|
||||
}
|
||||
|
||||
const { global, local } = options;
|
||||
(global || []).forEach(({ shortcut, action }) => {
|
||||
console.debug("Registering global shortcut", shortcut, ":", action);
|
||||
if (!action || !songControls[action]) {
|
||||
console.warn("Invalid action", action);
|
||||
return;
|
||||
}
|
||||
const shortcutOptions = { global, local };
|
||||
|
||||
_registerGlobalShortcut(win.webContents, shortcut, songControls[action]);
|
||||
});
|
||||
(local || []).forEach(({ shortcut, action }) => {
|
||||
console.debug("Registering local shortcut", shortcut, ":", action);
|
||||
if (!action || !songControls[action]) {
|
||||
console.warn("Invalid action", action);
|
||||
return;
|
||||
}
|
||||
for (const optionType in shortcutOptions) {
|
||||
registerAllShortcuts(shortcutOptions[optionType], optionType);
|
||||
}
|
||||
|
||||
_registerLocalShortcut(win, shortcut, songControls[action]);
|
||||
});
|
||||
function registerAllShortcuts(container, type) {
|
||||
for (const action in container) {
|
||||
if (!container[action]) {
|
||||
continue; // Action accelerator is empty
|
||||
}
|
||||
|
||||
console.debug(`Registering ${type} shortcut`, container[action], ":", action);
|
||||
if (!songControls[action]) {
|
||||
console.warn("Invalid action", action);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type === "global") {
|
||||
_registerGlobalShortcut(win.webContents, container[action], songControls[action]);
|
||||
} else { // type === "local"
|
||||
_registerLocalShortcut(win, local[action], songControls[action]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = registerShortcuts;
|
||||
|
||||
53
plugins/shortcuts/menu.js
Normal file
53
plugins/shortcuts/menu.js
Normal file
@ -0,0 +1,53 @@
|
||||
const { setOptions } = require("../../config/plugins");
|
||||
const prompt = require("custom-electron-prompt");
|
||||
const promptOptions = require("../../providers/prompt-options");
|
||||
|
||||
module.exports = (win, options) => [
|
||||
{
|
||||
label: "Set Global Song Controls",
|
||||
click: () => promptKeybind(options, win)
|
||||
},
|
||||
{
|
||||
label: "Override MediaKeys",
|
||||
type: "checkbox",
|
||||
checked: options.overrideMediaKeys,
|
||||
click: item => setOption(options, "overrideMediaKeys", item.checked)
|
||||
}
|
||||
];
|
||||
|
||||
function setOption(options, key = null, newValue = null) {
|
||||
if (key && newValue !== null) {
|
||||
options[key] = newValue;
|
||||
}
|
||||
|
||||
setOptions("shortcuts", options);
|
||||
}
|
||||
|
||||
// Helper function for keybind prompt
|
||||
const kb = (label_, value_, default_) => { return { value: value_, label: label_, default: default_ }; };
|
||||
|
||||
async function promptKeybind(options, win) {
|
||||
const output = await prompt({
|
||||
title: "Global Keybinds",
|
||||
label: "Choose Global Keybinds for Songs Control:",
|
||||
type: "keybind",
|
||||
keybindOptions: [ // If default=undefined then no default is used
|
||||
kb("Previous", "previous", options.global?.previous),
|
||||
kb("Play / Pause", "playPause", options.global?.playPause),
|
||||
kb("Next", "next", options.global?.next)
|
||||
],
|
||||
height: 270,
|
||||
...promptOptions()
|
||||
}, win);
|
||||
|
||||
if (output) {
|
||||
if (!options.global) {
|
||||
options.global = {};
|
||||
}
|
||||
for (const { value, accelerator } of output) {
|
||||
options.global[value] = accelerator;
|
||||
}
|
||||
setOption(options);
|
||||
}
|
||||
// else -> pressed cancel
|
||||
}
|
||||
@ -5,7 +5,6 @@ const { remote } = require("electron");
|
||||
const config = require("./config");
|
||||
const { fileExists } = require("./plugins/utils");
|
||||
const setupFrontLogger = require("./providers/front-logger");
|
||||
const setupSongControl = require("./providers/song-controls-front");
|
||||
const setupSongInfo = require("./providers/song-info-front");
|
||||
|
||||
const plugins = config.plugins.getEnabled();
|
||||
@ -46,15 +45,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// inject song-info provider
|
||||
setupSongInfo();
|
||||
|
||||
// inject song-control provider
|
||||
setupSongControl();
|
||||
|
||||
// inject front logger
|
||||
setupFrontLogger();
|
||||
|
||||
// Add action for reloading
|
||||
global.reload = () =>
|
||||
remote.getCurrentWindow().webContents.loadURL(config.get("url"));
|
||||
|
||||
// Blocks the "Are You Still There?" popup by setting the last active time to Date.now every 15min
|
||||
setInterval(() => window._lact = Date.now(), 900000);
|
||||
});
|
||||
|
||||
function listenForApiLoad() {
|
||||
@ -80,6 +79,6 @@ function onApiLoaded() {
|
||||
|
||||
// Remove upgrade button
|
||||
if (config.get("options.removeUpgradeButton")) {
|
||||
document.querySelector('ytmusic-pivot-bar-item-renderer[tab-id="SPunlimited"]').style.display = "none";
|
||||
document.querySelector('ytmusic-pivot-bar-item-renderer[tab-id="SPunlimited"]')?.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
14
providers/prompt-custom-titlebar.js
Normal file
14
providers/prompt-custom-titlebar.js
Normal file
@ -0,0 +1,14 @@
|
||||
const customTitlebar = require("custom-electron-titlebar");
|
||||
|
||||
module.exports = () => {
|
||||
new customTitlebar.Titlebar({
|
||||
backgroundColor: customTitlebar.Color.fromHex("#050505"),
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
menu: null
|
||||
});
|
||||
const mainStyle = document.querySelector("#container").style;
|
||||
mainStyle.width = "100%";
|
||||
mainStyle.position = "fixed";
|
||||
mainStyle.border = "unset";
|
||||
};
|
||||
18
providers/prompt-options.js
Normal file
18
providers/prompt-options.js
Normal file
@ -0,0 +1,18 @@
|
||||
const path = require("path");
|
||||
const is = require("electron-is");
|
||||
|
||||
const iconPath = path.join(__dirname, "..", "assets", "youtube-music-tray.png");
|
||||
const customTitlebarPath = path.join(__dirname, "prompt-custom-titlebar.js");
|
||||
|
||||
const promptOptions = is.macOS() ? {
|
||||
customStylesheet: "dark",
|
||||
icon: iconPath
|
||||
} : {
|
||||
customStylesheet: "dark",
|
||||
// The following are used for custom titlebar
|
||||
frame: false,
|
||||
customScript: customTitlebarPath,
|
||||
enableRemoteModule: true
|
||||
};
|
||||
|
||||
module.exports = () => promptOptions;
|
||||
@ -1,18 +0,0 @@
|
||||
const { ipcRenderer } = require("electron");
|
||||
|
||||
let videoStream = document.querySelector(".video-stream");
|
||||
module.exports = () => {
|
||||
ipcRenderer.on("playPause", () => {
|
||||
if (!videoStream) {
|
||||
videoStream = document.querySelector(".video-stream");
|
||||
}
|
||||
|
||||
if (videoStream.paused) {
|
||||
videoStream.play();
|
||||
} else {
|
||||
videoStream.yns_pause ?
|
||||
videoStream.yns_pause() :
|
||||
videoStream.pause();
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -12,7 +12,7 @@ module.exports = (win) => {
|
||||
// Playback
|
||||
previous: () => pressKey(win, "k"),
|
||||
next: () => pressKey(win, "j"),
|
||||
playPause: () => win.webContents.send("playPause"),
|
||||
playPause: () => pressKey(win, "space"),
|
||||
like: () => pressKey(win, "_"),
|
||||
dislike: () => pressKey(win, "+"),
|
||||
go10sBack: () => pressKey(win, "h"),
|
||||
|
||||
@ -40,6 +40,9 @@ const getArtist = async (win) => {
|
||||
}
|
||||
|
||||
// Fill songInfo with empty values
|
||||
/**
|
||||
* @typedef {songInfo} SongInfo
|
||||
*/
|
||||
const songInfo = {
|
||||
title: "",
|
||||
artist: "",
|
||||
@ -72,6 +75,14 @@ const handleData = async (responseText, win) => {
|
||||
const callbacks = [];
|
||||
|
||||
// This function will allow plugins to register callback that will be triggered when data changes
|
||||
/**
|
||||
* @callback songInfoCallback
|
||||
* @param {songInfo} songInfo
|
||||
* @returns {void}
|
||||
*/
|
||||
/**
|
||||
* @param {songInfoCallback} callback
|
||||
*/
|
||||
const registerCallback = (callback) => {
|
||||
callbacks.push(callback);
|
||||
};
|
||||
|
||||
@ -35,7 +35,6 @@ Install the `youtube-music-bin` package from the AUR. For AUR installation instr
|
||||
|
||||
## Available plugins:
|
||||
- **Ad Blocker**: block all ads and tracking out of the box
|
||||
- **Auto confirm when paused**: when the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png) modal appears, automatically click "Yes"
|
||||
- **Disable autoplay**: makes every song start in "paused" mode
|
||||
- [**Discord**](https://discord.com/): show your friends what you listen to with [Rich Presence](https://user-images.githubusercontent.com/28219076/104362104-a7a0b980-5513-11eb-9744-bb89eabe0016.png)
|
||||
- **Downloader**: downloads MP3 [directly from the interface](https://user-images.githubusercontent.com/61631665/129977677-83a7d067-c192-45e1-98ae-b5a4927393be.png) [(youtube-dl)](https://github.com/ytdl-org/youtube-dl)
|
||||
@ -51,6 +50,7 @@ Install the `youtube-music-bin` package from the AUR. For AUR installation instr
|
||||
- [**SponsorBlock**](https://github.com/ajayyy/SponsorBlock): skips non-music parts
|
||||
- **Taskbar media control**: control app from your [Windows taskbar](https://user-images.githubusercontent.com/78568641/111916130-24a35e80-8a82-11eb-80c8-5021c1aa27f4.png)
|
||||
- **Touchbar**: custom TouchBar layout for macOS
|
||||
- **Auto confirm when paused** (Always Enabled): disable the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png) popup that pause music after a certain time
|
||||
|
||||
## Dev
|
||||
|
||||
|
||||
135
yarn.lock
135
yarn.lock
@ -439,45 +439,45 @@
|
||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@cliqz/adblocker-content@^1.22.5":
|
||||
version "1.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@cliqz/adblocker-content/-/adblocker-content-1.22.5.tgz#84a82a630b3824623fff203ffaa9f4831f902eaa"
|
||||
integrity sha512-79UAWMLZ5Na3FJey/RkYbUcPsmRbfkWq7LzUVffRR+yL8B24P6ptd+T7/qBFAULbJ0unp1DBtJ/i1P1QOPktXw==
|
||||
"@cliqz/adblocker-content@^1.22.7":
|
||||
version "1.22.7"
|
||||
resolved "https://registry.yarnpkg.com/@cliqz/adblocker-content/-/adblocker-content-1.22.7.tgz#02bfa1775d8be1efa82e9d5802cc17cfa9dc0712"
|
||||
integrity sha512-pAGxQ7sKCk7r0g/d7vyxY1MhkLfxfwMzd4WPL9234HmU/blCGCg1QL8wkjLTNYJERXZ+xoJ2dqMOisR/nE0ypA==
|
||||
dependencies:
|
||||
"@cliqz/adblocker-extended-selectors" "^1.22.5"
|
||||
"@cliqz/adblocker-extended-selectors" "^1.22.7"
|
||||
|
||||
"@cliqz/adblocker-electron-preload@^1.22.5":
|
||||
version "1.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron-preload/-/adblocker-electron-preload-1.22.5.tgz#3a9067279bdab0fd5e6d147b5a36c15c2273c83d"
|
||||
integrity sha512-O0Baz5SXT4cSGkwcmPiTFWmeLGFYy/vQxRsh2C8sJzkmCj8593rHOVgvVUL4M9o1wKTh+RycmWpoi6dWu75mZw==
|
||||
"@cliqz/adblocker-electron-preload@^1.22.7":
|
||||
version "1.22.7"
|
||||
resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron-preload/-/adblocker-electron-preload-1.22.7.tgz#1de6c4c3e66d566149cf60785b85d6f3fdb9a9ea"
|
||||
integrity sha512-1eUQvmmDd5Wi0ka48SRggd80ZBilnA7uRePni/cVGfH5LSecCs5WoIpEhuoc1G333DXeBv9W0sRc1wVlKBsszg==
|
||||
dependencies:
|
||||
"@cliqz/adblocker-content" "^1.22.5"
|
||||
"@cliqz/adblocker-content" "^1.22.7"
|
||||
|
||||
"@cliqz/adblocker-electron@^1.22.5":
|
||||
version "1.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron/-/adblocker-electron-1.22.5.tgz#3b45851d47c1835bb7f6476f4e29ae43606c90cf"
|
||||
integrity sha512-yAfbPszTT5ZDIA4PfPwoImdUJLhAMXZg9G2wfpZP3uadMZHS6E1wJaufjMelDDo/UL5xjH/Mi4ZCRmGZPhZ7Wg==
|
||||
"@cliqz/adblocker-electron@^1.22.6":
|
||||
version "1.22.7"
|
||||
resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron/-/adblocker-electron-1.22.7.tgz#bd29cf47835bc5c0c4d146333de3944096d23b4d"
|
||||
integrity sha512-/O5Dskkr7R/8RKOby04VZ7HbnU/OD6Kk+raLxyP858FOYccHOnBjkAcUcUzun2x0UXmHX9Cus8SJ3h8GbrG7ZA==
|
||||
dependencies:
|
||||
"@cliqz/adblocker" "^1.22.5"
|
||||
"@cliqz/adblocker-electron-preload" "^1.22.5"
|
||||
"@cliqz/adblocker" "^1.22.7"
|
||||
"@cliqz/adblocker-electron-preload" "^1.22.7"
|
||||
tldts-experimental "^5.6.21"
|
||||
|
||||
"@cliqz/adblocker-extended-selectors@^1.22.5":
|
||||
version "1.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@cliqz/adblocker-extended-selectors/-/adblocker-extended-selectors-1.22.5.tgz#228fdec6a58e313225a0be4266529f495432db0e"
|
||||
integrity sha512-5HTrNq1TaTXpTOddBo7z7GMEE7r44J6HusMVaDmbMpoA115YoQJyMfD82OYgoddj4Zdvp63/pjP+MBlE0WZyJg==
|
||||
"@cliqz/adblocker-extended-selectors@^1.22.7":
|
||||
version "1.22.7"
|
||||
resolved "https://registry.yarnpkg.com/@cliqz/adblocker-extended-selectors/-/adblocker-extended-selectors-1.22.7.tgz#ca0f219d773af5e8013287e5ddd8b00761359d87"
|
||||
integrity sha512-eHZWYJsgPZPaiLyQtZF7phDFoEqAzk54eno0P+Daal+QwiNixobsk3V4Uh1AVGFcmFEG/Z2CbC/vdWSHpSlImw==
|
||||
|
||||
"@cliqz/adblocker@^1.22.5":
|
||||
version "1.22.5"
|
||||
resolved "https://registry.yarnpkg.com/@cliqz/adblocker/-/adblocker-1.22.5.tgz#26e675078db9d9854f295a87e969db0c95a15764"
|
||||
integrity sha512-hQSyedelwG2+D6RDorJkarIUd9mln5IyxnRKO4ADGXvdQhZ5G/E90bTyJ2apDBdnvaUwXpmhhXs2cK12HhN14g==
|
||||
"@cliqz/adblocker@^1.22.7":
|
||||
version "1.22.7"
|
||||
resolved "https://registry.yarnpkg.com/@cliqz/adblocker/-/adblocker-1.22.7.tgz#e2ee745c663ad6862e627de240973bdb4f1dc100"
|
||||
integrity sha512-DxHlLJmV1q05F1I6BORqvBaOc43Vqts8c+vZdsjYdfiwfrPtuVRFYVTwZeMlZHBhmWx4HyxnfFakzJ8hw/B09A==
|
||||
dependencies:
|
||||
"@cliqz/adblocker-content" "^1.22.5"
|
||||
"@cliqz/adblocker-extended-selectors" "^1.22.5"
|
||||
"@cliqz/adblocker-content" "^1.22.7"
|
||||
"@cliqz/adblocker-extended-selectors" "^1.22.7"
|
||||
"@remusao/guess-url-type" "^1.1.2"
|
||||
"@remusao/small" "^1.1.2"
|
||||
"@remusao/smaz" "^1.7.1"
|
||||
"@types/chrome" "^0.0.154"
|
||||
"@types/chrome" "^0.0.157"
|
||||
"@types/firefox-webext-browser" "^82.0.0"
|
||||
tldts-experimental "^5.6.21"
|
||||
|
||||
@ -1208,10 +1208,10 @@
|
||||
"@types/node" "*"
|
||||
"@types/responselike" "*"
|
||||
|
||||
"@types/chrome@^0.0.154":
|
||||
version "0.0.154"
|
||||
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.154.tgz#7992e97364f4447e961028ad07ac843d0b052c2d"
|
||||
integrity sha512-6QmP744MeMUZUIUHED4d4L2la5dIF1e6bcrkGF4yGQTyO94ER+r++Ss165wkzA5cAGUYt8kToDa6L9xtNqVMxg==
|
||||
"@types/chrome@^0.0.157":
|
||||
version "0.0.157"
|
||||
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.157.tgz#5a50bd378f4f632383c6ebbc34c88fb87d501f58"
|
||||
integrity sha512-q5SSmA9nKaDzFi8QkJW9kW8MYwWg9O7PKPUdBxsz+3rPcIF1Kxw0Bexpd70Uq1mU6PN4DBp4qKMXQDybUeiI9w==
|
||||
dependencies:
|
||||
"@types/filesystem" "*"
|
||||
"@types/har-format" "*"
|
||||
@ -1316,12 +1316,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256"
|
||||
integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==
|
||||
|
||||
"@types/node@*":
|
||||
version "14.14.25"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.25.tgz#15967a7b577ff81383f9b888aa6705d43fbbae93"
|
||||
integrity sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==
|
||||
|
||||
"@types/node@^14.6.2":
|
||||
"@types/node@*", "@types/node@^14.6.2":
|
||||
version "14.14.44"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.44.tgz#df7503e6002847b834371c004b372529f3f85215"
|
||||
integrity sha512-+gaugz6Oce6ZInfI/tK4Pq5wIIkJMEJUu92RB3Eu93mtj4wjjjz9EB5mLp5s1pSsLXdC/CPut/xF20ZzAQJbTA==
|
||||
@ -1523,10 +1518,6 @@
|
||||
dependencies:
|
||||
"@wdio/logger" "6.10.10"
|
||||
|
||||
"YoutubeNonStop@git://github.com/lawfx/YoutubeNonStop.git#v0.9.0":
|
||||
version "0.0.0"
|
||||
resolved "git://github.com/lawfx/YoutubeNonStop.git#7b6b97b31bb3fd2078179660db0fd3fcc7062259"
|
||||
|
||||
abab@^2.0.3:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
|
||||
@ -1913,12 +1904,12 @@ async-exit-hook@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3"
|
||||
integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==
|
||||
|
||||
async-mutex@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.3.1.tgz#7033af665f1c7cebed8b878267a43ba9e77c5f67"
|
||||
integrity sha512-vRfQwcqBnJTLzVQo72Sf7KIUbcSUP5hNchx6udI1U6LuPQpfePgdjJzlCe76yFZ8pxlLjn9lwcl/Ya0TSOv0Tw==
|
||||
async-mutex@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.3.2.tgz#1485eda5bda1b0ec7c8df1ac2e815757ad1831df"
|
||||
integrity sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
tslib "^2.3.1"
|
||||
|
||||
async@0.9.x:
|
||||
version "0.9.2"
|
||||
@ -2965,6 +2956,11 @@ cssstyle@^2.2.0:
|
||||
dependencies:
|
||||
cssom "~0.3.6"
|
||||
|
||||
custom-electron-prompt@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/custom-electron-prompt/-/custom-electron-prompt-1.1.0.tgz#611b790047c91f6b532c7861355a0e1f9a81aef2"
|
||||
integrity sha512-YZYmwZnMOdoWROUlJ+rEMHYsp4XJNNqLj6sZnx5aKBJ8cprEjKP4L5wfo6U+yyX/L9fxVOtvYD0Mp8ki5I9Kow==
|
||||
|
||||
custom-electron-titlebar@^3.2.7:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/custom-electron-titlebar/-/custom-electron-titlebar-3.2.7.tgz#fb249d6180cbda074b1d392bea755fa0743012a8"
|
||||
@ -3511,10 +3507,10 @@ electron-updater@^4.4.6:
|
||||
lodash.isequal "^4.5.0"
|
||||
semver "^7.3.5"
|
||||
|
||||
electron@^12.0.8:
|
||||
version "12.0.8"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-12.0.8.tgz#e52583b2b4f1eaa6fbb0e3666b9907f99f1f24c7"
|
||||
integrity sha512-bN2wYNnnma7ugBsiwysO1LI6oTTV1lDHOXuWip+OGjDUiz0IiJmZ+r0gAIMMeypVohZh7AA1ftnKad7tJ8sH4A==
|
||||
electron@^12.1.0:
|
||||
version "12.1.0"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-12.1.0.tgz#615a7f9dbb2fc79cc72361fba9f39d005c697bca"
|
||||
integrity sha512-joQlYI/nTIrTUldO3GENZ2j225eKar9nTQBSEwSUSWN4h65QGDmXNQ7dbWPmLlkUQWtHhz8lXhFk30OLG9ZjLw==
|
||||
dependencies:
|
||||
"@electron/get" "^1.0.1"
|
||||
"@types/node" "^14.6.2"
|
||||
@ -6784,6 +6780,13 @@ node-fetch@^2.6.1:
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
node-fetch@^2.6.2:
|
||||
version "2.6.5"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd"
|
||||
integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
@ -8826,9 +8829,9 @@ tmp@^0.2.0:
|
||||
rimraf "^3.0.0"
|
||||
|
||||
tmpl@1.0.x:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
|
||||
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
|
||||
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
|
||||
|
||||
to-absolute-glob@^2.0.2:
|
||||
version "2.0.2"
|
||||
@ -8909,6 +8912,11 @@ tr46@^2.0.2:
|
||||
dependencies:
|
||||
punycode "^2.1.1"
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
|
||||
|
||||
trim-newlines@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
|
||||
@ -8943,10 +8951,10 @@ tslib@^1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
||||
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
||||
tslib@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
@ -9308,6 +9316,11 @@ webdriverio@^6.9.1:
|
||||
serialize-error "^8.0.0"
|
||||
webdriver "6.12.1"
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
|
||||
|
||||
webidl-conversions@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
|
||||
@ -9330,6 +9343,14 @@ whatwg-mimetype@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
|
||||
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
|
||||
|
||||
whatwg-url@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
||||
integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
|
||||
dependencies:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
whatwg-url@^8.0.0:
|
||||
version "8.4.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.4.0.tgz#50fb9615b05469591d2b2bd6dfaed2942ed72837"
|
||||
|
||||
Reference in New Issue
Block a user