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

8
.editorconfig Normal file
View File

@ -0,0 +1,8 @@
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
* text=auto
*.js text eol=lf

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
/dist
/assets/generated

BIN
assets/youtube-music.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

137
index.js Normal file
View File

@ -0,0 +1,137 @@
"use strict";
const path = require("path");
const electron = require("electron");
const isDev = require("electron-is-dev");
const { setApplicationMenu } = require("./menu");
const { getEnabledPlugins, store } = require("./store");
const { fileExists, injectCSS } = require("./plugins/utils");
const app = electron.app;
// Adds debug features like hotkeys for triggering dev tools and reload
require("electron-debug")();
// Prevent window being garbage collected
let mainWindow;
let icon = "assets/youtube-music.png";
if (process.platform == "win32") {
icon = "assets/generated/icon.ico";
} else if (process.platform == "darwin") {
icon = "assets/generated/icon.icns";
}
function onClosed() {
// Dereference the window
// For multiple windows store them in an array
mainWindow = null;
}
function createMainWindow() {
const windowSize = store.get("window-size");
const windowMaximized = store.get("window-maximized");
const win = new electron.BrowserWindow({
icon : icon,
width : windowSize.width,
height : windowSize.height,
backgroundColor: "#000",
show : false,
webPreferences : {
nodeIntegration: false,
preload : path.join(__dirname, "preload.js")
},
frame : false,
titleBarStyle: "hiddenInset"
});
if (windowMaximized) {
win.maximize();
}
win.webContents.loadURL(store.get("url"));
win.on("closed", onClosed);
injectCSS(win.webContents, path.join(__dirname, "youtube-music.css"));
win.webContents.on("did-finish-load", () => {
if (isDev) {
console.log("did finish load");
win.webContents.openDevTools();
}
});
getEnabledPlugins().forEach(plugin => {
console.log("Loaded plugin - " + plugin);
const pluginPath = path.join(__dirname, "plugins", plugin, "back.js");
fileExists(pluginPath, () => {
const handle = require(pluginPath);
handle(win);
});
});
win.webContents.on("did-navigate-in-page", () => {
const url = win.webContents.getURL();
if (url.startsWith("https://music.youtube.com")) {
store.set("url", url);
}
});
win.on("move", () => {
let position = win.getPosition();
store.set("window-position", { x: position[0], y: position[1] });
});
win.on("resize", () => {
const windowSize = win.getSize();
store.set("window-maximized", win.isMaximized());
if (!win.isMaximized()) {
store.set("window-size", { width: windowSize[0], height: windowSize[1] });
}
});
win.once("ready-to-show", () => {
win.show();
});
return win;
}
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
// Unregister all shortcuts.
electron.globalShortcut.unregisterAll();
});
app.on("activate", () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
mainWindow = createMainWindow();
} else if (!mainWindow.isVisible()) {
mainWindow.show();
}
});
app.on("ready", () => {
setApplicationMenu();
mainWindow = createMainWindow();
// Optimized for Mac OS X
if (process.platform === "darwin") {
var forceQuit = false;
app.on("before-quit", () => {
forceQuit = true;
});
mainWindow.on("close", event => {
if (!forceQuit) {
event.preventDefault();
mainWindow.hide();
}
});
}
});

