mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-14 03:41:46 +00:00
Initial commit - app + 4 plugins
This commit is contained in:
1
plugins/adblocker/.gitignore
vendored
Normal file
1
plugins/adblocker/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
detector.buffer
|
||||
3
plugins/adblocker/back.js
Normal file
3
plugins/adblocker/back.js
Normal file
@ -0,0 +1,3 @@
|
||||
const { blockWindowAds } = require("./blocker");
|
||||
|
||||
module.exports = win => blockWindowAds(win.webContents);
|
||||
12
plugins/adblocker/blocker.js
Normal file
12
plugins/adblocker/blocker.js
Normal file
@ -0,0 +1,12 @@
|
||||
const { initialize, containsAds } = require("./contains-ads");
|
||||
|
||||
module.exports.blockWindowAds = webContents => {
|
||||
initialize();
|
||||
webContents.session.webRequest.onBeforeRequest(
|
||||
["*://*./*"],
|
||||
(details, cb) => {
|
||||
const shouldBeBlocked = containsAds(details.url);
|
||||
cb({ cancel: shouldBeBlocked });
|
||||
}
|
||||
);
|
||||
};
|
||||
24
plugins/adblocker/contains-ads.js
Normal file
24
plugins/adblocker/contains-ads.js
Normal file
@ -0,0 +1,24 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const Blocker = require("ad-block");
|
||||
|
||||
const client = new Blocker.AdBlockClient();
|
||||
const file = path.resolve(__dirname, "detector.buffer");
|
||||
|
||||
module.exports.client = client;
|
||||
module.exports.initialize = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
fs.readFile(file, (err, buffer) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
client.deserialize(buffer);
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
|
||||
const none = Blocker.FilterOptions.noFilterOption;
|
||||
const isAd = (req, base) => client.matches(req, none, base);
|
||||
|
||||
module.exports.containsAds = (req, base) => isAd(req, base);
|
||||
module.exports.isAd = isAd;
|
||||
67
plugins/adblocker/generator.js
Normal file
67
plugins/adblocker/generator.js
Normal file
@ -0,0 +1,67 @@
|
||||
// This file generates the detector buffer
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const Blocker = require("ad-block");
|
||||
const https = require("https");
|
||||
|
||||
const SOURCES = [
|
||||
"https://raw.githubusercontent.com/kbinani/adblock-youtube-ads/master/signed.txt"
|
||||
];
|
||||
|
||||
function parseAdblockList(client, adblockList) {
|
||||
const urls = adblockList.split("\n");
|
||||
const totalSize = urls.length;
|
||||
console.log(
|
||||
"Parsing " + totalSize + " urls (this can take a couple minutes)."
|
||||
);
|
||||
urls.map(line => client.parse(line));
|
||||
console.log("Created buffer.");
|
||||
}
|
||||
|
||||
function writeBuffer(client) {
|
||||
const output = path.resolve(__dirname, "detector.buffer");
|
||||
fs.writeFile(output, client.serialize(64), err => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
console.log("Wrote buffer to detector.buffer!");
|
||||
});
|
||||
}
|
||||
|
||||
function generateDetectorBuffer() {
|
||||
const client = new Blocker.AdBlockClient();
|
||||
let nbSourcesFetched = 0;
|
||||
|
||||
// fetch updated versions
|
||||
SOURCES.forEach(source => {
|
||||
console.log("Downloading " + source);
|
||||
https
|
||||
.get(source, resp => {
|
||||
let data = "";
|
||||
|
||||
// A chunk of data has been recieved.
|
||||
resp.on("data", chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
// The whole response has been received. Print out the result.
|
||||
resp.on("end", () => {
|
||||
parseAdblockList(client, data);
|
||||
nbSourcesFetched++;
|
||||
|
||||
if (nbSourcesFetched === SOURCES.length) {
|
||||
writeBuffer(client);
|
||||
}
|
||||
});
|
||||
})
|
||||
.on("error", err => {
|
||||
console.log("Error: " + err.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = generateDetectorBuffer;
|
||||
if (require.main === module) {
|
||||
generateDetectorBuffer();
|
||||
}
|
||||
24
plugins/navigation/actions.js
Normal file
24
plugins/navigation/actions.js
Normal file
@ -0,0 +1,24 @@
|
||||
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: CHANNEL,
|
||||
ACTIONS: ACTIONS,
|
||||
global: {
|
||||
goToNextPage: goToNextPage,
|
||||
goToPreviousPage: goToPreviousPage,
|
||||
}
|
||||
};
|
||||
23
plugins/navigation/back.js
Normal file
23
plugins/navigation/back.js
Normal file
@ -0,0 +1,23 @@
|
||||
const { listenAction } = require("../utils");
|
||||
const { ACTIONS, CHANNEL } = require("./actions.js");
|
||||
|
||||
function handle(win) {
|
||||
listenAction(CHANNEL, (event, 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = handle;
|
||||
14
plugins/navigation/front.js
Normal file
14
plugins/navigation/front.js
Normal file
@ -0,0 +1,14 @@
|
||||
const { ElementFromFile, templatePath } = require('../utils');
|
||||
|
||||
function run() {
|
||||
const forwardButton = ElementFromFile(templatePath(__dirname, 'forward.html'));
|
||||
const backButton = ElementFromFile(templatePath(__dirname, 'back.html'));
|
||||
const menu = document.querySelector("ytmusic-pivot-bar-renderer");
|
||||
|
||||
if (menu) {
|
||||
menu.prepend(forwardButton);
|
||||
menu.prepend(backButton);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = run;
|
||||
21
plugins/navigation/templates/back.html
Normal file
21
plugins/navigation/templates/back.html
Normal file
@ -0,0 +1,21 @@
|
||||
<ytmusic-pivot-bar-item-renderer class="style-scope ytmusic-pivot-bar-renderer" tab-id="FEmusic_back" role="tab" onclick="goToPreviousPage()">
|
||||
<yt-icon class="tab-icon style-scope ytmusic-pivot-bar-item-renderer">
|
||||
<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope yt-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;">
|
||||
<g class="style-scope yt-icon">
|
||||
<path class="st0" d="M109.3 265.2l218.9 218.9c5.1 5.1 11.8 7.9 19 7.9s14-2.8 19-7.9l16.1-16.1c10.5-10.5 10.5-27.6 0-38.1L198.6 246.1 382.7 62c5.1-5.1 7.9-11.8 7.9-19 0-7.2-2.8-14-7.9-19L366.5 7.9c-5.1-5.1-11.8-7.9-19-7.9-7.2 0-14 2.8-19 7.9L109.3 227c-5.1 5.1-7.9 11.9-7.8 19.1 0 7.2 2.8 14 7.8 19.1z" class="style-scope yt-icon">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
</yt-icon>
|
||||
|
||||
<paper-icon-button class="search-icon style-scope ytmusic-search-box" role="button" tabindex="0" aria-disabled="false" title="Go to previous page">
|
||||
<iron-icon id="icon" class="style-scope paper-icon-button">
|
||||
<svg viewBox="0 0 492 492" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope iron-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;">
|
||||
<g class="style-scope iron-icon">
|
||||
<path class="st0" d="M109.3 265.2l218.9 218.9c5.1 5.1 11.8 7.9 19 7.9s14-2.8 19-7.9l16.1-16.1c10.5-10.5 10.5-27.6 0-38.1L198.6 246.1 382.7 62c5.1-5.1 7.9-11.8 7.9-19 0-7.2-2.8-14-7.9-19L366.5 7.9c-5.1-5.1-11.8-7.9-19-7.9-7.2 0-14 2.8-19 7.9L109.3 227c-5.1 5.1-7.9 11.9-7.8 19.1 0 7.2 2.8 14 7.8 19.1z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
</iron-icon>
|
||||
</paper-icon-button>
|
||||
</ytmusic-pivot-bar-item-renderer>
|
||||
26
plugins/navigation/templates/forward.html
Normal file
26
plugins/navigation/templates/forward.html
Normal file
@ -0,0 +1,26 @@
|
||||
<ytmusic-pivot-bar-item-renderer class="style-scope ytmusic-pivot-bar-renderer" tab-id="FEmusic_next" role="tab" onclick="goToNextPage()">
|
||||
<yt-icon class="tab-icon style-scope ytmusic-pivot-bar-item-renderer">
|
||||
<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope yt-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;">
|
||||
<g class="style-scope yt-icon">
|
||||
<path d="M382.678,226.804L163.73,7.86C158.666,2.792,151.906,0,144.698,0s-13.968,2.792-19.032,7.86l-16.124,16.12
|
||||
c-10.492,10.504-10.492,27.576,0,38.064L293.398,245.9l-184.06,184.06c-5.064,5.068-7.86,11.824-7.86,19.028
|
||||
c0,7.212,2.796,13.968,7.86,19.04l16.124,16.116c5.068,5.068,11.824,7.86,19.032,7.86s13.968-2.792,19.032-7.86L382.678,265
|
||||
c5.076-5.084,7.864-11.872,7.848-19.088C390.542,238.668,387.754,231.884,382.678,226.804z" class="style-scope yt-icon">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
</yt-icon>
|
||||
|
||||
<paper-icon-button class="search-icon style-scope ytmusic-search-box" role="button" tabindex="0" aria-disabled="false" title="Go to next page">
|
||||
<iron-icon id="icon" class="style-scope paper-icon-button">
|
||||
<svg viewBox="0 0 492 492" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope iron-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;">
|
||||
<g class="style-scope iron-icon">
|
||||
<path class="st0" d="M382.7,226.8L163.7,7.9c-5.1-5.1-11.8-7.9-19-7.9s-14,2.8-19,7.9L109.5,24c-10.5,10.5-10.5,27.6,0,38.1
|
||||
l183.9,183.9L109.3,430c-5.1,5.1-7.9,11.8-7.9,19c0,7.2,2.8,14,7.9,19l16.1,16.1c5.1,5.1,11.8,7.9,19,7.9s14-2.8,19-7.9L382.7,265
|
||||
c5.1-5.1,7.9-11.9,7.8-19.1C390.5,238.7,387.8,231.9,382.7,226.8z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
</iron-icon>
|
||||
</paper-icon-button>
|
||||
</ytmusic-pivot-bar-item-renderer>
|
||||
6
plugins/no-google-login/back.js
Normal file
6
plugins/no-google-login/back.js
Normal file
@ -0,0 +1,6 @@
|
||||
const { injectCSS } = require("../utils");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = win => {
|
||||
injectCSS(win.webContents, path.join(__dirname, "style.css"));
|
||||
};
|
||||
15
plugins/no-google-login/front.js
Normal file
15
plugins/no-google-login/front.js
Normal file
@ -0,0 +1,15 @@
|
||||
function removeLoginElements() {
|
||||
const elementsToRemove = [
|
||||
".sign-in-link.ytmusic-nav-bar",
|
||||
'.ytmusic-pivot-bar-renderer[tab-id="FEmusic_liked"]'
|
||||
];
|
||||
|
||||
elementsToRemove.forEach(selector => {
|
||||
const node = document.querySelector(selector);
|
||||
if (node) {
|
||||
node.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = removeLoginElements;
|
||||
3
plugins/no-google-login/style.css
Normal file
3
plugins/no-google-login/style.css
Normal file
@ -0,0 +1,3 @@
|
||||
.ytmusic-pivot-bar-renderer[tab-id="FEmusic_liked"] {
|
||||
display: none !important;
|
||||
}
|
||||
31
plugins/shortcuts/back.js
Normal file
31
plugins/shortcuts/back.js
Normal file
@ -0,0 +1,31 @@
|
||||
const { globalShortcut } = require("electron");
|
||||
const electronLocalshortcut = require("electron-localshortcut");
|
||||
|
||||
const {
|
||||
playPause,
|
||||
nextTrack,
|
||||
previousTrack,
|
||||
startSearch
|
||||
} = require("./youtube.js");
|
||||
|
||||
function _registerGlobalShortcut(webContents, shortcut, action) {
|
||||
globalShortcut.register(shortcut, () => {
|
||||
action(webContents);
|
||||
});
|
||||
}
|
||||
|
||||
function _registerLocalShortcut(win, shortcut, action) {
|
||||
electronLocalshortcut.register(win, shortcut, () => {
|
||||
action(win.webContents);
|
||||
});
|
||||
}
|
||||
|
||||
function registerShortcuts(win) {
|
||||
_registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause);
|
||||
_registerGlobalShortcut(win.webContents, "MediaNextTrack", nextTrack);
|
||||
_registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previousTrack);
|
||||
_registerLocalShortcut(win, "CommandOrControl+F", startSearch);
|
||||
_registerLocalShortcut(win, "CommandOrControl+L", startSearch);
|
||||
}
|
||||
|
||||
module.exports = registerShortcuts;
|
||||
29
plugins/shortcuts/youtube.js
Normal file
29
plugins/shortcuts/youtube.js
Normal file
@ -0,0 +1,29 @@
|
||||
function _keyboardInput(webContents, key) {
|
||||
return webContents.sendInputEvent({
|
||||
type : "keydown",
|
||||
keyCode: key
|
||||
});
|
||||
}
|
||||
|
||||
function playPause(webContents) {
|
||||
return _keyboardInput(webContents, "Space");
|
||||
}
|
||||
|
||||
function nextTrack(webContents) {
|
||||
return _keyboardInput(webContents, "j");
|
||||
}
|
||||
|
||||
function previousTrack(webContents) {
|
||||
return _keyboardInput(webContents, "k");
|
||||
}
|
||||
|
||||
function startSearch(webContents) {
|
||||
return _keyboardInput(webContents, "/");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
playPause : playPause,
|
||||
nextTrack : nextTrack,
|
||||
previousTrack: previousTrack,
|
||||
startSearch : startSearch
|
||||
};
|
||||
54
plugins/utils.js
Normal file
54
plugins/utils.js
Normal file
@ -0,0 +1,54 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const { ipcMain, ipcRenderer } = require("electron");
|
||||
|
||||
// Creates a DOM element from a HTML string
|
||||
module.exports.ElementFromHtml = html => {
|
||||
var 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 => {
|
||||
return module.exports.ElementFromHtml(fs.readFileSync(filepath, "utf8"));
|
||||
};
|
||||
|
||||
module.exports.templatePath = (pluginPath, name) => {
|
||||
return path.join(pluginPath, "templates", name);
|
||||
};
|
||||
|
||||
module.exports.triggerAction = (channel, action) => {
|
||||
return ipcRenderer.send(channel, action);
|
||||
};
|
||||
|
||||
module.exports.listenAction = (channel, callback) => {
|
||||
return ipcMain.on(channel, callback);
|
||||
};
|
||||
|
||||
module.exports.fileExists = (path, callbackIfExists) => {
|
||||
fs.access(path, fs.F_OK, err => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
callbackIfExists();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.injectCSS = (webContents, filepath) => {
|
||||
webContents.on("did-finish-load", () => {
|
||||
webContents.insertCSS(fs.readFileSync(filepath, "utf8"));
|
||||
});
|
||||
};
|
||||
|
||||
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));
|
||||
};
|
||||
Reference in New Issue
Block a user