UNPKG

@electorrent/node-qbittorrent

Version:

A client implementation of qBittorrent WebAPI

270 lines (217 loc) 7.38 kB
const fs = require('fs') const url = require('url') const path = require('path') const request = require('request') function ApiV1(option) { this.path = option.path this.user = option.user this.pass = option.pass this.ca = option.ca this.baseurl = option.baseurl this.timeout = option.timeout this.rid = 0 /* used for syncing data */ this.jar = request.jar() this.options = { timeout: this.timeout, ca: this.ca, jar: this.jar, headers: { 'Referer': this.baseurl.origin, }, } } const AUTH_ERRORS = { 403: new Error('User\'s IP is banned for too many failed login attempts'), } const TORRENT_ERRORS = { 404: new Error('Torrent hash was not found'), } ApiV1.prototype.handleError = function(cb, errors) { return function(err, res, body) { if (err) { return cb(err, body) } else if (errors && errors.hasOwnProperty(res.statusCode)) { return cb(errors[res.statusCode], body) } else if (body && body === 'Fails.') { return cb(new Error('Request failed')) } else { return cb(err, body) } } } ApiV1.prototype.url = function(name) { return url.resolve(this.baseurl.toString(), path.join(this.path, name)) } ApiV1.prototype.http = function(method, path, options, cb) { request(Object.assign({}, this.options, { method: method, uri: this.url(path), }, options), function(err, res, body) { if (err) { cb(...arguments) } else if (res && res.statusCode !== 200) { cb(new Error(res.statusCode), ...Array.from(arguments).slice(1)) } else { cb(...arguments) } }) } ApiV1.prototype.get = function(path, options, cb) { this.http('GET', path, options, cb) } ApiV1.prototype.getJson = function(path, options, cb) { this.get(path, Object.assign({json: true}, options), cb) } ApiV1.prototype.post = function(path, options, cb) { this.http('POST', path, options, cb) } ApiV1.prototype.login = function(cb) { this.post('login', {form: { username: this.user, password: this.pass, }}, function(err, res) { if (res && !res.headers.hasOwnProperty('set-cookie')) { err = new Error('Invalid login') } this.handleError(cb, AUTH_ERRORS)(...arguments) }.bind(this)) } ApiV1.prototype.reset = function(cb) { this.rid = 0 cb() } ApiV1.prototype.getTorrents = function(cb) { this.getJson('query/torrents', {}, this.handleError(cb)) } ApiV1.prototype.syncMaindata = function(cb) { this.getJson('sync/maindata', {qs: {rid: this.rid}}, this.handleError(function(err, body) { this.rid = (body && body.rid || 0) cb(...arguments) }.bind(this))) } ApiV1.prototype.addTorrentFile = function(filepath, options, cb) { let data try { data = fs.readFileSync(filepath); } catch (err) { return cb(err) } let filename = path.basename(filepath) this.addTorrentFileContent(data, filename, options, cb) } ApiV1.prototype.addTorrentFileContent = function(content, filename, options, cb) { if (!Buffer.isBuffer(content)) { content = Buffer.from(content) } let formData = { torrents: { value: content, options: { filename: filename, contentType: 'application/x-bittorrent', } }, } if (typeof options === 'object') { Object.assign(formData, options) } this.post('command/upload', {formData}, this.handleError(cb)) } ApiV1.prototype.addTorrentURL = function(magneturl, options, cb) { let formData = { urls: magneturl, } if (typeof options === 'object') { Object.assign(formData, options) } this.post('command/download', {formData}, this.handleError(cb)) } ApiV1.prototype.performPostAction = function(action, hashes, form, cb) { hashes = Array.isArray(hashes) ? hashes : [hashes] form = Object.assign({ hashes: hashes.join('|') }, form) this.post('command/' + action, {form}, this.handleError(cb, TORRENT_ERRORS)) } ApiV1.prototype.performMultiPostAction = function(action, hashes, form, cb) { hashes = Array.isArray(hashes) ? hashes : [hashes] let counter = 0 for (let hash of hashes) { let hashform = Object.assign({hash}, form) this.post('command/' + action, {form: hashform}, this.handleError(function() { counter++ if (hashes.length === counter) { cb(...arguments) } }.bind(this), TORRENT_ERRORS)) } } ApiV1.prototype.pause = function(hashes, cb) { this.performMultiPostAction('pause', hashes, {}, cb) } ApiV1.prototype.pauseAll = function(cb) { this.post('command/pauseAll', {}, cb) } ApiV1.prototype.resume = function(hashes, cb) { this.performMultiPostAction('resume', hashes, {}, cb) } ApiV1.prototype.resumeAll = function(cb) { this.post('command/resumeAll', {}, cb) } ApiV1.prototype.delete = function(hashes, cb) { this.performPostAction('delete', hashes, {}, cb) } ApiV1.prototype.deleteAndRemove = function(hashes, cb) { this.performPostAction('deletePerm', hashes, {}, cb) } ApiV1.prototype.recheck = function(hashes, cb) { this.performMultiPostAction('recheck', hashes, {}, cb) } ApiV1.prototype.increasePrio = function(hashes, cb) { this.performPostAction('increasePrio', hashes, {}, cb) } ApiV1.prototype.decreasePrio = function(hashes, cb) { this.performPostAction('decreasePrio', hashes, {}, cb) } ApiV1.prototype.topPrio = function(hashes, cb) { this.performPostAction('topPrio', hashes, {}, cb) } ApiV1.prototype.bottomPrio = function(hashes, cb) { this.performPostAction('bottomPrio', hashes, {}, cb) } ApiV1.prototype.rename = function(hash, name, cb) { this.performMultiPostAction('rename', hash, {name}, cb) } ApiV1.prototype.setCategory = function(hashes, category, cb) { this.performPostAction('setCategory', hashes, {category}, cb) } ApiV1.prototype.setLocation = function(hashes, location, cb) { this.performPostAction('setLocation', hashes, {location}, cb) } ApiV1.prototype.createCategory = function(category, savePath, cb) { this.post('command/addCategory', {form: {category}}, cb) } ApiV1.prototype.removeCategories = function(categories, cb) { categories = Array.isArray(categories) ? categories : [categories] this.post('command/removeCategories', { form: { categories: categories.join('\n'), } }, this.handleError(cb)) } ApiV1.prototype.setAutoManagement = function(hashes, enable, cb) { this.performPostAction('setAutoTMM', hashes, {enable}, cb) } ApiV1.prototype.toggleSequentialDownload = function(hashes, cb) { this.performPostAction('toggleSequentialDownload', hashes, {}, cb) } ApiV1.prototype.toggleFirstLastPiecePrio = function(hashes, cb) { this.performPostAction('toggleFirstLastPiecePrio', hashes, {}, cb) } ApiV1.prototype.setForceStart = function(hashes, value, cb) { this.performPostAction('setForceStart', hashes, {value}, cb) } ApiV1.prototype.setSuperSeeding = function(hashes, value, cb) { this.performPostAction('setSuperSeeding', hashes, {value}, cb) } module.exports = ApiV1