From f1ddb928864bbb954b74e477cecc24d1a024b287 Mon Sep 17 00:00:00 2001 From: TC Date: Tue, 12 Jan 2021 21:16:29 +0100 Subject: [PATCH 1/7] nit: prettier --- plugins/notifications/back.js | 8 ++++---- plugins/touchbar/back.js | 34 ++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/plugins/notifications/back.js b/plugins/notifications/back.js index ac5e6660..a55bffe4 100644 --- a/plugins/notifications/back.js +++ b/plugins/notifications/back.js @@ -1,18 +1,18 @@ -const {Notification} = require('electron'); +const { Notification } = require("electron"); const notify = info => { let notificationImage = 'assets/youtube-music.png'; if (info.image) { - notificationImage = info.image.resize({height: 256, width: 256}); + notificationImage = info.image.resize({ height: 256, width: 256 }); } // Fill the notification with content const notification = { - title: info.title || 'Playing', + title: info.title || "Playing", body: info.artist, icon: notificationImage, - silent: true + silent: true, }; // Send the notification new Notification(notification).show(); diff --git a/plugins/touchbar/back.js b/plugins/touchbar/back.js index 5e0a41bf..5eeba67f 100644 --- a/plugins/touchbar/back.js +++ b/plugins/touchbar/back.js @@ -1,15 +1,15 @@ -const {TouchBar} = require('electron'); +const { TouchBar } = require("electron"); const { TouchBarButton, TouchBarLabel, TouchBarSpacer, TouchBarSegmentedControl, - TouchBarScrubber + TouchBarScrubber, } = TouchBar; // Songtitle label const songTitle = new TouchBarLabel({ - label: '' + label: "", }); // This will store the song controls once available let controls = []; @@ -22,23 +22,23 @@ const pausePlayButton = new TouchBarButton(); // The song control buttons (control functions are in the same order) const buttons = new TouchBarSegmentedControl({ - mode: 'buttons', + mode: "buttons", segments: [ new TouchBarButton({ - label: '⏮' + label: "⏮", }), pausePlayButton, new TouchBarButton({ - label: '⏭' + label: "⏭", }), new TouchBarButton({ - label: '👎' + label: "👎", }), new TouchBarButton({ - label: '👍' - }) + label: "👍", + }), ], - change: i => controls[i]() + change: (i) => controls[i](), }); // This is the touchbar object, this combines everything with proper layout @@ -46,13 +46,13 @@ const touchBar = new TouchBar({ items: [ new TouchBarScrubber({ items: [songImage, songTitle], - continuous: false + continuous: false, }), new TouchBarSpacer({ - size: 'flexible' + size: "flexible", }), - buttons - ] + buttons, + ], }); module.exports = win => { @@ -74,10 +74,12 @@ module.exports = win => { songTitle.label = songInfo.title; // Changes the pause button if paused - pausePlayButton.label = songInfo.isPaused ? '▶️' : '⏸'; + pausePlayButton.label = songInfo.isPaused ? "▶️" : "⏸"; // Get image source - songImage.icon = songInfo.image ? songInfo.image.resize({height: 23}) : null; + songImage.icon = songInfo.image + ? songInfo.image.resize({ height: 23 }) + : null; win.setTouchBar(touchBar); }); From 0743034de0443e889ec11d7ea83727ff4fb96599 Mon Sep 17 00:00:00 2001 From: TC Date: Tue, 12 Jan 2021 21:17:08 +0100 Subject: [PATCH 2/7] Split providers in 2 --- providers/song-controls.js | 18 ++++++ providers/song-info.js | 114 ++++++++++++++++++++++++++++++++++ providers/song-info/back.js | 120 ------------------------------------ 3 files changed, 132 insertions(+), 120 deletions(-) create mode 100644 providers/song-controls.js create mode 100644 providers/song-info.js delete mode 100644 providers/song-info/back.js diff --git a/providers/song-controls.js b/providers/song-controls.js new file mode 100644 index 00000000..20946a38 --- /dev/null +++ b/providers/song-controls.js @@ -0,0 +1,18 @@ +// This is used for to control the songs +const pressKey = (window, key) => { + window.webContents.sendInputEvent({ + type: "keydown", + keyCode: key, + }); +}; + +module.exports = (win) => { + return { + previous: () => pressKey(win, "k"), + next: () => pressKey(win, "j"), + playPause: () => pressKey(win, "space"), + like: () => pressKey(win, "_"), + dislike: () => pressKey(win, "+"), + search: () => pressKey(win, "/"), + }; +}; diff --git a/providers/song-info.js b/providers/song-info.js new file mode 100644 index 00000000..1c3333e0 --- /dev/null +++ b/providers/song-info.js @@ -0,0 +1,114 @@ +const { nativeImage } = require("electron"); + +const fetch = require("node-fetch"); + +// This selects the song title +const titleSelector = ".title.style-scope.ytmusic-player-bar"; + +// This selects the song image +const imageSelector = + "#layout > ytmusic-player-bar > div.middle-controls.style-scope.ytmusic-player-bar > img"; + +// This selects the song subinfo, this includes artist, views, likes +const subInfoSelector = + "#layout > ytmusic-player-bar > div.middle-controls.style-scope.ytmusic-player-bar > div.content-info-wrapper.style-scope.ytmusic-player-bar > span"; + +// Grab the title using the selector +const getTitle = (win) => { + return win.webContents + .executeJavaScript( + "document.querySelector('" + titleSelector + "').innerText" + ) + .catch((error) => { + console.log(error); + }); +}; + +// Grab the image src using the selector +const getImageSrc = (win) => { + return win.webContents + .executeJavaScript("document.querySelector('" + imageSelector + "').src") + .catch((error) => { + console.log(error); + }); +}; + +// Grab the subinfo using the selector +const getSubInfo = async (win) => { + // Get innerText of subinfo element + const subInfoString = await win.webContents.executeJavaScript( + 'document.querySelector("' + subInfoSelector + '").innerText' + ); + + // Split and clean the string + const splittedSubInfo = subInfoString.replaceAll("\n", "").split(" • "); + + // Make sure we always return 3 elements in the aray + const subInfo = []; + for (let i = 0; i < 3; i++) { + // Fill array with empty string if not defined + subInfo.push(splittedSubInfo[i] || ""); + } + + return subInfo; +}; + +// Grab the native image using the src +const getImage = async (src) => { + const result = await fetch(src); + const buffer = await result.buffer(); + return nativeImage.createFromBuffer(buffer); +}; + +const getPausedStatus = async (win) => { + const title = await win.webContents.executeJavaScript("document.title"); + return !title.includes("-"); +}; + +// Fill songInfo with empty values +const songInfo = { + title: "", + artist: "", + views: "", + likes: "", + imageSrc: "", + image: null, + isPaused: true, +}; + +const registerProvider = (win) => { + // This variable will be filled with the callbacks once they register + const callbacks = []; + + // This function will allow plugins to register callback that will be triggered when data changes + const registerCallback = (callback) => { + callbacks.push(callback); + }; + + win.on("page-title-updated", async () => { + // Save the old title temporarily + const oldTitle = songInfo.title; + // Get and set the new data + songInfo.title = await getTitle(win); + songInfo.isPaused = await getPausedStatus(win); + + // If title changed then we do need to update other info + if (oldTitle !== songInfo.title) { + const subInfo = await getSubInfo(win); + songInfo.artist = subInfo[0]; + songInfo.views = subInfo[1]; + songInfo.likes = subInfo[2]; + songInfo.imageSrc = await getImageSrc(win); + songInfo.image = await getImage(songInfo.imageSrc); + } + + // Trigger the callbacks + callbacks.forEach((c) => { + c(songInfo); + }); + }); + + return registerCallback; +}; + +module.exports = registerProvider; diff --git a/providers/song-info/back.js b/providers/song-info/back.js deleted file mode 100644 index c7c78a19..00000000 --- a/providers/song-info/back.js +++ /dev/null @@ -1,120 +0,0 @@ -const {nativeImage} = require('electron'); -const fetch = require('node-fetch'); - -// This selects the song title -const titleSelector = '.title.style-scope.ytmusic-player-bar'; - -// This selects the song image -const imageSelector = '#layout > ytmusic-player-bar > div.middle-controls.style-scope.ytmusic-player-bar > img'; - -// This selects the song subinfo, this includes artist, views, likes -const subInfoSelector = '#layout > ytmusic-player-bar > div.middle-controls.style-scope.ytmusic-player-bar > div.content-info-wrapper.style-scope.ytmusic-player-bar > span'; - -// This is used for to control the songs -const presskey = (window, key) => { - window.webContents.sendInputEvent({ - type: 'keydown', - keyCode: key - }); -}; - -// Grab the title using the selector -const getTitle = win => { - return win.webContents.executeJavaScript( - 'document.querySelector(\'' + titleSelector + '\').innerText' - ).catch(error => { - console.log(error); - }); -}; - -// Grab the image src using the selector -const getImageSrc = win => { - return win.webContents.executeJavaScript( - 'document.querySelector(\'' + imageSelector + '\').src' - ).catch(error => { - console.log(error); - }); -}; - -// Grab the subinfo using the selector -const getSubInfo = async win => { - // Get innerText of subinfo element - const subInfoString = await win.webContents.executeJavaScript( - 'document.querySelector("' + subInfoSelector + '").innerText'); - - // Split and clean the string - const splittedSubInfo = subInfoString.replaceAll('\n', '').split(' • '); - - // Make sure we always return 3 elements in the aray - const subInfo = []; - for (let i = 0; i < 3; i++) { - // Fill array with empty string if not defined - subInfo.push(splittedSubInfo[i] || ''); - } - - return subInfo; -}; - -// Grab the native image using the src -const getImage = async src => { - const result = await fetch(src); - const buffer = await result.buffer(); - return nativeImage.createFromBuffer(buffer); -}; - -const getPausedStatus = async win => { - const title = await win.webContents.executeJavaScript('document.title'); - return !title.includes('-'); -}; - -// This variable will be filled with the callbacks once they register -const callbacks = []; - -module.exports = win => { - // Fill songInfo with empty values - global.songInfo = { - title: '', - artist: '', - views: '', - likes: '', - imageSrc: '', - image: null, - isPaused: true - }; - // The song control functions - global.songControls = { - previous: () => presskey(win, 'k'), - next: () => presskey(win, 'j'), - pause: () => presskey(win, 'space'), - like: () => presskey(win, '_'), - dislike: () => presskey(win, '+') - }; - - // This function will allow plugins to register callback that will be triggered when data changes - global.songInfo.onNewData = callback => { - callbacks.push(callback); - }; - - win.on('page-title-updated', async () => { - // Save the old title temporarily - const oldTitle = global.songInfo.title; - // Get and set the new data - global.songInfo.title = await getTitle(win); - global.songInfo.isPaused = await getPausedStatus(win); - - // If title changed then we do need to update other info - if (oldTitle !== global.songInfo.title) { - const subInfo = await getSubInfo(win); - global.songInfo.artist = subInfo[0]; - global.songInfo.views = subInfo[1]; - global.songInfo.likes = subInfo[2]; - global.songInfo.imageSrc = await getImageSrc(win); - global.songInfo.image = await getImage(global.songInfo.imageSrc); - } - - // Trigger the callbacks - callbacks.forEach(c => { - c(global.songInfo); - }); - }); -}; From 9110e79c16d25a3fd1989d1839b758e661a4a9c2 Mon Sep 17 00:00:00 2001 From: TC Date: Tue, 12 Jan 2021 21:17:40 +0100 Subject: [PATCH 3/7] Use refactored providers in touchbar plugin --- plugins/touchbar/back.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/touchbar/back.js b/plugins/touchbar/back.js index 5eeba67f..87240ed0 100644 --- a/plugins/touchbar/back.js +++ b/plugins/touchbar/back.js @@ -7,6 +7,9 @@ const { TouchBarScrubber, } = TouchBar; +const getSongInfo = require("../../providers/song-info"); +const getSongControls = require("../../providers/song-controls"); + // Songtitle label const songTitle = new TouchBarLabel({ label: "", @@ -55,19 +58,16 @@ const touchBar = new TouchBar({ ], }); -module.exports = win => { +module.exports = (win) => { + const registerCallback = getSongInfo(win); + const { playPause, next, previous, like, dislike } = getSongControls(win); + // If the page is ready, register the callback - win.on('ready-to-show', () => { - controls = [ - global.songControls.previous, - global.songControls.pause, - global.songControls.next, - global.songControls.like, - global.songControls.dislike - ]; + win.on("ready-to-show", () => { + controls = [previous, playPause, next, like, dislike]; // Register the callback - global.songInfo.onNewData(songInfo => { + registerCallback((songInfo) => { // Song information changed, so lets update the touchBar // Set the song title From de1e4196d9dd841f9344ad8bcaf2b30b03836589 Mon Sep 17 00:00:00 2001 From: TC Date: Tue, 12 Jan 2021 21:18:05 +0100 Subject: [PATCH 4/7] Use refactored provider in notification plugin --- plugins/notifications/back.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/notifications/back.js b/plugins/notifications/back.js index a55bffe4..d70898cc 100644 --- a/plugins/notifications/back.js +++ b/plugins/notifications/back.js @@ -1,7 +1,9 @@ const { Notification } = require("electron"); -const notify = info => { - let notificationImage = 'assets/youtube-music.png'; +const getSongInfo = require("../../providers/song-info"); + +const notify = (info) => { + let notificationImage = "assets/youtube-music.png"; if (info.image) { notificationImage = info.image.resize({ height: 256, width: 256 }); @@ -18,10 +20,12 @@ const notify = info => { new Notification(notification).show(); }; -module.exports = win => { - win.on('ready-to-show', () => { +module.exports = (win) => { + const registerCallback = getSongInfo(win); + + win.on("ready-to-show", () => { // Register the callback for new song information - global.songInfo.onNewData(songInfo => { + registerCallback((songInfo) => { // If song is playing send notification if (!songInfo.isPaused) { notify(songInfo); From 87d2693e2b021d109c1ca6dd4b4466bfb40d3ef3 Mon Sep 17 00:00:00 2001 From: TC Date: Tue, 12 Jan 2021 21:18:32 +0100 Subject: [PATCH 5/7] Use refactored provider in shortcuts plugin --- plugins/shortcuts/back.js | 17 +++++++---------- plugins/shortcuts/youtube.js | 29 ----------------------------- 2 files changed, 7 insertions(+), 39 deletions(-) delete mode 100644 plugins/shortcuts/youtube.js diff --git a/plugins/shortcuts/back.js b/plugins/shortcuts/back.js index 5a25a379..36efd0bc 100644 --- a/plugins/shortcuts/back.js +++ b/plugins/shortcuts/back.js @@ -1,12 +1,7 @@ const { globalShortcut } = require("electron"); const electronLocalshortcut = require("electron-localshortcut"); -const { - playPause, - nextTrack, - previousTrack, - startSearch -} = require("./youtube.js"); +const getSongControls = require("../../providers/song-controls"); function _registerGlobalShortcut(webContents, shortcut, action) { globalShortcut.register(shortcut, () => { @@ -21,11 +16,13 @@ function _registerLocalShortcut(win, shortcut, action) { } function registerShortcuts(win) { + const { playPause, next, previous, search } = getSongControls(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); + _registerGlobalShortcut(win.webContents, "MediaNextTrack", next); + _registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previous); + _registerLocalShortcut(win, "CommandOrControl+F", search); + _registerLocalShortcut(win, "CommandOrControl+L", search); } module.exports = registerShortcuts; diff --git a/plugins/shortcuts/youtube.js b/plugins/shortcuts/youtube.js deleted file mode 100644 index 6dd26199..00000000 --- a/plugins/shortcuts/youtube.js +++ /dev/null @@ -1,29 +0,0 @@ -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 -}; From f7cbf2c221dc817e754cb569d3191db8b12750a3 Mon Sep 17 00:00:00 2001 From: TC Date: Tue, 12 Jan 2021 21:19:01 +0100 Subject: [PATCH 6/7] No autoloading of providers (loaded on demand in plugins) --- index.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/index.js b/index.js index a083d4d9..4d5fb9d6 100644 --- a/index.js +++ b/index.js @@ -28,8 +28,6 @@ if (config.get("options.disableHardwareAcceleration")) { // Adds debug features like hotkeys for triggering dev tools and reload require("electron-debug")(); -// these are the providers for the plugins, this shouldn't be hardcoded but it's temporarily -const providers = ["song-info"]; // Prevent window being garbage collected let mainWindow; autoUpdater.autoDownload = false; @@ -56,15 +54,6 @@ function loadPlugins(win) { } }); - providers.forEach(provider => { - console.log("Loaded provider - " + provider); - const providerPath = path.join(__dirname, "providers", provider, "back.js"); - fileExists(providerPath, () => { - const handle = require(providerPath); - handle(win); - }); - }); - config.plugins.getEnabled().forEach(([plugin, options]) => { console.log("Loaded plugin - " + plugin); const pluginPath = path.join(__dirname, "plugins", plugin, "back.js"); From aec542e95e2837f54bf19de675f311444789ea4e Mon Sep 17 00:00:00 2001 From: TC Date: Wed, 13 Jan 2021 22:22:22 +0100 Subject: [PATCH 7/7] Update discord plugin for new provider + wait for ready --- plugins/discord-rpc/back.js | 72 +++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/plugins/discord-rpc/back.js b/plugins/discord-rpc/back.js index d984a300..3e4ab629 100644 --- a/plugins/discord-rpc/back.js +++ b/plugins/discord-rpc/back.js @@ -1,41 +1,51 @@ -const Discord = require('discord-rpc'); +const Discord = require("discord-rpc"); + +const getSongInfo = require("../../providers/song-info"); + const rpc = new Discord.Client({ - transport: 'ipc' + transport: "ipc", }); -const clientId = '790655993809338398'; +// Application ID registered by @semvis123 +const clientId = "790655993809338398"; + +module.exports = (win) => { + const registerCallback = getSongInfo(win); -module.exports = win => { // If the page is ready, register the callback - win.on('ready-to-show', () => { - // Startup the rpc client - rpc.login({ - clientId - }).catch(console.error); + win.on("ready-to-show", () => { + rpc.on("ready", () => { + // Register the callback + registerCallback((songInfo) => { + // Song information changed, so lets update the rich presence + const activityInfo = { + details: songInfo.title, + state: songInfo.artist, + largeImageKey: "logo", + largeImageText: songInfo.views + " - " + songInfo.likes, + }; - // Register the callback - global.songInfo.onNewData(songInfo => { - // Song information changed, so lets update the rich presence + if (songInfo.isPaused) { + // Add an idle icon to show that the song is paused + activityInfo.smallImageKey = "idle"; + activityInfo.smallImageText = "idle/paused"; + } 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; + } - const activityInfo = { - details: songInfo.title, - state: songInfo.artist, - largeImageKey: 'logo', - largeImageText: songInfo.views + ' - ' + songInfo.likes - }; - - if (songInfo.isPaused) { - // Add an idle icon to show that the song is paused - activityInfo.smallImageKey = 'idle'; - activityInfo.smallImageText = 'idle/paused'; - } 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); - } - - rpc.setActivity(activityInfo); + rpc.setActivity(activityInfo); + }); }); + + // Startup the rpc client + rpc + .login({ + clientId, + }) + .catch(console.error); }); };