diff --git a/src/classes/download/JDownloaderClient.js b/src/classes/download/JDownloaderClient.js new file mode 100644 index 0000000..4b5181b --- /dev/null +++ b/src/classes/download/JDownloaderClient.js @@ -0,0 +1,107 @@ +const { JdownloaderClient: Client, JdownloaderClient } = require('jdownloader-client-yaac'); +const Logger = require('../utils/Logger'); +const Utils = require('../utils/Utils'); +const { FatalError, JDownloaderError } = require('../../errors'); +const Show = require('../media/Show'); +const Movie = require('../media/Movie'); +const Media = require('../media/Media'); + +class JDownloaderClient { + constructor(credentials) { + this.client = new Client(credentials.email, credentials.password); + this.device = null; + } + + async load() { + await this.client.core.connect(); + this.device = (await this.client.core.listDevices())[0]; + } + + async addLinks(media) { + if (!(media instanceof Media)) { + throw new FatalError('Invalid Media instance passed to downloader!'); + } + + await this.client.linkgrabberV2.addLinks(this.device.id, { + destinationFolder: media.getDownloadDestinationFolder(), + packageName: media.getDownloadPackageName(), + links: media.downloadURLs + }); + + Logger.info(`Added links for ${media.name}`); + } + + async getCrawledLinks(mediaDownloadURLs) { + let crawledLinks = await this.client.linkgrabberV2.queryLinks(this.device.id, { + url: true + }); + let retries = 0; + + while (crawledLinks.length !== mediaDownloadURLs.length && retries < JDownloaderClient.DEFAULTS.MAX_QUERY_RETRIES) { + retries++; + Logger.warn(`Querying links from LinkGrabber, retry ${retries} of ${JDownloaderClient.DEFAULTS.MAX_QUERY_RETRIES}...`); + + crawledLinks = await this.client.linkgrabberV2.queryLinks(this.device.id, { + url: true + }); + + await Utils.wait(JDownloaderClient.DEFAULTS.RETRY_TIMEOUT); + } + + if (crawledLinks.length < 1) { + throw new JDownloaderError('Could not fetch crawled links from JDownloader!'); + } + + return crawledLinks; + } + + getRenamedCrawledLinks(crawledLinks, media) { + if (!(media instanceof Media)) { + throw new FatalError('Invalid Media instance passed to downloader!'); + } + + return crawledLinks.map((link) => { + const ext = link.name.substring(link.name.lastIndexOf('.') + 1); + + return { + linkId: link.uuid, + newName: media.getDownloadFilename(ext, link.url) + }; + }, []); + } + + async renameCrawledLinks(renamedCrawledLinks) { + Logger.info('Renaming crawled links...'); + + await Utils.mapSeries(renamedCrawledLinks, (renamedLink) => { + return this.client.linkgrabberV2.renameLink(this.device.id, renamedLink) + .then(() => { + Logger.info(`Renamed ${renamedLink.newName}`); + }); + }); + + Logger.success('Renaming complete.'); + } + + async startDownload(crawledLinks = []) { + if (!crawledLinks.packageUUID) { + throw new JDownloaderError('Cannot start download without packageUUID!'); + } + + const linkIDs = crawledLinks.map((link) => link.uuid); + + if (linkIDs.length < 1) { + throw new JDownloaderError('No links to download!'); + } + + await this.client.core.callAction('/linkgrabberv2/moveToDownloadlist', this.device.id, [linkIDs, [crawledLinks.packageUUID]]); + Logger.success('Download started.'); + } +} + +JDownloaderClient.DEFAULTS = { + MAX_QUERY_RETRIES: 6, + RETRY_TIMEOUT: 3000 +}; + +module.exports = JDownloaderClient; diff --git a/src/classes/utils/Utils.js b/src/classes/utils/Utils.js new file mode 100644 index 0000000..952f387 --- /dev/null +++ b/src/classes/utils/Utils.js @@ -0,0 +1,22 @@ +class Utils { + static async mapSeries(iterable, action) { + for (const x of iterable) { + await action(x); + } + return Promise.resolve(); + } + + static wait(duration) { + return new Promise((resolve) => { + setTimeout(resolve, duration); + }); + } + + static parseTemplate(template, data) { + return Object.keys(data).reduce((str, key) => { + return str.replace(new RegExp(`{${key}}`, 'gi'), data[key]); + }, template); + } +} + +module.exports = Utils; diff --git a/src/errors/JDownloaderError.js b/src/errors/JDownloaderError.js new file mode 100644 index 0000000..239637d --- /dev/null +++ b/src/errors/JDownloaderError.js @@ -0,0 +1,9 @@ +class JDownloaderError extends Error { + constructor(message) { + super(message); + + this.name = 'JDownloaderError'; + } +} + +module.exports = JDownloaderError; diff --git a/src/errors/index.js b/src/errors/index.js index f30a4a2..841618d 100644 --- a/src/errors/index.js +++ b/src/errors/index.js @@ -1,7 +1,9 @@ const FatalError = require('./FatalError'); const ScraperError = require('./ScraperError'); +const JDownloaderError = require('./JDownloaderError'); module.exports = { FatalError, - ScraperError + ScraperError, + JDownloaderError };