diff --git a/config/defaults.js b/config/defaults.js index 6aef55c9..2a18ce94 100644 --- a/config/defaults.js +++ b/config/defaults.js @@ -15,21 +15,22 @@ const defaultConfig = { trayClickPlayPause: false, autoResetAppCache: false, resumeOnStart: true, + proxy: "", }, plugins: { // Enabled plugins navigation: { enabled: true, }, - shortcuts: { - enabled: true, - }, adblocker: { enabled: true, cache: true, additionalBlockLists: [], // Additional list of filters, e.g "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt" }, // Disabled plugins + shortcuts: { + enabled: false, + }, downloader: { enabled: false, ffmpegArgs: [], // e.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s @@ -43,9 +44,15 @@ const defaultConfig = { suffixesToRemove: [' - Topic', 'VEVO'] // removes suffixes of the artist name, for better recognition }, 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 }, + notifications: { + enabled: false, + urgency: "normal", + unpauseNotification: false + } }, }; diff --git a/index.js b/index.js index 8b687673..82bb3320 100644 --- a/index.js +++ b/index.js @@ -32,6 +32,10 @@ if (config.get("options.disableHardwareAcceleration")) { app.disableHardwareAcceleration(); } +if (config.get("options.proxy")) { + app.commandLine.appendSwitch("proxy-server", config.get("options.proxy")); +} + // Adds debug features like hotkeys for triggering dev tools and reload require("electron-debug")(); @@ -75,6 +79,7 @@ function createMainWindow() { const windowSize = config.get("window-size"); const windowMaximized = config.get("window-maximized"); const windowPosition = config.get("window-position"); + const useInlineMenu = config.plugins.isEnabled("in-app-menu"); const win = new electron.BrowserWindow({ icon: icon, @@ -99,8 +104,12 @@ function createMainWindow() { } : undefined), }, - frame: !is.macOS(), - titleBarStyle: is.macOS() ? "hiddenInset" : "default", + frame: !is.macOS() && !useInlineMenu, + titleBarStyle: useInlineMenu + ? "hidden" + : is.macOS() + ? "hiddenInset" + : "default", autoHideMenuBar: config.get("options.hideMenu"), }); if (windowPosition) { diff --git a/menu.js b/menu.js index 91bb073d..f974c4e4 100644 --- a/menu.js +++ b/menu.js @@ -7,7 +7,7 @@ const is = require("electron-is"); const { getAllPlugins } = require("./plugins/utils"); const config = require("./config"); -const pluginEnabledMenu = (plugin, label = "") => ({ +const pluginEnabledMenu = (win, plugin, label = "", hasSubmenu = false) => ({ label: label || plugin, type: "checkbox", checked: config.plugins.isEnabled(plugin), @@ -17,26 +17,29 @@ const pluginEnabledMenu = (plugin, label = "") => ({ } else { config.plugins.disable(plugin); } + if (hasSubmenu) { + this.setApplicationMenu(win); + } }, }); -const mainMenuTemplate = (win) => [ +const mainMenuTemplate = (win, withRoles = true, isTray = false) => [ { label: "Plugins", submenu: [ ...getAllPlugins().map((plugin) => { const pluginPath = path.join(__dirname, "plugins", plugin, "menu.js"); - if (!config.plugins.isEnabled(plugin)) { - return pluginEnabledMenu(plugin); - } - if (existsSync(pluginPath)) { + if (!config.plugins.isEnabled(plugin)) { + return pluginEnabledMenu(win, plugin, "", true); + } + const getPluginMenu = require(pluginPath); return { label: plugin, submenu: [ - pluginEnabledMenu(plugin, "Enabled"), + pluginEnabledMenu(win, plugin, "Enabled", true), ...getPluginMenu(win, config.plugins.getOptions(plugin), () => module.exports.setApplicationMenu(win) ), @@ -44,7 +47,7 @@ const mainMenuTemplate = (win) => [ }; } - return pluginEnabledMenu(plugin); + return pluginEnabledMenu(win, plugin); }), { type: "separator" }, { @@ -189,17 +192,59 @@ const mainMenuTemplate = (win) => [ }, ], }, - { - label: "View", - submenu: [ - { role: "reload" }, - { role: "forceReload" }, - { type: "separator" }, - { role: "zoomIn" }, - { role: "zoomOut" }, - { role: "resetZoom" }, - ], - }, + ...(!isTray + ? [ + { + label: "View", + submenu: withRoles + ? [ + { role: "reload" }, + { role: "forceReload" }, + { type: "separator" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { role: "resetZoom" }, + ] + : [ + { + label: "Reload", + click: () => { + win.webContents.reload(); + }, + }, + { + label: "Force Reload", + click: () => { + win.webContents.reloadIgnoringCache(); + }, + }, + { type: "separator" }, + { + label: "Zoom In", + click: () => { + win.webContents.setZoomLevel( + win.webContents.getZoomLevel() + 1 + ); + }, + }, + { + label: "Zoom Out", + click: () => { + win.webContents.setZoomLevel( + win.webContents.getZoomLevel() - 1 + ); + }, + }, + { + label: "Reset Zoom", + click: () => { + win.webContents.setZoomLevel(0); + }, + }, + ], + }, + ] + : []), { label: "Navigation", submenu: [ @@ -219,6 +264,23 @@ const mainMenuTemplate = (win) => [ } }, }, + { + label: "Restart App", + click: () => { + app.relaunch(); + app.quit(); + }, + }, + ...(!isTray + ? [ + { + label: "Quit App", + click: () => { + app.quit(); + }, + }, + ] + : []), ], }, ]; diff --git a/package.json b/package.json index aa3f1b95..54a75d66 100644 --- a/package.json +++ b/package.json @@ -62,12 +62,13 @@ "npm": "Please use yarn and not npm" }, "dependencies": { - "@cliqz/adblocker-electron": "^1.20.0", + "@cliqz/adblocker-electron": "^1.20.1", "@ffmpeg/core": "^0.8.5", "@ffmpeg/ffmpeg": "^0.9.7", "YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.8.1", "async-mutex": "^0.3.1", "browser-id3-writer": "^4.4.0", + "custom-electron-titlebar": "^3.2.6", "discord-rpc": "^3.2.0", "downloads-folder": "^3.0.1", "electron-debug": "^3.2.0", diff --git a/plugins/downloader/back.js b/plugins/downloader/back.js index a1d2ff80..8d15a429 100644 --- a/plugins/downloader/back.js +++ b/plugins/downloader/back.js @@ -1,8 +1,6 @@ -const { writeFileSync } = require("fs"); const { join } = require("path"); -const ID3Writer = require("browser-id3-writer"); -const { dialog, ipcMain } = require("electron"); +const { dialog } = require("electron"); const getSongInfo = require("../../providers/song-info"); const { injectCSS, listenAction } = require("../utils"); @@ -40,35 +38,6 @@ function handle(win) { console.log("Unknown action: " + action); } }); - - ipcMain.on("add-metadata", (event, filePath, songBuffer) => { - let fileBuffer = songBuffer; - - try { - const writer = new ID3Writer(songBuffer); - if (metadata.image) { - const coverBuffer = metadata.image.toPNG(); - - // Create the metadata tags - writer - .setFrame("TIT2", metadata.title) - .setFrame("TPE1", [metadata.artist]) - .setFrame("APIC", { - type: 3, - data: coverBuffer, - description: "", - }); - writer.addTag(); - } - fileBuffer = Buffer.from(writer.arrayBuffer); - } catch (error) { - sendError(win, error); - } - - writeFileSync(filePath, fileBuffer); - // Notify the youtube-dl file - event.reply("add-metadata-done"); - }); } module.exports = handle; diff --git a/plugins/downloader/front.js b/plugins/downloader/front.js index 77485d4a..a29c9086 100644 --- a/plugins/downloader/front.js +++ b/plugins/downloader/front.js @@ -1,5 +1,6 @@ const { contextBridge } = require("electron"); +const { defaultConfig } = require("../../config"); const { getSongMenu } = require("../../providers/dom-elements"); const { ElementFromFile, templatePath, triggerAction } = require("../utils"); const { ACTIONS, CHANNEL } = require("./actions.js"); @@ -31,11 +32,19 @@ const reinit = () => { } }; +const baseUrl = defaultConfig.url; + // TODO: re-enable once contextIsolation is set to true // contextBridge.exposeInMainWorld("downloader", { // download: () => { global.download = () => { - const videoUrl = window.location.href; + let videoUrl = getSongMenu() + .querySelector("ytmusic-menu-navigation-item-renderer") + .querySelector("#navigation-endpoint") + .getAttribute("href"); + videoUrl = !videoUrl + ? global.songInfo.url || window.location.href + : baseUrl + videoUrl; downloadVideoToMP3( videoUrl, @@ -51,7 +60,8 @@ global.download = () => { reinit(); }, reinit, - pluginOptions + pluginOptions, + global.songInfo ); }; // }); diff --git a/plugins/downloader/menu.js b/plugins/downloader/menu.js index 4eda01a5..b4800808 100644 --- a/plugins/downloader/menu.js +++ b/plugins/downloader/menu.js @@ -2,62 +2,89 @@ const { existsSync, mkdirSync } = require("fs"); const { join } = require("path"); const { URL } = require("url"); -const { ipcMain } = require("electron"); +const { dialog, ipcMain } = require("electron"); const is = require("electron-is"); const ytpl = require("ytpl"); +const { setOptions } = require("../../config/plugins"); +const getSongInfo = require("../../providers/song-info"); const { sendError } = require("./back"); const { defaultMenuDownloadLabel, getFolder } = require("./utils"); let downloadLabel = defaultMenuDownloadLabel; +let metadataURL = undefined; +let callbackIsRegistered = false; -module.exports = (win, options, refreshMenu) => [ - { - label: downloadLabel, - click: async () => { - const currentURL = win.webContents.getURL(); - const playlistID = new URL(currentURL).searchParams.get("list"); - if (!playlistID) { - sendError(win, new Error("No playlist ID found")); - return; - } +module.exports = (win, options, refreshMenu) => { + if (!callbackIsRegistered) { + const registerCallback = getSongInfo(win); + registerCallback((info) => { + metadataURL = info.url; + }); + callbackIsRegistered = true; + } - const playlist = await ytpl(playlistID); - const playlistTitle = playlist.title; + return [ + { + label: downloadLabel, + click: async () => { + const currentURL = metadataURL || win.webContents.getURL(); + const playlistID = new URL(currentURL).searchParams.get("list"); + if (!playlistID) { + sendError(win, new Error("No playlist ID found")); + return; + } - const folder = getFolder(options.downloadFolder); - const playlistFolder = join(folder, playlistTitle); - if (existsSync(playlistFolder)) { - sendError( - win, - new Error(`The folder ${playlistFolder} already exists`) - ); - return; - } - mkdirSync(playlistFolder, { recursive: true }); + const playlist = await ytpl(playlistID); + const playlistTitle = playlist.title; - ipcMain.on("downloader-feedback", (_, feedback) => { - downloadLabel = feedback; + const folder = getFolder(options.downloadFolder); + const playlistFolder = join(folder, playlistTitle); + if (existsSync(playlistFolder)) { + sendError( + win, + new Error(`The folder ${playlistFolder} already exists`) + ); + return; + } + mkdirSync(playlistFolder, { recursive: true }); + + ipcMain.on("downloader-feedback", (_, feedback) => { + downloadLabel = feedback; + refreshMenu(); + }); + + downloadLabel = `Downloading "${playlistTitle}"`; refreshMenu(); - }); - downloadLabel = `Downloading "${playlistTitle}"`; - refreshMenu(); + if (is.dev()) { + console.log( + `Downloading playlist "${playlistTitle}" (${playlist.items.length} songs)` + ); + } - if (is.dev()) { - console.log( - `Downloading playlist "${playlistTitle}" (${playlist.items.length} songs)` - ); - } - - playlist.items.slice(0, options.playlistMaxItems).forEach((song) => { - win.webContents.send( - "downloader-download-playlist", - song, - playlistTitle, - options - ); - }); + playlist.items.slice(0, options.playlistMaxItems).forEach((song) => { + win.webContents.send( + "downloader-download-playlist", + song, + playlistTitle, + options + ); + }); + }, }, - }, -]; + { + label: "Choose download folder", + click: () => { + let result = dialog.showOpenDialogSync({ + properties: ["openDirectory", "createDirectory"], + defaultPath: getFolder(options.downloadFolder), + }); + if (result) { + options.downloadFolder = result[0]; + setOptions("downloader", options); + } // else = user pressed cancel + }, + }, + ]; +}; diff --git a/plugins/downloader/templates/download.html b/plugins/downloader/templates/download.html index 138464fb..d4f455d0 100644 --- a/plugins/downloader/templates/download.html +++ b/plugins/downloader/templates/download.html @@ -19,11 +19,11 @@ diff --git a/plugins/downloader/youtube-dl.js b/plugins/downloader/youtube-dl.js index 65907e2b..4fb8ac75 100644 --- a/plugins/downloader/youtube-dl.js +++ b/plugins/downloader/youtube-dl.js @@ -3,6 +3,7 @@ const { writeFileSync } = require("fs"); const { join } = require("path"); const Mutex = require("async-mutex").Mutex; +const ID3Writer = require("browser-id3-writer"); const { ipcRenderer } = require("electron"); const is = require("electron-is"); const filenamify = require("filenamify"); @@ -120,7 +121,7 @@ const toMP3 = async ( ); const folder = getFolder(options.downloadFolder); - const name = metadata + const name = metadata.title ? `${metadata.artist ? `${metadata.artist} - ` : ""}${metadata.title}` : videoName; const filename = filenamify(name + "." + extension, { @@ -130,15 +131,29 @@ const toMP3 = async ( const filePath = join(folder, subfolder, filename); const fileBuffer = ffmpeg.FS("readFile", safeVideoName + "." + extension); - if (existingMetadata) { - writeFileSync(filePath, fileBuffer); + // Add the metadata + try { + const writer = new ID3Writer(fileBuffer); + if (metadata.image) { + const coverBuffer = metadata.image.toPNG(); + + // Create the metadata tags + writer + .setFrame("TIT2", metadata.title) + .setFrame("TPE1", [metadata.artist]) + .setFrame("APIC", { + type: 3, + data: coverBuffer, + description: "", + }); + writer.addTag(); + } + + writeFileSync(filePath, Buffer.from(writer.arrayBuffer)); + } catch (error) { + sendError(error); + } finally { reinit(); - } else { - // Add the metadata - sendFeedback("Adding metadata…"); - ipcRenderer.send("add-metadata", filePath, fileBuffer); - ipcRenderer.once("add-metadata-done", reinit); - sendFeedback("Finished converting", metadata); } } catch (e) { sendError(e); diff --git a/plugins/in-app-menu/back.js b/plugins/in-app-menu/back.js new file mode 100644 index 00000000..3c77c377 --- /dev/null +++ b/plugins/in-app-menu/back.js @@ -0,0 +1,76 @@ +const path = require("path"); + +const { Menu } = require("electron"); +const electronLocalshortcut = require("electron-localshortcut"); + +const config = require("../../config"); +const { setApplicationMenu } = require("../../menu"); +const { injectCSS } = require("../utils"); + +//check that menu doesn't get created twice +let done = false; +// win hook for fixing menu +let win; + +const originalBuildMenu = Menu.buildFromTemplate; +// This function natively gets called on all submenu so no more reason to use recursion +Menu.buildFromTemplate = (template) => { + // Fix checkboxes and radio buttons + updateCheckboxesAndRadioButtons(win, template); + + // return as normal + return originalBuildMenu(template); +}; + +module.exports = (winImport) => { + win = winImport; + + // css for custom scrollbar + disable drag area(was causing bugs) + injectCSS(win.webContents, path.join(__dirname, "style.css")); + + win.on("ready-to-show", () => { + // (apparently ready-to-show is called twice) + if (done) { + return; + } + done = true; + + setApplicationMenu(win); + + //register keyboard shortcut && hide menu if hideMenu is enabled + if (config.get("options.hideMenu")) { + switchMenuVisibility(win); + electronLocalshortcut.register(win, "Esc", () => { + switchMenuVisibility(win); + }); + } + }); +}; + +let visible = true; +function switchMenuVisibility(win) { + visible = !visible; + win.webContents.send("updateMenu", visible); +} + +function checkCheckbox(win, item) { + //check item + item.checked = !item.checked; + //update menu (closes it) + win.webContents.send("updateMenu", true); +} + +// Update checkboxes/radio buttons +function updateCheckboxesAndRadioButtons(win, template) { + for (let item of template) { + // Change onClick of checkbox+radio + if ((item.type === "checkbox" || item.type === "radio") && !item.fixed) { + let originalOnclick = item.click; + item.click = (itemClicked) => { + originalOnclick(itemClicked); + checkCheckbox(win, itemClicked); + }; + item.fixed = true; + } + } +} diff --git a/plugins/in-app-menu/front.js b/plugins/in-app-menu/front.js new file mode 100644 index 00000000..3287dc5d --- /dev/null +++ b/plugins/in-app-menu/front.js @@ -0,0 +1,24 @@ +const { remote, ipcRenderer } = require("electron"); + +const customTitlebar = require("custom-electron-titlebar"); + +module.exports = () => { + const bar = new customTitlebar.Titlebar({ + backgroundColor: customTitlebar.Color.fromHex("#050505"), + itemBackgroundColor: customTitlebar.Color.fromHex("#121212"), + }); + bar.updateTitle(" "); + document.title = "Youtube Music"; + + ipcRenderer.on("updateMenu", function (event, menu) { + if (menu) { + bar.updateMenu(remote.Menu.getApplicationMenu()); + } else { + try { + bar.updateMenu(null); + } catch (e) { + //will always throw type error - null isn't menu, but it works + } + } + }); +}; diff --git a/plugins/in-app-menu/style.css b/plugins/in-app-menu/style.css new file mode 100644 index 00000000..25ae7c4a --- /dev/null +++ b/plugins/in-app-menu/style.css @@ -0,0 +1,69 @@ +/* increase font size for menu and menuItems */ +.titlebar, +.menubar-menu-container .action-label { + font-size: 14px !important; +} + +/* allow submenu's to show correctly */ +.menubar-menu-container { + overflow-y: visible !important; +} +/* fixes scrollbar positioning relative to nav bar */ +#nav-bar-background.ytmusic-app-layout { + right: 15px !important; +} +/* remove window dragging for nav bar (conflict with titlebar drag) */ +ytmusic-nav-bar, +.tab-titleiron-icon, +ytmusic-pivot-bar-item-renderer { + -webkit-app-region: unset; +} + +/* Move navBar downwards and make it opaque */ +ytmusic-app-layout { + --ytmusic-nav-bar-height: 120px; +} + +ytmusic-search-box.ytmusic-nav-bar { + margin-top: 29px; +} + +.center-content.ytmusic-nav-bar { + background: #030303; +} +yt-page-navigation-progress, +#progress.yt-page-navigation-progress, +ytmusic-item-section-renderer[has-item-section-tabbed-header-renderer_] + #header.ytmusic-item-section-renderer, +ytmusic-header-renderer.ytmusic-search-page { + top: 90px !important; +} +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 12px; + background-color: #030303; + border-radius: 100px; + -moz-border-radius: 100px; + -webkit-border-radius: 100px; +} +/* hover effect for both scrollbar area, and scrollbar 'thumb' */ +::-webkit-scrollbar:hover { + background-color: rgba(15, 15, 15, 0.699); +} + +/* The scrollbar 'thumb' ...that marque oval shape in a scrollbar */ +::-webkit-scrollbar-thumb:vertical { + background-clip: padding-box; + border: 2px solid rgba(0, 0, 0, 0); + + background: rgb(49, 0, 0); + border-radius: 100px; + -moz-border-radius: 100px; + -webkit-border-radius: 100px; +} +::-webkit-scrollbar-thumb:vertical:active { + background: rgb(56, 0, 0); /* Some darker color when you click it */ + border-radius: 100px; + -moz-border-radius: 100px; + -webkit-border-radius: 100px; +} diff --git a/plugins/notifications/back.js b/plugins/notifications/back.js index 4fc1d87d..dedd9379 100644 --- a/plugins/notifications/back.js +++ b/plugins/notifications/back.js @@ -1,7 +1,7 @@ const { Notification } = require("electron"); const getSongInfo = require("../../providers/song-info"); -const notify = info => { +const notify = (info, options) => { let notificationImage = "assets/youtube-music.png"; if (info.image) { @@ -14,27 +14,38 @@ const notify = info => { body: info.artist, icon: notificationImage, silent: true, + urgency: options.urgency, }; - + // Send the notification - currentNotification = new Notification(notification); + const currentNotification = new Notification(notification); currentNotification.show() - + return currentNotification; }; -module.exports = (win) => { +module.exports = (win, options) => { const registerCallback = getSongInfo(win); let oldNotification; + let oldURL = ""; win.on("ready-to-show", () => { // Register the callback for new song information registerCallback(songInfo => { - // If song is playing send notification - if (!songInfo.isPaused) { + // on pause - reset url? and skip notification + if (songInfo.isPaused) { + //reset oldURL if unpause notification option is on + if (options.unpauseNotification) { + oldURL = ""; + } + return; + } + // If url isn't the same as last one - send notification + if (songInfo.url !== oldURL) { + oldURL = songInfo.url; // Close the old notification oldNotification?.close(); // This fixes a weird bug that would cause the notification to be updated instead of showing - setTimeout(()=>{ oldNotification = notify(songInfo) }, 10); + setTimeout(()=>{ oldNotification = notify(songInfo, options) }, 10); } }); }); diff --git a/plugins/notifications/menu.js b/plugins/notifications/menu.js new file mode 100644 index 00000000..a61cac67 --- /dev/null +++ b/plugins/notifications/menu.js @@ -0,0 +1,19 @@ +const {urgencyLevels, setUrgency, setUnpause} = require("./utils"); + +module.exports = (win, options) => [ + { + label: "Notification Priority", + submenu: urgencyLevels.map(level => ({ + label: level.name, + type: "radio", + checked: options.urgency === level.value, + click: () => setUrgency(options, level.value) + })), + }, + { + label: "Show notification on unpause", + type: "checkbox", + checked: options.unpauseNotification, + click: (item) => setUnpause(options, item.checked) + } +]; diff --git a/plugins/notifications/utils.js b/plugins/notifications/utils.js new file mode 100644 index 00000000..c43ecb20 --- /dev/null +++ b/plugins/notifications/utils.js @@ -0,0 +1,19 @@ +const {setOptions} = require("../../config/plugins"); + +module.exports.urgencyLevels = [ + {name: "Low", value: "low"}, + {name: "Normal", value: "normal"}, + {name: "High", value: "critical"}, +]; +module.exports.setUrgency = (options, level) => { + options.urgency = level + setOption(options) +}; +module.exports.setUnpause = (options, value) => { + options.unpauseNotification = value + setOption(options) +}; + +let setOption = options => { + setOptions("notifications", options) +}; diff --git a/plugins/taskbar-mediacontrol/assets/backward.png b/plugins/taskbar-mediacontrol/assets/backward.png new file mode 100644 index 00000000..2dd278eb Binary files /dev/null and b/plugins/taskbar-mediacontrol/assets/backward.png differ diff --git a/plugins/taskbar-mediacontrol/assets/forward.png b/plugins/taskbar-mediacontrol/assets/forward.png new file mode 100644 index 00000000..a8339d29 Binary files /dev/null and b/plugins/taskbar-mediacontrol/assets/forward.png differ diff --git a/plugins/taskbar-mediacontrol/assets/pause.png b/plugins/taskbar-mediacontrol/assets/pause.png new file mode 100644 index 00000000..2df0f58a Binary files /dev/null and b/plugins/taskbar-mediacontrol/assets/pause.png differ diff --git a/plugins/taskbar-mediacontrol/assets/play.png b/plugins/taskbar-mediacontrol/assets/play.png new file mode 100644 index 00000000..7a2cfa50 Binary files /dev/null and b/plugins/taskbar-mediacontrol/assets/play.png differ diff --git a/plugins/taskbar-mediacontrol/back.js b/plugins/taskbar-mediacontrol/back.js new file mode 100644 index 00000000..b4d7dd3c --- /dev/null +++ b/plugins/taskbar-mediacontrol/back.js @@ -0,0 +1,58 @@ +const getSongControls = require('../../providers/song-controls'); +const getSongInfo = require('../../providers/song-info'); +const path = require('path'); + +module.exports = win => { + win.hide = function () { + win.minimize(); + win.setSkipTaskbar(true); + }; + + const show = win.show; + win.show = function () { + win.restore(); + win.focus(); + win.setSkipTaskbar(false); + show.apply(win); + }; + + win.isVisible = function () { + return !win.isMinimized(); + }; + + const registerCallback = getSongInfo(win); + const {playPause, next, previous} = getSongControls(win); + + // If the page is ready, register the callback + win.on('ready-to-show', () => { + registerCallback(songInfo => { + // Wait for song to start before setting thumbar + if (songInfo.title === '') { + return; + } + + // Win32 require full rewrite of components + win.setThumbarButtons([ + { + tooltip: 'Previous', + icon: get('backward.png'), + click() {previous(win.webContents);} + }, { + tooltip: 'Play/Pause', + // Update icon based on play state + icon: songInfo.isPaused ? get('play.png') : get('pause.png'), + click() {playPause(win.webContents);} + }, { + tooltip: 'Next', + icon: get('forward.png'), + click() {next(win.webContents);} + } + ]); + }); + }); +}; + +// Util +function get(file) { + return path.join(__dirname,"assets", file); +} diff --git a/providers/song-info-front.js b/providers/song-info-front.js index 42c3afb2..fbc98b99 100644 --- a/providers/song-info-front.js +++ b/providers/song-info-front.js @@ -1,22 +1,32 @@ const { ipcRenderer } = require("electron"); +const { getImage } = require("./song-info"); + +global.songInfo = {}; + +ipcRenderer.on("update-song-info", async (_, extractedSongInfo) => { + global.songInfo = JSON.parse(extractedSongInfo); + global.songInfo.image = await getImage(global.songInfo.imageSrc); +}); + const injectListener = () => { var oldXHR = window.XMLHttpRequest; function newXHR() { var realXHR = new oldXHR(); - realXHR.addEventListener("readystatechange", () => { - if(realXHR.readyState==4 && realXHR.status==200){ - if (realXHR.responseURL.includes('/player')){ - // if the request is the contains the song info send the response to ipcMain - ipcRenderer.send( - "song-info-request", - realXHR.responseText - ); + realXHR.addEventListener( + "readystatechange", + () => { + if (realXHR.readyState == 4 && realXHR.status == 200) { + if (realXHR.responseURL.includes("/player")) { + // if the request contains the song info, send the response to ipcMain + ipcRenderer.send("song-info-request", realXHR.responseText); + } } - } - }, false); + }, + false + ); return realXHR; } window.XMLHttpRequest = newXHR; -} +}; module.exports = injectListener; diff --git a/providers/song-info.js b/providers/song-info.js index 067c5718..9b434361 100644 --- a/providers/song-info.js +++ b/providers/song-info.js @@ -1,5 +1,4 @@ -const { ipcMain } = require("electron"); -const { nativeImage } = require("electron"); +const { ipcMain, nativeImage } = require("electron"); const fetch = require("node-fetch"); @@ -38,21 +37,25 @@ const songInfo = { uploadDate: "", imageSrc: "", image: null, - isPaused: true, + isPaused: undefined, songDuration: 0, elapsedSeconds: 0, + url: "", }; -const handleData = async (_event, responseText) => { - data = JSON.parse(responseText); +const handleData = async (responseText, win) => { + let data = JSON.parse(responseText); songInfo.title = data?.videoDetails?.title; songInfo.artist = data?.videoDetails?.author; songInfo.views = data?.videoDetails?.viewCount; - songInfo.imageSrc = data?.videoDetails?.thumbnail?.thumbnails?.[0]?.url; + songInfo.imageSrc = data?.videoDetails?.thumbnail?.thumbnails?.pop()?.url; songInfo.songDuration = data?.videoDetails?.lengthSeconds; songInfo.image = await getImage(songInfo.imageSrc); songInfo.uploadDate = data?.microformat?.microformatDataRenderer?.uploadDate; -} + songInfo.url = data?.microformat?.microformatDataRenderer?.urlCanonical; + + win.webContents.send("update-song-info", JSON.stringify(songInfo)); +}; const registerProvider = (win) => { // This variable will be filled with the callbacks once they register @@ -77,9 +80,15 @@ const registerProvider = (win) => { }); // This will be called when the song-info-front finds a new request with song data - ipcMain.on('song-info-request', handleData); + ipcMain.on("song-info-request", async (_, responseText) => { + await handleData(responseText, win); + callbacks.forEach((c) => { + c(songInfo); + }); + }); return registerCallback; }; module.exports = registerProvider; +module.exports.getImage = getImage; diff --git a/tray.js b/tray.js index 75af7494..e871f4bf 100644 --- a/tray.js +++ b/tray.js @@ -32,7 +32,7 @@ module.exports.setUpTray = (app, win) => { } }); - const trayMenu = Menu.buildFromTemplate([ + let template = [ { label: "Play/Pause", click: () => { @@ -57,13 +57,15 @@ module.exports.setUpTray = (app, win) => { win.show(); }, }, - ...mainMenuTemplate(win), + ...mainMenuTemplate(win, true, true), { label: "Quit", click: () => { app.quit(); }, }, - ]); + ]; + + const trayMenu = Menu.buildFromTemplate(template); tray.setContextMenu(trayMenu); }; diff --git a/yarn.lock b/yarn.lock index f428bd55..b3e533cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -282,45 +282,45 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@cliqz/adblocker-content@^1.20.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-content/-/adblocker-content-1.20.0.tgz#fcfa2845a577ba8d9af282afbae2fc437b3f1c70" - integrity sha512-KcokmK2B+tAnVMi7nGHgzXUVf78wAODG1Uk+K3tBPf9VAo3mwldYZ472uTj6LUfZv5oeTwe4PwfmPWXWZy3Eew== +"@cliqz/adblocker-content@^1.20.3": + version "1.20.3" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-content/-/adblocker-content-1.20.3.tgz#198c8719cd62ef3c67a5c98e7a54336b7812ed86" + integrity sha512-aCBTiIiNgVbmDIQyUcsn0j3n+umvs0DuVlL6dccPE3qfeFxT4whUvMwjxUS2/dIBfJK9A1LywmvVke2eSPw9wg== dependencies: - "@cliqz/adblocker-extended-selectors" "^1.20.0" + "@cliqz/adblocker-extended-selectors" "^1.20.3" -"@cliqz/adblocker-electron-preload@^1.20.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron-preload/-/adblocker-electron-preload-1.20.0.tgz#997b694fbb1b1206e04b1fd570690234cc7ef630" - integrity sha512-brNQFjIoGTMClmFphtoK0EnjOlbqfxr6sA3CrOZiHfG0e07Id5GoU95re8+s8xA+/nd1GrJl/k5/b4aks+S9Gw== +"@cliqz/adblocker-electron-preload@^1.20.3": + version "1.20.3" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron-preload/-/adblocker-electron-preload-1.20.3.tgz#17dff446ad742cb6e68a4572e7a75cff1fa33f95" + integrity sha512-fWAFEGj+F0VOUKZd2FqWLuguXmGzkRQz5wTCqasvndX4HSe0P8Pd2666pWK9RJW1dLJE7U61mQfTbYqlUFVTMA== dependencies: - "@cliqz/adblocker-content" "^1.20.0" + "@cliqz/adblocker-content" "^1.20.3" -"@cliqz/adblocker-electron@^1.20.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron/-/adblocker-electron-1.20.0.tgz#bacfb9feaf1d3dab339b992e3defa111a4b5ed3c" - integrity sha512-zD881g+YxxO4BM6NB5qZtSevg9Cj7QtlCJ4tkcKZnD9MDQsNXQVIFFEWwqhd00kLkTUS0+jT0px9b81cigAPNg== +"@cliqz/adblocker-electron@^1.20.1": + version "1.20.3" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron/-/adblocker-electron-1.20.3.tgz#f2b4bf5dddf90f64251c46f89238526dc0037384" + integrity sha512-ZcEl3W7R/aoUA0IPIMtvdn7gVE6O9+rDQ9OllIH/s/gVeElXZsgPEtpPMSuoJWbi9d2mlr8yo3UFvkV3u7c4gw== dependencies: - "@cliqz/adblocker" "^1.20.0" - "@cliqz/adblocker-electron-preload" "^1.20.0" + "@cliqz/adblocker" "^1.20.3" + "@cliqz/adblocker-electron-preload" "^1.20.3" tldts-experimental "^5.6.21" -"@cliqz/adblocker-extended-selectors@^1.20.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-extended-selectors/-/adblocker-extended-selectors-1.20.0.tgz#95ede657b670f627b39f92d85a97093cecee6ffe" - integrity sha512-dnBPIngGe1eDWvYX49eP2yyCE2AY1QD5E+8SaXW6lslnjS0GQnkcXCAkkGR2am4Qdk78HAiWTXL65Zt9hdkupA== +"@cliqz/adblocker-extended-selectors@^1.20.3": + version "1.20.3" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-extended-selectors/-/adblocker-extended-selectors-1.20.3.tgz#a817915948ec4e64c8b878a80a71d911ea0412c8" + integrity sha512-Xsrqg4qgpNVx80UJrAz/nS8jcbgCTIGvir0MrjoXrw0GheqRxsgE540XXP9JA7QlifLNVEOO44DpHvhUmISkQw== -"@cliqz/adblocker@^1.20.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker/-/adblocker-1.20.0.tgz#514746e9ee72fcd886f1e2e1aaf13b28fc63f232" - integrity sha512-lkEj0Pj1ikwMURrvoFv0YnLfaXFuJI+jexI7zdh4fDmlwRppzDDgOhPXgCczoAlYacJk5x2mf7pan6JybRD9Kw== +"@cliqz/adblocker@^1.20.3": + version "1.20.3" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker/-/adblocker-1.20.3.tgz#4e8d03ed03c476f7b4388d25f910b1b9e0b15cc9" + integrity sha512-Dqj8fJ399kFsFQ53uW0ajA5jH5VJ5ppawOjtoV2s+7NILj1ydvw40jTrr3l/ObMvxaAGaDUj2Euo4beg3/EtRQ== dependencies: - "@cliqz/adblocker-content" "^1.20.0" - "@cliqz/adblocker-extended-selectors" "^1.20.0" + "@cliqz/adblocker-content" "^1.20.3" + "@cliqz/adblocker-extended-selectors" "^1.20.3" "@remusao/guess-url-type" "^1.1.2" "@remusao/small" "^1.1.2" "@remusao/smaz" "^1.7.1" - "@types/chrome" "^0.0.128" + "@types/chrome" "^0.0.133" "@types/firefox-webext-browser" "^82.0.0" tldts-experimental "^5.6.21" @@ -1014,10 +1014,10 @@ "@types/node" "*" "@types/responselike" "*" -"@types/chrome@^0.0.128": - version "0.0.128" - resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.128.tgz#5dbd8b2539a367353fbe4386f119b510105f8b6a" - integrity sha512-eGc599TDtersMBW1cSnExHm0IHrXrO5xdk6Sa2Dq30ED+hR1rpT1ez0NNcCgvGO52nmktGfyvd3Uyquzv3LL4g== +"@types/chrome@^0.0.133": + version "0.0.133" + resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.133.tgz#9e1d55441584ba2d5274ca84db36427da9c5dc6e" + integrity sha512-G8uIUdaCTBILprQvQXBWGXZxjAWbkCkFQit17cdH3zYQEwU8f/etNl8+M7e8MRz9Xj8daHaVpysneMZMx8/ldQ== dependencies: "@types/filesystem" "*" "@types/har-format" "*" @@ -2653,6 +2653,11 @@ cssstyle@^2.2.0: dependencies: cssom "~0.3.6" +custom-electron-titlebar@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/custom-electron-titlebar/-/custom-electron-titlebar-3.2.6.tgz#4cd064efa5020954c09732efa8c667a7ee3636e3" + integrity sha512-P3ZGEr0eouUHqhdBBXllpuy2bFhfSmp+32HQBPcwzujjIsUhQxQj/nCpJiFa4SUGAEp1ifu/icuZdDKNNX72Tw== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"