rome lint

This commit is contained in:
Araxeus
2023-03-19 20:15:15 +02:00
parent 51871a3fec
commit 325026e3ea
2 changed files with 388 additions and 381 deletions

View File

@ -1,47 +1,47 @@
const { const {
existsSync, existsSync,
mkdirSync, mkdirSync,
createWriteStream, createWriteStream,
writeFileSync, writeFileSync,
} = require("fs"); } = require('fs');
const { join } = require("path"); const { join } = require('path');
const { fetchFromGenius } = require("../lyrics-genius/back"); const { fetchFromGenius } = require('../lyrics-genius/back');
const { isEnabled } = require("../../config/plugins"); const { isEnabled } = require('../../config/plugins');
const { getImage } = require("../../providers/song-info"); const { getImage } = require('../../providers/song-info');
const { injectCSS } = require("../utils"); const { injectCSS } = require('../utils');
const { const {
presets, presets,
cropMaxWidth, cropMaxWidth,
getFolder, getFolder,
setBadge, setBadge,
sendFeedback: sendFeedback_, sendFeedback: sendFeedback_,
} = require("./utils"); } = require('./utils');
const { ipcMain, app, dialog } = require("electron"); const { ipcMain, app, dialog } = require('electron');
const is = require("electron-is"); const is = require('electron-is');
const { Innertube, UniversalCache, Utils } = require("youtubei.js"); const { Innertube, UniversalCache, Utils } = require('youtubei.js');
const ytpl = require("ytpl"); // REPLACE with youtubei getplaylist https://github.com/LuanRT/YouTube.js#getplaylistid const ytpl = require('ytpl'); // REPLACE with youtubei getplaylist https://github.com/LuanRT/YouTube.js#getplaylistid
const filenamify = require("filenamify"); const filenamify = require('filenamify');
const ID3Writer = require("browser-id3-writer"); const ID3Writer = require('browser-id3-writer');
const { randomBytes } = require("crypto"); const { randomBytes } = require('crypto');
const Mutex = require("async-mutex").Mutex; const Mutex = require('async-mutex').Mutex;
const ffmpeg = require("@ffmpeg/ffmpeg").createFFmpeg({ const ffmpeg = require('@ffmpeg/ffmpeg').createFFmpeg({
log: false, log: false,
logger: () => {}, // console.log, logger: () => {}, // console.log,
progress: () => {}, // console.log, progress: () => {}, // console.log,
}); });
const ffmpegMutex = new Mutex(); const ffmpegMutex = new Mutex();
const cache = { const cache = {
getCoverBuffer: { getCoverBuffer: {
buffer: null, buffer: null,
url: null, url: null,
}, },
}; };
const config = require("./config"); const config = require('./config');
/** @type {Innertube} */ /** @type {Innertube} */
let yt; let yt;
@ -49,431 +49,438 @@ let win;
let playingUrl = undefined; let playingUrl = undefined;
const sendError = (error) => { const sendError = (error) => {
win.setProgressBar(-1); // close progress bar win.setProgressBar(-1); // close progress bar
setBadge(0); // close badge setBadge(0); // close badge
sendFeedback_(win); // reset feedback sendFeedback_(win); // reset feedback
console.error(error); console.error(error);
dialog.showMessageBox({ dialog.showMessageBox({
type: "info", type: 'info',
buttons: ["OK"], buttons: ['OK'],
title: "Error in download!", title: 'Error in download!',
message: "Argh! Apologies, download failed…", message: 'Argh! Apologies, download failed…',
detail: `${error.toString()} ${error.cause ? `\n\n${error.cause.toString()}` : ''}`, detail: `${error.toString()} ${
}); error.cause ? `\n\n${error.cause.toString()}` : ''
}`,
});
}; };
module.exports = async (win_, options) => { module.exports = async (win_, options) => {
win = win_; win = win_;
config.init(options); config.init(options);
injectCSS(win.webContents, join(__dirname, "style.css")); injectCSS(win.webContents, join(__dirname, 'style.css'));
yt = await Innertube.create({ yt = await Innertube.create({
cache: new UniversalCache(false), cache: new UniversalCache(false),
generate_session_locally: true, generate_session_locally: true,
}); });
ipcMain.on("download-song", (_, url) => downloadSong(url)); ipcMain.on('download-song', (_, url) => downloadSong(url));
ipcMain.on("video-src-changed", async (_, data) => { ipcMain.on('video-src-changed', async (_, data) => {
playingUrl = playingUrl =
JSON.parse(data)?.microformat?.microformatDataRenderer?.urlCanonical; JSON.parse(data)?.microformat?.microformatDataRenderer?.urlCanonical;
}); });
ipcMain.on("download-playlist-request", async (_event, url) => ipcMain.on('download-playlist-request', async (_event, url) =>
downloadPlaylist(url), downloadPlaylist(url),
); );
}; };
module.exports.downloadSong = downloadSong; module.exports.downloadSong = downloadSong;
async function downloadSong( async function downloadSong(
url, url,
playlistFolder = undefined, playlistFolder = undefined,
trackId = undefined, trackId = undefined,
increasePlaylistProgress = () => {}, increasePlaylistProgress = () => {},
) { ) {
try { try {
await downloadSongUnsafe(url, playlistFolder, trackId, increasePlaylistProgress); await downloadSongUnsafe(
} catch (error) { url,
sendError(error); playlistFolder,
} trackId,
increasePlaylistProgress,
);
} catch (error) {
sendError(error);
}
} }
async function downloadSongUnsafe( async function downloadSongUnsafe(
url, url,
playlistFolder = undefined, playlistFolder = undefined,
trackId = undefined, trackId = undefined,
increasePlaylistProgress = () => {}, increasePlaylistProgress = () => {},
) { ) {
const sendFeedback = (message, progress) => { const sendFeedback = (message, progress) => {
if (!playlistFolder) { if (!playlistFolder) {
sendFeedback_(win, message); sendFeedback_(win, message);
if (!isNaN(progress)) { if (!isNaN(progress)) {
win.setProgressBar(progress); win.setProgressBar(progress);
} }
} }
}; };
sendFeedback("Downloading...", 2); sendFeedback('Downloading...', 2);
const id = getVideoId(url); const id = getVideoId(url);
const info = await yt.music.getInfo(id); const info = await yt.music.getInfo(id);
const metadata = getMetadata(info); const metadata = getMetadata(info);
if (metadata.album === "N/A") metadata.album = ""; if (metadata.album === 'N/A') metadata.album = '';
metadata.trackId = trackId; metadata.trackId = trackId;
const dir = const dir =
playlistFolder || config.get("downloadFolder") || app.getPath("downloads"); playlistFolder || config.get('downloadFolder') || app.getPath('downloads');
const name = `${metadata.artist ? `${metadata.artist} - ` : ""}${ const name = `${metadata.artist ? `${metadata.artist} - ` : ''}${
metadata.title metadata.title
}`; }`;
const extension = presets[config.get("preset")]?.extension || "mp3"; const extension = presets[config.get('preset')]?.extension || 'mp3';
const filename = filenamify(`${name}.${extension}`, { const filename = filenamify(`${name}.${extension}`, {
replacement: "_", replacement: '_',
maxLength: 255, maxLength: 255,
}); });
const filePath = join(dir, filename); const filePath = join(dir, filename);
if (config.get("skipExisting") && existsSync(filePath)) { if (config.get('skipExisting') && existsSync(filePath)) {
sendFeedback(null, -1); sendFeedback(null, -1);
return; return;
} }
const download_options = { const download_options = {
type: "audio", // audio, video or video+audio type: 'audio', // audio, video or video+audio
quality: "best", // best, bestefficiency, 144p, 240p, 480p, 720p and so on. quality: 'best', // best, bestefficiency, 144p, 240p, 480p, 720p and so on.
format: "any", // media container format format: 'any', // media container format
}; };
const format = info.chooseFormat(download_options); const format = info.chooseFormat(download_options);
const stream = await info.download(download_options); const stream = await info.download(download_options);
console.info( console.info(
`Downloading ${metadata.artist} - ${metadata.title} [${metadata.id}]`, `Downloading ${metadata.artist} - ${metadata.title} [${metadata.id}]`,
); );
const iterableStream = Utils.streamToIterable(stream); const iterableStream = Utils.streamToIterable(stream);
if (!existsSync(dir)) { if (!existsSync(dir)) {
mkdirSync(dir); mkdirSync(dir);
} }
if (!presets[config.get("preset")]) { if (!presets[config.get('preset')]) {
const fileBuffer = await iterableStreamToMP3( const fileBuffer = await iterableStreamToMP3(
iterableStream, iterableStream,
metadata, metadata,
format.content_length, format.content_length,
sendFeedback, sendFeedback,
increasePlaylistProgress, increasePlaylistProgress,
); );
writeFileSync(filePath, await writeID3(fileBuffer, metadata, sendFeedback)); writeFileSync(filePath, await writeID3(fileBuffer, metadata, sendFeedback));
} else { } else {
const file = createWriteStream(filePath); const file = createWriteStream(filePath);
let downloaded = 0; let downloaded = 0;
const total = format.content_length; const total = format.content_length;
for await (const chunk of iterableStream) { for await (const chunk of iterableStream) {
downloaded += chunk.length; downloaded += chunk.length;
const ratio = downloaded / total; const ratio = downloaded / total;
const progress = Math.floor(ratio * 100); const progress = Math.floor(ratio * 100);
sendFeedback(`Download: ${progress}%`, ratio); sendFeedback(`Download: ${progress}%`, ratio);
increasePlaylistProgress(ratio); increasePlaylistProgress(ratio);
file.write(chunk); file.write(chunk);
} }
await ffmpegWriteTags( await ffmpegWriteTags(
filePath, filePath,
metadata, metadata,
presets[config.get("preset")]?.ffmpegArgs, presets[config.get('preset')]?.ffmpegArgs,
); );
sendFeedback(null, -1); sendFeedback(null, -1);
} }
sendFeedback(null, -1); sendFeedback(null, -1);
console.info(`Done: "${filePath}"`); console.info(`Done: "${filePath}"`);
} }
async function iterableStreamToMP3( async function iterableStreamToMP3(
stream, stream,
metadata, metadata,
content_length, content_length,
sendFeedback, sendFeedback,
increasePlaylistProgress = () => {}, increasePlaylistProgress = () => {},
) { ) {
const chunks = []; const chunks = [];
let downloaded = 0; let downloaded = 0;
const total = content_length; const total = content_length;
for await (const chunk of stream) { for await (const chunk of stream) {
downloaded += chunk.length; downloaded += chunk.length;
chunks.push(chunk); chunks.push(chunk);
const ratio = downloaded / total; const ratio = downloaded / total;
const progress = Math.floor(ratio * 100); const progress = Math.floor(ratio * 100);
sendFeedback(`Download: ${progress}%`, ratio); sendFeedback(`Download: ${progress}%`, ratio);
// 15% for download, 85% for conversion // 15% for download, 85% for conversion
// This is a very rough estimate, trying to make the progress bar look nice // This is a very rough estimate, trying to make the progress bar look nice
increasePlaylistProgress(ratio * 0.15); increasePlaylistProgress(ratio * 0.15);
} }
sendFeedback("Loading…", 2); // indefinite progress bar after download sendFeedback('Loading…', 2); // indefinite progress bar after download
const buffer = Buffer.concat(chunks); const buffer = Buffer.concat(chunks);
const safeVideoName = randomBytes(32).toString("hex"); const safeVideoName = randomBytes(32).toString('hex');
const releaseFFmpegMutex = await ffmpegMutex.acquire(); const releaseFFmpegMutex = await ffmpegMutex.acquire();
try { try {
if (!ffmpeg.isLoaded()) { if (!ffmpeg.isLoaded()) {
await ffmpeg.load(); await ffmpeg.load();
} }
sendFeedback("Preparing file…"); sendFeedback('Preparing file…');
ffmpeg.FS("writeFile", safeVideoName, buffer); ffmpeg.FS('writeFile', safeVideoName, buffer);
sendFeedback("Converting…"); sendFeedback('Converting…');
ffmpeg.setProgress(({ ratio }) => { ffmpeg.setProgress(({ ratio }) => {
sendFeedback(`Converting: ${Math.floor(ratio * 100)}%`, ratio); sendFeedback(`Converting: ${Math.floor(ratio * 100)}%`, ratio);
increasePlaylistProgress(0.15 + ratio * 0.85); increasePlaylistProgress(0.15 + ratio * 0.85);
}); });
await ffmpeg.run( await ffmpeg.run(
"-i", '-i',
safeVideoName, safeVideoName,
...getFFmpegMetadataArgs(metadata), ...getFFmpegMetadataArgs(metadata),
`${safeVideoName}.mp3`, `${safeVideoName}.mp3`,
); );
sendFeedback("Saving…"); sendFeedback('Saving…');
return ffmpeg.FS("readFile", `${safeVideoName}.mp3`); return ffmpeg.FS('readFile', `${safeVideoName}.mp3`);
} catch (e) { } catch (e) {
sendError(e); sendError(e);
} finally { } finally {
releaseFFmpegMutex(); releaseFFmpegMutex();
} }
} }
async function getCoverBuffer(url) { async function getCoverBuffer(url) {
const store = cache.getCoverBuffer; const store = cache.getCoverBuffer;
if (store.url === url) { if (store.url === url) {
return store.buffer; return store.buffer;
} }
store.url = url; store.url = url;
const nativeImage = cropMaxWidth(await getImage(url)); const nativeImage = cropMaxWidth(await getImage(url));
store.buffer = store.buffer =
nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null; nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null;
return store.buffer; return store.buffer;
} }
async function writeID3(buffer, metadata, sendFeedback) { async function writeID3(buffer, metadata, sendFeedback) {
try { try {
sendFeedback("Writing ID3 tags..."); sendFeedback('Writing ID3 tags...');
const coverBuffer = await getCoverBuffer(metadata.image); const coverBuffer = await getCoverBuffer(metadata.image);
const writer = new ID3Writer(buffer); const writer = new ID3Writer(buffer);
// Create the metadata tags // Create the metadata tags
writer.setFrame("TIT2", metadata.title).setFrame("TPE1", [metadata.artist]); writer.setFrame('TIT2', metadata.title).setFrame('TPE1', [metadata.artist]);
if (metadata.album) { if (metadata.album) {
writer.setFrame("TALB", metadata.album); writer.setFrame('TALB', metadata.album);
} }
if (coverBuffer) { if (coverBuffer) {
writer.setFrame("APIC", { writer.setFrame('APIC', {
type: 3, type: 3,
data: coverBuffer, data: coverBuffer,
description: "", description: '',
}); });
} }
if (isEnabled("lyrics-genius")) { if (isEnabled('lyrics-genius')) {
const lyrics = await fetchFromGenius(metadata); const lyrics = await fetchFromGenius(metadata);
if (lyrics) { if (lyrics) {
writer.setFrame("USLT", { writer.setFrame('USLT', {
description: "", description: '',
lyrics: lyrics, lyrics: lyrics,
}); });
} }
} }
if (metadata.trackId) { if (metadata.trackId) {
writer.setFrame("TRCK", metadata.trackId); writer.setFrame('TRCK', metadata.trackId);
} }
writer.addTag(); writer.addTag();
return Buffer.from(writer.arrayBuffer); return Buffer.from(writer.arrayBuffer);
} catch (e) { } catch (e) {
sendError(e); sendError(e);
} }
} }
async function downloadPlaylist(givenUrl) { async function downloadPlaylist(givenUrl) {
if (givenUrl) { if (givenUrl) {
try { try {
givenUrl = new URL(givenUrl); givenUrl = new URL(givenUrl);
} catch { } catch {
givenUrl = undefined; givenUrl = undefined;
} }
} }
const playlistId = const playlistId =
getPlaylistID(givenUrl) || getPlaylistID(givenUrl) ||
getPlaylistID(new URL(win.webContents.getURL())) || getPlaylistID(new URL(win.webContents.getURL())) ||
getPlaylistID(new URL(playingUrl)); getPlaylistID(new URL(playingUrl));
if (!playlistId) { if (!playlistId) {
sendError(new Error("No playlist ID found")); sendError(new Error('No playlist ID found'));
return; return;
} }
const sendFeedback = (message) => sendFeedback_(win, message); const sendFeedback = (message) => sendFeedback_(win, message);
console.log(`trying to get playlist ID: '${playlistId}'`); console.log(`trying to get playlist ID: '${playlistId}'`);
sendFeedback("Getting playlist info…"); sendFeedback('Getting playlist info…');
let playlist; let playlist;
try { try {
playlist = await ytpl(playlistId, { playlist = await ytpl(playlistId, {
limit: config.get("playlistMaxItems") || Infinity, limit: config.get('playlistMaxItems') || Infinity,
}); });
} catch (e) { } catch (e) {
sendError( sendError(
`Error getting playlist info: make sure it isn\'t a private or "Mixed for you" playlist\n\n${e}`, `Error getting playlist info: make sure it isn\'t a private or "Mixed for you" playlist\n\n${e}`,
); );
return; return;
} }
if (playlist.items.length === 0) sendError(new Error("Playlist is empty")); if (playlist.items.length === 0) sendError(new Error('Playlist is empty'));
if (playlist.items.length === 1) { if (playlist.items.length === 1) {
sendFeedback("Playlist has only one item, downloading it directly"); sendFeedback('Playlist has only one item, downloading it directly');
await downloadSong(playlist.items[0].url); await downloadSong(playlist.items[0].url);
return; return;
} }
const isAlbum = playlist.title.startsWith("Album - "); const isAlbum = playlist.title.startsWith('Album - ');
if (isAlbum) { if (isAlbum) {
playlist.title = playlist.title.slice(8); playlist.title = playlist.title.slice(8);
} }
const safePlaylistTitle = filenamify(playlist.title, { replacement: " " }); const safePlaylistTitle = filenamify(playlist.title, { replacement: ' ' });
const folder = getFolder(config.get("downloadFolder")); const folder = getFolder(config.get('downloadFolder'));
const playlistFolder = join(folder, safePlaylistTitle); const playlistFolder = join(folder, safePlaylistTitle);
if (existsSync(playlistFolder)) { if (existsSync(playlistFolder)) {
if (!config.get("skipExisting")) { if (!config.get('skipExisting')) {
sendError(new Error(`The folder ${playlistFolder} already exists`)); sendError(new Error(`The folder ${playlistFolder} already exists`));
return; return;
} }
} else { } else {
mkdirSync(playlistFolder, { recursive: true }); mkdirSync(playlistFolder, { recursive: true });
} }
dialog.showMessageBox({ dialog.showMessageBox({
type: "info", type: 'info',
buttons: ["OK"], buttons: ['OK'],
title: "Started Download", title: 'Started Download',
message: `Downloading Playlist "${playlist.title}"`, message: `Downloading Playlist "${playlist.title}"`,
detail: `(${playlist.items.length} songs)`, detail: `(${playlist.items.length} songs)`,
}); });
if (is.dev()) { if (is.dev()) {
console.log( console.log(
`Downloading playlist "${playlist.title}" - ${playlist.items.length} songs (${playlistId})`, `Downloading playlist "${playlist.title}" - ${playlist.items.length} songs (${playlistId})`,
); );
} }
win.setProgressBar(2); // starts with indefinite bar win.setProgressBar(2); // starts with indefinite bar
setBadge(playlist.items.length); setBadge(playlist.items.length);
let counter = 1; let counter = 1;
const progressStep = 1 / playlist.items.length; const progressStep = 1 / playlist.items.length;
const increaseProgress = (itemPercentage) => { const increaseProgress = (itemPercentage) => {
const currentProgress = (counter - 1) / playlist.items.length; const currentProgress = (counter - 1) / playlist.items.length;
const newProgress = currentProgress + progressStep * itemPercentage; const newProgress = currentProgress + progressStep * itemPercentage;
win.setProgressBar(newProgress); win.setProgressBar(newProgress);
}; };
try { try {
for (const song of playlist.items) { for (const song of playlist.items) {
sendFeedback(`Downloading ${counter}/${playlist.items.length}...`); sendFeedback(`Downloading ${counter}/${playlist.items.length}...`);
const trackId = isAlbum ? counter : undefined; const trackId = isAlbum ? counter : undefined;
await downloadSong( await downloadSong(
song.url, song.url,
playlistFolder, playlistFolder,
trackId, trackId,
increaseProgress, increaseProgress,
).catch((e) => ).catch((e) =>
sendError( sendError(
`Error downloading "${song.author.name} - ${song.title}":\n ${e}`, `Error downloading "${song.author.name} - ${song.title}":\n ${e}`,
), ),
); );
win.setProgressBar(counter / playlist.items.length); win.setProgressBar(counter / playlist.items.length);
setBadge(playlist.items.length - counter); setBadge(playlist.items.length - counter);
counter++; counter++;
} }
} catch (e) { } catch (e) {
sendError(e); sendError(e);
} finally { } finally {
win.setProgressBar(-1); // close progress bar win.setProgressBar(-1); // close progress bar
setBadge(0); // close badge counter setBadge(0); // close badge counter
sendFeedback(); // clear feedback sendFeedback(); // clear feedback
} }
} }
async function ffmpegWriteTags(filePath, metadata, ffmpegArgs = []) { async function ffmpegWriteTags(filePath, metadata, ffmpegArgs = []) {
const releaseFFmpegMutex = await ffmpegMutex.acquire(); const releaseFFmpegMutex = await ffmpegMutex.acquire();
try { try {
if (!ffmpeg.isLoaded()) { if (!ffmpeg.isLoaded()) {
await ffmpeg.load(); await ffmpeg.load();
} }
await ffmpeg.run( await ffmpeg.run(
"-i", '-i',
filePath, filePath,
...getFFmpegMetadataArgs(metadata), ...getFFmpegMetadataArgs(metadata),
...ffmpegArgs, ...ffmpegArgs,
filePath, filePath,
); );
} catch (e) { } catch (e) {
sendError(e); sendError(e);
} finally { } finally {
releaseFFmpegMutex(); releaseFFmpegMutex();
} }
} }
function getFFmpegMetadataArgs(metadata) { function getFFmpegMetadataArgs(metadata) {
if (!metadata) { if (!metadata) {
return; return;
} }
return [ return [
...(metadata.title ? ["-metadata", `title=${metadata.title}`] : []), ...(metadata.title ? ['-metadata', `title=${metadata.title}`] : []),
...(metadata.artist ? ["-metadata", `artist=${metadata.artist}`] : []), ...(metadata.artist ? ['-metadata', `artist=${metadata.artist}`] : []),
...(metadata.album ? ["-metadata", `album=${metadata.album}`] : []), ...(metadata.album ? ['-metadata', `album=${metadata.album}`] : []),
...(metadata.trackId ? ["-metadata", `track=${metadata.trackId}`] : []), ...(metadata.trackId ? ['-metadata', `track=${metadata.trackId}`] : []),
]; ];
} }
// Playlist radio modifier needs to be cut from playlist ID // Playlist radio modifier needs to be cut from playlist ID
const INVALID_PLAYLIST_MODIFIER = "RDAMPL"; const INVALID_PLAYLIST_MODIFIER = 'RDAMPL';
const getPlaylistID = (aURL) => { const getPlaylistID = (aURL) => {
const result = const result =
aURL?.searchParams.get("list") || aURL?.searchParams.get("playlist"); aURL?.searchParams.get('list') || aURL?.searchParams.get('playlist');
if (result?.startsWith(INVALID_PLAYLIST_MODIFIER)) { if (result?.startsWith(INVALID_PLAYLIST_MODIFIER)) {
return result.slice(6); return result.slice(6);
} }
return result; return result;
}; };
const getVideoId = (url) => { const getVideoId = (url) => {
if (typeof url === "string") { if (typeof url === 'string') {
url = new URL(url); url = new URL(url);
} }
return url.searchParams.get("v"); return url.searchParams.get('v');
}; };
const getMetadata = (info) => ({ const getMetadata = (info) => ({
id: info.basic_info.id, id: info.basic_info.id,
title: info.basic_info.title, title: info.basic_info.title,
artist: info.basic_info.author, artist: info.basic_info.author,
album: info.player_overlays?.browser_media_session?.album?.text, album: info.player_overlays?.browser_media_session?.album?.text,
image: info.basic_info.thumbnail[0].url, image: info.basic_info.thumbnail[0].url,
}); });

View File

@ -1,23 +1,23 @@
const { setOptions, setMenuOptions } = require("../../config/plugins"); const { setOptions, setMenuOptions } = require('../../config/plugins');
const defaultConfig = require("../../config/defaults"); const defaultConfig = require('../../config/defaults');
let config = defaultConfig.plugins["downloader"]; let config = defaultConfig.plugins['downloader'];
module.exports.init = (options) => { module.exports.init = (options) => {
config = { ...config, ...options }; config = { ...config, ...options };
}; };
module.exports.setAndMaybeRestart = (option, value) => { module.exports.setAndMaybeRestart = (option, value) => {
config[option] = value; config[option] = value;
setMenuOptions("downloader", config); setMenuOptions('downloader', config);
}; };
module.exports.set = (option, value) => { module.exports.set = (option, value) => {
config[option] = value; config[option] = value;
setOptions("downloader", config); setOptions('downloader', config);
}; };
module.exports.get = (option) => { module.exports.get = (option) => {
let res = config[option]; const res = config[option];
return res; return res;
}; };