From d852029d25e2af919adeef1461139398903209cb Mon Sep 17 00:00:00 2001 From: Sem Visscher Date: Thu, 18 Mar 2021 17:48:56 +0100 Subject: [PATCH 1/3] Improved songinfo provider, by using the data from the '/player' request --- plugins/discord/back.js | 8 +-- preload.js | 4 ++ providers/song-info-front.js | 21 ++++++++ providers/song-info.js | 96 +++++++++--------------------------- 4 files changed, 52 insertions(+), 77 deletions(-) create mode 100644 providers/song-info-front.js diff --git a/plugins/discord/back.js b/plugins/discord/back.js index 3e4ab629..5c53193b 100644 --- a/plugins/discord/back.js +++ b/plugins/discord/back.js @@ -22,7 +22,10 @@ module.exports = (win) => { details: songInfo.title, state: songInfo.artist, largeImageKey: "logo", - largeImageText: songInfo.views + " - " + songInfo.likes, + largeImageText: [ + songInfo.uploadDate, + songInfo.views.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " views" + ].join(' || '), }; if (songInfo.isPaused) { @@ -42,8 +45,7 @@ module.exports = (win) => { }); // Startup the rpc client - rpc - .login({ + rpc.login({ clientId, }) .catch(console.error); diff --git a/preload.js b/preload.js index c3d51d16..94f2a72a 100644 --- a/preload.js +++ b/preload.js @@ -29,6 +29,10 @@ document.addEventListener("DOMContentLoaded", () => { }); }); + // inject song-info provider + const songInfoProviderPath = path.join(__dirname, "providers", "song-info-front.js") + fileExists(songInfoProviderPath, require(songInfoProviderPath)); + // Add action for reloading global.reload = () => remote.getCurrentWindow().webContents.loadURL(config.get("url")); diff --git a/providers/song-info-front.js b/providers/song-info-front.js new file mode 100644 index 00000000..5cd94a56 --- /dev/null +++ b/providers/song-info-front.js @@ -0,0 +1,21 @@ +const { ipcRenderer } = require("electron"); + +const injectListener = () => { + var oldXHR = window.XMLHttpRequest; + function newXHR() { + var realXHR = new oldXHR(); + realXHR.addEventListener("readystatechange", function() { + 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 + ); + } + }, false); + return realXHR; + } + window.XMLHttpRequest = newXHR; +} +module.exports = injectListener \ No newline at end of file diff --git a/providers/song-info.js b/providers/song-info.js index 4ddf9cab..22f1a99c 100644 --- a/providers/song-info.js +++ b/providers/song-info.js @@ -1,73 +1,20 @@ +const { ipcMain } = require("electron"); 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 selects the progress bar, used for songlength and current progress +// This selects the progress bar, used for current progress const progressSelector = "#progress-bar"; -// 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 progress using the selector const getProgress = async (win) => { - // Get max value of the progressbar element - const songDuration = await win.webContents.executeJavaScript( - 'document.querySelector("' + progressSelector + '").max' - ); // Get current value of the progressbar element const elapsedSeconds = await win.webContents.executeJavaScript( 'document.querySelector("' + progressSelector + '").value' ); - return { songDuration, elapsedSeconds }; + return elapsedSeconds; }; // Grab the native image using the src @@ -77,6 +24,7 @@ const getImage = async (src) => { return nativeImage.createFromBuffer(buffer); }; +// To find the paused status, we check if the title contains `-` const getPausedStatus = async (win) => { const title = await win.webContents.executeJavaScript("document.title"); return !title.includes("-"); @@ -86,8 +34,8 @@ const getPausedStatus = async (win) => { const songInfo = { title: "", artist: "", - views: "", - likes: "", + views: 0, + uploadDate: "", imageSrc: "", image: null, isPaused: true, @@ -95,6 +43,17 @@ const songInfo = { elapsedSeconds: 0, }; +const handleData = async (_event, responseText) => { + 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.songDuration = data?.videoDetails?.lengthSeconds; + songInfo.image = await getImage(songInfo.imageSrc); + songInfo.uploadDate = data?.microformat?.microformatDataRenderer?.uploadDate; +} + const registerProvider = (win) => { // This variable will be filled with the callbacks once they register const callbacks = []; @@ -105,33 +64,22 @@ const registerProvider = (win) => { }; 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); - const { songDuration, elapsedSeconds } = await getProgress(win); - songInfo.songDuration = songDuration; + const elapsedSeconds = await getProgress(win); songInfo.elapsedSeconds = elapsedSeconds; - // 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); }); }); + // This will be called when the song-info-front finds a new request with song data + ipcMain.on('song-info-request', handleData); + return registerCallback; }; -module.exports = registerProvider; +module.exports = registerProvider; \ No newline at end of file From 3464b0383cdb868e7d4979a482525a1d1a7ddcfd Mon Sep 17 00:00:00 2001 From: Sem Visscher Date: Thu, 18 Mar 2021 17:53:30 +0100 Subject: [PATCH 2/3] small readability changes --- providers/song-info-front.js | 7 ++++--- providers/song-info.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/providers/song-info-front.js b/providers/song-info-front.js index 5cd94a56..42c3afb2 100644 --- a/providers/song-info-front.js +++ b/providers/song-info-front.js @@ -4,18 +4,19 @@ const injectListener = () => { var oldXHR = window.XMLHttpRequest; function newXHR() { var realXHR = new oldXHR(); - realXHR.addEventListener("readystatechange", function() { + realXHR.addEventListener("readystatechange", () => { if(realXHR.readyState==4 && realXHR.status==200){ - if (realXHR.responseURL.includes('/player')) + 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 ); + } } }, false); return realXHR; } window.XMLHttpRequest = newXHR; } -module.exports = injectListener \ No newline at end of file +module.exports = injectListener; diff --git a/providers/song-info.js b/providers/song-info.js index 22f1a99c..067c5718 100644 --- a/providers/song-info.js +++ b/providers/song-info.js @@ -82,4 +82,4 @@ const registerProvider = (win) => { return registerCallback; }; -module.exports = registerProvider; \ No newline at end of file +module.exports = registerProvider; From 204f384d0106b6accd3ad1836b8a19f6fdbc540e Mon Sep 17 00:00:00 2001 From: Sem Visscher Date: Sun, 21 Mar 2021 14:14:26 +0100 Subject: [PATCH 3/3] fetch highest resolution image instead of lowest resolution --- providers/song-info.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/song-info.js b/providers/song-info.js index 067c5718..5bea89b8 100644 --- a/providers/song-info.js +++ b/providers/song-info.js @@ -44,11 +44,11 @@ const songInfo = { }; const handleData = async (_event, responseText) => { - data = JSON.parse(responseText); + 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;