diff --git a/plugins/songInfo/back.js b/plugins/songInfo/back.js new file mode 100644 index 00000000..94d7618a --- /dev/null +++ b/plugins/songInfo/back.js @@ -0,0 +1,120 @@ +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 = async win => { + // Fill songInfo with empty values + global.songInfo = { + title: '', + artist: '', + views: '', + likes: '', + imageSrc: '', + image: null, + isPaused: true + }; + // The song control funcions + 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); + }); + }); +}; diff --git a/plugins/touchbar/back.js b/plugins/touchbar/back.js index e165950f..5e0a41bf 100644 --- a/plugins/touchbar/back.js +++ b/plugins/touchbar/back.js @@ -1,6 +1,4 @@ -const { - TouchBar, nativeImage -} = require('electron'); +const {TouchBar} = require('electron'); const { TouchBarButton, TouchBarLabel, @@ -8,103 +6,80 @@ const { TouchBarSegmentedControl, TouchBarScrubber } = TouchBar; -const fetch = require('node-fetch'); -// This selects the song title -const titleSelector = '.title.style-scope.ytmusic-player-bar'; +// Songtitle label +const songTitle = new TouchBarLabel({ + label: '' +}); +// This will store the song controls once available +let controls = []; -// This selects the song image -const imageSelector = '#layout > ytmusic-player-bar > div.middle-controls.style-scope.ytmusic-player-bar > img'; +// This will store the song image once available +const songImage = {}; -// These keys will be used to go backwards, pause, skip songs, like songs, dislike songs -const keys = ['k', 'space', 'j', '_', '+']; +// Pause/play button +const pausePlayButton = new TouchBarButton(); -const presskey = (window, key) => { - window.webContents.sendInputEvent({ - type: 'keydown', - keyCode: key - }); -}; +// The song control buttons (control functions are in the same order) +const buttons = new TouchBarSegmentedControl({ + mode: 'buttons', + segments: [ + new TouchBarButton({ + label: '⏮' + }), + pausePlayButton, + new TouchBarButton({ + label: '⏭' + }), + new TouchBarButton({ + label: '👎' + }), + new TouchBarButton({ + label: '👍' + }) + ], + change: i => controls[i]() +}); -// 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 getImage = win => { - return win.webContents.executeJavaScript( - 'document.querySelector(\'' + imageSelector + '\').src' - ).catch(error => { - console.log(error); - }); -}; +// This is the touchbar object, this combines everything with proper layout +const touchBar = new TouchBar({ + items: [ + new TouchBarScrubber({ + items: [songImage, songTitle], + continuous: false + }), + new TouchBarSpacer({ + size: 'flexible' + }), + buttons + ] +}); module.exports = win => { - // Songtitle label - const songTitle = new TouchBarLabel({ - label: '' - }); + // 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 + ]; - // This will store the song image once available - const songImage = {}; + // Register the callback + global.songInfo.onNewData(songInfo => { + // Song information changed, so lets update the touchBar - // The song control buttons (keys to press are in the same order) - const buttons = new TouchBarSegmentedControl({ - mode: 'buttons', - segments: [ - new TouchBarButton({ - label: '⏮' - }), - new TouchBarButton({ - label: '⏯️' - }), - new TouchBarButton({ - label: '⏭' - }), - new TouchBarButton({ - label: '👎' - }), - new TouchBarButton({ - label: '👍' - }) - ], - change: i => presskey(win, keys[i]) - }); + // Set the song title + songTitle.label = songInfo.title; - // This is the touchbar object, this combines everything with proper layout - const touchBar = new TouchBar({ - items: [ - new TouchBarScrubber({ - items: [songImage, songTitle], - continuous: false - }), - new TouchBarSpacer({ - size: 'flexible' - }), - buttons - ] - }); + // Changes the pause button if paused + pausePlayButton.label = songInfo.isPaused ? '▶️' : '⏸'; - // If the page title changes, update touchbar and song title - win.on('page-title-updated', async () => { - // Set the song title - songTitle.label = await getTitle(win); + // Get image source + songImage.icon = songInfo.image ? songInfo.image.resize({height: 23}) : null; - // Get image source - const imageSrc = await getImage(win); - - // Fetch and set song image - await fetch(imageSrc) - .then(response => response.buffer()) - .then(data => { - songImage.icon = nativeImage.createFromBuffer(data).resize({height: 23}); - }); - - win.setTouchBar(touchBar); + win.setTouchBar(touchBar); + }); }); };