21
license Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) th-ch <th-ch@users.noreply.github.com> (https://github.com/th-ch/youtube-music)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

56
menu.js Normal file
View File

@ -0,0 +1,56 @@
const { app, Menu } = require("electron");
const { getAllPlugins } = require("./plugins/utils");
const { isPluginEnabled, enablePlugin, disablePlugin } = require("./store");
module.exports.setApplicationMenu = () => {
const menuTemplate = [
{
label : "Plugins",
submenu: getAllPlugins().map(plugin => {
return {
label : plugin,
type : "checkbox",
checked: isPluginEnabled(plugin),
click : item => {
if (item.checked) {
enablePlugin(plugin);
} else {
disablePlugin(plugin);
}
}
};
})
}
];
if (process.platform === "darwin") {
const name = app.getName();
menuTemplate.unshift({
label : name,
submenu: [
{ role: "about" },
{ type: "separator" },
{ role: "hide" },
{ role: "hideothers" },
{ role: "unhide" },
{ type: "separator" },
{
label : "Select All",
accelerator: "CmdOrCtrl+A",
selector : "selectAll:"
},
{ label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" },
{ label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
{ label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
{ type: "separator" },
{ role: "minimize" },
{ role: "close" },
{ role: "quit" }
]
});
}
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
};

7065
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name" : "youtube-music",
"productName": "YouTube Music",
"version" : "1.0.0",
"description": "",
"license" : "MIT",
"repository" : "th-ch/youtube-music",
"author" : {
"name" : "th-ch",
"email": "th-ch@users.noreply.github.com",
"url" : "https://github.com/th-ch/youtube-music"
},
"scripts": {
"test" : "xo",
"start" : "electron .",
"icon" : "electron-icon-maker --input=assets/youtube-music.png --output=assets/generated",
"postinstall": "npm run icon && npm rebuild && node plugins/adblocker/generator.js && electron-rebuild",
"build" : "electron-packager . --out=dist --asar --overwrite --all --icon=assets/generated/icons/mac/icon.icns --prune=true",
"build:macos": "electron-packager . --platform=darwin --arch=x64 --out=dist --asar --overwrite --icon=assets/generated/icons/mac/icon.icns --prune=true"
},
"dependencies": {
"ad-block" : "^4.1.3",
"electron-debug" : "^2.0.0",
"electron-is-dev" : "^1.0.1",
"electron-localshortcut": "^3.1.0",
"electron-store" : "^2.0.0"
},
"devDependencies": {
"devtron" : "^1.4.0",
"electron" : "^4.0.8",
"electron-devtools-installer": "^2.2.4",
"electron-icon-maker" : "0.0.4",
"electron-packager" : "^13.1.1",
"electron-rebuild" : "^1.8.4",
"xo" : "^0.24.0"
},
"xo": {
"envs": [
"node",
"browser"
]
}
}

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

26
preload.js Normal file
View File

@ -0,0 +1,26 @@
const path = require("path");
const { getEnabledPlugins } = require("./store");
const { fileExists } = require("./plugins/utils");
const plugins = getEnabledPlugins();
plugins.forEach(plugin => {
const pluginPath = path.join(__dirname, "plugins", plugin, "actions.js");
fileExists(pluginPath, () => {
const actions = require(pluginPath).global || {};
Object.keys(actions).forEach(actionName => {
global[actionName] = actions[actionName];
});
});
});
document.addEventListener("DOMContentLoaded", () => {
plugins.forEach(plugin => {
const pluginPath = path.join(__dirname, "plugins", plugin, "front.js");
fileExists(pluginPath, () => {
const run = require(pluginPath);
run();
});
});
});

87
readme.md Normal file
View File

@ -0,0 +1,87 @@
# YouTube Music [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)
![Screenshot](screenshot.png "Screenshot)
**Electron wrapper around YouTube Music featuring:**
- Native look & feel, aims at keeping the original interface
- Framework for custom plugins: change YouTube Music to your needs (style, content, features), enable/disable plugins in one click
## Available plugins:
- **Ad Blocker**: block all ads and tracking out of the box
- **No Google Login**: remove Google login buttons and links from the interface
- **Shortcuts**: use your usual shortcuts (media keys, Ctrl/CMD + F…) to control YouTube Music
- **Navigation**: next/back navigation arrows directly integrated in the interface, like in your favorite browser
## Dev
```
git clone https://github.com/th-ch/youtube-music
cd youtube-music
npm install
npm start
```
## Build your own plugins
Using plugins, you can:
- manipulate the app - the `BrowserWindow` from electron is passed to the plugin handler
- change the front by manipulating the HTML/CSS
### Creating a plugin
Create a folder in `plugins/YOUR-PLUGIN-NAME`:
- if you need to manipulate the BrowserWindow, create a file `back.js` with the following template:
```
module.exports = win => {
// win is the BrowserWindow object
};
```
- if you need to change the front, create a file `front.js` with the following template:
```
module.exports = () => {
// This function will be called as a preload script
// So you can use front features like `document.querySelector`
};
```
### Common use cases
- injecting custom CSS: create a `style.css` file in the same folder then:
```
// back.js
module.exports = win => {
injectCSS(win.webContents, path.join(__dirname, "style.css"));
};
```
- changing the HTML:
```
// front.js
module.exports = () => {
// Remove the login button
document.querySelector('.sign-in-link.ytmusic-nav-bar').remove();
};
```
- communicating between the front and back: can be done using the ipcMain module from electron. See `utils.js` file and example in `navigation` plugin.
## Build
```
$ npm run build
```
Builds the app for macOS, Linux, and Windows, using [electron-packager](https://github.com/electron-userland/electron-packager).
## License
MIT © [th-ch](https://github.com/th-ch/youtube-music)

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1024 KiB

21
store/index.js Normal file
View File

@ -0,0 +1,21 @@
const Store = require("electron-store");
const plugins = require("./plugins");
const store = new Store({
defaults: {
"window-size": {
width : 1100,
height: 550
},
url : "https://music.youtube.com",
plugins: ["navigation", "shortcuts", "adblocker", "no-google-login"]
}
});
module.exports = {
store : store,
isPluginEnabled : plugin => plugins.isEnabled(store, plugin),
getEnabledPlugins: () => plugins.getEnabledPlugins(store),
enableplugin : plugin => plugins.enablePlugin(store, plugin),
disablePlugin : plugin => plugins.disablePlugin(store, plugin)
};

31
store/plugins.js Normal file
View File

@ -0,0 +1,31 @@
function getEnabledPlugins(store) {
return store.get("plugins");
}
function isEnabled(store, plugin) {
return store.get("plugins").indexOf(plugin) > -1;
}
function enablePlugin(store, plugin) {
let plugins = getEnabledPlugins(store);
if (plugins.indexOf(plugin) === -1) {
plugins.push(plugin);
store.set("plugins", plugins);
}
}
function disablePlugin(store, plugin) {
let plugins = getEnabledPlugins(store);
let index = plugins.indexOf(plugin);
if (index > -1) {
plugins.splice(index, 1);
store.set("plugins", plugins);
}
}
module.exports = {
isEnabled : isEnabled,
getEnabledPlugins: getEnabledPlugins,
enableplugin : enablePlugin,
disableplugin : disablePlugin
};

25
youtube-music.css Normal file
View File

@ -0,0 +1,25 @@
/**
* Overriding YouTube Music style
*/
/* Allow window dragging */
ytmusic-nav-bar {
-webkit-user-select: none;
-webkit-app-region : drag;
}
iron-icon,
ytmusic-pivot-bar-item-renderer,
.tab-title,
a {
-webkit-app-region: no-drag;
}
/* custom style for navbar */
ytmusic-app-layout {
--ytmusic-nav-bar-height: 85px;
}
ytmusic-search-box.ytmusic-nav-bar {
margin-top: 12px;
}