mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 10:31:47 +00:00
Merge branch 'master' into custom-electron-prompt
This commit is contained in:
@ -42,7 +42,6 @@ const defaultConfig = {
|
|||||||
api_root: "http://ws.audioscrobbler.com/2.0/",
|
api_root: "http://ws.audioscrobbler.com/2.0/",
|
||||||
api_key: "04d76faaac8726e60988e14c105d421a", // api key registered by @semvis123
|
api_key: "04d76faaac8726e60988e14c105d421a", // api key registered by @semvis123
|
||||||
secret: "a5d2a36fdf64819290f6982481eaffa2",
|
secret: "a5d2a36fdf64819290f6982481eaffa2",
|
||||||
suffixesToRemove: [' - Topic', 'VEVO'] // removes suffixes of the artist name, for better recognition
|
|
||||||
},
|
},
|
||||||
discord: {
|
discord: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "youtube-music",
|
"name": "youtube-music",
|
||||||
"productName": "YouTube Music",
|
"productName": "YouTube Music",
|
||||||
"version": "1.12.0",
|
"version": "1.12.1",
|
||||||
"description": "YouTube Music Desktop App - including custom plugins",
|
"description": "YouTube Music Desktop App - including custom plugins",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "th-ch/youtube-music",
|
"repository": "th-ch/youtube-music",
|
||||||
@ -42,6 +42,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
|
"start:debug": "ELECTRON_ENABLE_LOGGING=1 electron .",
|
||||||
"icon": "rimraf assets/generated && electron-icon-maker --input=assets/youtube-music.png --output=assets/generated",
|
"icon": "rimraf assets/generated && electron-icon-maker --input=assets/youtube-music.png --output=assets/generated",
|
||||||
"generate:package": "node utils/generate-package-json.js",
|
"generate:package": "node utils/generate-package-json.js",
|
||||||
"postinstall": "yarn run icon && yarn run plugins",
|
"postinstall": "yarn run icon && yarn run plugins",
|
||||||
@ -69,6 +70,7 @@
|
|||||||
"async-mutex": "^0.3.1",
|
"async-mutex": "^0.3.1",
|
||||||
"browser-id3-writer": "^4.4.0",
|
"browser-id3-writer": "^4.4.0",
|
||||||
"custom-electron-prompt": "^1.1.0",
|
"custom-electron-prompt": "^1.1.0",
|
||||||
|
"chokidar": "^3.5.1",
|
||||||
"custom-electron-titlebar": "^3.2.6",
|
"custom-electron-titlebar": "^3.2.6",
|
||||||
"discord-rpc": "^3.2.0",
|
"discord-rpc": "^3.2.0",
|
||||||
"electron-debug": "^3.2.0",
|
"electron-debug": "^3.2.0",
|
||||||
|
|||||||
@ -7,6 +7,7 @@ const { dialog, ipcMain } = require("electron");
|
|||||||
const getSongInfo = require("../../providers/song-info");
|
const getSongInfo = require("../../providers/song-info");
|
||||||
const { injectCSS, listenAction } = require("../utils");
|
const { injectCSS, listenAction } = require("../utils");
|
||||||
const { ACTIONS, CHANNEL } = require("./actions.js");
|
const { ACTIONS, CHANNEL } = require("./actions.js");
|
||||||
|
const { getImage } = require("../../providers/song-info");
|
||||||
|
|
||||||
const sendError = (win, err) => {
|
const sendError = (win, err) => {
|
||||||
const dialogOpts = {
|
const dialogOpts = {
|
||||||
@ -41,23 +42,29 @@ function handle(win) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on("add-metadata", (event, filePath, songBuffer, currentMetadata) => {
|
ipcMain.on("add-metadata", async (event, filePath, songBuffer, currentMetadata) => {
|
||||||
let fileBuffer = songBuffer;
|
let fileBuffer = songBuffer;
|
||||||
const songMetadata = { ...metadata, ...currentMetadata };
|
const songMetadata = { ...metadata, ...currentMetadata };
|
||||||
|
|
||||||
|
if (!songMetadata.image && songMetadata.imageSrc) {
|
||||||
|
songMetadata.image = await getImage(songMetadata.imageSrc);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const coverBuffer = songMetadata.image.toPNG();
|
const coverBuffer = songMetadata.image ? songMetadata.image.toPNG() : null;
|
||||||
const writer = new ID3Writer(songBuffer);
|
const writer = new ID3Writer(songBuffer);
|
||||||
|
|
||||||
// Create the metadata tags
|
// Create the metadata tags
|
||||||
writer
|
writer
|
||||||
.setFrame("TIT2", songMetadata.title)
|
.setFrame("TIT2", songMetadata.title)
|
||||||
.setFrame("TPE1", [songMetadata.artist])
|
.setFrame("TPE1", [songMetadata.artist]);
|
||||||
.setFrame("APIC", {
|
if (coverBuffer) {
|
||||||
|
writer.setFrame("APIC", {
|
||||||
type: 3,
|
type: 3,
|
||||||
data: coverBuffer,
|
data: coverBuffer,
|
||||||
description: "",
|
description: "",
|
||||||
});
|
});
|
||||||
|
}
|
||||||
writer.addTag();
|
writer.addTag();
|
||||||
fileBuffer = Buffer.from(writer.arrayBuffer);
|
fileBuffer = Buffer.from(writer.arrayBuffer);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -38,13 +38,18 @@ const baseUrl = defaultConfig.url;
|
|||||||
// contextBridge.exposeInMainWorld("downloader", {
|
// contextBridge.exposeInMainWorld("downloader", {
|
||||||
// download: () => {
|
// download: () => {
|
||||||
global.download = () => {
|
global.download = () => {
|
||||||
|
let metadata;
|
||||||
let videoUrl = getSongMenu()
|
let videoUrl = getSongMenu()
|
||||||
.querySelector("ytmusic-menu-navigation-item-renderer")
|
.querySelector("ytmusic-menu-navigation-item-renderer")
|
||||||
.querySelector("#navigation-endpoint")
|
.querySelector("#navigation-endpoint")
|
||||||
.getAttribute("href");
|
.getAttribute("href");
|
||||||
videoUrl = !videoUrl
|
if (videoUrl) {
|
||||||
? global.songInfo.url || window.location.href
|
videoUrl = baseUrl + "/" + videoUrl;
|
||||||
: baseUrl + "/" + videoUrl;
|
metadata = null;
|
||||||
|
} else {
|
||||||
|
metadata = global.songInfo;
|
||||||
|
videoUrl = metadata.url || window.location.href;
|
||||||
|
}
|
||||||
|
|
||||||
downloadVideoToMP3(
|
downloadVideoToMP3(
|
||||||
videoUrl,
|
videoUrl,
|
||||||
@ -61,7 +66,7 @@ global.download = () => {
|
|||||||
},
|
},
|
||||||
reinit,
|
reinit,
|
||||||
pluginOptions,
|
pluginOptions,
|
||||||
global.songInfo
|
metadata
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
// });
|
// });
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const { URL } = require("url");
|
|||||||
const { dialog, ipcMain } = require("electron");
|
const { dialog, ipcMain } = require("electron");
|
||||||
const is = require("electron-is");
|
const is = require("electron-is");
|
||||||
const ytpl = require("ytpl");
|
const ytpl = require("ytpl");
|
||||||
|
const chokidar = require('chokidar');
|
||||||
|
|
||||||
const { setOptions } = require("../../config/plugins");
|
const { setOptions } = require("../../config/plugins");
|
||||||
const getSongInfo = require("../../providers/song-info");
|
const getSongInfo = require("../../providers/song-info");
|
||||||
@ -15,7 +16,7 @@ let downloadLabel = defaultMenuDownloadLabel;
|
|||||||
let metadataURL = undefined;
|
let metadataURL = undefined;
|
||||||
let callbackIsRegistered = false;
|
let callbackIsRegistered = false;
|
||||||
|
|
||||||
module.exports = (win, options, refreshMenu) => {
|
module.exports = (win, options) => {
|
||||||
if (!callbackIsRegistered) {
|
if (!callbackIsRegistered) {
|
||||||
const registerCallback = getSongInfo(win);
|
const registerCallback = getSongInfo(win);
|
||||||
registerCallback((info) => {
|
registerCallback((info) => {
|
||||||
@ -35,7 +36,10 @@ module.exports = (win, options, refreshMenu) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const playlist = await ytpl(playlistID);
|
console.log("trying to get playlist ID" +playlistID);
|
||||||
|
const playlist = await ytpl(playlistID,
|
||||||
|
{ limit: options.playlistMaxItems || Infinity }
|
||||||
|
);
|
||||||
const playlistTitle = playlist.title;
|
const playlistTitle = playlist.title;
|
||||||
|
|
||||||
const folder = getFolder(options.downloadFolder);
|
const folder = getFolder(options.downloadFolder);
|
||||||
@ -49,24 +53,40 @@ module.exports = (win, options, refreshMenu) => {
|
|||||||
}
|
}
|
||||||
mkdirSync(playlistFolder, { recursive: true });
|
mkdirSync(playlistFolder, { recursive: true });
|
||||||
|
|
||||||
ipcMain.on("downloader-feedback", (_, feedback) => {
|
dialog.showMessageBox({
|
||||||
downloadLabel = feedback;
|
type: "info",
|
||||||
refreshMenu();
|
buttons: ["OK"],
|
||||||
|
title: "Started Download",
|
||||||
|
message: `Downloading Playlist "${playlistTitle}"`,
|
||||||
|
detail: `(${playlist.items.length} songs)`,
|
||||||
});
|
});
|
||||||
|
|
||||||
downloadLabel = `Downloading "${playlistTitle}"`;
|
|
||||||
refreshMenu();
|
|
||||||
|
|
||||||
if (is.dev()) {
|
if (is.dev()) {
|
||||||
console.log(
|
console.log(
|
||||||
`Downloading playlist "${playlistTitle}" (${playlist.items.length} songs)`
|
`Downloading playlist "${playlistTitle}" (${playlist.items.length} songs)`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist.items.slice(0, options.playlistMaxItems).forEach((song) => {
|
const steps = 1 / playlist.items.length;
|
||||||
|
let progress = 0;
|
||||||
|
|
||||||
|
win.setProgressBar(2); // starts with indefinite bar
|
||||||
|
|
||||||
|
let dirWatcher = chokidar.watch(playlistFolder);
|
||||||
|
dirWatcher.on('add', () => {
|
||||||
|
progress += steps;
|
||||||
|
if (progress >= 0.9999) {
|
||||||
|
win.setProgressBar(-1); // close progress bar
|
||||||
|
dirWatcher.close().then(() => dirWatcher = null);
|
||||||
|
} else {
|
||||||
|
win.setProgressBar(progress);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
playlist.items.forEach((song) => {
|
||||||
win.webContents.send(
|
win.webContents.send(
|
||||||
"downloader-download-playlist",
|
"downloader-download-playlist",
|
||||||
song,
|
song.url,
|
||||||
playlistTitle,
|
playlistTitle,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|||||||
@ -14,7 +14,8 @@ const ytdl = require("ytdl-core");
|
|||||||
|
|
||||||
const { triggerAction, triggerActionSync } = require("../utils");
|
const { triggerAction, triggerActionSync } = require("../utils");
|
||||||
const { ACTIONS, CHANNEL } = require("./actions.js");
|
const { ACTIONS, CHANNEL } = require("./actions.js");
|
||||||
const { defaultMenuDownloadLabel, getFolder } = require("./utils");
|
const { getFolder } = require("./utils");
|
||||||
|
const { cleanupArtistName } = require("../../providers/song-info");
|
||||||
|
|
||||||
const { createFFmpeg } = FFmpeg;
|
const { createFFmpeg } = FFmpeg;
|
||||||
const ffmpeg = createFFmpeg({
|
const ffmpeg = createFFmpeg({
|
||||||
@ -24,7 +25,7 @@ const ffmpeg = createFFmpeg({
|
|||||||
});
|
});
|
||||||
const ffmpegMutex = new Mutex();
|
const ffmpegMutex = new Mutex();
|
||||||
|
|
||||||
const downloadVideoToMP3 = (
|
const downloadVideoToMP3 = async (
|
||||||
videoUrl,
|
videoUrl,
|
||||||
sendFeedback,
|
sendFeedback,
|
||||||
sendError,
|
sendError,
|
||||||
@ -35,6 +36,16 @@ const downloadVideoToMP3 = (
|
|||||||
) => {
|
) => {
|
||||||
sendFeedback("Downloading…");
|
sendFeedback("Downloading…");
|
||||||
|
|
||||||
|
if (metadata === null) {
|
||||||
|
const info = await ytdl.getInfo(videoUrl);
|
||||||
|
const thumbnails = info.videoDetails?.author?.thumbnails;
|
||||||
|
metadata = {
|
||||||
|
artist: info.videoDetails?.media?.artist || cleanupArtistName(info.videoDetails?.author?.name) || "",
|
||||||
|
title: info.videoDetails?.media?.song || info.videoDetails?.title || "",
|
||||||
|
imageSrc: thumbnails ? thumbnails[thumbnails.length - 1].url : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let videoName = "YouTube Music - Unknown title";
|
let videoName = "YouTube Music - Unknown title";
|
||||||
let videoReadableStream;
|
let videoReadableStream;
|
||||||
try {
|
try {
|
||||||
@ -135,6 +146,7 @@ const toMP3 = async (
|
|||||||
ipcRenderer.send("add-metadata", filePath, fileBuffer, {
|
ipcRenderer.send("add-metadata", filePath, fileBuffer, {
|
||||||
artist: metadata.artist,
|
artist: metadata.artist,
|
||||||
title: metadata.title,
|
title: metadata.title,
|
||||||
|
imageSrc: metadata.imageSrc
|
||||||
});
|
});
|
||||||
ipcRenderer.once("add-metadata-done", reinit);
|
ipcRenderer.once("add-metadata-done", reinit);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -165,22 +177,16 @@ module.exports = {
|
|||||||
|
|
||||||
ipcRenderer.on(
|
ipcRenderer.on(
|
||||||
"downloader-download-playlist",
|
"downloader-download-playlist",
|
||||||
(_, songMetadata, playlistFolder, options) => {
|
(_, url, playlistFolder, options) => {
|
||||||
const reinit = () =>
|
|
||||||
ipcRenderer.send("downloader-feedback", defaultMenuDownloadLabel);
|
|
||||||
|
|
||||||
downloadVideoToMP3(
|
downloadVideoToMP3(
|
||||||
songMetadata.url,
|
url,
|
||||||
(feedback) => {
|
() => {},
|
||||||
ipcRenderer.send("downloader-feedback", feedback);
|
|
||||||
},
|
|
||||||
(error) => {
|
(error) => {
|
||||||
triggerAction(CHANNEL, ACTIONS.ERROR, error);
|
triggerAction(CHANNEL, ACTIONS.ERROR, error);
|
||||||
reinit();
|
|
||||||
},
|
},
|
||||||
reinit,
|
() => {},
|
||||||
options,
|
options,
|
||||||
songMetadata,
|
null,
|
||||||
playlistFolder
|
playlistFolder
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,21 +5,10 @@ const { setOptions } = require('../../config/plugins');
|
|||||||
const getSongInfo = require('../../providers/song-info');
|
const getSongInfo = require('../../providers/song-info');
|
||||||
const defaultConfig = require('../../config/defaults');
|
const defaultConfig = require('../../config/defaults');
|
||||||
|
|
||||||
const cleanupArtistName = (config, artist) => {
|
|
||||||
// removes the suffixes of the artist name for more recognition by last.fm
|
|
||||||
const { suffixesToRemove } = config;
|
|
||||||
if (suffixesToRemove === undefined) return artist;
|
|
||||||
|
|
||||||
for (suffix of suffixesToRemove) {
|
|
||||||
artist = artist.replace(suffix, '');
|
|
||||||
}
|
|
||||||
return artist;
|
|
||||||
}
|
|
||||||
|
|
||||||
const createFormData = params => {
|
const createFormData = params => {
|
||||||
// creates the body for in the post request
|
// creates the body for in the post request
|
||||||
const formData = new URLSearchParams();
|
const formData = new URLSearchParams();
|
||||||
for (key in params) {
|
for (const key in params) {
|
||||||
formData.append(key, params[key]);
|
formData.append(key, params[key]);
|
||||||
}
|
}
|
||||||
return formData;
|
return formData;
|
||||||
@ -28,7 +17,7 @@ const createQueryString = (params, api_sig) => {
|
|||||||
// creates a querystring
|
// creates a querystring
|
||||||
const queryData = [];
|
const queryData = [];
|
||||||
params.api_sig = api_sig;
|
params.api_sig = api_sig;
|
||||||
for (key in params) {
|
for (const key in params) {
|
||||||
queryData.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`);
|
queryData.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`);
|
||||||
}
|
}
|
||||||
return '?'+queryData.join('&');
|
return '?'+queryData.join('&');
|
||||||
@ -37,12 +26,12 @@ const createQueryString = (params, api_sig) => {
|
|||||||
const createApiSig = (params, secret) => {
|
const createApiSig = (params, secret) => {
|
||||||
// this function creates the api signature, see: https://www.last.fm/api/authspec
|
// this function creates the api signature, see: https://www.last.fm/api/authspec
|
||||||
const keys = [];
|
const keys = [];
|
||||||
for (key in params) {
|
for (const key in params) {
|
||||||
keys.push(key);
|
keys.push(key);
|
||||||
}
|
}
|
||||||
keys.sort();
|
keys.sort();
|
||||||
let sig = '';
|
let sig = '';
|
||||||
for (key of keys) {
|
for (const key of keys) {
|
||||||
if (String(key) === 'format')
|
if (String(key) === 'format')
|
||||||
continue
|
continue
|
||||||
sig += `${key}${params[key]}`;
|
sig += `${key}${params[key]}`;
|
||||||
@ -157,8 +146,6 @@ const lastfm = async (win, config) => {
|
|||||||
registerCallback( songInfo => {
|
registerCallback( songInfo => {
|
||||||
// set remove the old scrobble timer
|
// set remove the old scrobble timer
|
||||||
clearTimeout(scrobbleTimer);
|
clearTimeout(scrobbleTimer);
|
||||||
// make the artist name a bit cleaner
|
|
||||||
songInfo.artist = cleanupArtistName(config, songInfo.artist);
|
|
||||||
if (!songInfo.isPaused) {
|
if (!songInfo.isPaused) {
|
||||||
setNowPlaying(songInfo, config);
|
setNowPlaying(songInfo, config);
|
||||||
// scrobble when the song is half way through, or has passed the 4 minute mark
|
// scrobble when the song is half way through, or has passed the 4 minute mark
|
||||||
|
|||||||
@ -192,12 +192,13 @@ function setupLocalArrowShortcuts(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function callback(event) {
|
function callback(event) {
|
||||||
event.preventDefault();
|
|
||||||
switch (event.code) {
|
switch (event.code) {
|
||||||
case "ArrowUp":
|
case "ArrowUp":
|
||||||
|
event.preventDefault();
|
||||||
changeVolume(true, options);
|
changeVolume(true, options);
|
||||||
break;
|
break;
|
||||||
case "ArrowDown":
|
case "ArrowDown":
|
||||||
|
event.preventDefault();
|
||||||
changeVolume(false, options);
|
changeVolume(false, options);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,33 @@
|
|||||||
const { ipcRenderer } = require("electron");
|
const { ipcRenderer } = require("electron");
|
||||||
|
const is = require("electron-is");
|
||||||
|
|
||||||
|
let ignored = {
|
||||||
|
id: ["volume-slider", "expand-volume-slider"],
|
||||||
|
types: ["mousewheel", "keydown", "keyup"]
|
||||||
|
};
|
||||||
|
|
||||||
// Override specific listeners of volume-slider by modifying Element.prototype
|
|
||||||
function overrideAddEventListener() {
|
function overrideAddEventListener() {
|
||||||
// Events to ignore
|
|
||||||
const nativeEvents = ["mousewheel", "keydown", "keyup"];
|
|
||||||
// Save native addEventListener
|
// Save native addEventListener
|
||||||
Element.prototype._addEventListener = Element.prototype.addEventListener;
|
Element.prototype._addEventListener = Element.prototype.addEventListener;
|
||||||
// Override addEventListener to Ignore specific events in volume-slider
|
// Override addEventListener to Ignore specific events in volume-slider
|
||||||
Element.prototype.addEventListener = function (type, listener, useCapture = false) {
|
Element.prototype.addEventListener = function (type, listener, useCapture = false) {
|
||||||
if (this.tagName === "TP-YT-PAPER-SLIDER") { // tagName of #volume-slider
|
if (!(
|
||||||
for (const eventType of nativeEvents) {
|
ignored.id.includes(this.id) &&
|
||||||
if (eventType === type) {
|
ignored.types.includes(type)
|
||||||
return;
|
)) {
|
||||||
}
|
this._addEventListener(type, listener, useCapture);
|
||||||
}
|
} else if (is.dev()) {
|
||||||
}//else
|
console.log(`Ignoring event: "${this.id}.${type}()"`);
|
||||||
this._addEventListener(type, listener, useCapture);
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = () => {
|
module.exports = () => {
|
||||||
overrideAddEventListener();
|
overrideAddEventListener();
|
||||||
// Restore original function after did-finish-load to avoid keeping Element.prototype altered
|
// Restore original function after did-finish-load to avoid keeping Element.prototype altered
|
||||||
ipcRenderer.once("restoreAddEventListener", () => { //called from Main to make sure page is completly loaded
|
ipcRenderer.once("restoreAddEventListener", () => { // Called from main to make sure page is completly loaded
|
||||||
Element.prototype.addEventListener = Element.prototype._addEventListener;
|
Element.prototype.addEventListener = Element.prototype._addEventListener;
|
||||||
|
Element.prototype._addEventListener = undefined;
|
||||||
|
ignored = undefined;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -43,7 +43,7 @@ module.exports.fileExists = (path, callbackIfExists) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports.injectCSS = (webContents, filepath, cb = undefined) => {
|
module.exports.injectCSS = (webContents, filepath, cb = undefined) => {
|
||||||
webContents.once("did-finish-load", async () => {
|
webContents.on("did-finish-load", async () => {
|
||||||
await webContents.insertCSS(fs.readFileSync(filepath, "utf8"));
|
await webContents.insertCSS(fs.readFileSync(filepath, "utf8"));
|
||||||
if (cb) {
|
if (cb) {
|
||||||
cb();
|
cb();
|
||||||
|
|||||||
@ -5,6 +5,8 @@ const { remote } = require("electron");
|
|||||||
const config = require("./config");
|
const config = require("./config");
|
||||||
const { fileExists } = require("./plugins/utils");
|
const { fileExists } = require("./plugins/utils");
|
||||||
const setupFrontLogger = require("./providers/front-logger");
|
const setupFrontLogger = require("./providers/front-logger");
|
||||||
|
const setupSongControl = require("./providers/song-controls-front");
|
||||||
|
const setupSongInfo = require("./providers/song-info-front");
|
||||||
|
|
||||||
const plugins = config.plugins.getEnabled();
|
const plugins = config.plugins.getEnabled();
|
||||||
|
|
||||||
@ -37,8 +39,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// inject song-info provider
|
// inject song-info provider
|
||||||
const songInfoProviderPath = path.join(__dirname, "providers", "song-info-front.js")
|
setupSongInfo();
|
||||||
fileExists(songInfoProviderPath, require(songInfoProviderPath));
|
|
||||||
|
// inject song-control provider
|
||||||
|
setupSongControl();
|
||||||
|
|
||||||
// inject front logger
|
// inject front logger
|
||||||
setupFrontLogger();
|
setupFrontLogger();
|
||||||
|
|||||||
18
providers/song-controls-front.js
Normal file
18
providers/song-controls-front.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const { ipcRenderer } = require("electron");
|
||||||
|
|
||||||
|
let videoStream = document.querySelector(".video-stream");
|
||||||
|
module.exports = () => {
|
||||||
|
ipcRenderer.on("playPause", () => {
|
||||||
|
if (!videoStream) {
|
||||||
|
videoStream = document.querySelector(".video-stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoStream.paused) {
|
||||||
|
videoStream.play();
|
||||||
|
} else {
|
||||||
|
videoStream.yns_pause ?
|
||||||
|
videoStream.yns_pause() :
|
||||||
|
videoStream.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -12,7 +12,7 @@ module.exports = (win) => {
|
|||||||
// Playback
|
// Playback
|
||||||
previous: () => pressKey(win, "k"),
|
previous: () => pressKey(win, "k"),
|
||||||
next: () => pressKey(win, "j"),
|
next: () => pressKey(win, "j"),
|
||||||
playPause: () => pressKey(win, "space"),
|
playPause: () => win.webContents.send("playPause"),
|
||||||
like: () => pressKey(win, "_"),
|
like: () => pressKey(win, "_"),
|
||||||
dislike: () => pressKey(win, "+"),
|
dislike: () => pressKey(win, "+"),
|
||||||
go10sBack: () => pressKey(win, "h"),
|
go10sBack: () => pressKey(win, "h"),
|
||||||
|
|||||||
@ -10,17 +10,16 @@ ipcRenderer.on("update-song-info", async (_, extractedSongInfo) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const injectListener = () => {
|
const injectListener = () => {
|
||||||
var oldXHR = window.XMLHttpRequest;
|
const oldXHR = window.XMLHttpRequest;
|
||||||
function newXHR() {
|
function newXHR() {
|
||||||
var realXHR = new oldXHR();
|
const realXHR = new oldXHR();
|
||||||
realXHR.addEventListener(
|
realXHR.addEventListener(
|
||||||
"readystatechange",
|
"readystatechange",
|
||||||
() => {
|
() => {
|
||||||
if (realXHR.readyState == 4 && realXHR.status == 200) {
|
if (realXHR.readyState === 4 && realXHR.status === 200
|
||||||
if (realXHR.responseURL.includes("/player")) {
|
&& realXHR.responseURL.includes("/player")) {
|
||||||
// if the request contains the song info, send the response to ipcMain
|
// if the request contains the song info, send the response to ipcMain
|
||||||
ipcRenderer.send("song-info-request", realXHR.responseText);
|
ipcRenderer.send("song-info-request", realXHR.responseText);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const getArtist = async (win) => {
|
|||||||
artistName.textContent;
|
artistName.textContent;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill songInfo with empty values
|
// Fill songInfo with empty values
|
||||||
@ -57,8 +57,8 @@ const songInfo = {
|
|||||||
|
|
||||||
const handleData = async (responseText, win) => {
|
const handleData = async (responseText, win) => {
|
||||||
let data = JSON.parse(responseText);
|
let data = JSON.parse(responseText);
|
||||||
songInfo.title = data?.videoDetails?.title;
|
songInfo.title = data.videoDetails?.media?.song || data?.videoDetails?.title;
|
||||||
songInfo.artist = await getArtist(win) || data?.videoDetails?.author;
|
songInfo.artist = data.videoDetails?.media?.artist || await getArtist(win) || cleanupArtistName(data?.videoDetails?.author);
|
||||||
songInfo.views = data?.videoDetails?.viewCount;
|
songInfo.views = data?.videoDetails?.viewCount;
|
||||||
songInfo.imageSrc = data?.videoDetails?.thumbnail?.thumbnails?.pop()?.url;
|
songInfo.imageSrc = data?.videoDetails?.thumbnail?.thumbnails?.pop()?.url;
|
||||||
songInfo.songDuration = data?.videoDetails?.lengthSeconds;
|
songInfo.songDuration = data?.videoDetails?.lengthSeconds;
|
||||||
@ -102,5 +102,20 @@ const registerProvider = (win) => {
|
|||||||
return registerCallback;
|
return registerCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const suffixesToRemove = [' - Topic', 'VEVO'];
|
||||||
|
function cleanupArtistName(artist) {
|
||||||
|
if (!artist) {
|
||||||
|
return artist;
|
||||||
|
}
|
||||||
|
for (const suffix of suffixesToRemove) {
|
||||||
|
if (artist.endsWith(suffix)) {
|
||||||
|
return artist.slice(0, -suffix.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return artist;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = registerProvider;
|
module.exports = registerProvider;
|
||||||
module.exports.getImage = getImage;
|
module.exports.getImage = getImage;
|
||||||
|
module.exports.cleanupArtistName = cleanupArtistName;
|
||||||
|
|
||||||
|
|||||||
63
yarn.lock
63
yarn.lock
@ -1638,6 +1638,14 @@ anymatch@^3.0.3:
|
|||||||
normalize-path "^3.0.0"
|
normalize-path "^3.0.0"
|
||||||
picomatch "^2.0.4"
|
picomatch "^2.0.4"
|
||||||
|
|
||||||
|
anymatch@~3.1.1:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||||
|
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
|
||||||
|
dependencies:
|
||||||
|
normalize-path "^3.0.0"
|
||||||
|
picomatch "^2.0.4"
|
||||||
|
|
||||||
app-builder-bin@3.5.12:
|
app-builder-bin@3.5.12:
|
||||||
version "3.5.12"
|
version "3.5.12"
|
||||||
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.5.12.tgz#bbe174972cc1f481f73d6d92ad47a8b4c7eb4530"
|
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.5.12.tgz#bbe174972cc1f481f73d6d92ad47a8b4c7eb4530"
|
||||||
@ -1991,6 +1999,11 @@ bcrypt-pbkdf@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tweetnacl "^0.14.3"
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
|
binary-extensions@^2.0.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||||
|
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||||
|
|
||||||
binaryextensions@^4.15.0:
|
binaryextensions@^4.15.0:
|
||||||
version "4.15.0"
|
version "4.15.0"
|
||||||
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-4.15.0.tgz#c63a502e0078ff1b0e9b00a9f74d3c2b0f8bd32e"
|
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-4.15.0.tgz#c63a502e0078ff1b0e9b00a9f74d3c2b0f8bd32e"
|
||||||
@ -2075,7 +2088,7 @@ braces@^2.3.1:
|
|||||||
split-string "^3.0.2"
|
split-string "^3.0.2"
|
||||||
to-regex "^3.0.1"
|
to-regex "^3.0.1"
|
||||||
|
|
||||||
braces@^3.0.1:
|
braces@^3.0.1, braces@~3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||||
@ -2404,6 +2417,21 @@ charenc@0.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||||
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
|
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
|
||||||
|
|
||||||
|
chokidar@^3.5.1:
|
||||||
|
version "3.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
|
||||||
|
integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
|
||||||
|
dependencies:
|
||||||
|
anymatch "~3.1.1"
|
||||||
|
braces "~3.0.2"
|
||||||
|
glob-parent "~5.1.0"
|
||||||
|
is-binary-path "~2.1.0"
|
||||||
|
is-glob "~4.0.1"
|
||||||
|
normalize-path "~3.0.0"
|
||||||
|
readdirp "~3.5.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.1"
|
||||||
|
|
||||||
chownr@^1.1.1:
|
chownr@^1.1.1:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||||
@ -4262,7 +4290,7 @@ fs.realpath@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||||
|
|
||||||
fsevents@^2.1.2:
|
fsevents@^2.1.2, fsevents@~2.3.1:
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||||
@ -4375,6 +4403,13 @@ glob-parent@^5.0.0, glob-parent@^5.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-glob "^4.0.1"
|
is-glob "^4.0.1"
|
||||||
|
|
||||||
|
glob-parent@~5.1.0:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||||
|
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||||
|
dependencies:
|
||||||
|
is-glob "^4.0.1"
|
||||||
|
|
||||||
glob-to-regexp@^0.3.0:
|
glob-to-regexp@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
|
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
|
||||||
@ -4907,6 +4942,13 @@ is-arrayish@^0.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||||
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
|
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
|
||||||
|
|
||||||
|
is-binary-path@~2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||||
|
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||||
|
dependencies:
|
||||||
|
binary-extensions "^2.0.0"
|
||||||
|
|
||||||
is-buffer@^1.1.5, is-buffer@~1.1.6:
|
is-buffer@^1.1.5, is-buffer@~1.1.6:
|
||||||
version "1.1.6"
|
version "1.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||||
@ -5037,7 +5079,7 @@ is-glob@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.0"
|
is-extglob "^2.1.0"
|
||||||
|
|
||||||
is-glob@^4.0.0, is-glob@^4.0.1:
|
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
||||||
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
||||||
@ -6586,7 +6628,7 @@ normalize-path@^2.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
remove-trailing-separator "^1.0.1"
|
remove-trailing-separator "^1.0.1"
|
||||||
|
|
||||||
normalize-path@^3.0.0:
|
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||||
@ -7482,6 +7524,13 @@ readdir-glob@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
|
|
||||||
|
readdirp@~3.5.0:
|
||||||
|
version "3.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
|
||||||
|
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
|
||||||
|
dependencies:
|
||||||
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
redent@^3.0.0:
|
redent@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||||
@ -8676,9 +8725,9 @@ typescript@^4.1.5:
|
|||||||
integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==
|
integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==
|
||||||
|
|
||||||
ua-parser-js@^0.7.21:
|
ua-parser-js@^0.7.21:
|
||||||
version "0.7.23"
|
version "0.7.28"
|
||||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.23.tgz#704d67f951e13195fbcd3d78818577f5bc1d547b"
|
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
|
||||||
integrity sha512-m4hvMLxgGHXG3O3fQVAyyAQpZzDOvwnhOTjYz5Xmr7r/+LpkNy3vJXdVRWgd1TkAb7NGROZuSy96CrlNVjA7KA==
|
integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==
|
||||||
|
|
||||||
unbzip2-stream@^1.3.3:
|
unbzip2-stream@^1.3.3:
|
||||||
version "1.4.3"
|
version "1.4.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user