@ctrl/qbittorrent 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # qBittorrent [![npm](https://badgen.net/npm/v/@ctrl/qbittorrent)](https://www.npmjs.com/package/@ctrl/qbittorrent) [![CircleCI](https://badgen.net/circleci/github/scttcper/qbittorrent)](https://circleci.com/gh/scttcper/qbittorrent) [![coverage](https://badgen.net/codecov/c/github/scttcper/qbittorrent)](https://codecov.io/gh/scttcper/qbittorrent) [![bundlesize](https://badgen.net/bundlephobia/min/@ctrl/qbittorrent)](https://bundlephobia.com/result?p=@ctrl/qbittorrent)
1
+ # qBittorrent [![npm](https://badgen.net/npm/v/@ctrl/qbittorrent)](https://www.npmjs.com/package/@ctrl/qbittorrent) [![CircleCI](https://badgen.net/circleci/github/scttcper/qbittorrent)](https://circleci.com/gh/scttcper/qbittorrent) [![coverage](https://badgen.net/codecov/c/github/scttcper/qbittorrent)](https://codecov.io/gh/scttcper/qbittorrent)
2
2
 
3
3
  > TypeScript api wrapper for [qBittorrent](https://www.qbittorrent.org/) using [got](https://github.com/sindresorhus/got)
4
4
 
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
- import { Options as GotOptions, Response } from 'got';
3
- import { AddTorrentOptions as NormalizedAddTorrentOptions, AllClientData, NormalizedTorrent, TorrentClient, TorrentSettings } from '@ctrl/shared-torrent';
4
- import { AddMagnetOptions, AddTorrentOptions, BuildInfo, Preferences, Torrent, TorrentCategories, TorrentFile, TorrentFilePriority, TorrentFilters, TorrentPieceState, TorrentProperties, TorrentTrackers, WebSeed } from './types.js';
2
+ import type { Options as GotOptions, Response } from 'got';
3
+ import type { AddTorrentOptions as NormalizedAddTorrentOptions, AllClientData, NormalizedTorrent, TorrentClient, TorrentSettings } from '@ctrl/shared-torrent';
4
+ import type { AddMagnetOptions, AddTorrentOptions, BuildInfo, Preferences, Torrent, TorrentCategories, TorrentFile, TorrentFilePriority, TorrentFilters, TorrentPieceState, TorrentProperties, TorrentTrackers, WebSeed } from './types.js';
5
5
  export declare class QBittorrent implements TorrentClient {
6
6
  config: TorrentSettings;
7
7
  /**
@@ -160,6 +160,10 @@ export declare class QBittorrent implements TorrentClient {
160
160
  * @param name new name to be assigned to the file
161
161
  */
162
162
  renameFile(hash: string, id: number, name: string): Promise<boolean>;
163
+ /**
164
+ * {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#rename-folder}
165
+ */
166
+ renameFolder(hash: string, oldPath: string, newPath: string): Promise<boolean>;
163
167
  /**
164
168
  * @param urls URLs separated with newlines
165
169
  * @param options
@@ -7,10 +7,10 @@ import { fileFromPath } from 'formdata-node/file-from-path';
7
7
  import got from 'got';
8
8
  import { Cookie } from 'tough-cookie';
9
9
  import { magnetDecode } from '@ctrl/magnet-link';
10
- import { TorrentState as NormalizedTorrentState, } from '@ctrl/shared-torrent';
10
+ import { TorrentState as NormalizedTorrentState } from '@ctrl/shared-torrent';
11
11
  import { hash } from '@ctrl/torrent-file';
12
12
  import { urlJoin } from '@ctrl/url-join';
13
- import { TorrentState, } from './types.js';
13
+ import { TorrentState } from './types.js';
14
14
  const defaults = {
15
15
  baseUrl: 'http://localhost:9091/',
16
16
  path: '/api/v2',
@@ -449,6 +449,17 @@ export class QBittorrent {
449
449
  await this.request('/torrents/renameFile', 'POST', undefined, form, undefined, false);
450
450
  return true;
451
451
  }
452
+ /**
453
+ * {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#rename-folder}
454
+ */
455
+ async renameFolder(hash, oldPath, newPath) {
456
+ const form = new FormData();
457
+ form.append('hash', hash);
458
+ form.append('oldPath', oldPath);
459
+ form.append('newPath', newPath);
460
+ await this.request('/torrents/renameFolder', 'POST', undefined, form, undefined, false);
461
+ return true;
462
+ }
452
463
  /**
453
464
  * @param urls URLs separated with newlines
454
465
  * @param options
@@ -612,39 +623,59 @@ export class QBittorrent {
612
623
  }
613
624
  _normalizeTorrentData(torrent) {
614
625
  let state = NormalizedTorrentState.unknown;
626
+ let stateMessage = '';
627
+ let { eta } = torrent;
628
+ /**
629
+ * Good references https://github.com/qbittorrent/qBittorrent/blob/master/src/webui/www/private/scripts/dynamicTable.js#L933
630
+ * https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs#L242
631
+ */
615
632
  switch (torrent.state) {
616
- case TorrentState.ForcedDL:
617
- case TorrentState.MetaDL:
633
+ case TorrentState.Error:
634
+ state = NormalizedTorrentState.warning;
635
+ stateMessage = 'qBittorrent is reporting an error';
636
+ break;
637
+ case TorrentState.PausedDL:
638
+ state = NormalizedTorrentState.paused;
639
+ break;
640
+ case TorrentState.QueuedDL: // queuing is enabled and torrent is queued for download
641
+ case TorrentState.CheckingDL: // same as checkingUP, but torrent has NOT finished downloading
642
+ case TorrentState.CheckingUP: // torrent has finished downloading and is being checked. Set when `recheck torrent on completion` is enabled. In the event the check fails we shouldn't treat it as completed.
643
+ state = NormalizedTorrentState.queued;
644
+ break;
645
+ case TorrentState.MetaDL: // Metadl could be an error if DHT is not enabled
646
+ case TorrentState.ForcedDL: // torrent is being downloaded, and was forced started
647
+ case TorrentState.ForcedMetaDL: // torrent metadata is being forcibly downloaded
648
+ case TorrentState.Downloading: // torrent is being downloaded and data is being transferred
618
649
  state = NormalizedTorrentState.downloading;
619
650
  break;
620
651
  case TorrentState.Allocating:
621
652
  // state = 'stalledDL';
622
653
  state = NormalizedTorrentState.queued;
623
654
  break;
624
- case TorrentState.ForcedUP:
625
- state = NormalizedTorrentState.seeding;
626
- break;
627
- case TorrentState.PausedDL:
628
- state = NormalizedTorrentState.paused;
655
+ case TorrentState.StalledDL:
656
+ state = NormalizedTorrentState.warning;
657
+ stateMessage = 'The download is stalled with no connection';
629
658
  break;
630
- case TorrentState.PausedUP:
659
+ case TorrentState.PausedUP: // torrent is paused and has finished downloading:
660
+ case TorrentState.Uploading: // torrent is being seeded and data is being transferred
661
+ case TorrentState.StalledUP: // torrent is being seeded, but no connection were made
662
+ case TorrentState.QueuedUP: // queuing is enabled and torrent is queued for upload
663
+ case TorrentState.ForcedUP: // torrent has finished downloading and is being forcibly seeded
631
664
  // state = 'completed';
632
- state = NormalizedTorrentState.paused;
633
- break;
634
- case TorrentState.QueuedDL:
635
- case TorrentState.QueuedUP:
636
- state = NormalizedTorrentState.queued;
665
+ state = NormalizedTorrentState.seeding;
666
+ eta = 0; // qBittorrent sends eta=8640000 for completed torrents
637
667
  break;
638
- case TorrentState.CheckingDL:
639
- case TorrentState.CheckingUP:
668
+ case TorrentState.Moving: // torrent is being moved from a folder
640
669
  case TorrentState.QueuedForChecking:
641
670
  case TorrentState.CheckingResumeData:
642
- case TorrentState.Moving:
643
671
  state = NormalizedTorrentState.checking;
644
672
  break;
645
673
  case TorrentState.Unknown:
674
+ state = NormalizedTorrentState.error;
675
+ break;
646
676
  case TorrentState.MissingFiles:
647
677
  state = NormalizedTorrentState.error;
678
+ stateMessage = 'The download is missing files';
648
679
  break;
649
680
  default:
650
681
  break;
@@ -653,8 +684,9 @@ export class QBittorrent {
653
684
  const result = {
654
685
  id: torrent.hash,
655
686
  name: torrent.name,
656
- stateMessage: '',
687
+ stateMessage,
657
688
  state,
689
+ eta,
658
690
  dateAdded: new Date(torrent.added_on * 1000).toISOString(),
659
691
  isCompleted,
660
692
  progress: torrent.progress,
@@ -663,7 +695,6 @@ export class QBittorrent {
663
695
  savePath: torrent.save_path,
664
696
  uploadSpeed: torrent.upspeed,
665
697
  downloadSpeed: torrent.dlspeed,
666
- eta: torrent.eta,
667
698
  queuePosition: torrent.priority,
668
699
  connectedPeers: torrent.num_leechs,
669
700
  connectedSeeds: torrent.num_seeds,
@@ -220,6 +220,10 @@ export declare enum TorrentState {
220
220
  * Torrent is forced to downloading to ignore queue limit
221
221
  */
222
222
  ForcedDL = "forcedDL",
223
+ /**
224
+ * Forced Downloading Metadata
225
+ */
226
+ ForcedMetaDL = "ForcedMetaDL",
223
227
  /**
224
228
  * Torrent is forced to uploading and ignore queue limit
225
229
  */
@@ -424,21 +428,21 @@ export declare enum TorrentTrackerStatus {
424
428
  */
425
429
  Disabled = 0,
426
430
  /**
427
- * Tracker has been contacted and is working
431
+ * Tracker has not been contacted yet
428
432
  */
429
- Working = 1,
433
+ Waiting = 1,
430
434
  /**
431
- * Tracker is currently being updated
435
+ * Tracker has been contacted and is working
432
436
  */
433
- Updating = 2,
437
+ Working = 2,
434
438
  /**
435
- * Tracker has been contacted, but it is not working (or doesn't send proper replies)
439
+ * Tracker is updating
436
440
  */
437
- Errored = 3,
441
+ Updating = 3,
438
442
  /**
439
- * Tracker has not been contacted yet
443
+ * Tracker has been contacted, but it is not working (or doesn't send proper replies)
440
444
  */
441
- Waiting = 4
445
+ Errored = 4
442
446
  }
443
447
  export interface WebSeed {
444
448
  /**
package/dist/src/types.js CHANGED
@@ -48,6 +48,10 @@ export var TorrentState;
48
48
  * Torrent is forced to downloading to ignore queue limit
49
49
  */
50
50
  TorrentState["ForcedDL"] = "forcedDL";
51
+ /**
52
+ * Forced Downloading Metadata
53
+ */
54
+ TorrentState["ForcedMetaDL"] = "ForcedMetaDL";
51
55
  /**
52
56
  * Torrent is forced to uploading and ignore queue limit
53
57
  */
@@ -85,21 +89,21 @@ export var TorrentTrackerStatus;
85
89
  */
86
90
  TorrentTrackerStatus[TorrentTrackerStatus["Disabled"] = 0] = "Disabled";
87
91
  /**
88
- * Tracker has been contacted and is working
92
+ * Tracker has not been contacted yet
89
93
  */
90
- TorrentTrackerStatus[TorrentTrackerStatus["Working"] = 1] = "Working";
94
+ TorrentTrackerStatus[TorrentTrackerStatus["Waiting"] = 1] = "Waiting";
91
95
  /**
92
- * Tracker is currently being updated
96
+ * Tracker has been contacted and is working
93
97
  */
94
- TorrentTrackerStatus[TorrentTrackerStatus["Updating"] = 2] = "Updating";
98
+ TorrentTrackerStatus[TorrentTrackerStatus["Working"] = 2] = "Working";
95
99
  /**
96
- * Tracker has been contacted, but it is not working (or doesn't send proper replies)
100
+ * Tracker is updating
97
101
  */
98
- TorrentTrackerStatus[TorrentTrackerStatus["Errored"] = 3] = "Errored";
102
+ TorrentTrackerStatus[TorrentTrackerStatus["Updating"] = 3] = "Updating";
99
103
  /**
100
- * Tracker has not been contacted yet
104
+ * Tracker has been contacted, but it is not working (or doesn't send proper replies)
101
105
  */
102
- TorrentTrackerStatus[TorrentTrackerStatus["Waiting"] = 4] = "Waiting";
106
+ TorrentTrackerStatus[TorrentTrackerStatus["Errored"] = 4] = "Errored";
103
107
  })(TorrentTrackerStatus = TorrentTrackerStatus || (TorrentTrackerStatus = {}));
104
108
  export var TorrentFilePriority;
105
109
  (function (TorrentFilePriority) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl/qbittorrent",
3
- "version": "4.0.1",
3
+ "version": "5.0.0",
4
4
  "description": "TypeScript api wrapper for qbittorrent using got",
5
5
  "author": "Scott Cooper <scttcper@gmail.com>",
6
6
  "license": "MIT",
@@ -32,23 +32,24 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@ctrl/magnet-link": "^3.1.1",
35
- "@ctrl/shared-torrent": "^4.1.1",
36
- "@ctrl/torrent-file": "^2.0.1",
37
- "@ctrl/url-join": "^2.0.0",
38
- "formdata-node": "^4.3.2",
39
- "got": "^12.1.0",
40
- "tough-cookie": "^4.0.0"
35
+ "@ctrl/shared-torrent": "^4.2.0",
36
+ "@ctrl/torrent-file": "^2.0.2",
37
+ "@ctrl/url-join": "^2.0.2",
38
+ "formdata-node": "^5.0.0",
39
+ "got": "^12.3.1",
40
+ "tough-cookie": "^4.1.2"
41
41
  },
42
42
  "devDependencies": {
43
- "@ctrl/eslint-config": "3.4.4",
43
+ "@ctrl/eslint-config": "3.4.10",
44
44
  "@sindresorhus/tsconfig": "3.0.1",
45
- "@types/node": "17.0.40",
45
+ "@types/node": "18.7.14",
46
46
  "@types/tough-cookie": "4.0.2",
47
- "c8": "7.11.3",
48
- "p-wait-for": "4.1.0",
49
- "typedoc": "0.22.17",
50
- "typescript": "4.7.3",
51
- "vitest": "0.14.0"
47
+ "@vitest/coverage-c8": "0.22.1",
48
+ "c8": "7.12.0",
49
+ "p-wait-for": "5.0.0",
50
+ "typedoc": "0.23.11",
51
+ "typescript": "4.8.2",
52
+ "vitest": "0.22.1"
52
53
  },
53
54
  "release": {
54
55
  "branches": [