UNPKG

@georgemiao/qbit.js

Version:

a qBittorrent library made with typescript

1,121 lines 51.7 kB
import { stat } from "fs/promises"; import { fileFrom, FormData } from "node-fetch"; export var RawScanDirTarget; (function (RawScanDirTarget) { RawScanDirTarget[RawScanDirTarget["monitoredFolder"] = 0] = "monitoredFolder"; RawScanDirTarget[RawScanDirTarget["defaultSavePath"] = 1] = "defaultSavePath"; })(RawScanDirTarget || (RawScanDirTarget = {})); export var RawMaxRatioAction; (function (RawMaxRatioAction) { RawMaxRatioAction[RawMaxRatioAction["pause"] = 0] = "pause"; RawMaxRatioAction[RawMaxRatioAction["remove"] = 1] = "remove"; })(RawMaxRatioAction || (RawMaxRatioAction = {})); export var RawBittorrentProtocol; (function (RawBittorrentProtocol) { RawBittorrentProtocol[RawBittorrentProtocol["both"] = 0] = "both"; RawBittorrentProtocol[RawBittorrentProtocol["TCP"] = 1] = "TCP"; RawBittorrentProtocol[RawBittorrentProtocol["uTP"] = 2] = "uTP"; })(RawBittorrentProtocol || (RawBittorrentProtocol = {})); export var RawSchedulerDays; (function (RawSchedulerDays) { RawSchedulerDays[RawSchedulerDays["everyDay"] = 0] = "everyDay"; RawSchedulerDays[RawSchedulerDays["everyWeekday"] = 1] = "everyWeekday"; RawSchedulerDays[RawSchedulerDays["everyWeekend"] = 2] = "everyWeekend"; RawSchedulerDays[RawSchedulerDays["everyMonday"] = 3] = "everyMonday"; RawSchedulerDays[RawSchedulerDays["everyTuesday"] = 4] = "everyTuesday"; RawSchedulerDays[RawSchedulerDays["everyWednesday"] = 5] = "everyWednesday"; RawSchedulerDays[RawSchedulerDays["everyThursday"] = 6] = "everyThursday"; RawSchedulerDays[RawSchedulerDays["everyFriday"] = 7] = "everyFriday"; RawSchedulerDays[RawSchedulerDays["everySaturday"] = 8] = "everySaturday"; RawSchedulerDays[RawSchedulerDays["everySunday"] = 9] = "everySunday"; })(RawSchedulerDays || (RawSchedulerDays = {})); export var RawEncryptionOption; (function (RawEncryptionOption) { RawEncryptionOption[RawEncryptionOption["prefer"] = 0] = "prefer"; RawEncryptionOption[RawEncryptionOption["on"] = 1] = "on"; RawEncryptionOption[RawEncryptionOption["off"] = 2] = "off"; })(RawEncryptionOption || (RawEncryptionOption = {})); export var RawProxyType; (function (RawProxyType) { RawProxyType[RawProxyType["disabled"] = -1] = "disabled"; RawProxyType[RawProxyType["httpWithouAuthentication"] = 1] = "httpWithouAuthentication"; RawProxyType[RawProxyType["socks5WithoutAuthentication"] = 2] = "socks5WithoutAuthentication"; RawProxyType[RawProxyType["httpWithAuthentication"] = 3] = "httpWithAuthentication"; RawProxyType[RawProxyType["socks5WithAuthentication"] = 4] = "socks5WithAuthentication"; RawProxyType[RawProxyType["socks4WithoutAuthentication"] = 5] = "socks4WithoutAuthentication"; })(RawProxyType || (RawProxyType = {})); export var RawDynDnsService; (function (RawDynDnsService) { RawDynDnsService[RawDynDnsService["DyDNS"] = 0] = "DyDNS"; RawDynDnsService[RawDynDnsService["NOIP"] = 1] = "NOIP"; })(RawDynDnsService || (RawDynDnsService = {})); export var RawUploadChokingAlgorithm; (function (RawUploadChokingAlgorithm) { RawUploadChokingAlgorithm[RawUploadChokingAlgorithm["roundRobin"] = 0] = "roundRobin"; RawUploadChokingAlgorithm[RawUploadChokingAlgorithm["fastestUpload"] = 1] = "fastestUpload"; RawUploadChokingAlgorithm[RawUploadChokingAlgorithm["antiLeech"] = 2] = "antiLeech"; })(RawUploadChokingAlgorithm || (RawUploadChokingAlgorithm = {})); export var RawUploadSlotsBehavior; (function (RawUploadSlotsBehavior) { RawUploadSlotsBehavior[RawUploadSlotsBehavior["fixedSlots"] = 0] = "fixedSlots"; RawUploadSlotsBehavior[RawUploadSlotsBehavior["uploadRateBased"] = 1] = "uploadRateBased"; })(RawUploadSlotsBehavior || (RawUploadSlotsBehavior = {})); export var RawUtpTcpMixedMode; (function (RawUtpTcpMixedMode) { RawUtpTcpMixedMode[RawUtpTcpMixedMode["preferTcp"] = 0] = "preferTcp"; RawUtpTcpMixedMode[RawUtpTcpMixedMode["peerProportional"] = 1] = "peerProportional"; })(RawUtpTcpMixedMode || (RawUtpTcpMixedMode = {})); export var RawLogEntryType; (function (RawLogEntryType) { RawLogEntryType[RawLogEntryType["normal"] = 1] = "normal"; RawLogEntryType[RawLogEntryType["info"] = 2] = "info"; RawLogEntryType[RawLogEntryType["warning"] = 4] = "warning"; RawLogEntryType[RawLogEntryType["critical"] = 8] = "critical"; })(RawLogEntryType || (RawLogEntryType = {})); export var RawTorrentState; (function (RawTorrentState) { RawTorrentState["error"] = "error"; RawTorrentState["missingFiles"] = "missingFiles"; RawTorrentState["uploading"] = "uploading"; RawTorrentState["pausedUP"] = "pausedUP"; RawTorrentState["queuedUP"] = "queuedUP"; RawTorrentState["stalledUP"] = "stalledUP"; RawTorrentState["checkingUP"] = "checkingUP"; RawTorrentState["forcedUP"] = "forcedUP"; RawTorrentState["allocating"] = "allocating"; RawTorrentState["downloading"] = "downloading"; RawTorrentState["metaDL"] = "metaDL"; RawTorrentState["pausedDL"] = "pausedDL"; RawTorrentState["queuedDL"] = "queuedDL"; RawTorrentState["stalledDL"] = "stalledDL"; RawTorrentState["checkingDL"] = "checkingDL"; RawTorrentState["forcedDL"] = "forcedDL"; RawTorrentState["checkingResumeData"] = "checkingResumeData"; RawTorrentState["moving"] = "moving"; RawTorrentState["unknown"] = "unknown"; })(RawTorrentState || (RawTorrentState = {})); export var RawConnectionStatus; (function (RawConnectionStatus) { RawConnectionStatus["connected"] = "connected"; RawConnectionStatus["firewalled"] = "firewalled"; RawConnectionStatus["disconnected"] = "disconnected"; })(RawConnectionStatus || (RawConnectionStatus = {})); export var RawConnectionType; (function (RawConnectionType) { RawConnectionType["uTP"] = "\u03BCTP"; RawConnectionType["BT"] = "BT"; })(RawConnectionType || (RawConnectionType = {})); export var RawTorrentListFilter; (function (RawTorrentListFilter) { RawTorrentListFilter["all"] = "all"; RawTorrentListFilter["downloading"] = "downloading"; RawTorrentListFilter["seeding"] = "seeding"; RawTorrentListFilter["completed"] = "completed"; RawTorrentListFilter["paused"] = "paused"; RawTorrentListFilter["active"] = "active"; RawTorrentListFilter["inactive"] = "inactive"; RawTorrentListFilter["resumed"] = "resumed"; RawTorrentListFilter["stalled"] = "stalled"; RawTorrentListFilter["stalled_uploading"] = "stalled_uploading"; RawTorrentListFilter["stalled_downloading"] = "stalled_downloading"; RawTorrentListFilter["errored"] = "errored"; })(RawTorrentListFilter || (RawTorrentListFilter = {})); export var RawTorrentSortKey; (function (RawTorrentSortKey) { RawTorrentSortKey["added_on"] = "added_on"; RawTorrentSortKey["amount_left"] = "amount_left"; RawTorrentSortKey["auto_tmm"] = "auto_tmm"; RawTorrentSortKey["availability"] = "availability"; RawTorrentSortKey["category"] = "category"; RawTorrentSortKey["completed"] = "completed"; RawTorrentSortKey["completion_on"] = "completion_on"; RawTorrentSortKey["content_path"] = "content_path"; RawTorrentSortKey["dl_limit"] = "dl_limit"; RawTorrentSortKey["dlspeed"] = "dlspeed"; RawTorrentSortKey["downloaded"] = "downloaded"; RawTorrentSortKey["downloaded_session"] = "downloaded_session"; RawTorrentSortKey["eta"] = "eta"; RawTorrentSortKey["f_l_piece_prio"] = "f_l_piece_prio"; RawTorrentSortKey["force_start"] = "force_start"; RawTorrentSortKey["hash"] = "hash"; RawTorrentSortKey["last_activity"] = "last_activity"; RawTorrentSortKey["magnet_uri"] = "magnet_uri"; RawTorrentSortKey["max_ratio"] = "max_ratio"; RawTorrentSortKey["max_seeding_time"] = "max_seeding_time"; RawTorrentSortKey["name"] = "name"; RawTorrentSortKey["num_complete"] = "num_complete"; RawTorrentSortKey["num_incomplete"] = "num_incomplete"; RawTorrentSortKey["num_leechs"] = "num_leechs"; RawTorrentSortKey["num_seeds"] = "num_seeds"; RawTorrentSortKey["priority"] = "priority"; RawTorrentSortKey["progress"] = "progress"; RawTorrentSortKey["ratio"] = "ratio"; RawTorrentSortKey["ratio_limit"] = "ratio_limit"; RawTorrentSortKey["save_path"] = "save_path"; RawTorrentSortKey["seeding_time"] = "seeding_time"; RawTorrentSortKey["seeding_time_limit"] = "seeding_time_limit"; RawTorrentSortKey["seen_complete"] = "seen_complete"; RawTorrentSortKey["seq_dl"] = "seq_dl"; RawTorrentSortKey["size"] = "size"; RawTorrentSortKey["state"] = "state"; RawTorrentSortKey["super_seeding"] = "super_seeding"; RawTorrentSortKey["tags"] = "tags"; RawTorrentSortKey["time_active"] = "time_active"; RawTorrentSortKey["total_size"] = "total_size"; RawTorrentSortKey["tracker"] = "tracker"; RawTorrentSortKey["up_limit"] = "up_limit"; RawTorrentSortKey["uploaded"] = "uploaded"; RawTorrentSortKey["uploaded_session"] = "uploaded_session"; RawTorrentSortKey["upspeed"] = "upspeed"; })(RawTorrentSortKey || (RawTorrentSortKey = {})); export var RawTrackerStatus; (function (RawTrackerStatus) { RawTrackerStatus[RawTrackerStatus["disabled"] = 0] = "disabled"; RawTrackerStatus[RawTrackerStatus["notContacted"] = 1] = "notContacted"; RawTrackerStatus[RawTrackerStatus["working"] = 2] = "working"; RawTrackerStatus[RawTrackerStatus["updating"] = 3] = "updating"; RawTrackerStatus[RawTrackerStatus["notWorking"] = 4] = "notWorking"; })(RawTrackerStatus || (RawTrackerStatus = {})); export var RawTorrentFilePriority; (function (RawTorrentFilePriority) { RawTorrentFilePriority[RawTorrentFilePriority["doNotDownload"] = 0] = "doNotDownload"; RawTorrentFilePriority[RawTorrentFilePriority["normal"] = 1] = "normal"; RawTorrentFilePriority[RawTorrentFilePriority["high"] = 6] = "high"; RawTorrentFilePriority[RawTorrentFilePriority["maximal"] = 7] = "maximal"; })(RawTorrentFilePriority || (RawTorrentFilePriority = {})); export var RawPieceState; (function (RawPieceState) { RawPieceState[RawPieceState["notDownloaded"] = 0] = "notDownloaded"; RawPieceState[RawPieceState["nowDownloading"] = 1] = "nowDownloading"; RawPieceState[RawPieceState["alreadyDownloaded"] = 2] = "alreadyDownloaded"; })(RawPieceState || (RawPieceState = {})); export var RawSearchStatusType; (function (RawSearchStatusType) { RawSearchStatusType["Stopped"] = "Stopped"; RawSearchStatusType["Running"] = "Running"; })(RawSearchStatusType || (RawSearchStatusType = {})); function safeURL(str) { let url; try { url = new URL(str); } catch (e) { return null; } return url; } export class Api { constructor(qbit) { this.qbit = qbit; } async login(username, password) { const res = await this.qbit.fetch("auth/login", { method: "POST", body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 403) throw new Error("IP Banned"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); const session = res.headers.get("set-cookie")?.split(";")?.[0]; if (session === null || session === undefined) { throw new Error("Invalid credentials"); } return session; } async logout() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("auth/logout"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getApplicationVersion() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("app/version"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.text(); } async getApiVersion() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("app/webapiVersion"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.text(); } async getBuildInfo() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("app/buildInfo"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async shutdown() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("app/shutdown"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getPreferences() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("app/preferences"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async setPreferences(preferences) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("app/preferences", { method: "POST", body: `json=${encodeURIComponent(JSON.stringify(preferences))}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getDefaultSavePath() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("app/defaultSavePath"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.text(); } async getLog(opts = {}) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`log/main?${Object.entries(opts) .map(([key, val]) => `${key}=${encodeURIComponent(val)}`) .join("&")}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getPeerLog(last_known_id) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`log/peers${last_known_id ? `?last_known_id=${last_known_id}` : ""}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getMainData(rid) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`sync/maindata${rid ? `?rid=${rid}` : ""}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getTorrentPeers(hash, rid) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`sync/torrentPeers?hash=${encodeURIComponent(hash)}${rid ? `&rid=${rid}` : ""}`); if (res.status === 404) throw new Error("Torrent not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getTransferInfo() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("transfer/info"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async isAlternativeSpeedLimitEnabled() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("transfer/speedLimitsMode"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return (await res.text()) === "1"; } async toggleAlternativeSpeedLimit() { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`transfer/toggleSpeedLimitsMode`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getGlobalDownloadLimit() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("transfer/downloadLimit"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return parseFloat(await res.text()); } async setGlobalDownloadLimit(limit) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("transfer/setDownloadLimit", { method: "POST", body: `limit=${Math.round(limit)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getGlobalUploadLimit() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("transfer/uploadLimit"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return parseFloat(await res.text()); } async setGlobalUploadLimit(limit) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("transfer/setUploadLimit", { method: "POST", body: `limit=${Math.round(limit)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async banPeers(peers) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("transfer/banPeers", { method: "POST", body: `peers=${encodeURIComponent(peers.join("|"))}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getTorrents(opts = {}) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/info?${Object.entries(opts) .map(([key, val]) => `${key}=${encodeURIComponent(val)}`) .join("&")}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getTorrent(hash) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/properties?hash=${encodeURIComponent(hash)}`); if (res.status === 404) throw new Error("Torrent not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getTorrentTrackers(hash) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/trackers?hash=${encodeURIComponent(hash)}`); if (res.status === 404) throw new Error("Torrent not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getTorrentWebSeeds(hash) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/webseeds?hash=${encodeURIComponent(hash)}`); if (res.status === 404) throw new Error("Torrent not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getTorrentFiles(hash, indexes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/files?hash=${encodeURIComponent(hash)}${indexes ? `&indexes=${encodeURIComponent(indexes.join("|"))}` : ""}`); if (res.status === 404) throw new Error("Torrent not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getTorrentPieceStates(hash) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/pieceStates?hash=${encodeURIComponent(hash)}`); if (res.status === 404) throw new Error("Torrent not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getTorrentPieceHashes(hash) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/pieceHashes?hash=${encodeURIComponent(hash)}`); if (res.status === 404) throw new Error("Torrent not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async pauseTorrents(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/pause?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async resumeTorrents(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/resume?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async deleteTorrents(hashes, deleteFiles = false) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/delete?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&deleteFiles=${deleteFiles}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async recheckTorrents(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/recheck?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async reannounceTorrents(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/reannounce?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async addTorrent(torrent, opts) { await this.qbit.checkLogin(); const data = new FormData(); const torrents = Array.isArray(torrent) ? torrent : [torrent]; const links = []; for (const torrent of torrents) { if (Buffer.isBuffer(torrent)) { data.append("torrents", new File([torrent], "upload.torrent", { type: "application/x-bittorrent", }), "upload.torrent"); } else { const parsed = safeURL(torrent); if (parsed && ["http:", "https:", "magnet:"].includes(parsed.protocol)) { links.push(torrent); } else { const res = await stat(torrent).catch(() => null); if (res) data.append("torrents", await fileFrom(torrent, "application/x-bittorrent")); else throw Error(`Invalid torrent string "${torrent}"`); } } } if (links.length > 0) data.set("urls", links.join("\n")); if (opts) { for (const [key, val] of Object.entries(opts)) { if (key === "tags") data.set(key, val.join(",")); else data.set(key, val.toString()); } } // fix for bullshit formdata bugs data.set("fix", "fix"); const res = await this.qbit.fetch("torrents/add", { method: "POST", body: data, }); if (res.status === 415) throw new Error("Torrent file not valid"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async addTorrentTrackers(hash, trackers) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/addTrackers?hash=${encodeURIComponent(hash)}&urls=${encodeURIComponent(Array.isArray(trackers) ? trackers.join("\n") : trackers)}`); if (res.status === 404) throw new Error("Torrent not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async editTorrentTrackers(hash, origUrl, newUrl) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/editTrackers?hash=${encodeURIComponent(hash)}&origUrl=${encodeURIComponent(origUrl)}&newUrl=${encodeURIComponent(newUrl)}`); if (res.status === 400) throw new Error("New url is not valid"); if (res.status === 404) throw new Error("Torrent not found"); if (res.status === 409) throw new Error("Old url is not used or new one is already used"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async removeTorrentTracker(hash, trackers) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/removeTrackers?hash=${encodeURIComponent(hash)}&urls=${encodeURIComponent(Array.isArray(trackers) ? trackers.join("\n") : trackers)}`); if (res.status === 404) throw new Error("Torrent not found"); if (res.status === 409) throw new Error("Trackers not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async addTorrentPeers(hashes, peers) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/addPeers?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&peers=${encodeURIComponent(Array.isArray(peers) ? peers.join("|") : peers)}`); if (res.status === 404) throw new Error("Torrent not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async increaseTorrentPriority(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/increasePrio?hashes=${encodeURIComponent(hashes)}`); if (res.status === 409) throw new Error("Torrent queueing is not enabled"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async decreaseTorrentPriority(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/decreasePrio?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`); if (res.status === 409) throw new Error("Torrent queueing is not enabled"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setTopPriority(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/topPrio?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`); if (res.status === 409) throw new Error("Torrent queueing is not enabled"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setBottomPriority(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/bottomPrio?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`); if (res.status === 409) throw new Error("Torrent queueing is not enabled"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setTorrentFilePriority(hash, files, priority) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/filePrio?hash=${encodeURIComponent(hash)}&id=${encodeURIComponent(files.join("|"))}&priority=${priority}`); if (res.status === 400) throw new Error("Invalid file index or priority"); if (res.status === 404) throw new Error("Torrent not found"); if (res.status === 409) throw new Error("File id not found or torrent metadata not yet downloaded"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getTorrentDownloadLimit(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/downloadLimit?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async setTorrentDownloadLimit(hashes, limit) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/setDownloadLimit`, { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&limit=${limit}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setTorrentShareLimits(opts) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/setShareLimits", { method: "POST", body: Object.entries(opts) .map(([key, val]) => `${key}=${encodeURIComponent(Array.isArray(val) ? val.join("|") : val)}`) .join("&"), headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getTorrentUploadLimit(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/uploadLimit?hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async setTorrentUploadLimit(hashes, limit) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`torrents/setUploadLimit`, { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&limit=${limit}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setTorrentLocation(hashes, location) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/setLocation", { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&location=${encodeURIComponent(location)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 400) throw new Error("Invalid location"); if (res.status === 403) throw new Error("No write perms to direcotry"); if (res.status === 409) throw new Error("Unable to create directory"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setTorrentName(hash, name) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/rename", { method: "POST", body: `hash=${encodeURIComponent(hash)}&name=${encodeURIComponent(name)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 404) throw new Error("Torrent not found"); if (res.status === 409) throw new Error("Torrent name is invalid"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setTorrentCategory(hashes, category) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/setCategory", { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&category=${encodeURIComponent(category)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 409) throw new Error("Torrent category is invalid"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getCategories() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/categories"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async createCategory(name, savePath = "") { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/createCategory", { method: "POST", body: `category=${encodeURIComponent(name)}&savePath=${encodeURIComponent(savePath)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 400 || res.status === 409) throw new Error("Category name is invalid"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async editCategory(name, savePath) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/editCategory", { method: "POST", body: `category=${encodeURIComponent(name)}&savePath=${encodeURIComponent(savePath)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 400) throw new Error("Category name is invalid"); if (res.status === 409) throw new Error("Category editing failed"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async removeCategories(names) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/removeCategories", { method: "POST", body: `categories=${encodeURIComponent(Array.isArray(names) ? names.join("\n") : names)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async addTorrentTags(hashes, tags) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/addTags", { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&tags=${encodeURIComponent(Array.isArray(tags) ? tags.join(",") : tags)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async removeTorrentTags(hashes, tags) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/removeTags", { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&tags=${encodeURIComponent(Array.isArray(tags) ? tags.join(",") : tags)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getTags() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/tags"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async createTags(names) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/createTags", { method: "POST", body: `tags=${encodeURIComponent(Array.isArray(names) ? names.join(",") : names)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async deleteTags(names) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/deleteTags", { method: "POST", body: `tags=${encodeURIComponent(Array.isArray(names) ? names.join(",") : names)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setTorrentAutoManagement(hashes, enabled) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/setAutoManagement", { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&enabled=${enabled}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async toggleTorrentSequentialDownload(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/toggleSequentialDownload", { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async toggleTorrentFirstLastPiecePriority(hashes) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/toggleFirstLastPiecePrio", { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setTorrentForceStart(hashes, enabled) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/setForceStart", { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&value=${enabled}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setTorrentSuperSeeding(hashes, enabled) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/setSuperSeeding", { method: "POST", body: `hashes=${encodeURIComponent(Array.isArray(hashes) ? hashes.join("|") : hashes)}&value=${enabled}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async renameTorrentFile(hash, oldPath, newPath) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/renameFile", { method: "POST", body: `hash=${encodeURIComponent(hash)}&oldPath=${encodeURIComponent(oldPath)}&newPath=${encodeURIComponent(newPath)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 400 || res.status === 409) throw new Error("Invalid path"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async renameTorrentFolder(hash, oldPath, newPath) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("torrents/renameFolder", { method: "POST", body: `hash=${encodeURIComponent(hash)}&oldPath=${encodeURIComponent(oldPath)}&newPath=${encodeURIComponent(newPath)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 400 || res.status === 409) throw new Error("Invalid path"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async addRssFolder(path) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/addFolder", { method: "POST", body: `path=${encodeURIComponent(path)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 409) throw new Error("Failed to add folder"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async addRssFeed(url, path) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/addFeed", { method: "POST", body: `url=${encodeURIComponent(url)}${path ? `&path=${encodeURIComponent(path)}` : ""}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 409) throw new Error("Failed to add feed"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async removeRssItem(path) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/removeItem", { method: "POST", body: `path=${encodeURIComponent(path)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 409) throw new Error("Failed to remove item"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async moveRssItem(itemPath, destPath) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/moveItem", { method: "POST", body: `itemPath=${encodeURIComponent(itemPath)}&destPath=${encodeURIComponent(destPath)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 409) throw new Error("Failed to move item"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getRssItems(withdata = false) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`rss/items?withdata=${withdata}`); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async markRssAsRead(itemPath, articleId) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/markAsRead", { method: "POST", body: `itemPath=${encodeURIComponent(itemPath)}${articleId ? `&articleId=${encodeURIComponent(articleId)}` : ""}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async refreshRssItem(itemPath) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/refreshItem", { method: "POST", body: `itemPath=${encodeURIComponent(itemPath)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async setRssRule(ruleName, ruleDef) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/setRule", { method: "POST", body: `ruleName=${encodeURIComponent(ruleName)}&ruleDef=${encodeURIComponent(JSON.stringify(ruleDef))}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async renameRssRule(ruleName, newRuleName) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/renameRule", { method: "POST", body: `ruleName=${encodeURIComponent(ruleName)}&newRuleName=${encodeURIComponent(newRuleName)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async removeRssRule(ruleName) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/removeRule", { method: "POST", body: `ruleName=${encodeURIComponent(ruleName)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getRssRules() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/rules"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getRssRuleArticles(ruleName) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("rss/matchingArticles", { method: "POST", body: `ruleName=${encodeURIComponent(ruleName)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async startSearch(query, plugins, category) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("search/start", { method: "POST", body: `pattern=${encodeURIComponent(query)}&plugins=${encodeURIComponent(Array.isArray(plugins) ? plugins.join("|") : plugins)}&category=${encodeURIComponent(category)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 409) throw new Error("Reached max number of concurrent queries"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); const { id } = (await res.json()); return id; } async stopSearch(id) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("search/stop", { method: "POST", body: `id=${encodeURIComponent(id)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 404) throw new Error("Search not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getSearchStatus(id) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`search/status${id ? `?id=${encodeURIComponent(id)}` : ""}`); if (res.status === 404) throw new Error("Search not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async getSearchResults(opts) { await this.qbit.checkLogin(); const res = await this.qbit.fetch(`search/results?${Object.entries(opts) .map(([key, val]) => `${key}=${encodeURIComponent(val)}`) .join("&")}`); if (res.status === 404) throw new Error("Search not found"); if (res.status === 409) throw new Error("Offset out of range"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async deleteSearch(id) { await this.qbit.checkLogin(); const res = await this.qbit.fetch("search/delete", { method: "POST", body: `id=${encodeURIComponent(id)}`, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); if (res.status === 404) throw new Error("Search not found"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); } async getSearchPlugins() { await this.qbit.checkLogin(); const res = await this.qbit.fetch("search/plugins"); if (res.status !== 200) throw new Error(`Unexpected status "${res.status}"`); return res.json(); } async installSearchPlugin(sources) { await this.qbit.checkLogin(); const