123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- #!/usr/bin/env node
- 'use strict';
- const fs = require('fs-extra');
- const axios = require('axios');
- const chalk = require('chalk');
- const nodePath = require('path');
- const signale = require('signale');
- const { prompt } = require('prompts');
- const commandLineArgs = require('command-line-args');
- const commandLineUsage = require('command-line-usage');
- // import components
- const log = require('./src/components/logger');
- const downloadState = require('./src/components/downloadState');
- const { ensureDir } = require('./src/components/utils');
- // import service
- const defaults = require('./src/defaults');
- const musicQualities = require('./src/musicQualities');
- const downloadTypes = require('./src/downloadTypes');
- const downloadMultiple = require('./src/downloadMultiple');
- // check and notify about update
- const updateNotifier = require('./src/updateCheck');
- const pkg = require('./package.json');
- updateNotifier(pkg);
- // init deezer api
- const config = require('./src/config');
- const deezerApi = require('deezer-request2');
- const isCli = process.argv.length > 2;
- const cliOptionDefinitions = [
- {
- name: 'help',
- alias: 'h',
- description: 'Print this usage guide :)',
- },
- {
- name: 'quality',
- alias: 'q',
- type: String,
- description: 'The quality of the files to download: 128/320/FLAC',
- },
- {
- name: 'path',
- alias: 'p',
- type: String,
- description: 'The path to download the files to: path with / in the end',
- },
- {
- name: 'url',
- alias: 'u',
- type: String,
- defaultOption: true,
- description: 'Downloads single deezer url: album/artist/playlist/track url',
- },
- {
- name: 'downloadmode',
- alias: 'd',
- type: String,
- description: 'Downloads multiple urls from list: "all" for downloadLinks.txt',
- },
- {
- name: 'concurrency',
- alias: 'c',
- type: Number,
- description: 'Download concurrency for album, artists and playlist',
- },
- {
- name: 'no-progress',
- alias: 'n',
- type: Boolean,
- description: 'Hide download progress bar',
- },
- {
- name: 'set-arl',
- alias: 'a',
- type: String,
- description: 'Set arl cookie',
- },
- ];
- const onCancel = (prompt) => {
- console.log('Abort!');
- process.exit();
- };
- global.bar = true;
- /**
- * Application init.
- */
- const initApp = async () => {
- // App info
- console.log(chalk.cyan('╔══════════════════════════════════════════════════════════╗'));
- console.log(
- chalk.cyan('║') +
- chalk.bold.yellow(` d-fi (${pkg.version}) `) +
- chalk.cyan('║')
- );
- console.log(chalk.cyan('╠══════════════════════════════════════════════════════════╣'));
- console.log(
- chalk.redBright(' ♥ REPO ') +
- chalk.cyan('║') +
- ' https://notabug.org/sayem314/d-fi ' +
- chalk.cyan('║')
- );
- console.log(chalk.cyan('╠══════════════════════════════════════════════════════════╣'));
- console.log(
- chalk.redBright(' ♥ DONATE ') +
- chalk.cyan('║') +
- ' https://sayem.eu.org/donate ' +
- chalk.cyan('║')
- );
- console.log(chalk.cyan('╚══════════════════════════════════════════════════════════╝\n'));
- // console.log(chalk.yellow('Please read the latest manual thoroughly before asking for help!\n'));
- if (isCli) {
- try {
- let cliOptions = commandLineArgs(cliOptionDefinitions);
- for (let [key, value] of Object.entries(cliOptions)) {
- switch (key) {
- case 'url':
- defaults.DOWNLOAD_URL = value;
- break;
- case 'quality':
- defaults.DOWNLOAD_QUALITY = value;
- break;
- case 'path':
- defaults.DOWNLOAD_DIR = value;
- break;
- case 'downloadmode':
- defaults.DOWNLOAD_MODE = value;
- break;
- case 'concurrency':
- defaults.CONCURRENCY = value;
- break;
- case 'no-progress':
- global.bar = false;
- break;
- case 'set-arl':
- if (value) {
- config.set('cookies.arl', value);
- signale.info('arl cookie set to: ' + value);
- }
- process.exit();
- break;
- default:
- const helpSections = [
- {
- header: 'CLI Options',
- optionList: cliOptionDefinitions,
- },
- {
- content: 'More info here: https://notabug.org/sayem314/d-fi',
- },
- ];
- console.log(commandLineUsage(helpSections));
- process.exit();
- }
- }
- } catch (err) {
- log.debug(err.message || err);
- signale.fatal(err.message || err);
- process.exit(1);
- }
- }
- try {
- // init api
- signale.info('Initializing session..');
- await deezerApi.initDeezerApi(config.get('cookies.arl'));
- defaults.DOWNLOAD_DIR = nodePath.normalize(nodePath.resolve(defaults.DOWNLOAD_DIR.replace(/\/$|\\$/, '')));
- selectMusicQuality();
- } catch (err) {
- log.debug(err);
- process.exit(1);
- }
- };
- /**
- * Show user selection for the music download quality.
- */
- const selectMusicQuality = async () => {
- if (isCli) {
- switch (defaults.DOWNLOAD_QUALITY) {
- case '128':
- case 'MP3_128':
- musicQualities.selectedQuality = musicQualities.qualities.MP3_128;
- break;
- case '320':
- case 'MP3_320':
- musicQualities.selectedQuality = musicQualities.qualities.MP3_320;
- break;
- case 'flac':
- case 'Flac':
- case 'FLAC':
- musicQualities.selectedQuality = musicQualities.qualities.FLAC;
- break;
- default:
- musicQualities.selectedQuality = musicQualities.qualities.MP3_320;
- }
- if (defaults.DOWNLOAD_MODE == 'all') {
- downloadLinksFromFile();
- } else {
- try {
- await startDownload(defaults.DOWNLOAD_URL);
- process.exit();
- } catch (err) {
- log.debug(err.message);
- signale.fatal(err);
- downloadState.finish();
- process.exit(1);
- }
- }
- } else {
- let answers = await prompt(
- [
- {
- type: 'select',
- name: 'musicQuality',
- message: 'Select music quality:',
- choices: [{ title: 'MP3 - 128 kbps' }, { title: 'MP3 - 320 kbps' }, { title: 'FLAC - 1411 kbps' }],
- initial: 1,
- },
- ],
- {
- onCancel,
- }
- );
- switch (answers.musicQuality) {
- case 0:
- musicQualities.selectedQuality = musicQualities.qualities.MP3_128;
- break;
- case 2:
- musicQualities.selectedQuality = musicQualities.qualities.FLAC;
- break;
- default:
- musicQualities.selectedQuality = musicQualities.qualities.MP3_320;
- }
- log.debug('Selected music quality: ' + answers.musicQuality);
- selectDownloadMode();
- }
- };
- /**
- * Ask for download mode (single or all).
- */
- const selectDownloadMode = async () => {
- let answers = await prompt(
- [
- {
- type: 'select',
- name: 'downloadMode',
- message: 'Select download mode:',
- choices: [
- { title: 'Single (Download single link)' },
- { title: 'All (Download all links in "' + defaults.DOWNLOAD_LINKS_FILE + '")' },
- ],
- initial: 0,
- },
- ],
- {
- onCancel,
- }
- );
- if (answers.downloadMode === 1) {
- downloadLinksFromFile();
- } else {
- askForNewDownload();
- }
- };
- /**
- * Download all links from file
- */
- const downloadLinksFromFile = async () => {
- if (!fs.existsSync(defaults.DOWNLOAD_LINKS_FILE)) {
- ensureDir(defaults.DOWNLOAD_LINKS_FILE);
- fs.writeFileSync(defaults.DOWNLOAD_LINKS_FILE, '');
- }
- const lines = fs
- .readFileSync(defaults.DOWNLOAD_LINKS_FILE, 'utf-8')
- .split(/^(.*)[\r|\n]/)
- .filter(Boolean);
- if (lines[0]) {
- const firstLine = lines[0].trim();
- if ('' === firstLine) {
- removeFirstLineFromFile(defaults.DOWNLOAD_LINKS_FILE);
- await downloadLinksFromFile();
- } else {
- try {
- await startDownload(firstLine, defaults.DOWNLOAD_DIR, musicQualities.selectedQuality.id, true);
- removeFirstLineFromFile(defaults.DOWNLOAD_LINKS_FILE);
- await downloadLinksFromFile();
- } catch (err) {
- log.debug(err);
- signale.fatal(err.message || err);
- downloadState.finish(false);
- removeFirstLineFromFile(defaults.DOWNLOAD_LINKS_FILE);
- await downloadLinksFromFile();
- }
- }
- } else {
- signale.success('Finished downloading from text file');
- if (isCli) {
- process.exit();
- } else {
- console.log('\n');
- selectDownloadMode();
- }
- }
- };
- /**
- * Remove the first line from the given file.
- *
- * @param {String} filePath
- */
- const removeFirstLineFromFile = (filePath) => {
- const lines = fs
- .readFileSync(filePath, 'utf-8')
- .split(/^(.*)[\r|\n]/)
- .filter(Boolean);
- let contentToWrite = '';
- if (lines[1]) {
- contentToWrite = lines[1].trim();
- }
- fs.writeFileSync(filePath, contentToWrite);
- };
- /**
- * Ask for a album, playlist or track link to start the download.
- */
- const askForNewDownload = async () => {
- let questions = [
- {
- type: 'text',
- name: 'deezerUrl',
- message: 'Query:',
- },
- ];
- let answers = await prompt(questions, { onCancel });
- if (!answers.deezerUrl) {
- process.exit();
- }
- try {
- await startDownload(answers.deezerUrl);
- askForNewDownload();
- } catch (err) {
- log.debug(err.message || err);
- signale.fatal(err.message || err);
- downloadState.finish();
- askForNewDownload();
- }
- };
- /**
- * Start a deezer download.
- *
- * @param {String} deezerUrl
- * @param {Boolean} downloadFromFile
- */
- const startDownload = async (
- deezerUrl,
- path = defaults.DOWNLOAD_DIR,
- quality = musicQualities.selectedQuality.id,
- downloadFromFile = false
- ) => {
- log.debug('Started download task: ' + deezerUrl);
- try {
- let downloadType = await downloadTypes(deezerUrl);
- if (downloadType.type == 'unknown') {
- const { data } = await axios.get('https://api.deezer.com/search?q=' + encodeURIComponent(deezerUrl));
- const answer = await prompt(
- [
- {
- type: 'select',
- name: 'url',
- message: 'Select a song:',
- choices: data.data.map((item) => {
- item.description = `${item.artist.name} - ${item.link} - ${item.duration}s`;
- item.value = item.link;
- return item;
- }),
- initial: 0,
- },
- ],
- {
- onCancel,
- }
- );
- downloadType = await downloadTypes(answer.url);
- }
- downloadState.start(downloadType.type, downloadType.id);
- const value = await downloadMultiple(downloadType, path, quality);
- downloadState.finish(!downloadFromFile);
- } catch (err) {
- log.debug(err);
- signale.fatal(err.message || err);
- }
- };
- initApp();
|