UNPKG

@phatnguyenuit/nhaccuatui-downloader

Version:

Simple CLI to download playlist and songs from nhaccuatui.com.

136 lines (118 loc) 3.52 kB
const fs = require('fs'); const path = require('path'); const fetch = require('node-fetch'); const { parseStringPromise } = require('xml2js'); /** * Read URL as HTML string * @param {string} url * @returns Promise<string> */ const requestHtmlText = (url) => { return new Promise((resolve, reject) => { fetch(url) .then((response) => response.text()) .then(resolve) .catch(reject); }); }; const FAILED_TO_EXTRACT_XML_URL_ERROR = new Error( 'Failed to extract xml playlist location.', ); const getTracks = async (nhaccuatuiYUrl) => { const htmlText = await requestHtmlText(nhaccuatuiYUrl); const regex = /player\.peConfig\.xmlURL\s=\s"(?<xmlUrl>.+)";/; const match = regex.exec(htmlText); if (!match) { throw FAILED_TO_EXTRACT_XML_URL_ERROR; } const xmlUrl = match.groups.xmlUrl; if (!xmlUrl) { throw FAILED_TO_EXTRACT_XML_URL_ERROR; } const xmlText = await requestHtmlText(xmlUrl); const parsedXmlObj = await parseStringPromise(xmlText); const tracks = parsedXmlObj.tracklist.track || []; return tracks .map((track) => { const title = track.title[0].trim(); const url = track.location[0].trim(); const fileNameReg = /(?<fileName>[\w-\d]+\.mp3)/; const match = fileNameReg.exec(url); if (!match) return null; const { fileName = `${title}.mp3` } = match.groups; return { fileName, title, url, }; }) .filter(Boolean); }; async function exists(path) { try { await fs.promises.access(path); return true; } catch (e) { return false; } } const stdoutLogging = (input) => { process.stdout.clearLine(); process.stdout.cursorTo(0); process.stdout.write(input); }; const download = async (track, folderPath) => { try { const absolutePath = path.resolve(folderPath); const isExisted = await exists(absolutePath); if (!isExisted) { await new Promise((resolve, reject) => { fs.mkdir(absolutePath, { recursive: true }, (err, path) => { if (!err) { resolve(); } else { reject(new Error(`Failed to create directory ${path}`)); } }); }); } const response = await fetch(track.url); const fileStream = fs.createWriteStream( path.resolve(absolutePath, track.fileName), ); await new Promise((resolve, reject) => { const contentLength = +response.headers.get('Content-Length'); let totalReceivedLength = 0; response.body.pipe(fileStream); response.body.on('error', reject); response.body.on('data', (data) => { totalReceivedLength += data.length; const percent = Math.round((totalReceivedLength / contentLength) * 100); stdoutLogging( `Downloading file ${JSON.stringify(track.fileName)}...${percent}%`, ); }); fileStream.on('finish', () => { // eslint-disable-next-line no-console console.info(`\nDownloaded file ${JSON.stringify(track.fileName)}!`); resolve(); }); }); return true; } catch (error) { throw new Error('Fail to download.'); } }; const runPromiseSequentially = (iterator, promiseCreator, ...params) => { return iterator.reduce( (prevPromise, item) => // eslint-disable-next-line no-useless-call prevPromise.then((_) => promiseCreator.apply(null, [item, ...params])), Promise.resolve(), ); }; module.exports = { getTracks, runPromiseSequentially, download, };