diff --git a/config/defaults.js b/config/defaults.js index 2a8810a3..cbabaef6 100644 --- a/config/defaults.js +++ b/config/defaults.js @@ -63,7 +63,19 @@ const defaultConfig = { volumeDown: "" }, savedVolume: undefined //plugin save volume between session here - } + }, + sponsorblock: { + enabled: false, + apiURL: "https://sponsor.ajay.app", + categories: [ + "sponsor", + "intro", + "outro", + "interaction", + "selfpromo", + "music_offtopic", + ], + }, }, }; diff --git a/index.js b/index.js index 180eb62e..8802a798 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,7 @@ const { setApplicationMenu } = require("./menu"); const { fileExists, injectCSS } = require("./plugins/utils"); const { isTesting } = require("./utils/testing"); const { setUpTray } = require("./tray"); +const { setupSongInfo } = require("./providers/song-info"); // Catch errors and log them unhandled({ @@ -159,6 +160,7 @@ function createMainWindow() { } app.once("browser-window-created", (event, win) => { + setupSongInfo(win); loadPlugins(win); win.webContents.on("did-fail-load", ( @@ -257,6 +259,35 @@ app.on("ready", () => { }, 20000); } + // Register appID on windows + if (is.windows()) { + const appID = "com.github.th-ch.youtube-music"; + app.setAppUserModelId(appID); + const appLocation = process.execPath; + const appData = app.getPath("appData"); + // check shortcut validity if not in dev mode / running portable app + if (!is.dev() && !appLocation.startsWith(path.join(appData, "..", "Local", "Temp"))) { + const shortcutPath = path.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "YouTube Music.lnk"); + try { // check if shortcut is registered and valid + const shortcutDetails = electron.shell.readShortcutLink(shortcutPath); // throw error if doesn't exist yet + if (shortcutDetails.target !== appLocation || shortcutDetails.appUserModelId !== appID) { + throw "needUpdate"; + } + } catch (error) { // if not valid -> Register shortcut + electron.shell.writeShortcutLink( + shortcutPath, + error === "needUpdate" ? "update" : "create", + { + target: appLocation, + cwd: appLocation.slice(0, appLocation.lastIndexOf(path.sep)), + description: "YouTube Music Desktop App - including custom plugins", + appUserModelId: appID + } + ); + } + } + } + mainWindow = createMainWindow(); setApplicationMenu(mainWindow); if (config.get("options.restartOnConfigChanges")) { diff --git a/jest.config.js b/jest.config.js index 21e1944c..472d5584 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,6 +2,5 @@ module.exports = { globals: { __APP__: undefined, // A different app will be launched in each test environment }, - testEnvironment: "./tests/environment", testTimeout: 30000, // 30s }; diff --git a/package.json b/package.json index 16c880a6..4980f060 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "scripts": { "test": "jest", "start": "electron .", - "start:debug": "ELECTRON_ENABLE_LOGGING=1 electron .", + "start:debug": "ELECTRON_ENABLE_LOGGING=1 electron .", "icon": "rimraf assets/generated && electron-icon-maker --input=assets/youtube-music.png --output=assets/generated", "generate:package": "node utils/generate-package-json.js", "postinstall": "yarn run icon && yarn run plugins", @@ -63,39 +63,38 @@ "npm": "Please use yarn and not npm" }, "dependencies": { - "@cliqz/adblocker-electron": "^1.20.4", - "@ffmpeg/core": "^0.8.5", - "@ffmpeg/ffmpeg": "^0.9.7", + "@cliqz/adblocker-electron": "^1.22.0", + "@ffmpeg/core": "^0.10.0", + "@ffmpeg/ffmpeg": "^0.10.0", "YoutubeNonStop": "git://github.com/lawfx/YoutubeNonStop.git#v0.9.0", "async-mutex": "^0.3.1", "browser-id3-writer": "^4.4.0", "custom-electron-prompt": "^1.1.0", "chokidar": "^3.5.1", - "custom-electron-titlebar": "^3.2.6", + "custom-electron-titlebar": "^3.2.7", "discord-rpc": "^3.2.0", "electron-debug": "^3.2.0", "electron-is": "^3.0.0", "electron-localshortcut": "^3.2.1", "electron-store": "^7.0.3", "electron-unhandled": "^3.0.2", - "electron-updater": "^4.3.8", - "filenamify": "^4.2.0", + "electron-updater": "^4.3.9", + "filenamify": "^4.3.0", "md5": "^2.3.0", "node-fetch": "^2.6.1", "node-notifier": "^9.0.1", - "open": "^8.0.3", - "ytdl-core": "^4.5.0", - "ytpl": "^2.1.1" + "ytdl-core": "^4.8.2", + "ytpl": "^2.2.0" }, "devDependencies": { - "electron": "^11.4.4", + "electron": "^12.0.8", "electron-builder": "^22.10.5", "electron-devtools-installer": "^3.1.1", "electron-icon-maker": "0.0.5", "get-port": "^5.1.1", "jest": "^26.6.3", "rimraf": "^3.0.2", - "spectron": "^13.0.0", + "spectron": "^14.0.0", "xo": "^0.38.2" }, "resolutions": { diff --git a/plugins/adblocker/blocker.js b/plugins/adblocker/blocker.js index f1b73b7b..81d6ec5f 100644 --- a/plugins/adblocker/blocker.js +++ b/plugins/adblocker/blocker.js @@ -8,7 +8,9 @@ const SOURCES = [ "https://raw.githubusercontent.com/kbinani/adblock-youtube-ads/master/signed.txt", // uBlock Origin "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt", - "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2020.txt", + "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters-2021.txt", + // Fanboy Annoyances + "https://secure.fanboy.co.nz/fanboy-annoyance_ubo.txt", ]; const loadAdBlockerEngine = ( diff --git a/plugins/disable-autoplay/front.js b/plugins/disable-autoplay/front.js index c40eb7fd..eb1b72db 100644 --- a/plugins/disable-autoplay/front.js +++ b/plugins/disable-autoplay/front.js @@ -1,25 +1,10 @@ -let videoElement = null; +const { ontimeupdate } = require("../../providers/video-element"); -const observer = new MutationObserver((mutations, observer) => { - if (!videoElement) { - videoElement = document.querySelector("video"); - } - - if (videoElement) { - videoElement.ontimeupdate = () => { - if (videoElement.currentTime === 0 && videoElement.duration !== NaN) { - // auto-confirm-when-paused plugin can interfere here if not disabled! - videoElement.pause(); - } - }; - } -}); - -function observeVideoElement() { - observer.observe(document, { - childList: true, - subtree: true, +module.exports = () => { + ontimeupdate((videoElement) => { + if (videoElement.currentTime === 0 && videoElement.duration !== NaN) { + // auto-confirm-when-paused plugin can interfere here if not disabled! + videoElement.pause(); + } }); -} - -module.exports = observeVideoElement; +}; diff --git a/plugins/discord/back.js b/plugins/discord/back.js index 33dbb38b..c6faadd7 100644 --- a/plugins/discord/back.js +++ b/plugins/discord/back.js @@ -1,6 +1,6 @@ const Discord = require("discord-rpc"); -const getSongInfo = require("../../providers/song-info"); +const registerCallback = require("../../providers/song-info"); const rpc = new Discord.Client({ transport: "ipc", @@ -12,8 +12,6 @@ const clientId = "790655993809338398"; let clearActivity; module.exports = (win, {activityTimoutEnabled, activityTimoutTime}) => { - const registerCallback = getSongInfo(win); - // If the page is ready, register the callback win.once("ready-to-show", () => { rpc.once("ready", () => { diff --git a/plugins/downloader/actions.js b/plugins/downloader/actions.js index da75f181..0d6c3426 100644 --- a/plugins/downloader/actions.js +++ b/plugins/downloader/actions.js @@ -2,6 +2,7 @@ const CHANNEL = "downloader"; const ACTIONS = { ERROR: "error", METADATA: "metadata", + PROGRESS: "progress", }; module.exports = { diff --git a/plugins/downloader/back.js b/plugins/downloader/back.js index ac32623d..060245ed 100644 --- a/plugins/downloader/back.js +++ b/plugins/downloader/back.js @@ -4,38 +4,41 @@ const { join } = require("path"); const ID3Writer = require("browser-id3-writer"); const { dialog, ipcMain } = require("electron"); -const getSongInfo = require("../../providers/song-info"); +const registerCallback = require("../../providers/song-info"); const { injectCSS, listenAction } = require("../utils"); +const { cropMaxWidth } = require("./utils"); const { ACTIONS, CHANNEL } = require("./actions.js"); const { getImage } = require("../../providers/song-info"); -const sendError = (win, err) => { - const dialogOpts = { +const sendError = (win, error) => { + win.setProgressBar(-1); // close progress bar + dialog.showMessageBox({ type: "info", buttons: ["OK"], title: "Error in download!", message: "Argh! Apologies, download failed…", - detail: err.toString(), - }; - dialog.showMessageBox(dialogOpts); + detail: error.toString(), + }); }; -let metadata = {}; +let nowPlayingMetadata = {}; function handle(win) { injectCSS(win.webContents, join(__dirname, "style.css")); - const registerCallback = getSongInfo(win); registerCallback((info) => { - metadata = info; + nowPlayingMetadata = info; }); - listenAction(CHANNEL, (event, action, error) => { + listenAction(CHANNEL, (event, action, arg) => { switch (action) { - case ACTIONS.ERROR: - sendError(win, error); + case ACTIONS.ERROR: // arg = error + sendError(win, arg); break; case ACTIONS.METADATA: - event.returnValue = JSON.stringify(metadata); + event.returnValue = JSON.stringify(nowPlayingMetadata); + break; + case ACTIONS.PROGRESS: // arg = progress + win.setProgressBar(arg); break; default: console.log("Unknown action: " + action); @@ -44,14 +47,17 @@ function handle(win) { ipcMain.on("add-metadata", async (event, filePath, songBuffer, currentMetadata) => { let fileBuffer = songBuffer; - const songMetadata = { ...metadata, ...currentMetadata }; - - if (!songMetadata.image && songMetadata.imageSrc) { - songMetadata.image = await getImage(songMetadata.imageSrc); - } + const songMetadata = currentMetadata.imageSrcYTPL ? // This means metadata come from ytpl.getInfo(); + { + ...currentMetadata, + image: cropMaxWidth(await getImage(currentMetadata.imageSrcYTPL)) + } : + { ...nowPlayingMetadata, ...currentMetadata }; try { - const coverBuffer = songMetadata.image ? songMetadata.image.toPNG() : null; + const coverBuffer = songMetadata.image && !songMetadata.image.isEmpty() ? + songMetadata.image.toPNG() : null; + const writer = new ID3Writer(songBuffer); // Create the metadata tags @@ -62,7 +68,7 @@ function handle(win) { writer.setFrame("APIC", { type: 3, data: coverBuffer, - description: "", + description: "" }); } writer.addTag(); diff --git a/plugins/downloader/front.js b/plugins/downloader/front.js index 200a7e15..879ba222 100644 --- a/plugins/downloader/front.js +++ b/plugins/downloader/front.js @@ -25,6 +25,7 @@ const observer = new MutationObserver((mutations, observer) => { }); const reinit = () => { + triggerAction(CHANNEL, ACTIONS.PROGRESS, -1); // closes progress bar if (!progress) { console.warn("Cannot update progress"); } else { @@ -38,11 +39,12 @@ const baseUrl = defaultConfig.url; // contextBridge.exposeInMainWorld("downloader", { // download: () => { global.download = () => { + triggerAction(CHANNEL, ACTIONS.PROGRESS, 2); // starts with indefinite progress bar let metadata; let videoUrl = getSongMenu() - .querySelector("ytmusic-menu-navigation-item-renderer") - .querySelector("#navigation-endpoint") - .getAttribute("href"); + // selector of first button which is always "Start Radio" + ?.querySelector('ytmusic-menu-navigation-item-renderer.iron-selected[tabindex="0"] #navigation-endpoint') + ?.getAttribute("href"); if (videoUrl) { videoUrl = baseUrl + "/" + videoUrl; metadata = null; @@ -53,12 +55,15 @@ global.download = () => { downloadVideoToMP3( videoUrl, - (feedback) => { + (feedback, ratio = undefined) => { if (!progress) { console.warn("Cannot update progress"); } else { progress.innerHTML = feedback; } + if (ratio) { + triggerAction(CHANNEL, ACTIONS.PROGRESS, ratio); + } }, (error) => { triggerAction(CHANNEL, ACTIONS.ERROR, error); diff --git a/plugins/downloader/menu.js b/plugins/downloader/menu.js index f5b99d73..ce7f5643 100644 --- a/plugins/downloader/menu.js +++ b/plugins/downloader/menu.js @@ -2,13 +2,13 @@ const { existsSync, mkdirSync } = require("fs"); const { join } = require("path"); const { URL } = require("url"); -const { dialog, ipcMain } = require("electron"); +const { dialog } = require("electron"); const is = require("electron-is"); const ytpl = require("ytpl"); const chokidar = require('chokidar'); const { setOptions } = require("../../config/plugins"); -const getSongInfo = require("../../providers/song-info"); +const registerCallback = require("../../providers/song-info"); const { sendError } = require("./back"); const { defaultMenuDownloadLabel, getFolder } = require("./utils"); @@ -18,7 +18,6 @@ let callbackIsRegistered = false; module.exports = (win, options) => { if (!callbackIsRegistered) { - const registerCallback = getSongInfo(win); registerCallback((info) => { metadataURL = info.url; }); @@ -36,10 +35,16 @@ module.exports = (win, options) => { return; } - console.log("trying to get playlist ID" +playlistID); - const playlist = await ytpl(playlistID, - { limit: options.playlistMaxItems || Infinity } - ); + 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); diff --git a/plugins/downloader/utils.js b/plugins/downloader/utils.js index e2763541..07bca3a5 100644 --- a/plugins/downloader/utils.js +++ b/plugins/downloader/utils.js @@ -3,3 +3,29 @@ const electron = require("electron"); module.exports.getFolder = (customFolder) => customFolder || (electron.app || electron.remote.app).getPath("downloads"); module.exports.defaultMenuDownloadLabel = "Download playlist"; + +const orderedQualityList = ["maxresdefault", "hqdefault", "mqdefault", "sdddefault"]; +module.exports.urlToJPG = (imgUrl, videoId) => { + if (!imgUrl || imgUrl.includes(".jpg")) return imgUrl; + //it will almost never get further than hqdefault + for (const quality of orderedQualityList) { + if (imgUrl.includes(quality)) { + return `https://img.youtube.com/vi/${videoId}/${quality}.jpg`; + } + } + return `https://img.youtube.com/vi/${videoId}/default.jpg`; +} + +module.exports.cropMaxWidth = (image) => { + const imageSize = image.getSize(); + // standart youtube artwork width with margins from both sides is 280 + 720 + 280 + if (imageSize.width === 1280 && imageSize.height === 720) { + return image.crop({ + x: 280, + y: 0, + width: 720, + height: 720 + }); + } + return image; +} diff --git a/plugins/downloader/youtube-dl.js b/plugins/downloader/youtube-dl.js index 814d9924..f0f47cec 100644 --- a/plugins/downloader/youtube-dl.js +++ b/plugins/downloader/youtube-dl.js @@ -14,7 +14,7 @@ const ytdl = require("ytdl-core"); const { triggerAction, triggerActionSync } = require("../utils"); const { ACTIONS, CHANNEL } = require("./actions.js"); -const { getFolder } = require("./utils"); +const { getFolder, urlToJPG } = require("./utils"); const { cleanupArtistName } = require("../../providers/song-info"); const { createFFmpeg } = FFmpeg; @@ -37,12 +37,14 @@ const downloadVideoToMP3 = async ( sendFeedback("Downloading…"); if (metadata === null) { - const info = await ytdl.getInfo(videoUrl); - const thumbnails = info.videoDetails?.author?.thumbnails; + const { videoDetails } = await ytdl.getInfo(videoUrl); + const thumbnails = videoDetails?.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 : "" + artist: videoDetails?.media?.artist || cleanupArtistName(videoDetails?.author?.name) || "", + title: videoDetails?.media?.song || videoDetails?.title || "", + imageSrcYTPL: thumbnails ? + urlToJPG(thumbnails[thumbnails.length - 1].url, videoDetails?.videoId) + : "" } } @@ -65,9 +67,10 @@ const downloadVideoToMP3 = async ( .on("data", (chunk) => { chunks.push(chunk); }) - .on("progress", (chunkLength, downloaded, total) => { - const progress = Math.floor((downloaded / total) * 100); - sendFeedback("Download: " + progress + "%"); + .on("progress", (_chunkLength, downloaded, total) => { + const ratio = downloaded / total; + const progress = Math.floor(ratio * 100); + sendFeedback("Download: " + progress + "%", ratio); }) .on("info", (info, format) => { videoName = info.videoDetails.title.replace("|", "").toString("ascii"); @@ -112,7 +115,7 @@ const toMP3 = async ( try { if (!ffmpeg.isLoaded()) { - sendFeedback("Loading…"); + sendFeedback("Loading…", 2); // indefinite progress bar after download await ffmpeg.load(); } @@ -146,7 +149,7 @@ const toMP3 = async ( ipcRenderer.send("add-metadata", filePath, fileBuffer, { artist: metadata.artist, title: metadata.title, - imageSrc: metadata.imageSrc + imageSrcYTPL: metadata.imageSrcYTPL }); ipcRenderer.once("add-metadata-done", reinit); } catch (e) { diff --git a/plugins/last-fm/back.js b/plugins/last-fm/back.js index efa4f41e..567dccc0 100644 --- a/plugins/last-fm/back.js +++ b/plugins/last-fm/back.js @@ -1,8 +1,8 @@ const fetch = require('node-fetch'); const md5 = require('md5'); -const open = require("open"); +const { shell } = require('electron'); const { setOptions } = require('../../config/plugins'); -const getSongInfo = require('../../providers/song-info'); +const registerCallback = require('../../providers/song-info'); const defaultConfig = require('../../config/defaults'); const createFormData = params => { @@ -58,7 +58,7 @@ const authenticate = async config => { // asks the user for authentication config.token = await createToken(config); setOptions('last-fm', config); - open(`https://www.last.fm/api/auth/?api_key=${config.api_key}&token=${config.token}`); + shell.openExternal(`https://www.last.fm/api/auth/?api_key=${config.api_key}&token=${config.token}`); return config; } @@ -128,10 +128,8 @@ const setNowPlaying = (songInfo, config) => { // this will store the timeout that will trigger addScrobble let scrobbleTimer = undefined; -const lastfm = async (win, config) => { - const registerCallback = getSongInfo(win); - - if (!config.api_root || !config.suffixesToRemove) { +const lastfm = async (_win, config) => { + if (!config.api_root) { // settings are not present, creating them with the default values config = defaultConfig.plugins['last-fm']; config.enabled = true; diff --git a/plugins/notifications/back.js b/plugins/notifications/back.js index d1ef3114..654e3b54 100644 --- a/plugins/notifications/back.js +++ b/plugins/notifications/back.js @@ -1,9 +1,9 @@ const { Notification } = require("electron"); const is = require("electron-is"); -const getSongInfo = require("../../providers/song-info"); +const registerCallback = require("../../providers/song-info"); const { notificationImage } = require("./utils"); -const { setupInteractive, notifyInteractive } = require("./interactive") +const setupInteractive = require("./interactive") const notify = (info, options) => { @@ -23,38 +23,24 @@ const notify = (info, options) => { return currentNotification; }; -module.exports = (win, options) => { - const isInteractive = is.windows() && options.interactive; - //setup interactive notifications for windows - if (isInteractive) { - setupInteractive(win, options.unpauseNotification); - } - const registerCallback = getSongInfo(win); +const setup = (options) => { let oldNotification; - let oldURL = ""; - win.once("ready-to-show", () => { - // Register the callback for new song information - registerCallback(songInfo => { - // on pause - reset url? and skip notification - if (songInfo.isPaused) { - //reset oldURL if unpause notification option is on - if (options.unpauseNotification) { - oldURL = ""; - } - return; - } - // If url isn't the same as last one - send notification - if (songInfo.url !== oldURL) { - oldURL = songInfo.url; - if (isInteractive) { - notifyInteractive(songInfo); - } else { - // Close the old notification - oldNotification?.close(); - // This fixes a weird bug that would cause the notification to be updated instead of showing - setTimeout(() => { oldNotification = notify(songInfo, options) }, 10); - } - } - }); + let currentUrl; + + registerCallback(songInfo => { + if (!songInfo.isPaused && (songInfo.url !== currentUrl || options.unpauseNotification)) { + // Close the old notification + oldNotification?.close(); + currentUrl = songInfo.url; + // This fixes a weird bug that would cause the notification to be updated instead of showing + setTimeout(() => { oldNotification = notify(songInfo, options) }, 10); + } }); +} + +module.exports = (win, options) => { + // Register the callback for new song information + is.windows() && options.interactive ? + setupInteractive(win, options.unpauseNotification) : + setup(options); }; diff --git a/plugins/notifications/interactive.js b/plugins/notifications/interactive.js index cb487cae..c30c1020 100644 --- a/plugins/notifications/interactive.js +++ b/plugins/notifications/interactive.js @@ -1,17 +1,27 @@ const { notificationImage, icons } = require("./utils"); const getSongControls = require('../../providers/song-controls'); +const registerCallback = require("../../providers/song-info"); const notifier = require("node-notifier"); //store song controls reference on launch let controls; -let notificationOnPause; +let notificationOnUnpause; -//Save controls and onPause option -module.exports.setupInteractive = (win, unpauseNotification) => { +module.exports = (win, unpauseNotification) => { + //Save controls and onPause option const { playPause, next, previous } = getSongControls(win); controls = { playPause, next, previous }; + notificationOnUnpause = unpauseNotification; - notificationOnPause = unpauseNotification; + let currentUrl; + + // Register songInfoCallback + registerCallback(songInfo => { + if (!songInfo.isPaused && (songInfo.url !== currentUrl || notificationOnUnpause)) { + currentUrl = songInfo.url; + sendToaster(songInfo); + } + }); win.webContents.once("closed", () => { deleteNotification() @@ -33,7 +43,7 @@ function deleteNotification() { } //New notification -module.exports.notifyInteractive = function sendToaster(songInfo) { +function sendToaster(songInfo) { deleteNotification(); //download image and get path let imgSrc = notificationImage(songInfo, true); @@ -71,7 +81,7 @@ module.exports.notifyInteractive = function sendToaster(songInfo) { // dont delete notification on play/pause toDelete = undefined; //manually send notification if not sending automatically - if (!notificationOnPause) { + if (!notificationOnUnpause) { songInfo.isPaused = false; sendToaster(songInfo); } diff --git a/plugins/sponsorblock/back.js b/plugins/sponsorblock/back.js new file mode 100644 index 00000000..3c7eb5b4 --- /dev/null +++ b/plugins/sponsorblock/back.js @@ -0,0 +1,51 @@ +const fetch = require("node-fetch"); + +const defaultConfig = require("../../config/defaults"); +const registerCallback = require("../../providers/song-info"); +const { sortSegments } = require("./segments"); + +let videoID; + +module.exports = (win, options) => { + const { apiURL, categories } = { + ...defaultConfig.plugins.sponsorblock, + ...options, + }; + + registerCallback(async (info) => { + const newURL = info.url || win.webContents.getURL(); + const newVideoID = new URL(newURL).searchParams.get("v"); + + if (videoID !== newVideoID) { + videoID = newVideoID; + const segments = await fetchSegments(apiURL, categories); + win.webContents.send("sponsorblock-skip", segments); + } + }); +}; + +const fetchSegments = async (apiURL, categories) => { + const sponsorBlockURL = `${apiURL}/api/skipSegments?videoID=${videoID}&categories=${JSON.stringify( + categories + )}`; + try { + const resp = await fetch(sponsorBlockURL, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + redirect: "follow", + }); + if (resp.status !== 200) { + return []; + } + const segments = await resp.json(); + const sortedSegments = sortSegments( + segments.map((submission) => submission.segment) + ); + + return sortedSegments; + } catch { + return []; + } +}; diff --git a/plugins/sponsorblock/front.js b/plugins/sponsorblock/front.js new file mode 100644 index 00000000..4f248bfa --- /dev/null +++ b/plugins/sponsorblock/front.js @@ -0,0 +1,27 @@ +const { ipcRenderer } = require("electron"); + +const is = require("electron-is"); + +const { ontimeupdate } = require("../../providers/video-element"); + +let currentSegments = []; + +module.exports = () => { + ipcRenderer.on("sponsorblock-skip", (_, segments) => { + currentSegments = segments; + }); + + ontimeupdate((videoElement) => { + currentSegments.forEach((segment) => { + if ( + videoElement.currentTime >= segment[0] && + videoElement.currentTime <= segment[1] + ) { + videoElement.currentTime = segment[1]; + if (is.dev()) { + console.log("SponsorBlock: skipping segment", segment); + } + } + }); + }); +}; diff --git a/plugins/sponsorblock/segments.js b/plugins/sponsorblock/segments.js new file mode 100644 index 00000000..c12a9e88 --- /dev/null +++ b/plugins/sponsorblock/segments.js @@ -0,0 +1,29 @@ +// Segments are an array [ [start, end], … ] +module.exports.sortSegments = (segments) => { + segments.sort((segment1, segment2) => + segment1[0] === segment2[0] + ? segment1[1] - segment2[1] + : segment1[0] - segment2[0] + ); + + const compiledSegments = []; + let currentSegment; + + segments.forEach((segment) => { + if (!currentSegment) { + currentSegment = segment; + return; + } + + if (currentSegment[1] < segment[0]) { + compiledSegments.push(currentSegment); + currentSegment = segment; + return; + } + + currentSegment[1] = Math.max(currentSegment[1], segment[1]); + }); + compiledSegments.push(currentSegment); + + return compiledSegments; +}; diff --git a/plugins/sponsorblock/tests/segments.test.js b/plugins/sponsorblock/tests/segments.test.js new file mode 100644 index 00000000..dbc3d4b0 --- /dev/null +++ b/plugins/sponsorblock/tests/segments.test.js @@ -0,0 +1,34 @@ +const { sortSegments } = require("../segments"); + +test("Segment sorting", () => { + expect( + sortSegments([ + [0, 3], + [7, 8], + [5, 6], + ]) + ).toEqual([ + [0, 3], + [5, 6], + [7, 8], + ]); + + expect( + sortSegments([ + [0, 5], + [6, 8], + [4, 6], + ]) + ).toEqual([[0, 8]]); + + expect( + sortSegments([ + [0, 6], + [7, 8], + [4, 6], + ]) + ).toEqual([ + [0, 6], + [7, 8], + ]); +}); diff --git a/plugins/taskbar-mediacontrol/back.js b/plugins/taskbar-mediacontrol/back.js index 46cb399e..26a11732 100644 --- a/plugins/taskbar-mediacontrol/back.js +++ b/plugins/taskbar-mediacontrol/back.js @@ -1,12 +1,11 @@ const getSongControls = require('../../providers/song-controls'); -const getSongInfo = require('../../providers/song-info'); +const registerCallback = require('../../providers/song-info'); const path = require('path'); let controls; let currentSongInfo; module.exports = win => { - const registerCallback = getSongInfo(win); const { playPause, next, previous } = getSongControls(win); controls = { playPause, next, previous }; diff --git a/plugins/touchbar/back.js b/plugins/touchbar/back.js index 0fca17a7..acaee583 100644 --- a/plugins/touchbar/back.js +++ b/plugins/touchbar/back.js @@ -7,7 +7,7 @@ const { TouchBarScrubber, } = TouchBar; -const getSongInfo = require("../../providers/song-info"); +const registerCallback = require("../../providers/song-info"); const getSongControls = require("../../providers/song-controls"); // Songtitle label @@ -59,7 +59,6 @@ const touchBar = new TouchBar({ }); module.exports = (win) => { - const registerCallback = getSongInfo(win); const { playPause, next, previous, like, dislike } = getSongControls(win); // If the page is ready, register the callback diff --git a/providers/song-info.js b/providers/song-info.js index 15b51459..08eaf944 100644 --- a/providers/song-info.js +++ b/providers/song-info.js @@ -9,18 +9,21 @@ const progressSelector = "#progress-bar"; // Grab the progress using the selector const getProgress = async (win) => { // Get current value of the progressbar element - const elapsedSeconds = await win.webContents.executeJavaScript( + return win.webContents.executeJavaScript( 'document.querySelector("' + progressSelector + '").value' ); - - return 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 output = nativeImage.createFromBuffer(buffer); + if (output.isEmpty() && !src.endsWith(".jpg") && src.includes(".jpg")) { // fix hidden webp files (https://github.com/th-ch/youtube-music/issues/315) + return getImage(src.slice(0, src.lastIndexOf(".jpg")+4)); + } else { + return output; + } }; // To find the paused status, we check if the title contains `-` @@ -30,15 +33,10 @@ const getPausedStatus = async (win) => { }; const getArtist = async (win) => { - return await win.webContents.executeJavaScript( - ` - var bar = document.getElementsByClassName('subtitle ytmusic-player-bar')[0]; - var artistName = (bar.getElementsByClassName('yt-formatted-string')[0]) || (bar.getElementsByClassName('byline ytmusic-player-bar')[0]); - if (artistName) { - artistName.textContent; - } - ` - ); + return win.webContents.executeJavaScript(` + document.querySelector(".subtitle.ytmusic-player-bar .yt-formatted-string") + ?.textContent + `); } // Fill songInfo with empty values @@ -57,8 +55,8 @@ const songInfo = { const handleData = async (responseText, win) => { let data = JSON.parse(responseText); - songInfo.title = data.videoDetails?.media?.song || data?.videoDetails?.title; - songInfo.artist = data.videoDetails?.media?.artist || await getArtist(win) || cleanupArtistName(data?.videoDetails?.author); + songInfo.title = data?.videoDetails?.title; + songInfo.artist = await getArtist(win) || cleanupArtistName(data?.videoDetails?.author); songInfo.views = data?.videoDetails?.viewCount; songInfo.imageSrc = data?.videoDetails?.thumbnail?.thumbnails?.pop()?.url; songInfo.songDuration = data?.videoDetails?.lengthSeconds; @@ -69,15 +67,15 @@ const handleData = async (responseText, win) => { win.webContents.send("update-song-info", JSON.stringify(songInfo)); }; +// 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); +}; + 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 () => { // Get and set the new data songInfo.isPaused = await getPausedStatus(win); @@ -98,8 +96,6 @@ const registerProvider = (win) => { c(songInfo); }); }); - - return registerCallback; }; const suffixesToRemove = [' - Topic', 'VEVO']; @@ -115,7 +111,7 @@ function cleanupArtistName(artist) { return artist; } -module.exports = registerProvider; +module.exports = registerCallback; +module.exports.setupSongInfo = registerProvider; module.exports.getImage = getImage; module.exports.cleanupArtistName = cleanupArtistName; - diff --git a/providers/video-element.js b/providers/video-element.js new file mode 100644 index 00000000..7be61c89 --- /dev/null +++ b/providers/video-element.js @@ -0,0 +1,22 @@ +let videoElement = null; + +module.exports.ontimeupdate = (cb) => { + const observer = new MutationObserver((mutations, observer) => { + if (!videoElement) { + videoElement = document.querySelector("video"); + if (videoElement) { + observer.disconnect(); + videoElement.ontimeupdate = () => cb(videoElement); + } + } + }); + + if (!videoElement) { + observer.observe(document, { + childList: true, + subtree: true, + }); + } else { + videoElement.ontimeupdate = () => cb(videoElement); + } +}; diff --git a/tests/index.test.js b/tests/index.test.js index 9ab4bf63..ccb261ca 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -1,3 +1,7 @@ +/** + * @jest-environment ./tests/environment + */ + describe("YouTube Music App", () => { const app = global.__APP__; diff --git a/yarn.lock b/yarn.lock index f4957437..68b5397f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -439,45 +439,45 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@cliqz/adblocker-content@^1.20.4": - version "1.20.4" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-content/-/adblocker-content-1.20.4.tgz#68c0c628acd6da49bb5a6ad9ee0cb540a8d50acd" - integrity sha512-Cp6M6MERCsLwklX6lAmrgOxom0pr4DjxmUGLcmM9MDACOIzk/m7ya1e82bXzEWAU1Jni2Bp91xUUWxg+DLWJgQ== +"@cliqz/adblocker-content@^1.22.0": + version "1.22.0" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-content/-/adblocker-content-1.22.0.tgz#4a625ddfcefa203d98e27f16c16328c655842b8c" + integrity sha512-M6L6OVcuxdu1YA7sSjTtU1CV+taMx+fCd5Wqqfepp2o8eEo9HxNdWrKRfnOG6O0vmpvdRLtdVMPvd74wPr2vvQ== dependencies: - "@cliqz/adblocker-extended-selectors" "^1.20.4" + "@cliqz/adblocker-extended-selectors" "^1.22.0" -"@cliqz/adblocker-electron-preload@^1.20.4": - version "1.20.4" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron-preload/-/adblocker-electron-preload-1.20.4.tgz#b7d6606dfc24e7b3f80109cc6820bd203faaf26e" - integrity sha512-tIEgFJJhEDTYrSUzAL+wbw+BBVwCtuFtckA/scka990DGlXsEmkJ7HxNXvUPwhOQiV4YUwN5bsqxCDA8VDTZNw== +"@cliqz/adblocker-electron-preload@^1.22.0": + version "1.22.0" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron-preload/-/adblocker-electron-preload-1.22.0.tgz#ea04db27572b8be4c34457ecf91f9f6d6d9e470f" + integrity sha512-LwdRIslyPW0tkAua2pqSDCGC89gjTOUToY2/1i6Hdm3ErTw2H3YbIl5/xZJd2pmU56kPtojDsREHowzdxvsXXw== dependencies: - "@cliqz/adblocker-content" "^1.20.4" + "@cliqz/adblocker-content" "^1.22.0" -"@cliqz/adblocker-electron@^1.20.4": - version "1.20.4" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron/-/adblocker-electron-1.20.4.tgz#6d7de52cff013ef3cd0f4a7850ebfc31f6240a46" - integrity sha512-HaHexPnJL1BBvloXuqmSh8WtpPKYHyZ+o6f+9SciySN4dJAX9BIGTk9D/V6eJWLmy6+wY7/Bpcn2Q4nrYXsqBw== +"@cliqz/adblocker-electron@^1.22.0": + version "1.22.0" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-electron/-/adblocker-electron-1.22.0.tgz#6648a5c0e648f95eaa12481593b0acafbeb6621a" + integrity sha512-eEv3Y6fKu7529ZDZknVla3PmdsEuychQP+S/d1Ck5s5S0xumnIoh4s9VW5u7XoJJ1dRqy+hUd03la2xoiueQVA== dependencies: - "@cliqz/adblocker" "^1.20.4" - "@cliqz/adblocker-electron-preload" "^1.20.4" + "@cliqz/adblocker" "^1.22.0" + "@cliqz/adblocker-electron-preload" "^1.22.0" tldts-experimental "^5.6.21" -"@cliqz/adblocker-extended-selectors@^1.20.4": - version "1.20.4" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-extended-selectors/-/adblocker-extended-selectors-1.20.4.tgz#6f5ab8251a0d40cacf3703f5621025e0d85d6348" - integrity sha512-VBP8iv1IdYpwQ0hbbeiXCSW7ppzK05dbPM4DyeCb54mB0CjWj/pMQwEvjMZKLWTkEyPd26oMqnxNQz1UgGaZag== +"@cliqz/adblocker-extended-selectors@^1.22.0": + version "1.22.0" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-extended-selectors/-/adblocker-extended-selectors-1.22.0.tgz#29d7f615db8b7bf79e1cdcbd4526f39355230a19" + integrity sha512-tZVPySwPFjYwiUU9wFWbn1VtT3B0BSSdeQT2HjB9CF5EvU33RxywIiF7Kg8gQ3JWwbCeONcmDNQybyOv5hMXQw== -"@cliqz/adblocker@^1.20.4": - version "1.20.4" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker/-/adblocker-1.20.4.tgz#63f75456b6d63f66dc73b9ac2971ed073bf26722" - integrity sha512-ylwc4fScwgDjh9mKAvBQ+oCNyZWncrPakU17KbMtq+l82LkzJ0ND0wififpeq+nI9JBiQosW+eus5R08THpwCQ== +"@cliqz/adblocker@^1.22.0": + version "1.22.0" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker/-/adblocker-1.22.0.tgz#4588ea79d9493638c8babec128e78947d92fee0b" + integrity sha512-vZuZlG0q8+AaEBsfI/b/CvgpSPR5Ipg8GfSZcD/UxtiHHP3otkVDSMKP65T8ypzxh47VdXXEJx950vfDpqjfgA== dependencies: - "@cliqz/adblocker-content" "^1.20.4" - "@cliqz/adblocker-extended-selectors" "^1.20.4" + "@cliqz/adblocker-content" "^1.22.0" + "@cliqz/adblocker-extended-selectors" "^1.22.0" "@remusao/guess-url-type" "^1.1.2" "@remusao/small" "^1.1.2" "@remusao/smaz" "^1.7.1" - "@types/chrome" "^0.0.133" + "@types/chrome" "^0.0.143" "@types/firefox-webext-browser" "^82.0.0" tldts-experimental "^5.6.21" @@ -497,7 +497,7 @@ ajv "^6.12.0" ajv-keywords "^3.4.1" -"@electron/get@^1.0.1", "@electron/get@^1.12.2": +"@electron/get@^1.0.1", "@electron/get@^1.12.4": version "1.12.4" resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.12.4.tgz#a5971113fc1bf8fa12a8789dc20152a7359f06ab" integrity sha512-6nr9DbJPUR9Xujw6zD3y+rS95TyItEVM0NVjt1EehY2vUWfIgPiIPVHxCvaTS0xr2B+DRxovYVKbuOWqC35kjg== @@ -513,6 +513,11 @@ global-agent "^2.0.2" global-tunnel-ng "^2.7.1" +"@electron/remote@^1.0.4": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@electron/remote/-/remote-1.1.0.tgz#167d119c7c03c7778b556fdc4f1f38a44b23f1c2" + integrity sha512-yr8gZTkIgJYKbFqExI4QZqMSjn1kL/us9Dl46+TH1EZdhgRtsJ6HDfdsIxu0QEc6Hv+DMAXs69rgquH+8FDk4w== + "@electron/universal@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.0.4.tgz#231ac246c39d45b80e159bd21c3f9027dcaa10f5" @@ -539,15 +544,15 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@ffmpeg/core@^0.8.5": - version "0.8.5" - resolved "https://registry.yarnpkg.com/@ffmpeg/core/-/core-0.8.5.tgz#2d0b7d4409a4348fa6a1888c247de706ffc0e63f" - integrity sha512-hemVFmhVLbD/VZaCG2BvCzFglKytMIMJ5aJfc12eXN4O4cG0wXnGTMVzlK1KKW/6viHhJMPkc9h4UDnJW8Uivg== +"@ffmpeg/core@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@ffmpeg/core/-/core-0.10.0.tgz#f6a58361b22d7c23c6f7071b9fff6d572bc3f499" + integrity sha512-qunWJl5PezpXEm31tb8Qu5z37B5KVA1VYZCpXchMhuAb3X9T7PuE3SlhOwphEoRhzaOa3lpofDfzihAUMFaVPQ== -"@ffmpeg/ffmpeg@^0.9.7": - version "0.9.7" - resolved "https://registry.yarnpkg.com/@ffmpeg/ffmpeg/-/ffmpeg-0.9.7.tgz#f309d689c59e35d345c049bf5e35f1ccde28c215" - integrity sha512-WpZkNnqYGoaMcMd1EpaDi7nxRyEd05OjOTAfItH/ZwvAKJpr7ksvHKTC/NjP0li6mFrTFLGudP81J1tG0babdg== +"@ffmpeg/ffmpeg@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@ffmpeg/ffmpeg/-/ffmpeg-0.10.0.tgz#0bebd944d50ce11297b91883f94b2f6220b74e34" + integrity sha512-W+d0ysYTO6d4vue/0KMYrxaprh9wvmnPqh6qyHXavBWLrDcE7gI3cJ/EQVfwe9nrt2e0Pi7873P2I18VEDgRfA== dependencies: is-url "^1.2.4" node-fetch "^2.6.1" @@ -1188,10 +1193,10 @@ "@types/node" "*" "@types/responselike" "*" -"@types/chrome@^0.0.133": - version "0.0.133" - resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.133.tgz#9e1d55441584ba2d5274ca84db36427da9c5dc6e" - integrity sha512-G8uIUdaCTBILprQvQXBWGXZxjAWbkCkFQit17cdH3zYQEwU8f/etNl8+M7e8MRz9Xj8daHaVpysneMZMx8/ldQ== +"@types/chrome@^0.0.143": + version "0.0.143" + resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.143.tgz#0722fd6c76a73003fa220d869d2dbc8afd581810" + integrity sha512-tkPDutWjEl/9hPnfR48IJLpH2Xg1Zs/vxfODRp7duY5a4frkULOHvEED8moJsELTrFkiEciwCxAjxVk2XhKSsA== dependencies: "@types/filesystem" "*" "@types/har-format" "*" @@ -1301,10 +1306,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.25.tgz#15967a7b577ff81383f9b888aa6705d43fbbae93" integrity sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ== -"@types/node@^12.0.12": - version "12.19.16" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.16.tgz#15753af35cbef636182d8d8ca55b37c8583cecb3" - integrity sha512-7xHmXm/QJ7cbK2laF+YYD7gb5MggHIIQwqyjin3bpEGiSuvScMQ5JZZXPvRipi1MwckTQbJZROMns/JxdnIL1Q== +"@types/node@^14.6.2": + version "14.14.44" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.44.tgz#df7503e6002847b834371c004b372529f3f85215" + integrity sha512-+gaugz6Oce6ZInfI/tK4Pq5wIIkJMEJUu92RB3Eu93mtj4wjjjz9EB5mLp5s1pSsLXdC/CPut/xF20ZzAQJbTA== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -1350,10 +1355,10 @@ dependencies: "@types/node" "*" -"@types/semver@^7.3.4": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb" - integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ== +"@types/semver@^7.3.5": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.6.tgz#e9831776f4512a7ba6da53e71c26e5fb67882d63" + integrity sha512-0caWDWmpCp0uifxFh+FaqK3CuZ2SkRR/ZRxAV5+zNdC3QVUi6wyOJnefhPvtNt8NQWXB5OA93BUvZsXpWat2Xw== "@types/stack-utils@^2.0.0": version "2.0.0" @@ -2046,9 +2051,9 @@ bn.js@^5.0.0, bn.js@^5.1.1: integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ== boolean@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.2.tgz#df1baa18b6a2b0e70840475e1d93ec8fe75b2570" - integrity sha512-RwywHlpCRc3/Wh81MiCKun4ydaIFyW5Ea6JbL6sRCVx5q5irDw7pMXBUFYF/jArQ6YrG36q0kpovc9P/Kd3I4g== + version "3.0.3" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.3.tgz#0fee0c9813b66bef25a8a6a904bb46736d05f024" + integrity sha512-EqrTKXQX6Z3A2nRmMEIlAIfjQOgFnVO2nqZGpbcsPnYGWBwpFqzlrozU1dy+S2iqfYDLh26ef4KrgTxu9xQrxA== boxen@^5.0.0: version "5.0.0" @@ -2172,15 +2177,15 @@ browserify-zlib@^0.2.0: pako "~1.0.5" browserslist@^4.14.5: - version "4.16.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" - integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== + version "4.16.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" + integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== dependencies: - caniuse-lite "^1.0.30001181" - colorette "^1.2.1" - electron-to-chromium "^1.3.649" + caniuse-lite "^1.0.30001219" + colorette "^1.2.2" + electron-to-chromium "^1.3.723" escalade "^3.1.1" - node-releases "^1.1.70" + node-releases "^1.1.71" bser@2.1.1: version "2.1.1" @@ -2244,6 +2249,14 @@ builder-util-runtime@8.7.3: debug "^4.3.2" sax "^1.2.4" +builder-util-runtime@8.7.5: + version "8.7.5" + resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.7.5.tgz#fbe59e274818885e0d2e358d5b7017c34ae6b0f5" + integrity sha512-fgUFHKtMNjdvH6PDRFntdIGUPgwZ69sXsAqEulCtoiqgWes5agrMq/Ud274zjJRTbckYh2PHh8/1CpFc6dpsbQ== + dependencies: + debug "^4.3.2" + sax "^1.2.4" + builder-util@22.10.5: version "22.10.5" resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-22.10.5.tgz#8d0b04a3be6acc74938679aa90dcb3181b1ae86b" @@ -2362,10 +2375,10 @@ camelcase@^6.0.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001181: - version "1.0.30001208" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz#a999014a35cebd4f98c405930a057a0d75352eb9" - integrity sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA== +caniuse-lite@^1.0.30001219: + version "1.0.30001230" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71" + integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ== capture-exit@^2.0.0: version "2.0.0" @@ -2588,7 +2601,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^1.2.1: +colorette@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== @@ -2729,9 +2742,9 @@ core-assert@^0.2.0: is-error "^2.2.0" core-js@^3.6.5: - version "3.8.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.8.3.tgz#c21906e1f14f3689f93abcc6e26883550dd92dd0" - integrity sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q== + version "3.12.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.12.0.tgz#62bac86f7d7f087d40dba3e90a211c2c3c8559ea" + integrity sha512-SaMnchL//WwU2Ot1hhkPflE8gzo7uq1FGvUJ8GKmi3TOU7rGTHIU+eir1WGf6qOtTyxdfdcp10yPdGZ59sQ3hw== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -2882,10 +2895,10 @@ custom-electron-prompt@^1.1.0: resolved "https://registry.yarnpkg.com/custom-electron-prompt/-/custom-electron-prompt-1.1.0.tgz#611b790047c91f6b532c7861355a0e1f9a81aef2" integrity sha512-YZYmwZnMOdoWROUlJ+rEMHYsp4XJNNqLj6sZnx5aKBJ8cprEjKP4L5wfo6U+yyX/L9fxVOtvYD0Mp8ki5I9Kow== -custom-electron-titlebar@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/custom-electron-titlebar/-/custom-electron-titlebar-3.2.6.tgz#4cd064efa5020954c09732efa8c667a7ee3636e3" - integrity sha512-P3ZGEr0eouUHqhdBBXllpuy2bFhfSmp+32HQBPcwzujjIsUhQxQj/nCpJiFa4SUGAEp1ifu/icuZdDKNNX72Tw== +custom-electron-titlebar@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/custom-electron-titlebar/-/custom-electron-titlebar-3.2.7.tgz#fb249d6180cbda074b1d392bea755fa0743012a8" + integrity sha512-KO/6e3r6YflfNUOzi5QHLwkLHBP+ICtHPo70u/kUIKR8UUkDTPb4a9i19q0uDZQcjkH6oqRvFCz9wEHeEpCgxw== dashdash@^1.12.0: version "1.14.1" @@ -3000,11 +3013,6 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -3065,9 +3073,9 @@ detect-newline@^3.0.0: integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + version "2.0.5" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79" + integrity sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw== dev-null@^0.1.1: version "0.1.1" @@ -3281,12 +3289,12 @@ electron-builder@^22.10.5: update-notifier "^5.1.0" yargs "^16.2.0" -electron-chromedriver@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/electron-chromedriver/-/electron-chromedriver-11.0.0.tgz#49b034ed0ad12c12e3522862c7bb46875a0d85e1" - integrity sha512-ayMJPBbB4puU0SqYbcD9XvF3/7GWIhqKE1n5lG2/GQPRnrZkNoPIilsrS0rQcD50Xhl69KowatDqLhUznZWtbA== +electron-chromedriver@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/electron-chromedriver/-/electron-chromedriver-12.0.0.tgz#55bdc451b938b384642d613a05eadacb1fe476ee" + integrity sha512-zOs98o9+20Er8Q44z06h90VldwrJaoRCieW3Q8WkdDjA3cMRU5mlmm1kGDhPLMeYNuhq6e39aGMVH/IBFD97HQ== dependencies: - "@electron/get" "^1.12.2" + "@electron/get" "^1.12.4" extract-zip "^2.0.0" electron-debug@^3.2.0: @@ -3370,10 +3378,10 @@ electron-store@^7.0.3: conf "^9.0.0" type-fest "^0.20.2" -electron-to-chromium@^1.3.649: - version "1.3.711" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.711.tgz#92c3caf7ffed5e18bf63f66b4b57b4db2409c450" - integrity sha512-XbklBVCDiUeho0PZQCjC25Ha6uBwqqJeyDhPLwLwfWRAo4x+FZFsmu1pPPkXT+B4MQMQoQULfyaMltDopfeiHQ== +electron-to-chromium@^1.3.723: + version "1.3.740" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.740.tgz#e38b7d2b848f632191b643e6dabca51be2162922" + integrity sha512-Mi2m55JrX2BFbNZGKYR+2ItcGnR4O5HhrvgoRRyZQlaMGQULqDhoGkLWHzJoshSzi7k1PUofxcDbNhlFrDZNhg== electron-unhandled@^3.0.2: version "3.0.2" @@ -3385,26 +3393,27 @@ electron-unhandled@^3.0.2: ensure-error "^2.0.0" lodash.debounce "^4.0.8" -electron-updater@^4.3.8: - version "4.3.8" - resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.3.8.tgz#94f1731682a756385726183e2b04b959cb319456" - integrity sha512-/tB82Ogb2LqaXrUzAD8waJC+TZV52Pr0Znfj7w+i4D+jA2GgrKFI3Pxjp+36y9FcBMQz7kYsMHcB6c5zBJao+A== +electron-updater@^4.3.9: + version "4.3.9" + resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.3.9.tgz#247c660bafad7c07935e1b81acd3e9a5fd733154" + integrity sha512-LCNfedSwZfS4Hza+pDyPR05LqHtGorCStaBgVpRnfKxOlZcvpYEX0AbMeH5XUtbtGRoH2V8osbbf2qKPNb7AsA== dependencies: - "@types/semver" "^7.3.4" - builder-util-runtime "8.7.3" - fs-extra "^9.1.0" - js-yaml "^4.0.0" + "@types/semver" "^7.3.5" + builder-util-runtime "8.7.5" + fs-extra "^10.0.0" + js-yaml "^4.1.0" lazy-val "^1.0.4" + lodash.escaperegexp "^4.1.2" lodash.isequal "^4.5.0" - semver "^7.3.4" + semver "^7.3.5" -electron@^11.4.4: - version "11.4.4" - resolved "https://registry.yarnpkg.com/electron/-/electron-11.4.4.tgz#d6c046dedd9e22df5f6408841c3f8ae1a1d59414" - integrity sha512-m52nF85VADCmL9DpzJfgmkvc9fNiGZPYwptv/4fTYrYhAMiO+hmClGMXncCoSAzoULQjl+f+0b9CY4yd6nRFlQ== +electron@^12.0.8: + version "12.0.8" + resolved "https://registry.yarnpkg.com/electron/-/electron-12.0.8.tgz#e52583b2b4f1eaa6fbb0e3666b9907f99f1f24c7" + integrity sha512-bN2wYNnnma7ugBsiwysO1LI6oTTV1lDHOXuWip+OGjDUiz0IiJmZ+r0gAIMMeypVohZh7AA1ftnKad7tJ8sH4A== dependencies: "@electron/get" "^1.0.1" - "@types/node" "^12.0.12" + "@types/node" "^14.6.2" extract-zip "^1.0.3" elliptic@^6.5.3: @@ -3481,9 +3490,9 @@ env-editor@^0.4.1: integrity sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA== env-paths@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" - integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== errlop@^4.0.0: version "4.1.0" @@ -4135,10 +4144,10 @@ filename-reserved-regex@^2.0.0: resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik= -filenamify@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.2.0.tgz#c99716d676869585b3b5d328b3f06590d032e89f" - integrity sha512-pkgE+4p7N1n7QieOopmn3TqJaefjdWXwEkj2XLZJLKfOgcQKkn11ahvGNgTD8mLggexLiDFQxeTs14xVU22XPA== +filenamify@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106" + integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg== dependencies: filename-reserved-regex "^2.0.0" strip-outer "^1.0.1" @@ -4266,6 +4275,15 @@ fs-extra@^1.0.0: jsonfile "^2.1.0" klaw "^1.0.0" +fs-extra@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -4428,9 +4446,9 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: path-is-absolute "^1.0.0" global-agent@^2.0.2: - version "2.1.12" - resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.1.12.tgz#e4ae3812b731a9e81cbf825f9377ef450a8e4195" - integrity sha512-caAljRMS/qcDo69X9BfkgrihGUgGx44Fb4QQToNQjsiWh+YlQ66uqYVAdA8Olqit+5Ng0nkz09je3ZzANMZcjg== + version "2.2.0" + resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.2.0.tgz#566331b0646e6bf79429a16877685c4a1fbf76dc" + integrity sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg== dependencies: boolean "^3.0.1" core-js "^3.6.5" @@ -4485,9 +4503,9 @@ globals@^13.6.0: type-fest "^0.20.2" globalthis@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9" - integrity sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw== + version "1.0.2" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b" + integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ== dependencies: define-properties "^1.1.3" @@ -4545,6 +4563,23 @@ got@^11.0.2: p-cancelable "^2.0.0" responselike "^2.0.0" +got@^11.8.0: + version "11.8.2" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" + integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.1" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -4562,11 +4597,16 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.9, graceful-fs@^4.2.4: version "4.2.5" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.5.tgz#bc18864a6c9fc7b303f2e2abdb9155ad178fbe29" integrity sha512-kBBSQbz2K0Nyn+31j/w36fUfxkBW9/gfwRWdUY1ULReH3iokVJgddZAFcD1D0xlgTmFxJCbUkUclAlc6/IDJkw== +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" @@ -4700,9 +4740,9 @@ hmac-drbg@^1.0.1: minimalistic-crypto-utils "^1.0.1" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hosted-git-info@^3.0.6, hosted-git-info@^3.0.8: version "3.0.8" @@ -5010,7 +5050,7 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" -is-docker@^2.0.0, is-docker@^2.1.1: +is-docker@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== @@ -5767,6 +5807,13 @@ js-yaml@^4.0.0: dependencies: argparse "^2.0.1" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -6135,6 +6182,11 @@ lodash.difference@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= + lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" @@ -6175,12 +6227,7 @@ lodash.zip@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020" integrity sha1-7GZi5IlkCO1KtsVCo5kLcswIACA= -lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - -lodash@^4.17.21: +lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -6596,10 +6643,10 @@ node-notifier@^9.0.1: uuid "^8.3.0" which "^2.0.2" -node-releases@^1.1.70: - version "1.1.71" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" - integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== +node-releases@^1.1.71: + version "1.1.72" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" + integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" @@ -6634,9 +6681,9 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== npm-conf@^1.1.3: version "1.1.3" @@ -6775,15 +6822,6 @@ open@^7.3.0: is-docker "^2.0.0" is-wsl "^2.1.1" -open@^8.0.3: - version "8.0.5" - resolved "https://registry.yarnpkg.com/open/-/open-8.0.5.tgz#92ee3faafef4ddbe78006f7881572f3e81430b8f" - integrity sha512-hkPXCz7gijWp2GoWqsQ4O/5p7F6d5pIQ/+9NyeWG1nABJ4zvLi9kJRv1a44kVf5p13wK0WMoiRA+Xey68yOytA== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -7865,13 +7903,20 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: +semver@^7.2.1, semver@^7.3.4: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== dependencies: lru-cache "^6.0.0" +semver@^7.3.2, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -8084,14 +8129,15 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== -spectron@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/spectron/-/spectron-13.0.0.tgz#16bdfcf9a2b26cb5ee6c3e29b4f08101e339aa4d" - integrity sha512-7RPa6Fp8gqL4V0DubobnqIRFHIijkpjg6MFHcJlxoerWyvLJd+cQvOh756XpB1Z/U3DyA9jPcS+HE2PvYRP5+A== +spectron@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/spectron/-/spectron-14.0.0.tgz#c8160e38c30dcda39734f3e8e809162dc0805d14" + integrity sha512-88GM7D1eLiTxjByjtY7lxU7CJcQ92kX1x0WfnADaIXqqYRLbI1KlIWxXz1Xm5UxuMJh5N847K0NONG49mvZtuw== dependencies: + "@electron/remote" "^1.0.4" dev-null "^0.1.1" - electron-chromedriver "^11.0.0" - request "^2.88.2" + electron-chromedriver "^12.0.0" + got "^11.8.0" split "^1.0.1" webdriverio "^6.9.1" @@ -8586,9 +8632,9 @@ tr46@^2.0.2: punycode "^2.1.1" trim-newlines@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" - integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== trim-repeated@^1.0.0: version "1.0.0" @@ -9098,9 +9144,9 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.2.3, ws@^7.3.1: - version "7.4.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.3.tgz#1f9643de34a543b8edb124bdcbc457ae55a6e5cd" - integrity sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA== + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== xdg-basedir@^4.0.0: version "4.0.0" @@ -9314,19 +9360,19 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -ytdl-core@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/ytdl-core/-/ytdl-core-4.5.0.tgz#f07733387c548e5c3a5614c93ef55bde666eeaf4" - integrity sha512-e8r6skrakWNixsVlNPBMoRM1HrdW1swE97If9nenDUjF65uogYk4DvxIuqlmqRfBWKe+6aIZwqedNxUU9XLYJA== +ytdl-core@^4.8.2: + version "4.8.2" + resolved "https://registry.yarnpkg.com/ytdl-core/-/ytdl-core-4.8.2.tgz#f034ad942c5d958f5987fc8ff0b0639664ae2fb7" + integrity sha512-O3n++YcgZawaXJwbPmnRDgfN6b4kU0DpNdkI9Na5yM3JAdfJmoq5UHc8v9Xjgjr1RilQUUh7mhDnRRPDtKr0Kg== dependencies: m3u8stream "^0.8.3" miniget "^4.0.0" sax "^1.1.3" -ytpl@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ytpl/-/ytpl-2.1.1.tgz#c3c3e0e198e3fc7be13b52f5651e950b0aae94a0" - integrity sha512-yrU/w1k75f089zUONUm1QjlCv96QWhk/SS6jNEVJXMr8/9zEj4k2EIv81nk5wldJvpb+2rvEfm2zIwRqXRoZ9w== +ytpl@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ytpl/-/ytpl-2.2.1.tgz#e514eccdd46e02eeb0d16e08f8278489258ab31a" + integrity sha512-sxty58s4JTNCDkiaiTkcaXfWCOW5sfHOPwDQtWIkoU4C+Kht2qat8yaLVbWZIclUSZo+naANyaI7LGjhhrErGA== dependencies: miniget "^4.2.0"