#!/usr/bin/env node const path = require('path'); const fs = require('fs'); const yargs = require('yargs'); const { spawnSync } = require('child_process'); const { scrapeAnimevibeDownloadPage, scrapeVidCDNDownloadPage, scrapeAnimevibeSeriesPage, } = require('./scrape.js'); const argv = yargs .command('download ', 'Download one or more episodes', yargs => yargs.positional('urls', { describe: 'List of urls to download from', }) .option('start-episode', { alias: 's', type: 'number', describe: 'First episode to download. Default is current episode', }) .option('end-episode', { alias: 'e', type: 'number', describe: 'Last episode to download. Default is current episode', }) .option('all-episodes', { alias: 'a', type: 'boolean', describe: 'Download all available episodes', }) .option('sleep', { alias: 'S', type: 'number', describe: 'Seconds to sleep after each download', }), download ) .help().alias('help', 'h') .demandCommand() .strict() .argv; async function download(options) { for (const url of options.urls) { const info = await scrapeAnimevibeSeriesPage(url); let startEpisode; let endEpisode; if (options.allEpisodes) { startEpisode = 1; endEpisode = info.episodeCount; } else { startEpisode = options.startEpisode || info.currentEpisodeNumber || 1; endEpisode = options.endEpisode || info.currentEpisodeNumber || 1; } for (let i = startEpisode; i <= endEpisode; i++) { console.log(`Downloading '${info.titles.english}' episode ${i}`); const downloadInfo = await getDownloadOptions(info.downloadId, i); const download = getBestDownload(downloadInfo.downloads); const filename = getOutputFilename(info, download, pad(i, 4)); console.log(`Saving to ${filename}`); fs.mkdirSync(path.dirname(filename), { recursive: true }); spawnSync('wget', ['--output-document', filename, download.url], { stdio: 'inherit' }); if (i < endEpisode && options.sleep) { console.log(`Sleeping for ${options.sleep} seconds`); await sleep(options.sleep*1000); } } } } function getOutputFilename(seriesInfo, download, episode) { const basename = `${seriesInfo.titles.english}-${seriesInfo.titles.japanese}`; const filename = seriesInfo.type.startsWith('Movie') ? `${basename}.${download.format}` : `${basename}/${episode}.${download.format}`; return filename .replace(/ /g, '_') .replace(/[^\/\w\d-_\.]/g, ''); } function pad(n, width, z) { z = z || '0'; n = n + ''; return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; } function getBestDownload(downloads) { downloads.sort(flip(comparing(d => d.quality))); return downloads[0]; } async function getDownloadOptions(downloadId, episode) { const url = `https://animevibe.tv/anime-downloader/?id=${downloadId}&episode=${episode}`; const mirrors = await scrapeAnimevibeDownloadPage(url); return await scrapeVidCDNDownloadPage(mirrors.vidcdn); } function sleep(millis) { return new Promise(resolve => setTimeout(resolve, millis)); } function flip(f) { return (a, b) => f(b, a); } function comparing(key) { return (a, b) => key(a) > key(b) ? 1 : -1; }