Initial commit - app + 4 plugins

This commit is contained in:
TC
2019-04-19 20:12:36 +02:00
commit 8787b5c175
31 changed files with 7878 additions and 0 deletions

1
plugins/adblocker/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
detector.buffer

View File

@ -0,0 +1,3 @@
const { blockWindowAds } = require("./blocker");
module.exports = win => blockWindowAds(win.webContents);

View 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 });
}
);
};

View 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;

View 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();
}

View 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,
}
};

View 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;

View 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;

View 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>

View 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>

View File

@ -0,0 +1,6 @@
const { injectCSS } = require("../utils");
const path = require("path");
module.exports = win => {
injectCSS(win.webContents, path.join(__dirname, "style.css"));
};

View 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;

View File

@ -0,0 +1,3 @@
.ytmusic-pivot-bar-renderer[tab-id="FEmusic_liked"] {
display: none !important;
}

31
plugins/shortcuts/back.js Normal file
View 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;

View 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
View 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));
};