Merge pull request #125 from th-ch/refactor-providers

Refactor providers
This commit is contained in:
th-ch
2021-01-14 21:03:26 +01:00
committed by GitHub
9 changed files with 244 additions and 257 deletions

View File

@ -28,8 +28,6 @@ if (config.get("options.disableHardwareAcceleration")) {
// Adds debug features like hotkeys for triggering dev tools and reload // Adds debug features like hotkeys for triggering dev tools and reload
require("electron-debug")(); 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 // Prevent window being garbage collected
let mainWindow; let mainWindow;
autoUpdater.autoDownload = false; 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]) => { config.plugins.getEnabled().forEach(([plugin, options]) => {
console.log("Loaded plugin - " + plugin); console.log("Loaded plugin - " + plugin);
const pluginPath = path.join(__dirname, "plugins", plugin, "back.js"); const pluginPath = path.join(__dirname, "plugins", plugin, "back.js");

View File

@ -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({ 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 // If the page is ready, register the callback
win.on('ready-to-show', () => { win.on("ready-to-show", () => {
// Startup the rpc client rpc.on("ready", () => {
rpc.login({ // Register the callback
clientId registerCallback((songInfo) => {
}).catch(console.error); // 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 if (songInfo.isPaused) {
global.songInfo.onNewData(songInfo => { // Add an idle icon to show that the song is paused
// Song information changed, so lets update the rich presence 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 = { rpc.setActivity(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);
}); });
// Startup the rpc client
rpc
.login({
clientId,
})
.catch(console.error);
}); });
}; };

View File

@ -1,27 +1,31 @@
const {Notification} = require('electron'); const { Notification } = require("electron");
const notify = info => { const getSongInfo = require("../../providers/song-info");
let notificationImage = 'assets/youtube-music.png';
const notify = (info) => {
let notificationImage = "assets/youtube-music.png";
if (info.image) { if (info.image) {
notificationImage = info.image.resize({height: 256, width: 256}); notificationImage = info.image.resize({ height: 256, width: 256 });
} }
// Fill the notification with content // Fill the notification with content
const notification = { const notification = {
title: info.title || 'Playing', title: info.title || "Playing",
body: info.artist, body: info.artist,
icon: notificationImage, icon: notificationImage,
silent: true silent: true,
}; };
// Send the notification // Send the notification
new Notification(notification).show(); new Notification(notification).show();
}; };
module.exports = win => { module.exports = (win) => {
win.on('ready-to-show', () => { const registerCallback = getSongInfo(win);
win.on("ready-to-show", () => {
// Register the callback for new song information // Register the callback for new song information
global.songInfo.onNewData(songInfo => { registerCallback((songInfo) => {
// If song is playing send notification // If song is playing send notification
if (!songInfo.isPaused) { if (!songInfo.isPaused) {
notify(songInfo); notify(songInfo);

View File

@ -1,12 +1,7 @@
const { globalShortcut } = require("electron"); const { globalShortcut } = require("electron");
const electronLocalshortcut = require("electron-localshortcut"); const electronLocalshortcut = require("electron-localshortcut");
const { const getSongControls = require("../../providers/song-controls");
playPause,
nextTrack,
previousTrack,
startSearch
} = require("./youtube.js");
function _registerGlobalShortcut(webContents, shortcut, action) { function _registerGlobalShortcut(webContents, shortcut, action) {
globalShortcut.register(shortcut, () => { globalShortcut.register(shortcut, () => {
@ -21,11 +16,13 @@ function _registerLocalShortcut(win, shortcut, action) {
} }
function registerShortcuts(win) { function registerShortcuts(win) {
const { playPause, next, previous, search } = getSongControls(win);
_registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause); _registerGlobalShortcut(win.webContents, "MediaPlayPause", playPause);
_registerGlobalShortcut(win.webContents, "MediaNextTrack", nextTrack); _registerGlobalShortcut(win.webContents, "MediaNextTrack", next);
_registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previousTrack); _registerGlobalShortcut(win.webContents, "MediaPreviousTrack", previous);
_registerLocalShortcut(win, "CommandOrControl+F", startSearch); _registerLocalShortcut(win, "CommandOrControl+F", search);
_registerLocalShortcut(win, "CommandOrControl+L", startSearch); _registerLocalShortcut(win, "CommandOrControl+L", search);
} }
module.exports = registerShortcuts; module.exports = registerShortcuts;

View File

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

View File

@ -1,15 +1,18 @@
const {TouchBar} = require('electron'); const { TouchBar } = require("electron");
const { const {
TouchBarButton, TouchBarButton,
TouchBarLabel, TouchBarLabel,
TouchBarSpacer, TouchBarSpacer,
TouchBarSegmentedControl, TouchBarSegmentedControl,
TouchBarScrubber TouchBarScrubber,
} = TouchBar; } = TouchBar;
const getSongInfo = require("../../providers/song-info");
const getSongControls = require("../../providers/song-controls");
// Songtitle label // Songtitle label
const songTitle = new TouchBarLabel({ const songTitle = new TouchBarLabel({
label: '' label: "",
}); });
// This will store the song controls once available // This will store the song controls once available
let controls = []; let controls = [];
@ -22,23 +25,23 @@ const pausePlayButton = new TouchBarButton();
// The song control buttons (control functions are in the same order) // The song control buttons (control functions are in the same order)
const buttons = new TouchBarSegmentedControl({ const buttons = new TouchBarSegmentedControl({
mode: 'buttons', mode: "buttons",
segments: [ segments: [
new TouchBarButton({ new TouchBarButton({
label: '⏮' label: "⏮",
}), }),
pausePlayButton, pausePlayButton,
new TouchBarButton({ new TouchBarButton({
label: '⏭' label: "⏭",
}), }),
new TouchBarButton({ new TouchBarButton({
label: '👎' label: "👎",
}), }),
new TouchBarButton({ new TouchBarButton({
label: '👍' label: "👍",
}) }),
], ],
change: i => controls[i]() change: (i) => controls[i](),
}); });
// This is the touchbar object, this combines everything with proper layout // This is the touchbar object, this combines everything with proper layout
@ -46,38 +49,37 @@ const touchBar = new TouchBar({
items: [ items: [
new TouchBarScrubber({ new TouchBarScrubber({
items: [songImage, songTitle], items: [songImage, songTitle],
continuous: false continuous: false,
}), }),
new TouchBarSpacer({ new TouchBarSpacer({
size: 'flexible' size: "flexible",
}), }),
buttons buttons,
] ],
}); });
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 // If the page is ready, register the callback
win.on('ready-to-show', () => { win.on("ready-to-show", () => {
controls = [ controls = [previous, playPause, next, like, dislike];
global.songControls.previous,
global.songControls.pause,
global.songControls.next,
global.songControls.like,
global.songControls.dislike
];
// Register the callback // Register the callback
global.songInfo.onNewData(songInfo => { registerCallback((songInfo) => {
// Song information changed, so lets update the touchBar // Song information changed, so lets update the touchBar
// Set the song title // Set the song title
songTitle.label = songInfo.title; songTitle.label = songInfo.title;
// Changes the pause button if paused // Changes the pause button if paused
pausePlayButton.label = songInfo.isPaused ? '▶️' : '⏸'; pausePlayButton.label = songInfo.isPaused ? "▶️" : "⏸";
// Get image source // 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); win.setTouchBar(touchBar);
}); });

View File

@ -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, "/"),
};
};

137
providers/song-info.js Normal file
View File

@ -0,0 +1,137 @@
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
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 };
};
// 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,
songDuration: 0,
elapsedSeconds: 0,
};
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);
const { songDuration, elapsedSeconds } = await getProgress(win);
songInfo.songDuration = songDuration;
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);
});
});
return registerCallback;
};
module.exports = registerProvider;

View File

@ -1,141 +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 selects the progress bar, used for songlength and current progress
const progressSelector = '#progress-bar';
// 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 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};
};
// 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,
songDuration: 0,
elapsedSeconds: 0
};
// 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);
const {songDuration, elapsedSeconds} = await getProgress(win);
global.songInfo.songDuration = songDuration;
global.songInfo.elapsedSeconds = elapsedSeconds;
// 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);
});
});
};