@ctrl/qbittorrent 6.1.0 → 7.0.1
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 +2 -2
- package/dist/src/normalizeTorrentData.d.ts +3 -0
- package/dist/src/normalizeTorrentData.js +91 -0
- package/dist/src/qbittorrent.d.ts +1 -8
- package/dist/src/qbittorrent.js +135 -269
- package/dist/src/types.js +4 -4
- package/package.json +19 -19
package/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
# qBittorrent [](https://www.npmjs.com/package/@ctrl/qbittorrent) [](https://www.npmjs.com/package/@ctrl/qbittorrent) [](https://codecov.io/gh/scttcper/qbittorrent)
|
2
2
|
|
3
|
-
> TypeScript api wrapper for [qBittorrent](https://www.qbittorrent.org/) using [
|
3
|
+
> TypeScript api wrapper for [qBittorrent](https://www.qbittorrent.org/) using [ofetch](https://github.com/unjs/ofetch)
|
4
4
|
|
5
5
|
### Install
|
6
6
|
|
@@ -0,0 +1,91 @@
|
|
1
|
+
import { TorrentState as NormalizedTorrentState } from '@ctrl/shared-torrent';
|
2
|
+
import { TorrentState } from './types.js';
|
3
|
+
export function normalizeTorrentData(torrent) {
|
4
|
+
let state = NormalizedTorrentState.unknown;
|
5
|
+
let stateMessage = '';
|
6
|
+
let { eta } = torrent;
|
7
|
+
/**
|
8
|
+
* Good references https://github.com/qbittorrent/qBittorrent/blob/master/src/webui/www/private/scripts/dynamicTable.js#L933
|
9
|
+
* https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs#L242
|
10
|
+
*/
|
11
|
+
switch (torrent.state) {
|
12
|
+
case TorrentState.Error:
|
13
|
+
state = NormalizedTorrentState.warning;
|
14
|
+
stateMessage = 'qBittorrent is reporting an error';
|
15
|
+
break;
|
16
|
+
case TorrentState.PausedDL:
|
17
|
+
state = NormalizedTorrentState.paused;
|
18
|
+
break;
|
19
|
+
case TorrentState.QueuedDL: // queuing is enabled and torrent is queued for download
|
20
|
+
case TorrentState.CheckingDL: // same as checkingUP, but torrent has NOT finished downloading
|
21
|
+
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.
|
22
|
+
state = NormalizedTorrentState.queued;
|
23
|
+
break;
|
24
|
+
case TorrentState.MetaDL: // Metadl could be an error if DHT is not enabled
|
25
|
+
case TorrentState.ForcedDL: // torrent is being downloaded, and was forced started
|
26
|
+
case TorrentState.ForcedMetaDL: // torrent metadata is being forcibly downloaded
|
27
|
+
case TorrentState.Downloading: // torrent is being downloaded and data is being transferred
|
28
|
+
state = NormalizedTorrentState.downloading;
|
29
|
+
break;
|
30
|
+
case TorrentState.Allocating:
|
31
|
+
// state = 'stalledDL';
|
32
|
+
state = NormalizedTorrentState.queued;
|
33
|
+
break;
|
34
|
+
case TorrentState.StalledDL:
|
35
|
+
state = NormalizedTorrentState.warning;
|
36
|
+
stateMessage = 'The download is stalled with no connection';
|
37
|
+
break;
|
38
|
+
case TorrentState.PausedUP: // torrent is paused and has finished downloading:
|
39
|
+
case TorrentState.Uploading: // torrent is being seeded and data is being transferred
|
40
|
+
case TorrentState.StalledUP: // torrent is being seeded, but no connection were made
|
41
|
+
case TorrentState.QueuedUP: // queuing is enabled and torrent is queued for upload
|
42
|
+
case TorrentState.ForcedUP: // torrent has finished downloading and is being forcibly seeded
|
43
|
+
// state = 'completed';
|
44
|
+
state = NormalizedTorrentState.seeding;
|
45
|
+
eta = 0; // qBittorrent sends eta=8640000 for completed torrents
|
46
|
+
break;
|
47
|
+
case TorrentState.Moving: // torrent is being moved from a folder
|
48
|
+
case TorrentState.QueuedForChecking:
|
49
|
+
case TorrentState.CheckingResumeData:
|
50
|
+
state = NormalizedTorrentState.checking;
|
51
|
+
break;
|
52
|
+
case TorrentState.Unknown:
|
53
|
+
state = NormalizedTorrentState.error;
|
54
|
+
break;
|
55
|
+
case TorrentState.MissingFiles:
|
56
|
+
state = NormalizedTorrentState.error;
|
57
|
+
stateMessage = 'The download is missing files';
|
58
|
+
break;
|
59
|
+
default:
|
60
|
+
break;
|
61
|
+
}
|
62
|
+
const isCompleted = torrent.progress === 1;
|
63
|
+
const result = {
|
64
|
+
id: torrent.hash,
|
65
|
+
name: torrent.name,
|
66
|
+
stateMessage,
|
67
|
+
state,
|
68
|
+
eta,
|
69
|
+
dateAdded: new Date(torrent.added_on * 1000).toISOString(),
|
70
|
+
isCompleted,
|
71
|
+
progress: torrent.progress,
|
72
|
+
label: torrent.category,
|
73
|
+
tags: torrent.tags.split(', '),
|
74
|
+
dateCompleted: new Date(torrent.completion_on * 1000).toISOString(),
|
75
|
+
savePath: torrent.save_path,
|
76
|
+
uploadSpeed: torrent.upspeed,
|
77
|
+
downloadSpeed: torrent.dlspeed,
|
78
|
+
queuePosition: torrent.priority,
|
79
|
+
connectedPeers: torrent.num_leechs,
|
80
|
+
connectedSeeds: torrent.num_seeds,
|
81
|
+
totalPeers: torrent.num_incomplete,
|
82
|
+
totalSeeds: torrent.num_complete,
|
83
|
+
totalSelected: torrent.size,
|
84
|
+
totalSize: torrent.total_size,
|
85
|
+
totalUploaded: torrent.uploaded,
|
86
|
+
totalDownloaded: torrent.downloaded,
|
87
|
+
ratio: torrent.ratio,
|
88
|
+
raw: torrent,
|
89
|
+
};
|
90
|
+
return result;
|
91
|
+
}
|
@@ -1,5 +1,4 @@
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
2
|
-
import type { Options as GotOptions, Response } from 'got';
|
3
2
|
import type { AddTorrentOptions as NormalizedAddTorrentOptions, AllClientData, NormalizedTorrent, TorrentClient, TorrentSettings } from '@ctrl/shared-torrent';
|
4
3
|
import type { AddMagnetOptions, AddTorrentOptions, BuildInfo, Preferences, Torrent, TorrentCategories, TorrentFile, TorrentFilePriority, TorrentFilters, TorrentPeersResponse, TorrentPieceState, TorrentProperties, TorrentTrackers, WebSeed } from './types.js';
|
5
4
|
export declare class QBittorrent implements TorrentClient {
|
@@ -208,11 +207,5 @@ export declare class QBittorrent implements TorrentClient {
|
|
208
207
|
*/
|
209
208
|
login(): Promise<boolean>;
|
210
209
|
logout(): boolean;
|
211
|
-
request<T
|
212
|
-
/**
|
213
|
-
* Normalizes hashes
|
214
|
-
* @returns hashes as string seperated by `|`
|
215
|
-
*/
|
216
|
-
private _normalizeHashes;
|
217
|
-
private _normalizeTorrentData;
|
210
|
+
request<T>(path: string, method: 'GET' | 'POST', params?: Record<string, string | number>, body?: URLSearchParams | FormData, headers?: any, json?: boolean): Promise<T>;
|
218
211
|
}
|
package/dist/src/qbittorrent.js
CHANGED
@@ -1,16 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
import { existsSync } from 'fs';
|
4
|
-
import { URLSearchParams } from 'url';
|
5
|
-
import { File, FormData } from 'formdata-node';
|
6
|
-
import { fileFromPath } from 'formdata-node/file-from-path';
|
7
|
-
import got from 'got';
|
1
|
+
import { FormData } from 'node-fetch-native';
|
2
|
+
import { ofetch } from 'ofetch';
|
8
3
|
import { Cookie } from 'tough-cookie';
|
4
|
+
import { joinURL } from 'ufo';
|
9
5
|
import { magnetDecode } from '@ctrl/magnet-link';
|
10
|
-
import { TorrentState as NormalizedTorrentState } from '@ctrl/shared-torrent';
|
11
6
|
import { hash } from '@ctrl/torrent-file';
|
12
|
-
import {
|
13
|
-
import { TorrentState } from './types.js';
|
7
|
+
import { normalizeTorrentData } from './normalizeTorrentData.js';
|
14
8
|
const defaults = {
|
15
9
|
baseUrl: 'http://localhost:9091/',
|
16
10
|
path: '/api/v2',
|
@@ -19,31 +13,16 @@ const defaults = {
|
|
19
13
|
timeout: 5000,
|
20
14
|
};
|
21
15
|
export class QBittorrent {
|
16
|
+
config;
|
17
|
+
/**
|
18
|
+
* auth cookie
|
19
|
+
*/
|
20
|
+
_sid;
|
21
|
+
/**
|
22
|
+
* cookie expiration
|
23
|
+
*/
|
24
|
+
_exp;
|
22
25
|
constructor(options = {}) {
|
23
|
-
Object.defineProperty(this, "config", {
|
24
|
-
enumerable: true,
|
25
|
-
configurable: true,
|
26
|
-
writable: true,
|
27
|
-
value: void 0
|
28
|
-
});
|
29
|
-
/**
|
30
|
-
* auth cookie
|
31
|
-
*/
|
32
|
-
Object.defineProperty(this, "_sid", {
|
33
|
-
enumerable: true,
|
34
|
-
configurable: true,
|
35
|
-
writable: true,
|
36
|
-
value: void 0
|
37
|
-
});
|
38
|
-
/**
|
39
|
-
* cookie expiration
|
40
|
-
*/
|
41
|
-
Object.defineProperty(this, "_exp", {
|
42
|
-
enumerable: true,
|
43
|
-
configurable: true,
|
44
|
-
writable: true,
|
45
|
-
value: void 0
|
46
|
-
});
|
47
26
|
this.config = { ...defaults, ...options };
|
48
27
|
}
|
49
28
|
/**
|
@@ -57,19 +36,19 @@ export class QBittorrent {
|
|
57
36
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-application-version}
|
58
37
|
*/
|
59
38
|
async getAppVersion() {
|
60
|
-
const res = await this.request('/app/version', 'GET', undefined, undefined, undefined,
|
61
|
-
return res
|
39
|
+
const res = await this.request('/app/version', 'GET', undefined, undefined, undefined, false);
|
40
|
+
return res;
|
62
41
|
}
|
63
42
|
async getApiVersion() {
|
64
|
-
const res = await this.request('/app/webapiVersion', 'GET', undefined, undefined, undefined,
|
65
|
-
return res
|
43
|
+
const res = await this.request('/app/webapiVersion', 'GET', undefined, undefined, undefined, false);
|
44
|
+
return res;
|
66
45
|
}
|
67
46
|
/**
|
68
47
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-build-info}
|
69
48
|
*/
|
70
49
|
async getBuildInfo() {
|
71
50
|
const res = await this.request('/app/buildInfo', 'GET');
|
72
|
-
return res
|
51
|
+
return res;
|
73
52
|
}
|
74
53
|
async getTorrent(hash) {
|
75
54
|
const torrentsResponse = await this.listTorrents({ hashes: hash });
|
@@ -77,22 +56,22 @@ export class QBittorrent {
|
|
77
56
|
if (!torrentData) {
|
78
57
|
throw new Error('Torrent not found');
|
79
58
|
}
|
80
|
-
return
|
59
|
+
return normalizeTorrentData(torrentData);
|
81
60
|
}
|
82
61
|
/**
|
83
62
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-application-preferences}
|
84
63
|
*/
|
85
64
|
async getPreferences() {
|
86
65
|
const res = await this.request('/app/preferences', 'GET');
|
87
|
-
return res
|
66
|
+
return res;
|
88
67
|
}
|
89
68
|
/**
|
90
69
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#set-application-preferences}
|
91
70
|
*/
|
92
71
|
async setPreferences(preferences) {
|
93
|
-
await this.request('/app/setPreferences', 'POST', undefined,
|
72
|
+
await this.request('/app/setPreferences', 'POST', undefined, objToUrlSearchParams({
|
94
73
|
json: JSON.stringify(preferences),
|
95
|
-
});
|
74
|
+
}));
|
96
75
|
return true;
|
97
76
|
}
|
98
77
|
/**
|
@@ -105,7 +84,7 @@ export class QBittorrent {
|
|
105
84
|
async listTorrents({ hashes, filter, category, sort, offset, reverse, tag, } = {}) {
|
106
85
|
const params = {};
|
107
86
|
if (hashes) {
|
108
|
-
params.hashes =
|
87
|
+
params.hashes = normalizeHashes(hashes);
|
109
88
|
}
|
110
89
|
if (filter) {
|
111
90
|
params.filter = filter;
|
@@ -126,7 +105,7 @@ export class QBittorrent {
|
|
126
105
|
params.reverse = JSON.stringify(reverse);
|
127
106
|
}
|
128
107
|
const res = await this.request('/torrents/info', 'GET', params);
|
129
|
-
return res
|
108
|
+
return res;
|
130
109
|
}
|
131
110
|
async getAllData() {
|
132
111
|
const listTorrents = await this.listTorrents();
|
@@ -137,7 +116,7 @@ export class QBittorrent {
|
|
137
116
|
};
|
138
117
|
const labels = {};
|
139
118
|
for (const torrent of listTorrents) {
|
140
|
-
const torrentData =
|
119
|
+
const torrentData = normalizeTorrentData(torrent);
|
141
120
|
results.torrents.push(torrentData);
|
142
121
|
// setup label
|
143
122
|
if (torrentData.label) {
|
@@ -160,40 +139,40 @@ export class QBittorrent {
|
|
160
139
|
*/
|
161
140
|
async torrentProperties(hash) {
|
162
141
|
const res = await this.request('/torrents/properties', 'GET', { hash });
|
163
|
-
return res
|
142
|
+
return res;
|
164
143
|
}
|
165
144
|
/**
|
166
145
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-torrent-trackers}
|
167
146
|
*/
|
168
147
|
async torrentTrackers(hash) {
|
169
148
|
const res = await this.request('/torrents/trackers', 'GET', { hash });
|
170
|
-
return res
|
149
|
+
return res;
|
171
150
|
}
|
172
151
|
/**
|
173
152
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-torrent-web-seeds}
|
174
153
|
*/
|
175
154
|
async torrentWebSeeds(hash) {
|
176
155
|
const res = await this.request('/torrents/webseeds', 'GET', { hash });
|
177
|
-
return res
|
156
|
+
return res;
|
178
157
|
}
|
179
158
|
async torrentFiles(hash) {
|
180
159
|
const res = await this.request('/torrents/files', 'GET', { hash });
|
181
|
-
return res
|
160
|
+
return res;
|
182
161
|
}
|
183
162
|
async setFilePriority(hash, fileIds, priority) {
|
184
163
|
const res = await this.request('/torrents/filePrio', 'GET', {
|
185
164
|
hash,
|
186
|
-
id:
|
165
|
+
id: normalizeHashes(fileIds),
|
187
166
|
priority,
|
188
167
|
});
|
189
|
-
return res
|
168
|
+
return res;
|
190
169
|
}
|
191
170
|
/**
|
192
171
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-torrent-pieces-states}
|
193
172
|
*/
|
194
173
|
async torrentPieceStates(hash) {
|
195
174
|
const res = await this.request('/torrents/pieceStates', 'GET', { hash });
|
196
|
-
return res
|
175
|
+
return res;
|
197
176
|
}
|
198
177
|
/**
|
199
178
|
* Torrents piece hashes
|
@@ -202,43 +181,41 @@ export class QBittorrent {
|
|
202
181
|
*/
|
203
182
|
async torrentPieceHashes(hash) {
|
204
183
|
const res = await this.request('/torrents/pieceHashes', 'GET', { hash });
|
205
|
-
return res
|
184
|
+
return res;
|
206
185
|
}
|
207
186
|
/**
|
208
187
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#set-torrent-location}
|
209
188
|
*/
|
210
189
|
async setTorrentLocation(hashes, location) {
|
211
|
-
|
190
|
+
const data = {
|
212
191
|
location,
|
213
|
-
hashes:
|
214
|
-
}
|
192
|
+
hashes: normalizeHashes(hashes),
|
193
|
+
};
|
194
|
+
await this.request('/torrents/setLocation', 'POST', undefined, objToUrlSearchParams(data));
|
215
195
|
return true;
|
216
196
|
}
|
217
197
|
/**
|
218
198
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#set-torrent-name}
|
219
199
|
*/
|
220
200
|
async setTorrentName(hash, name) {
|
221
|
-
|
222
|
-
|
223
|
-
name,
|
224
|
-
});
|
201
|
+
const data = { hash, name };
|
202
|
+
await this.request('/torrents/rename', 'POST', undefined, objToUrlSearchParams(data));
|
225
203
|
return true;
|
226
204
|
}
|
227
205
|
/**
|
228
206
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-all-tags}
|
229
207
|
*/
|
230
208
|
async getTags() {
|
231
|
-
const res = await this.request('/torrents/tags', '
|
232
|
-
return res
|
209
|
+
const res = await this.request('/torrents/tags', 'GET');
|
210
|
+
return res;
|
233
211
|
}
|
234
212
|
/**
|
235
213
|
* @param tags comma separated list
|
236
214
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#create-tags}
|
237
215
|
*/
|
238
216
|
async createTags(tags) {
|
239
|
-
|
240
|
-
|
241
|
-
}, undefined, false);
|
217
|
+
const data = { tags };
|
218
|
+
await this.request('/torrents/createTags', 'POST', undefined, objToUrlSearchParams(data), undefined, false);
|
242
219
|
return true;
|
243
220
|
}
|
244
221
|
/**
|
@@ -246,53 +223,47 @@ export class QBittorrent {
|
|
246
223
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#delete-tags}
|
247
224
|
*/
|
248
225
|
async deleteTags(tags) {
|
249
|
-
|
226
|
+
const data = { tags };
|
227
|
+
await this.request('/torrents/deleteTags', 'POST', undefined, objToUrlSearchParams(data), undefined, false);
|
250
228
|
return true;
|
251
229
|
}
|
252
230
|
/**
|
253
231
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-all-categories}
|
254
232
|
*/
|
255
233
|
async getCategories() {
|
256
|
-
const res = await this.request('/torrents/categories', '
|
257
|
-
return res
|
234
|
+
const res = await this.request('/torrents/categories', 'GET');
|
235
|
+
return res;
|
258
236
|
}
|
259
237
|
/**
|
260
238
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#add-new-category}
|
261
239
|
*/
|
262
240
|
async createCategory(category, savePath = '') {
|
263
|
-
|
264
|
-
|
265
|
-
savePath,
|
266
|
-
}, undefined, false);
|
241
|
+
const data = { category, savePath };
|
242
|
+
await this.request('/torrents/createCategory', 'POST', undefined, objToUrlSearchParams(data), undefined, false);
|
267
243
|
return true;
|
268
244
|
}
|
269
245
|
/**
|
270
246
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#edit-category}
|
271
247
|
*/
|
272
248
|
async editCategory(category, savePath = '') {
|
273
|
-
|
274
|
-
|
275
|
-
savePath,
|
276
|
-
}, undefined, false);
|
249
|
+
const data = { category, savePath };
|
250
|
+
await this.request('/torrents/editCategory', 'POST', undefined, objToUrlSearchParams(data), undefined, false);
|
277
251
|
return true;
|
278
252
|
}
|
279
253
|
/**
|
280
254
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#remove-categories}
|
281
255
|
*/
|
282
256
|
async removeCategory(categories) {
|
283
|
-
|
284
|
-
|
285
|
-
}, undefined, false);
|
257
|
+
const data = { categories };
|
258
|
+
await this.request('/torrents/removeCategories', 'POST', undefined, objToUrlSearchParams(data), undefined, false);
|
286
259
|
return true;
|
287
260
|
}
|
288
261
|
/**
|
289
262
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#add-torrent-tags}
|
290
263
|
*/
|
291
264
|
async addTorrentTags(hashes, tags) {
|
292
|
-
|
293
|
-
|
294
|
-
tags,
|
295
|
-
}, undefined, false);
|
265
|
+
const data = { hashes: normalizeHashes(hashes), tags };
|
266
|
+
await this.request('/torrents/addTags', 'POST', undefined, objToUrlSearchParams(data), undefined, false);
|
296
267
|
return true;
|
297
268
|
}
|
298
269
|
/**
|
@@ -300,11 +271,11 @@ export class QBittorrent {
|
|
300
271
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#remove-torrent-tags}
|
301
272
|
*/
|
302
273
|
async removeTorrentTags(hashes, tags) {
|
303
|
-
const
|
274
|
+
const data = { hashes: normalizeHashes(hashes) };
|
304
275
|
if (tags) {
|
305
|
-
|
276
|
+
data.tags = tags;
|
306
277
|
}
|
307
|
-
await this.request('/torrents/removeTags', 'POST', undefined,
|
278
|
+
await this.request('/torrents/removeTags', 'POST', undefined, objToUrlSearchParams(data), undefined, false);
|
308
279
|
return true;
|
309
280
|
}
|
310
281
|
/**
|
@@ -317,61 +288,54 @@ export class QBittorrent {
|
|
317
288
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#set-torrent-category}
|
318
289
|
*/
|
319
290
|
async setTorrentCategory(hashes, category = '') {
|
320
|
-
|
321
|
-
hashes:
|
291
|
+
const data = {
|
292
|
+
hashes: normalizeHashes(hashes),
|
322
293
|
category,
|
323
|
-
}
|
294
|
+
};
|
295
|
+
await this.request('/torrents/setCategory', 'POST', undefined, objToUrlSearchParams(data));
|
324
296
|
return true;
|
325
297
|
}
|
326
298
|
/**
|
327
299
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#pause-torrents}
|
328
300
|
*/
|
329
301
|
async pauseTorrent(hashes) {
|
330
|
-
const
|
331
|
-
|
332
|
-
};
|
333
|
-
await this.request('/torrents/pause', 'POST', undefined, undefined, params);
|
302
|
+
const data = { hashes: normalizeHashes(hashes) };
|
303
|
+
await this.request('/torrents/pause', 'POST', undefined, objToUrlSearchParams(data));
|
334
304
|
return true;
|
335
305
|
}
|
336
306
|
/**
|
337
307
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#resume-torrents}
|
338
308
|
*/
|
339
309
|
async resumeTorrent(hashes) {
|
340
|
-
const
|
341
|
-
|
342
|
-
};
|
343
|
-
await this.request('/torrents/resume', 'POST', undefined, undefined, params);
|
310
|
+
const data = { hashes: normalizeHashes(hashes) };
|
311
|
+
await this.request('/torrents/resume', 'POST', undefined, objToUrlSearchParams(data));
|
344
312
|
return true;
|
345
313
|
}
|
346
314
|
/**
|
347
315
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#delete-torrents}
|
348
316
|
*/
|
349
317
|
async removeTorrent(hashes, deleteFiles = true) {
|
350
|
-
const
|
351
|
-
hashes:
|
318
|
+
const data = {
|
319
|
+
hashes: normalizeHashes(hashes),
|
352
320
|
deleteFiles,
|
353
321
|
};
|
354
|
-
await this.request('/torrents/delete', 'POST', undefined,
|
322
|
+
await this.request('/torrents/delete', 'POST', undefined, objToUrlSearchParams(data));
|
355
323
|
return true;
|
356
324
|
}
|
357
325
|
/**
|
358
326
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#recheck-torrents}
|
359
327
|
*/
|
360
328
|
async recheckTorrent(hashes) {
|
361
|
-
const
|
362
|
-
|
363
|
-
};
|
364
|
-
await this.request('/torrents/recheck', 'POST', undefined, undefined, params);
|
329
|
+
const data = { hashes: normalizeHashes(hashes) };
|
330
|
+
await this.request('/torrents/recheck', 'POST', undefined, objToUrlSearchParams(data));
|
365
331
|
return true;
|
366
332
|
}
|
367
333
|
/**
|
368
334
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#reannounce-torrents}
|
369
335
|
*/
|
370
336
|
async reannounceTorrent(hashes) {
|
371
|
-
const
|
372
|
-
|
373
|
-
};
|
374
|
-
await this.request('/torrents/reannounce', 'POST', undefined, undefined, params);
|
337
|
+
const data = { hashes: normalizeHashes(hashes) };
|
338
|
+
await this.request('/torrents/reannounce', 'POST', undefined, objToUrlSearchParams(data));
|
375
339
|
return true;
|
376
340
|
}
|
377
341
|
async addTorrent(torrent, options = {}) {
|
@@ -382,13 +346,7 @@ export class QBittorrent {
|
|
382
346
|
}
|
383
347
|
const type = { type: 'application/x-bittorrent' };
|
384
348
|
if (typeof torrent === 'string') {
|
385
|
-
|
386
|
-
const file = await fileFromPath(torrent, options.filename ?? 'torrent', type);
|
387
|
-
form.set('file', file);
|
388
|
-
}
|
389
|
-
else {
|
390
|
-
form.set('file', new File([Buffer.from(torrent, 'base64')], 'file.torrent', type));
|
391
|
-
}
|
349
|
+
form.set('file', new File([Buffer.from(torrent, 'base64')], 'file.torrent', type));
|
392
350
|
}
|
393
351
|
else {
|
394
352
|
const file = new File([torrent], options.filename ?? 'torrent', type);
|
@@ -403,11 +361,11 @@ export class QBittorrent {
|
|
403
361
|
options.useAutoTMM = 'false';
|
404
362
|
}
|
405
363
|
for (const [key, value] of Object.entries(options)) {
|
406
|
-
form.append(key, value);
|
364
|
+
form.append(key, `${value}`);
|
407
365
|
}
|
408
366
|
}
|
409
|
-
const res = await this.request('/torrents/add', 'POST', undefined, form, undefined,
|
410
|
-
if (res
|
367
|
+
const res = await this.request('/torrents/add', 'POST', undefined, form, undefined, false);
|
368
|
+
if (res === 'Fails.') {
|
411
369
|
throw new Error('Failed to add torrent');
|
412
370
|
}
|
413
371
|
return true;
|
@@ -445,9 +403,9 @@ export class QBittorrent {
|
|
445
403
|
async renameFile(hash, id, name) {
|
446
404
|
const form = new FormData();
|
447
405
|
form.append('hash', hash);
|
448
|
-
form.append('id', id);
|
406
|
+
form.append('id', id.toString());
|
449
407
|
form.append('name', name);
|
450
|
-
await this.request('/torrents/renameFile', 'POST', undefined,
|
408
|
+
await this.request('/torrents/renameFile', 'POST', undefined, undefined, form, false);
|
451
409
|
return true;
|
452
410
|
}
|
453
411
|
/**
|
@@ -458,7 +416,7 @@ export class QBittorrent {
|
|
458
416
|
form.append('hash', hash);
|
459
417
|
form.append('oldPath', oldPath);
|
460
418
|
form.append('newPath', newPath);
|
461
|
-
await this.request('/torrents/renameFolder', 'POST', undefined,
|
419
|
+
await this.request('/torrents/renameFolder', 'POST', undefined, undefined, form, false);
|
462
420
|
return true;
|
463
421
|
}
|
464
422
|
/**
|
@@ -477,11 +435,11 @@ export class QBittorrent {
|
|
477
435
|
options.useAutoTMM = 'false';
|
478
436
|
}
|
479
437
|
for (const [key, value] of Object.entries(options)) {
|
480
|
-
form.append(key, value);
|
438
|
+
form.append(key, `${value}`);
|
481
439
|
}
|
482
440
|
}
|
483
|
-
const res = await this.request('/torrents/add', 'POST', undefined, form, undefined,
|
484
|
-
if (res
|
441
|
+
const res = await this.request('/torrents/add', 'POST', undefined, form, undefined, false);
|
442
|
+
if (res === 'Fails.') {
|
485
443
|
throw new Error('Failed to add torrent');
|
486
444
|
}
|
487
445
|
return true;
|
@@ -490,64 +448,56 @@ export class QBittorrent {
|
|
490
448
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#add-trackers-to-torrent}
|
491
449
|
*/
|
492
450
|
async addTrackers(hash, urls) {
|
493
|
-
const
|
494
|
-
await this.request('/torrents/addTrackers', 'POST', undefined,
|
451
|
+
const data = { hash, urls };
|
452
|
+
await this.request('/torrents/addTrackers', 'POST', undefined, objToUrlSearchParams(data));
|
495
453
|
return true;
|
496
454
|
}
|
497
455
|
/**
|
498
456
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#edit-trackers}
|
499
457
|
*/
|
500
458
|
async editTrackers(hash, origUrl, newUrl) {
|
501
|
-
const
|
502
|
-
await this.request('/torrents/editTrackers', 'POST', undefined,
|
459
|
+
const data = { hash, origUrl, newUrl };
|
460
|
+
await this.request('/torrents/editTrackers', 'POST', undefined, objToUrlSearchParams(data));
|
503
461
|
return true;
|
504
462
|
}
|
505
463
|
/**
|
506
464
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#remove-trackers}
|
507
465
|
*/
|
508
466
|
async removeTrackers(hash, urls) {
|
509
|
-
const
|
510
|
-
await this.request('/torrents/removeTrackers', 'POST', undefined,
|
467
|
+
const data = { hash, urls };
|
468
|
+
await this.request('/torrents/removeTrackers', 'POST', undefined, objToUrlSearchParams(data));
|
511
469
|
return true;
|
512
470
|
}
|
513
471
|
/**
|
514
472
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#increase-torrent-priority}
|
515
473
|
*/
|
516
474
|
async queueUp(hashes) {
|
517
|
-
const
|
518
|
-
|
519
|
-
};
|
520
|
-
await this.request('/torrents/increasePrio', 'POST', undefined, undefined, params);
|
475
|
+
const data = { hashes: normalizeHashes(hashes) };
|
476
|
+
await this.request('/torrents/increasePrio', 'POST', undefined, objToUrlSearchParams(data));
|
521
477
|
return true;
|
522
478
|
}
|
523
479
|
/**
|
524
480
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#decrease-torrent-priority}
|
525
481
|
*/
|
526
482
|
async queueDown(hashes) {
|
527
|
-
const
|
528
|
-
|
529
|
-
};
|
530
|
-
await this.request('/torrents/decreasePrio', 'POST', undefined, undefined, params);
|
483
|
+
const data = { hashes: normalizeHashes(hashes) };
|
484
|
+
await this.request('/torrents/decreasePrio', 'POST', undefined, objToUrlSearchParams(data));
|
531
485
|
return true;
|
532
486
|
}
|
533
487
|
/**
|
534
488
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#maximal-torrent-priority}
|
535
489
|
*/
|
536
490
|
async topPriority(hashes) {
|
537
|
-
const
|
538
|
-
|
539
|
-
};
|
540
|
-
await this.request('/torrents/topPrio', 'POST', undefined, undefined, params);
|
491
|
+
const data = { hashes: normalizeHashes(hashes) };
|
492
|
+
await this.request('/torrents/topPrio', 'POST', undefined, objToUrlSearchParams(data));
|
541
493
|
return true;
|
542
494
|
}
|
543
495
|
/**
|
544
496
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#minimal-torrent-priority}
|
545
497
|
*/
|
546
498
|
async bottomPriority(hashes) {
|
547
|
-
const
|
548
|
-
|
549
|
-
};
|
550
|
-
await this.request('/torrents/bottomPrio', 'POST', undefined, undefined, params);
|
499
|
+
const data = { hashes: normalizeHashes(hashes) };
|
500
|
+
await this.request('/torrents/bottomPrio', 'POST', undefined, objToUrlSearchParams(data));
|
551
501
|
return true;
|
552
502
|
}
|
553
503
|
/**
|
@@ -561,31 +511,31 @@ export class QBittorrent {
|
|
561
511
|
params.rid = rid;
|
562
512
|
}
|
563
513
|
const res = await this.request('/sync/torrentPeers', 'GET', params);
|
564
|
-
return res
|
514
|
+
return res;
|
565
515
|
}
|
566
516
|
/**
|
567
517
|
* {@link https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#login}
|
568
518
|
*/
|
569
519
|
async login() {
|
570
|
-
const url =
|
571
|
-
const res = await
|
572
|
-
url,
|
520
|
+
const url = joinURL(this.config.baseUrl, this.config.path, '/auth/login');
|
521
|
+
const res = await ofetch.raw(url, {
|
573
522
|
method: 'POST',
|
574
|
-
|
523
|
+
headers: {
|
524
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
525
|
+
},
|
526
|
+
body: new URLSearchParams({
|
575
527
|
username: this.config.username ?? '',
|
576
528
|
password: this.config.password ?? '',
|
577
|
-
},
|
578
|
-
|
579
|
-
retry:
|
580
|
-
timeout:
|
581
|
-
//
|
582
|
-
...(this.config.agent ? { agent: this.config.agent } : {}),
|
529
|
+
}),
|
530
|
+
redirect: 'manual',
|
531
|
+
retry: false,
|
532
|
+
timeout: this.config.timeout,
|
533
|
+
// ...(this.config.agent ? { agent: this.config.agent } : {}),
|
583
534
|
});
|
584
|
-
|
585
|
-
if (!res.headers['set-cookie'] || !res.headers['set-cookie'].length) {
|
535
|
+
if (!res.headers.get('set-cookie')?.length) {
|
586
536
|
throw new Error('Cookie not found. Auth Failed.');
|
587
537
|
}
|
588
|
-
const cookie = Cookie.parse(res.headers
|
538
|
+
const cookie = Cookie.parse(res.headers.get('set-cookie'));
|
589
539
|
if (!cookie || cookie.key !== 'SID') {
|
590
540
|
throw new Error('Invalid cookie');
|
591
541
|
}
|
@@ -599,130 +549,46 @@ export class QBittorrent {
|
|
599
549
|
return true;
|
600
550
|
}
|
601
551
|
// eslint-disable-next-line max-params
|
602
|
-
async request(path, method, params
|
552
|
+
async request(path, method, params, body, headers = {}, json = true) {
|
603
553
|
if (!this._sid || !this._exp || this._exp.getTime() < new Date().getTime()) {
|
604
554
|
const authed = await this.login();
|
605
555
|
if (!authed) {
|
606
556
|
throw new Error('Auth Failed');
|
607
557
|
}
|
608
558
|
}
|
609
|
-
const url =
|
610
|
-
const res = await
|
611
|
-
isStream: false,
|
612
|
-
resolveBodyOnly: false,
|
559
|
+
const url = joinURL(this.config.baseUrl, this.config.path, path);
|
560
|
+
const res = await ofetch(url, {
|
613
561
|
method,
|
614
562
|
headers: {
|
615
563
|
Cookie: `SID=${this._sid ?? ''}`,
|
616
564
|
...headers,
|
617
565
|
},
|
618
|
-
retry: { limit: 0 },
|
619
566
|
body,
|
620
|
-
|
621
|
-
searchParams: new URLSearchParams(params),
|
567
|
+
params,
|
622
568
|
// allow proxy agent
|
623
|
-
|
569
|
+
retry: 0,
|
570
|
+
timeout: this.config.timeout,
|
624
571
|
responseType: json ? 'json' : 'text',
|
625
|
-
|
572
|
+
// @ts-expect-error for some reason agent is not in the type
|
573
|
+
agent: this.config.agent,
|
626
574
|
});
|
627
575
|
return res;
|
628
576
|
}
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
* Good references https://github.com/qbittorrent/qBittorrent/blob/master/src/webui/www/private/scripts/dynamicTable.js#L933
|
645
|
-
* https://github.com/Radarr/Radarr/blob/develop/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs#L242
|
646
|
-
*/
|
647
|
-
switch (torrent.state) {
|
648
|
-
case TorrentState.Error:
|
649
|
-
state = NormalizedTorrentState.warning;
|
650
|
-
stateMessage = 'qBittorrent is reporting an error';
|
651
|
-
break;
|
652
|
-
case TorrentState.PausedDL:
|
653
|
-
state = NormalizedTorrentState.paused;
|
654
|
-
break;
|
655
|
-
case TorrentState.QueuedDL: // queuing is enabled and torrent is queued for download
|
656
|
-
case TorrentState.CheckingDL: // same as checkingUP, but torrent has NOT finished downloading
|
657
|
-
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.
|
658
|
-
state = NormalizedTorrentState.queued;
|
659
|
-
break;
|
660
|
-
case TorrentState.MetaDL: // Metadl could be an error if DHT is not enabled
|
661
|
-
case TorrentState.ForcedDL: // torrent is being downloaded, and was forced started
|
662
|
-
case TorrentState.ForcedMetaDL: // torrent metadata is being forcibly downloaded
|
663
|
-
case TorrentState.Downloading: // torrent is being downloaded and data is being transferred
|
664
|
-
state = NormalizedTorrentState.downloading;
|
665
|
-
break;
|
666
|
-
case TorrentState.Allocating:
|
667
|
-
// state = 'stalledDL';
|
668
|
-
state = NormalizedTorrentState.queued;
|
669
|
-
break;
|
670
|
-
case TorrentState.StalledDL:
|
671
|
-
state = NormalizedTorrentState.warning;
|
672
|
-
stateMessage = 'The download is stalled with no connection';
|
673
|
-
break;
|
674
|
-
case TorrentState.PausedUP: // torrent is paused and has finished downloading:
|
675
|
-
case TorrentState.Uploading: // torrent is being seeded and data is being transferred
|
676
|
-
case TorrentState.StalledUP: // torrent is being seeded, but no connection were made
|
677
|
-
case TorrentState.QueuedUP: // queuing is enabled and torrent is queued for upload
|
678
|
-
case TorrentState.ForcedUP: // torrent has finished downloading and is being forcibly seeded
|
679
|
-
// state = 'completed';
|
680
|
-
state = NormalizedTorrentState.seeding;
|
681
|
-
eta = 0; // qBittorrent sends eta=8640000 for completed torrents
|
682
|
-
break;
|
683
|
-
case TorrentState.Moving: // torrent is being moved from a folder
|
684
|
-
case TorrentState.QueuedForChecking:
|
685
|
-
case TorrentState.CheckingResumeData:
|
686
|
-
state = NormalizedTorrentState.checking;
|
687
|
-
break;
|
688
|
-
case TorrentState.Unknown:
|
689
|
-
state = NormalizedTorrentState.error;
|
690
|
-
break;
|
691
|
-
case TorrentState.MissingFiles:
|
692
|
-
state = NormalizedTorrentState.error;
|
693
|
-
stateMessage = 'The download is missing files';
|
694
|
-
break;
|
695
|
-
default:
|
696
|
-
break;
|
697
|
-
}
|
698
|
-
const isCompleted = torrent.progress === 1;
|
699
|
-
const result = {
|
700
|
-
id: torrent.hash,
|
701
|
-
name: torrent.name,
|
702
|
-
stateMessage,
|
703
|
-
state,
|
704
|
-
eta,
|
705
|
-
dateAdded: new Date(torrent.added_on * 1000).toISOString(),
|
706
|
-
isCompleted,
|
707
|
-
progress: torrent.progress,
|
708
|
-
label: torrent.category,
|
709
|
-
tags: torrent.tags.split(', '),
|
710
|
-
dateCompleted: new Date(torrent.completion_on * 1000).toISOString(),
|
711
|
-
savePath: torrent.save_path,
|
712
|
-
uploadSpeed: torrent.upspeed,
|
713
|
-
downloadSpeed: torrent.dlspeed,
|
714
|
-
queuePosition: torrent.priority,
|
715
|
-
connectedPeers: torrent.num_leechs,
|
716
|
-
connectedSeeds: torrent.num_seeds,
|
717
|
-
totalPeers: torrent.num_incomplete,
|
718
|
-
totalSeeds: torrent.num_complete,
|
719
|
-
totalSelected: torrent.size,
|
720
|
-
totalSize: torrent.total_size,
|
721
|
-
totalUploaded: torrent.uploaded,
|
722
|
-
totalDownloaded: torrent.downloaded,
|
723
|
-
ratio: torrent.ratio,
|
724
|
-
raw: torrent,
|
725
|
-
};
|
726
|
-
return result;
|
577
|
+
}
|
578
|
+
/**
|
579
|
+
* Normalizes hashes
|
580
|
+
* @returns hashes as string seperated by `|`
|
581
|
+
*/
|
582
|
+
function normalizeHashes(hashes) {
|
583
|
+
if (Array.isArray(hashes)) {
|
584
|
+
return hashes.join('|');
|
585
|
+
}
|
586
|
+
return hashes;
|
587
|
+
}
|
588
|
+
function objToUrlSearchParams(obj) {
|
589
|
+
const params = new URLSearchParams();
|
590
|
+
for (const [key, value] of Object.entries(obj)) {
|
591
|
+
params.append(key, value.toString());
|
727
592
|
}
|
593
|
+
return params;
|
728
594
|
}
|
package/dist/src/types.js
CHANGED
@@ -81,7 +81,7 @@ export var TorrentState;
|
|
81
81
|
* Torrent data files is missing
|
82
82
|
*/
|
83
83
|
TorrentState["MissingFiles"] = "missingFiles";
|
84
|
-
})(TorrentState
|
84
|
+
})(TorrentState || (TorrentState = {}));
|
85
85
|
export var TorrentTrackerStatus;
|
86
86
|
(function (TorrentTrackerStatus) {
|
87
87
|
/**
|
@@ -104,7 +104,7 @@ export var TorrentTrackerStatus;
|
|
104
104
|
* Tracker has been contacted, but it is not working (or doesn't send proper replies)
|
105
105
|
*/
|
106
106
|
TorrentTrackerStatus[TorrentTrackerStatus["Errored"] = 4] = "Errored";
|
107
|
-
})(TorrentTrackerStatus
|
107
|
+
})(TorrentTrackerStatus || (TorrentTrackerStatus = {}));
|
108
108
|
export var TorrentFilePriority;
|
109
109
|
(function (TorrentFilePriority) {
|
110
110
|
/**
|
@@ -123,7 +123,7 @@ export var TorrentFilePriority;
|
|
123
123
|
* Maximal priority
|
124
124
|
*/
|
125
125
|
TorrentFilePriority[TorrentFilePriority["MaxPriority"] = 7] = "MaxPriority";
|
126
|
-
})(TorrentFilePriority
|
126
|
+
})(TorrentFilePriority || (TorrentFilePriority = {}));
|
127
127
|
export var TorrentPieceState;
|
128
128
|
(function (TorrentPieceState) {
|
129
129
|
/**
|
@@ -138,4 +138,4 @@ export var TorrentPieceState;
|
|
138
138
|
* Already downloaded
|
139
139
|
*/
|
140
140
|
TorrentPieceState[TorrentPieceState["Downloaded"] = 2] = "Downloaded";
|
141
|
-
})(TorrentPieceState
|
141
|
+
})(TorrentPieceState || (TorrentPieceState = {}));
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ctrl/qbittorrent",
|
3
|
-
"version": "
|
3
|
+
"version": "7.0.1",
|
4
4
|
"description": "TypeScript api wrapper for qbittorrent using got",
|
5
5
|
"author": "Scott Cooper <scttcper@gmail.com>",
|
6
6
|
"license": "MIT",
|
@@ -14,7 +14,8 @@
|
|
14
14
|
],
|
15
15
|
"sideEffects": false,
|
16
16
|
"publishConfig": {
|
17
|
-
"access": "public"
|
17
|
+
"access": "public",
|
18
|
+
"provenance": true
|
18
19
|
},
|
19
20
|
"keywords": [
|
20
21
|
"typescript",
|
@@ -31,25 +32,24 @@
|
|
31
32
|
"test:ci": "vitest run --coverage --reporter=default --reporter=junit --outputFile=./junit.xml"
|
32
33
|
},
|
33
34
|
"dependencies": {
|
34
|
-
"@ctrl/magnet-link": "^3.1.
|
35
|
-
"@ctrl/shared-torrent": "^
|
36
|
-
"@ctrl/torrent-file": "^
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"
|
40
|
-
"
|
35
|
+
"@ctrl/magnet-link": "^3.1.2",
|
36
|
+
"@ctrl/shared-torrent": "^5.0.0",
|
37
|
+
"@ctrl/torrent-file": "^3.0.0",
|
38
|
+
"node-fetch-native": "^1.4.1",
|
39
|
+
"ofetch": "^1.3.3",
|
40
|
+
"tough-cookie": "^4.1.3",
|
41
|
+
"ufo": "^1.3.1"
|
41
42
|
},
|
42
43
|
"devDependencies": {
|
43
|
-
"@ctrl/eslint-config": "
|
44
|
-
"@sindresorhus/tsconfig": "
|
45
|
-
"@types/node": "
|
46
|
-
"@types/tough-cookie": "4.0.
|
47
|
-
"@vitest/coverage-
|
48
|
-
"c8": "7.13.0",
|
44
|
+
"@ctrl/eslint-config": "4.0.9",
|
45
|
+
"@sindresorhus/tsconfig": "5.0.0",
|
46
|
+
"@types/node": "20.8.10",
|
47
|
+
"@types/tough-cookie": "4.0.4",
|
48
|
+
"@vitest/coverage-v8": "0.34.6",
|
49
49
|
"p-wait-for": "5.0.2",
|
50
|
-
"typedoc": "0.
|
51
|
-
"typescript": "5.
|
52
|
-
"vitest": "0.
|
50
|
+
"typedoc": "0.25.3",
|
51
|
+
"typescript": "5.2.2",
|
52
|
+
"vitest": "0.34.6"
|
53
53
|
},
|
54
54
|
"release": {
|
55
55
|
"branches": [
|
@@ -57,6 +57,6 @@
|
|
57
57
|
]
|
58
58
|
},
|
59
59
|
"engines": {
|
60
|
-
"node": ">=
|
60
|
+
"node": ">=18"
|
61
61
|
}
|
62
62
|
}
|