diff --git a/plugins/downloader/front.js b/plugins/downloader/front.js index 879ba222..c8731935 100644 --- a/plugins/downloader/front.js +++ b/plugins/downloader/front.js @@ -1,4 +1,4 @@ -const { contextBridge } = require("electron"); +const { ipcRenderer } = require("electron"); const { defaultConfig } = require("../../config"); const { getSongMenu } = require("../../providers/dom-elements"); @@ -13,15 +13,17 @@ const downloadButton = ElementFromFile( ); let pluginOptions = {}; -const observer = new MutationObserver((mutations, observer) => { +const observer = new MutationObserver(() => { if (!menu) { menu = getSongMenu(); + if (!menu) return; } + if (menu.contains(downloadButton)) return; + const menuUrl = document.querySelector('tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint')?.href; + if (menuUrl && !menuUrl.includes('watch?')) return; - if (menu && !menu.contains(downloadButton)) { - menu.prepend(downloadButton); - progress = document.querySelector("#ytmcustom-download"); - } + menu.prepend(downloadButton); + progress = document.querySelector("#ytmcustom-download"); }); const reinit = () => { @@ -46,7 +48,13 @@ global.download = () => { ?.querySelector('ytmusic-menu-navigation-item-renderer.iron-selected[tabindex="0"] #navigation-endpoint') ?.getAttribute("href"); if (videoUrl) { - videoUrl = baseUrl + "/" + videoUrl; + if (videoUrl.startsWith('watch?')) { + videoUrl = baseUrl + "/" + videoUrl; + } + if (videoUrl.includes('?playlist=')) { + ipcRenderer.send('download-playlist-request', videoUrl); + return; + } metadata = null; } else { metadata = global.songInfo; @@ -78,10 +86,13 @@ global.download = () => { function observeMenu(options) { pluginOptions = { ...pluginOptions, ...options }; - observer.observe(document, { - childList: true, - subtree: true, - }); + + document.addEventListener('apiLoaded', () => { + observer.observe(document.querySelector('ytmusic-popup-container'), { + childList: true, + subtree: true, + }); + }, { once: true, passive: true }) } module.exports = observeMenu; diff --git a/plugins/downloader/menu.js b/plugins/downloader/menu.js index a1ae503a..449fa2c3 100644 --- a/plugins/downloader/menu.js +++ b/plugins/downloader/menu.js @@ -14,87 +14,21 @@ let downloadLabel = defaultMenuDownloadLabel; let playingPlaylistId = undefined; let callbackIsRegistered = false; +const INVALID_PLAYLIST_MODIFIER = 'RDAMPLPL'; + module.exports = (win, options) => { if (!callbackIsRegistered) { ipcMain.on("video-src-changed", async (_, data) => { playingPlaylistId = JSON.parse(data)?.videoDetails?.playlistId; }); + ipcMain.on("download-playlist-request", async (_event, url) => downloadPlaylist(url, win, options)); callbackIsRegistered = true; } return [ { label: downloadLabel, - click: async () => { - const currentPagePlaylistId = new URL(win.webContents.getURL()).searchParams.get("list"); - const playlistId = currentPagePlaylistId || playingPlaylistId; - if (!playlistId) { - sendError(win, new Error("No playlist ID found")); - return; - } - - console.log(`trying to get playlist ID: '${playlistId}'`); - let playlist; - try { - playlist = await ytpl(playlistId, { - limit: options.playlistMaxItems || Infinity, - }); - } catch (e) { - sendError(win, e); - return; - } - const playlistTitle = playlist.title; - - const folder = getFolder(options.downloadFolder); - const playlistFolder = join(folder, playlistTitle); - if (existsSync(playlistFolder)) { - sendError( - win, - new Error(`The folder ${playlistFolder} already exists`) - ); - return; - } - mkdirSync(playlistFolder, { recursive: true }); - - dialog.showMessageBox({ - type: "info", - buttons: ["OK"], - title: "Started Download", - message: `Downloading Playlist "${playlistTitle}"`, - detail: `(${playlist.items.length} songs)`, - }); - - if (is.dev()) { - console.log( - `Downloading playlist "${playlistTitle}" (${playlist.items.length} songs)` - ); - } - - 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( - "downloader-download-playlist", - song.url, - playlistTitle, - options - ); - }); - }, + click: () => downloadPlaylist(undefined, win, options) }, { label: "Choose download folder", @@ -123,3 +57,87 @@ module.exports = (win, options) => { }, ]; }; + +async function downloadPlaylist(url, win, options) { + const getPlaylistID = aURL => { + const result = aURL?.searchParams.get("list") || aURL?.searchParams.get("playlist"); + return (!result || result.startsWith(INVALID_PLAYLIST_MODIFIER)) ? undefined : result; + }; + if (url) { + try { + url = new URL(url); + } catch { + url = undefined; + }; + } + const playlistId = getPlaylistID(url) + || getPlaylistID(new URL(win.webContents.getURL())) + || playingPlaylistId; + + if (!playlistId) { + sendError(win, new Error("No playlist ID found")); + return; + } + + console.log(`trying to get playlist ID: '${playlistId}'`); + let playlist; + try { + playlist = await ytpl(playlistId, { + limit: options.playlistMaxItems || Infinity, + }); + } catch (e) { + sendError(win, e); + return; + } + const playlistTitle = playlist.title; + + const folder = getFolder(options.downloadFolder); + const playlistFolder = join(folder, playlistTitle); + if (existsSync(playlistFolder)) { + sendError( + win, + new Error(`The folder ${playlistFolder} already exists`) + ); + return; + } + mkdirSync(playlistFolder, { recursive: true }); + + dialog.showMessageBox({ + type: "info", + buttons: ["OK"], + title: "Started Download", + message: `Downloading Playlist "${playlistTitle}"`, + detail: `(${playlist.items.length} songs)`, + }); + + if (is.dev()) { + console.log( + `Downloading playlist "${playlistTitle}" - ${playlist.items.length} songs (${playlistId})` + ); + } + + 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( + "downloader-download-playlist", + song.url, + playlistTitle, + options + ); + }); +} diff --git a/plugins/downloader/youtube-dl.js b/plugins/downloader/youtube-dl.js index a8194b0b..d7f34ff4 100644 --- a/plugins/downloader/youtube-dl.js +++ b/plugins/downloader/youtube-dl.js @@ -46,7 +46,7 @@ const downloadVideoToMP3 = async ( cleanupName(videoDetails?.author?.name) || "", title: videoDetails?.media?.song || videoDetails?.title || "", - imageSrcYTPL: thumbnails ? + imageSrcYTPL: thumbnails ? urlToJPG(thumbnails[thumbnails.length - 1].url, videoDetails?.videoId) : "" }