@ctrl/transmission 2.2.1 → 2.4.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 +1 -1
- package/dist/index.d.ts +2 -54
- package/dist/index.js +11 -341
- package/dist/transmission.d.ts +58 -0
- package/dist/transmission.js +350 -0
- package/dist/types.d.ts +1 -1
- package/package.json +21 -20
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
@@ -1,54 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
import { AddTorrentOptions as NormalizedAddTorrentOptions, AllClientData, NormalizedTorrent, TorrentClient, TorrentSettings } from '@ctrl/shared-torrent';
|
4
|
-
import { AddTorrentOptions, AddTorrentResponse, DefaultResponse, FreeSpaceResponse, GetTorrentRepsonse, RenamePathOptions, SessionArguments, SessionResponse, SetTorrentOptions, TorrentIds } from './types';
|
5
|
-
export declare class Transmission implements TorrentClient {
|
6
|
-
config: TorrentSettings;
|
7
|
-
sessionId?: string;
|
8
|
-
constructor(options?: Partial<TorrentSettings>);
|
9
|
-
getSession(): Promise<SessionResponse>;
|
10
|
-
setSession(args: Partial<SessionArguments>): Promise<SessionResponse>;
|
11
|
-
queueTop(ids: TorrentIds): Promise<DefaultResponse>;
|
12
|
-
queueBottom(ids: TorrentIds): Promise<DefaultResponse>;
|
13
|
-
queueUp(ids: TorrentIds): Promise<DefaultResponse>;
|
14
|
-
queueDown(ids: TorrentIds): Promise<DefaultResponse>;
|
15
|
-
freeSpace(path?: string): Promise<FreeSpaceResponse>;
|
16
|
-
pauseTorrent(ids: TorrentIds): Promise<DefaultResponse>;
|
17
|
-
resumeTorrent(ids: TorrentIds): Promise<DefaultResponse>;
|
18
|
-
verifyTorrent(ids: TorrentIds): Promise<DefaultResponse>;
|
19
|
-
/**
|
20
|
-
* ask tracker for more peers
|
21
|
-
*/
|
22
|
-
reannounceTorrent(ids: TorrentIds): Promise<DefaultResponse>;
|
23
|
-
moveTorrent(ids: TorrentIds, location: string): Promise<DefaultResponse>;
|
24
|
-
/**
|
25
|
-
* Torrent Mutators
|
26
|
-
*/
|
27
|
-
setTorrent(ids: TorrentIds, options?: Partial<SetTorrentOptions>): Promise<DefaultResponse>;
|
28
|
-
/**
|
29
|
-
* Renaming a Torrent's Path
|
30
|
-
*/
|
31
|
-
renamePath(ids: TorrentIds, options?: Partial<RenamePathOptions>): Promise<DefaultResponse>;
|
32
|
-
/**
|
33
|
-
* Removing a Torrent
|
34
|
-
*/
|
35
|
-
removeTorrent(ids: TorrentIds, removeData?: boolean): Promise<AddTorrentResponse>;
|
36
|
-
/**
|
37
|
-
* note: This is the same "torrent-add" action with different options,
|
38
|
-
* less confusing to add it as its own method
|
39
|
-
* @param url magnet link
|
40
|
-
* @param options
|
41
|
-
*/
|
42
|
-
addMagnet(url: string, options?: Partial<AddTorrentOptions>): Promise<AddTorrentResponse>;
|
43
|
-
/**
|
44
|
-
* Adding a torrent
|
45
|
-
* @param torrent a string of file path or contents of the file as base64 string
|
46
|
-
*/
|
47
|
-
addTorrent(torrent: string | Buffer, options?: Partial<AddTorrentOptions>): Promise<AddTorrentResponse>;
|
48
|
-
normalizedAddTorrent(torrent: string | Buffer, options?: Partial<NormalizedAddTorrentOptions>): Promise<NormalizedTorrent>;
|
49
|
-
getTorrent(id: TorrentIds): Promise<NormalizedTorrent>;
|
50
|
-
getAllData(): Promise<AllClientData>;
|
51
|
-
listTorrents(ids?: TorrentIds, additionalFields?: string[]): Promise<GetTorrentRepsonse>;
|
52
|
-
request<T>(method: string, args?: any): Promise<Response<T>>;
|
53
|
-
private _normalizeTorrentData;
|
54
|
-
}
|
1
|
+
export * from './transmission';
|
2
|
+
export * from './types';
|
package/dist/index.js
CHANGED
@@ -1,344 +1,14 @@
|
|
1
1
|
"use strict";
|
2
|
-
var
|
3
|
-
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
5
|
+
}) : (function(o, m, k, k2) {
|
6
|
+
if (k2 === undefined) k2 = k;
|
7
|
+
o[k2] = m[k];
|
8
|
+
}));
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
4
11
|
};
|
5
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports
|
7
|
-
|
8
|
-
const got_1 = __importDefault(require("got"));
|
9
|
-
const url_join_1 = require("@ctrl/url-join");
|
10
|
-
const shared_torrent_1 = require("@ctrl/shared-torrent");
|
11
|
-
const defaults = {
|
12
|
-
baseUrl: 'http://localhost:9091/',
|
13
|
-
path: '/transmission/rpc',
|
14
|
-
username: '',
|
15
|
-
password: '',
|
16
|
-
timeout: 5000,
|
17
|
-
};
|
18
|
-
class Transmission {
|
19
|
-
constructor(options = {}) {
|
20
|
-
this.config = { ...defaults, ...options };
|
21
|
-
}
|
22
|
-
async getSession() {
|
23
|
-
const res = await this.request('session-get');
|
24
|
-
return res.body;
|
25
|
-
}
|
26
|
-
async setSession(args) {
|
27
|
-
const res = await this.request('session-set', args);
|
28
|
-
return res.body;
|
29
|
-
}
|
30
|
-
async queueTop(ids) {
|
31
|
-
const res = await this.request('queue-move-top', { ids });
|
32
|
-
return res.body;
|
33
|
-
}
|
34
|
-
async queueBottom(ids) {
|
35
|
-
const res = await this.request('queue-move-bottom', { ids });
|
36
|
-
return res.body;
|
37
|
-
}
|
38
|
-
async queueUp(ids) {
|
39
|
-
const res = await this.request('queue-move-up', { ids });
|
40
|
-
return res.body;
|
41
|
-
}
|
42
|
-
async queueDown(ids) {
|
43
|
-
const res = await this.request('queue-move-down', { ids });
|
44
|
-
return res.body;
|
45
|
-
}
|
46
|
-
async freeSpace(path = '/downloads/complete') {
|
47
|
-
const res = await this.request('free-space', { path });
|
48
|
-
return res.body;
|
49
|
-
}
|
50
|
-
async pauseTorrent(ids) {
|
51
|
-
const res = await this.request('torrent-stop', { ids });
|
52
|
-
return res.body;
|
53
|
-
}
|
54
|
-
async resumeTorrent(ids) {
|
55
|
-
const res = await this.request('torrent-start', { ids });
|
56
|
-
return res.body;
|
57
|
-
}
|
58
|
-
async verifyTorrent(ids) {
|
59
|
-
const res = await this.request('torrent-verify', { ids });
|
60
|
-
return res.body;
|
61
|
-
}
|
62
|
-
/**
|
63
|
-
* ask tracker for more peers
|
64
|
-
*/
|
65
|
-
async reannounceTorrent(ids) {
|
66
|
-
const res = await this.request('torrent-reannounce', { ids });
|
67
|
-
return res.body;
|
68
|
-
}
|
69
|
-
async moveTorrent(ids, location) {
|
70
|
-
const res = await this.request('torrent-set-location', {
|
71
|
-
ids,
|
72
|
-
move: true,
|
73
|
-
location,
|
74
|
-
});
|
75
|
-
return res.body;
|
76
|
-
}
|
77
|
-
/**
|
78
|
-
* Torrent Mutators
|
79
|
-
*/
|
80
|
-
async setTorrent(ids, options = {}) {
|
81
|
-
options.ids = ids;
|
82
|
-
const res = await this.request('torrent-set', options);
|
83
|
-
return res.body;
|
84
|
-
}
|
85
|
-
/**
|
86
|
-
* Renaming a Torrent's Path
|
87
|
-
*/
|
88
|
-
async renamePath(ids, options = {}) {
|
89
|
-
options.ids = ids;
|
90
|
-
const res = await this.request('torrent-rename-path', options);
|
91
|
-
return res.body;
|
92
|
-
}
|
93
|
-
/**
|
94
|
-
* Removing a Torrent
|
95
|
-
*/
|
96
|
-
async removeTorrent(ids, removeData = true) {
|
97
|
-
const res = await this.request('torrent-remove', {
|
98
|
-
ids,
|
99
|
-
'delete-local-data': removeData,
|
100
|
-
});
|
101
|
-
return res.body;
|
102
|
-
}
|
103
|
-
/**
|
104
|
-
* note: This is the same "torrent-add" action with different options,
|
105
|
-
* less confusing to add it as its own method
|
106
|
-
* @param url magnet link
|
107
|
-
* @param options
|
108
|
-
*/
|
109
|
-
async addMagnet(url, options = {}) {
|
110
|
-
const args = {
|
111
|
-
'download-dir': '/downloads',
|
112
|
-
paused: false,
|
113
|
-
...options,
|
114
|
-
};
|
115
|
-
args.filename = url;
|
116
|
-
const res = await this.request('torrent-add', args);
|
117
|
-
return res.body;
|
118
|
-
}
|
119
|
-
/**
|
120
|
-
* Adding a torrent
|
121
|
-
* @param torrent a string of file path or contents of the file as base64 string
|
122
|
-
*/
|
123
|
-
async addTorrent(torrent, options = {}) {
|
124
|
-
const args = {
|
125
|
-
'download-dir': '/downloads',
|
126
|
-
paused: false,
|
127
|
-
...options,
|
128
|
-
};
|
129
|
-
if (typeof torrent === 'string') {
|
130
|
-
args.metainfo = fs_1.existsSync(torrent)
|
131
|
-
? Buffer.from(fs_1.readFileSync(torrent)).toString('base64')
|
132
|
-
: Buffer.from(torrent, 'base64').toString('base64');
|
133
|
-
}
|
134
|
-
else {
|
135
|
-
args.metainfo = torrent.toString('base64');
|
136
|
-
}
|
137
|
-
const res = await this.request('torrent-add', args);
|
138
|
-
return res.body;
|
139
|
-
}
|
140
|
-
async normalizedAddTorrent(torrent, options = {}) {
|
141
|
-
const torrentOptions = {};
|
142
|
-
if (options.startPaused) {
|
143
|
-
torrentOptions.paused = true;
|
144
|
-
}
|
145
|
-
if (!Buffer.isBuffer(torrent)) {
|
146
|
-
torrent = Buffer.from(torrent);
|
147
|
-
}
|
148
|
-
const res = await this.addTorrent(torrent, torrentOptions);
|
149
|
-
const torrentId = res.arguments['torrent-added'].id;
|
150
|
-
if (options.label) {
|
151
|
-
const res = await this.setTorrent(torrentId, { labels: [options.label] });
|
152
|
-
console.log(res);
|
153
|
-
}
|
154
|
-
return this.getTorrent(torrentId);
|
155
|
-
}
|
156
|
-
async getTorrent(id) {
|
157
|
-
const result = await this.listTorrents(id);
|
158
|
-
if (!result.arguments.torrents || result.arguments.torrents.length === 0) {
|
159
|
-
throw new Error('Torrent not found');
|
160
|
-
}
|
161
|
-
return this._normalizeTorrentData(result.arguments.torrents[0]);
|
162
|
-
}
|
163
|
-
async getAllData() {
|
164
|
-
const listTorrents = await this.listTorrents();
|
165
|
-
const torrents = listTorrents.arguments.torrents.map((n) => this._normalizeTorrentData(n));
|
166
|
-
const labels = [];
|
167
|
-
for (const torrent of torrents) {
|
168
|
-
if (!torrent.label) {
|
169
|
-
continue;
|
170
|
-
}
|
171
|
-
const existing = labels.find((n) => n.id === torrent.label);
|
172
|
-
if (existing) {
|
173
|
-
existing.count += 1;
|
174
|
-
continue;
|
175
|
-
}
|
176
|
-
labels.push({ id: torrent.label, name: torrent.label, count: 1 });
|
177
|
-
}
|
178
|
-
const results = {
|
179
|
-
torrents,
|
180
|
-
labels,
|
181
|
-
};
|
182
|
-
return results;
|
183
|
-
}
|
184
|
-
async listTorrents(ids, additionalFields = []) {
|
185
|
-
const fields = [
|
186
|
-
'id',
|
187
|
-
'addedDate',
|
188
|
-
'creator',
|
189
|
-
'doneDate',
|
190
|
-
'comment',
|
191
|
-
'name',
|
192
|
-
'totalSize',
|
193
|
-
'error',
|
194
|
-
'errorString',
|
195
|
-
'eta',
|
196
|
-
'etaIdle',
|
197
|
-
'isFinished',
|
198
|
-
'isStalled',
|
199
|
-
'isPrivate',
|
200
|
-
'files',
|
201
|
-
'fileStats',
|
202
|
-
'hashString',
|
203
|
-
'leftUntilDone',
|
204
|
-
'metadataPercentComplete',
|
205
|
-
'peers',
|
206
|
-
'peersFrom',
|
207
|
-
'peersConnected',
|
208
|
-
'peersGettingFromUs',
|
209
|
-
'peersSendingToUs',
|
210
|
-
'percentDone',
|
211
|
-
'queuePosition',
|
212
|
-
'rateDownload',
|
213
|
-
'rateUpload',
|
214
|
-
'secondsDownloading',
|
215
|
-
'secondsSeeding',
|
216
|
-
'recheckProgress',
|
217
|
-
'seedRatioMode',
|
218
|
-
'seedRatioLimit',
|
219
|
-
'seedIdleLimit',
|
220
|
-
'sizeWhenDone',
|
221
|
-
'status',
|
222
|
-
'trackers',
|
223
|
-
'downloadDir',
|
224
|
-
'downloadLimit',
|
225
|
-
'downloadLimited',
|
226
|
-
'uploadedEver',
|
227
|
-
'downloadedEver',
|
228
|
-
'corruptEver',
|
229
|
-
'uploadRatio',
|
230
|
-
'webseedsSendingToUs',
|
231
|
-
'haveUnchecked',
|
232
|
-
'haveValid',
|
233
|
-
'honorsSessionLimits',
|
234
|
-
'manualAnnounceTime',
|
235
|
-
'activityDate',
|
236
|
-
'desiredAvailable',
|
237
|
-
'labels',
|
238
|
-
'magnetLink',
|
239
|
-
'maxConnectedPeers',
|
240
|
-
'peer-limit',
|
241
|
-
'priorities',
|
242
|
-
'wanted',
|
243
|
-
'webseeds',
|
244
|
-
...additionalFields,
|
245
|
-
];
|
246
|
-
const args = { fields };
|
247
|
-
if (ids) {
|
248
|
-
args.ids = ids;
|
249
|
-
}
|
250
|
-
const res = await this.request('torrent-get', args);
|
251
|
-
return res.body;
|
252
|
-
}
|
253
|
-
// async getTorrent(id: TorrentIds): Promise<NormalizedTorrent> {
|
254
|
-
// const torrent: any = {};
|
255
|
-
// return torrent;
|
256
|
-
// }
|
257
|
-
async request(method, args = {}) {
|
258
|
-
var _a, _b, _c;
|
259
|
-
if (!this.sessionId && method !== 'session-get') {
|
260
|
-
await this.getSession();
|
261
|
-
}
|
262
|
-
const headers = {
|
263
|
-
'X-Transmission-Session-Id': this.sessionId,
|
264
|
-
};
|
265
|
-
if (this.config.username || this.config.password) {
|
266
|
-
const str = `${(_a = this.config.username) !== null && _a !== void 0 ? _a : ''}:${(_b = this.config.password) !== null && _b !== void 0 ? _b : ''}`;
|
267
|
-
headers.Authorization = 'Basic ' + str;
|
268
|
-
}
|
269
|
-
const url = url_join_1.urlJoin(this.config.baseUrl, this.config.path);
|
270
|
-
try {
|
271
|
-
const res = await got_1.default.post(url, {
|
272
|
-
json: {
|
273
|
-
method,
|
274
|
-
arguments: args,
|
275
|
-
},
|
276
|
-
headers,
|
277
|
-
retry: 0,
|
278
|
-
// allow proxy agent
|
279
|
-
agent: this.config.agent,
|
280
|
-
timeout: this.config.timeout,
|
281
|
-
responseType: 'json',
|
282
|
-
});
|
283
|
-
return res;
|
284
|
-
}
|
285
|
-
catch (error) {
|
286
|
-
if (((_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.statusCode) === 409) {
|
287
|
-
this.sessionId = error.response.headers['x-transmission-session-id'];
|
288
|
-
// eslint-disable-next-line no-return-await
|
289
|
-
return await this.request(method, args);
|
290
|
-
}
|
291
|
-
throw error;
|
292
|
-
}
|
293
|
-
}
|
294
|
-
_normalizeTorrentData(torrent) {
|
295
|
-
var _a;
|
296
|
-
const dateAdded = new Date(torrent.addedDate * 1000).toISOString();
|
297
|
-
const dateCompleted = new Date(torrent.doneDate * 1000).toISOString();
|
298
|
-
// normalize state to enum
|
299
|
-
// https://github.com/transmission/transmission/blob/c11f2870fd18ff781ca06ce84b6d43541f3293dd/web/javascript/torrent.js#L18
|
300
|
-
let state = shared_torrent_1.TorrentState.unknown;
|
301
|
-
if (torrent.status === 6) {
|
302
|
-
state = shared_torrent_1.TorrentState.seeding;
|
303
|
-
}
|
304
|
-
else if (torrent.status === 4) {
|
305
|
-
state = shared_torrent_1.TorrentState.downloading;
|
306
|
-
}
|
307
|
-
else if (torrent.status === 0) {
|
308
|
-
state = shared_torrent_1.TorrentState.paused;
|
309
|
-
}
|
310
|
-
else if (torrent.status === 2) {
|
311
|
-
state = shared_torrent_1.TorrentState.checking;
|
312
|
-
}
|
313
|
-
else if (torrent.status === 3 || torrent.status === 5) {
|
314
|
-
state = shared_torrent_1.TorrentState.queued;
|
315
|
-
}
|
316
|
-
const result = {
|
317
|
-
id: torrent.id,
|
318
|
-
name: torrent.name,
|
319
|
-
state,
|
320
|
-
isCompleted: torrent.leftUntilDone < 1,
|
321
|
-
stateMessage: '',
|
322
|
-
progress: torrent.percentDone,
|
323
|
-
ratio: torrent.uploadRatio,
|
324
|
-
dateAdded,
|
325
|
-
dateCompleted,
|
326
|
-
label: ((_a = torrent.labels) === null || _a === void 0 ? void 0 : _a.length) ? torrent.labels[0] : undefined,
|
327
|
-
savePath: torrent.downloadDir,
|
328
|
-
uploadSpeed: torrent.rateUpload,
|
329
|
-
downloadSpeed: torrent.rateDownload,
|
330
|
-
eta: torrent.eta,
|
331
|
-
queuePosition: torrent.queuePosition,
|
332
|
-
connectedPeers: torrent.peersSendingToUs,
|
333
|
-
connectedSeeds: torrent.peersGettingFromUs,
|
334
|
-
totalPeers: torrent.peersConnected,
|
335
|
-
totalSeeds: torrent.peersConnected,
|
336
|
-
totalSelected: torrent.sizeWhenDone,
|
337
|
-
totalSize: torrent.totalSize,
|
338
|
-
totalUploaded: torrent.uploadedEver,
|
339
|
-
totalDownloaded: torrent.downloadedEver,
|
340
|
-
};
|
341
|
-
return result;
|
342
|
-
}
|
343
|
-
}
|
344
|
-
exports.Transmission = Transmission;
|
13
|
+
__exportStar(require("./transmission"), exports);
|
14
|
+
__exportStar(require("./types"), exports);
|
@@ -0,0 +1,58 @@
|
|
1
|
+
/// <reference types="node" />
|
2
|
+
import { Response } from 'got';
|
3
|
+
import { AddTorrentOptions as NormalizedAddTorrentOptions, AllClientData, NormalizedTorrent, TorrentClient, TorrentSettings } from '@ctrl/shared-torrent';
|
4
|
+
import { AddTorrentOptions, AddTorrentResponse, DefaultResponse, FreeSpaceResponse, GetTorrentRepsonse, RenamePathOptions, SessionArguments, SessionResponse, SetTorrentOptions, TorrentIds } from './types';
|
5
|
+
export declare class Transmission implements TorrentClient {
|
6
|
+
config: TorrentSettings;
|
7
|
+
sessionId?: string;
|
8
|
+
constructor(options?: Partial<TorrentSettings>);
|
9
|
+
getSession(): Promise<SessionResponse>;
|
10
|
+
setSession(args: Partial<SessionArguments>): Promise<SessionResponse>;
|
11
|
+
queueTop(ids: TorrentIds): Promise<DefaultResponse>;
|
12
|
+
queueBottom(ids: TorrentIds): Promise<DefaultResponse>;
|
13
|
+
queueUp(ids: TorrentIds): Promise<DefaultResponse>;
|
14
|
+
queueDown(ids: TorrentIds): Promise<DefaultResponse>;
|
15
|
+
freeSpace(path?: string): Promise<FreeSpaceResponse>;
|
16
|
+
pauseTorrent(ids: TorrentIds): Promise<DefaultResponse>;
|
17
|
+
resumeTorrent(ids: TorrentIds): Promise<DefaultResponse>;
|
18
|
+
verifyTorrent(ids: TorrentIds): Promise<DefaultResponse>;
|
19
|
+
/**
|
20
|
+
* ask tracker for more peers
|
21
|
+
*/
|
22
|
+
reannounceTorrent(ids: TorrentIds): Promise<DefaultResponse>;
|
23
|
+
moveTorrent(ids: TorrentIds, location: string): Promise<DefaultResponse>;
|
24
|
+
/**
|
25
|
+
* Torrent Mutators
|
26
|
+
*/
|
27
|
+
setTorrent(ids: TorrentIds, options?: Partial<SetTorrentOptions>): Promise<DefaultResponse>;
|
28
|
+
/**
|
29
|
+
* Renaming a Torrent's Path
|
30
|
+
*/
|
31
|
+
renamePath(ids: TorrentIds, options?: Partial<RenamePathOptions>): Promise<DefaultResponse>;
|
32
|
+
/**
|
33
|
+
* Removing a Torrent
|
34
|
+
*/
|
35
|
+
removeTorrent(ids: TorrentIds, removeData?: boolean): Promise<AddTorrentResponse>;
|
36
|
+
/**
|
37
|
+
* An alias for {@link Transmission.addMagnet}
|
38
|
+
*/
|
39
|
+
addUrl(...args: Parameters<Transmission['addMagnet']>): Promise<AddTorrentResponse>;
|
40
|
+
/**
|
41
|
+
* note: This is the same "torrent-add" action with different options,
|
42
|
+
* less confusing to add it as its own method
|
43
|
+
* @param url magnet link
|
44
|
+
* @param options
|
45
|
+
*/
|
46
|
+
addMagnet(url: string, options?: Partial<AddTorrentOptions>): Promise<AddTorrentResponse>;
|
47
|
+
/**
|
48
|
+
* Adding a torrent
|
49
|
+
* @param torrent a string of file path or contents of the file as base64 string
|
50
|
+
*/
|
51
|
+
addTorrent(torrent: string | Buffer, options?: Partial<AddTorrentOptions>): Promise<AddTorrentResponse>;
|
52
|
+
normalizedAddTorrent(torrent: string | Buffer, options?: Partial<NormalizedAddTorrentOptions>): Promise<NormalizedTorrent>;
|
53
|
+
getTorrent(id: TorrentIds): Promise<NormalizedTorrent>;
|
54
|
+
getAllData(): Promise<AllClientData>;
|
55
|
+
listTorrents(ids?: TorrentIds, additionalFields?: string[]): Promise<GetTorrentRepsonse>;
|
56
|
+
request<T>(method: string, args?: any): Promise<Response<T>>;
|
57
|
+
private _normalizeTorrentData;
|
58
|
+
}
|
@@ -0,0 +1,350 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.Transmission = void 0;
|
7
|
+
const fs_1 = require("fs");
|
8
|
+
const got_1 = __importDefault(require("got"));
|
9
|
+
const shared_torrent_1 = require("@ctrl/shared-torrent");
|
10
|
+
const url_join_1 = require("@ctrl/url-join");
|
11
|
+
const defaults = {
|
12
|
+
baseUrl: 'http://localhost:9091/',
|
13
|
+
path: '/transmission/rpc',
|
14
|
+
username: '',
|
15
|
+
password: '',
|
16
|
+
timeout: 5000,
|
17
|
+
};
|
18
|
+
class Transmission {
|
19
|
+
constructor(options = {}) {
|
20
|
+
this.config = { ...defaults, ...options };
|
21
|
+
}
|
22
|
+
async getSession() {
|
23
|
+
const res = await this.request('session-get');
|
24
|
+
return res.body;
|
25
|
+
}
|
26
|
+
async setSession(args) {
|
27
|
+
const res = await this.request('session-set', args);
|
28
|
+
return res.body;
|
29
|
+
}
|
30
|
+
async queueTop(ids) {
|
31
|
+
const res = await this.request('queue-move-top', { ids });
|
32
|
+
return res.body;
|
33
|
+
}
|
34
|
+
async queueBottom(ids) {
|
35
|
+
const res = await this.request('queue-move-bottom', { ids });
|
36
|
+
return res.body;
|
37
|
+
}
|
38
|
+
async queueUp(ids) {
|
39
|
+
const res = await this.request('queue-move-up', { ids });
|
40
|
+
return res.body;
|
41
|
+
}
|
42
|
+
async queueDown(ids) {
|
43
|
+
const res = await this.request('queue-move-down', { ids });
|
44
|
+
return res.body;
|
45
|
+
}
|
46
|
+
async freeSpace(path = '/downloads/complete') {
|
47
|
+
const res = await this.request('free-space', { path });
|
48
|
+
return res.body;
|
49
|
+
}
|
50
|
+
async pauseTorrent(ids) {
|
51
|
+
const res = await this.request('torrent-stop', { ids });
|
52
|
+
return res.body;
|
53
|
+
}
|
54
|
+
async resumeTorrent(ids) {
|
55
|
+
const res = await this.request('torrent-start', { ids });
|
56
|
+
return res.body;
|
57
|
+
}
|
58
|
+
async verifyTorrent(ids) {
|
59
|
+
const res = await this.request('torrent-verify', { ids });
|
60
|
+
return res.body;
|
61
|
+
}
|
62
|
+
/**
|
63
|
+
* ask tracker for more peers
|
64
|
+
*/
|
65
|
+
async reannounceTorrent(ids) {
|
66
|
+
const res = await this.request('torrent-reannounce', { ids });
|
67
|
+
return res.body;
|
68
|
+
}
|
69
|
+
async moveTorrent(ids, location) {
|
70
|
+
const res = await this.request('torrent-set-location', {
|
71
|
+
ids,
|
72
|
+
move: true,
|
73
|
+
location,
|
74
|
+
});
|
75
|
+
return res.body;
|
76
|
+
}
|
77
|
+
/**
|
78
|
+
* Torrent Mutators
|
79
|
+
*/
|
80
|
+
async setTorrent(ids, options = {}) {
|
81
|
+
options.ids = ids;
|
82
|
+
const res = await this.request('torrent-set', options);
|
83
|
+
return res.body;
|
84
|
+
}
|
85
|
+
/**
|
86
|
+
* Renaming a Torrent's Path
|
87
|
+
*/
|
88
|
+
async renamePath(ids, options = {}) {
|
89
|
+
options.ids = ids;
|
90
|
+
const res = await this.request('torrent-rename-path', options);
|
91
|
+
return res.body;
|
92
|
+
}
|
93
|
+
/**
|
94
|
+
* Removing a Torrent
|
95
|
+
*/
|
96
|
+
async removeTorrent(ids, removeData = true) {
|
97
|
+
const res = await this.request('torrent-remove', {
|
98
|
+
ids,
|
99
|
+
'delete-local-data': removeData,
|
100
|
+
});
|
101
|
+
return res.body;
|
102
|
+
}
|
103
|
+
/**
|
104
|
+
* An alias for {@link Transmission.addMagnet}
|
105
|
+
*/
|
106
|
+
async addUrl(...args) {
|
107
|
+
return this.addMagnet(...args);
|
108
|
+
}
|
109
|
+
/**
|
110
|
+
* note: This is the same "torrent-add" action with different options,
|
111
|
+
* less confusing to add it as its own method
|
112
|
+
* @param url magnet link
|
113
|
+
* @param options
|
114
|
+
*/
|
115
|
+
async addMagnet(url, options = {}) {
|
116
|
+
const args = {
|
117
|
+
'download-dir': '/downloads',
|
118
|
+
paused: false,
|
119
|
+
...options,
|
120
|
+
};
|
121
|
+
args.filename = url;
|
122
|
+
const res = await this.request('torrent-add', args);
|
123
|
+
return res.body;
|
124
|
+
}
|
125
|
+
/**
|
126
|
+
* Adding a torrent
|
127
|
+
* @param torrent a string of file path or contents of the file as base64 string
|
128
|
+
*/
|
129
|
+
async addTorrent(torrent, options = {}) {
|
130
|
+
const args = {
|
131
|
+
'download-dir': '/downloads',
|
132
|
+
paused: false,
|
133
|
+
...options,
|
134
|
+
};
|
135
|
+
if (typeof torrent === 'string') {
|
136
|
+
args.metainfo = (0, fs_1.existsSync)(torrent)
|
137
|
+
? Buffer.from((0, fs_1.readFileSync)(torrent)).toString('base64')
|
138
|
+
: Buffer.from(torrent, 'base64').toString('base64');
|
139
|
+
}
|
140
|
+
else {
|
141
|
+
args.metainfo = torrent.toString('base64');
|
142
|
+
}
|
143
|
+
const res = await this.request('torrent-add', args);
|
144
|
+
return res.body;
|
145
|
+
}
|
146
|
+
async normalizedAddTorrent(torrent, options = {}) {
|
147
|
+
const torrentOptions = {};
|
148
|
+
if (options.startPaused) {
|
149
|
+
torrentOptions.paused = true;
|
150
|
+
}
|
151
|
+
if (!Buffer.isBuffer(torrent)) {
|
152
|
+
torrent = Buffer.from(torrent);
|
153
|
+
}
|
154
|
+
const res = await this.addTorrent(torrent, torrentOptions);
|
155
|
+
const torrentId = res.arguments['torrent-added'].id;
|
156
|
+
if (options.label) {
|
157
|
+
const res = await this.setTorrent(torrentId, { labels: [options.label] });
|
158
|
+
console.log(res);
|
159
|
+
}
|
160
|
+
return this.getTorrent(torrentId);
|
161
|
+
}
|
162
|
+
async getTorrent(id) {
|
163
|
+
const result = await this.listTorrents(id);
|
164
|
+
if (!result.arguments.torrents || result.arguments.torrents.length === 0) {
|
165
|
+
throw new Error('Torrent not found');
|
166
|
+
}
|
167
|
+
return this._normalizeTorrentData(result.arguments.torrents[0]);
|
168
|
+
}
|
169
|
+
async getAllData() {
|
170
|
+
const listTorrents = await this.listTorrents();
|
171
|
+
const torrents = listTorrents.arguments.torrents.map((n) => this._normalizeTorrentData(n));
|
172
|
+
const labels = [];
|
173
|
+
for (const torrent of torrents) {
|
174
|
+
if (!torrent.label) {
|
175
|
+
continue;
|
176
|
+
}
|
177
|
+
const existing = labels.find((n) => n.id === torrent.label);
|
178
|
+
if (existing) {
|
179
|
+
existing.count += 1;
|
180
|
+
continue;
|
181
|
+
}
|
182
|
+
labels.push({ id: torrent.label, name: torrent.label, count: 1 });
|
183
|
+
}
|
184
|
+
const results = {
|
185
|
+
torrents,
|
186
|
+
labels,
|
187
|
+
};
|
188
|
+
return results;
|
189
|
+
}
|
190
|
+
async listTorrents(ids, additionalFields = []) {
|
191
|
+
const fields = [
|
192
|
+
'id',
|
193
|
+
'addedDate',
|
194
|
+
'creator',
|
195
|
+
'doneDate',
|
196
|
+
'comment',
|
197
|
+
'name',
|
198
|
+
'totalSize',
|
199
|
+
'error',
|
200
|
+
'errorString',
|
201
|
+
'eta',
|
202
|
+
'etaIdle',
|
203
|
+
'isFinished',
|
204
|
+
'isStalled',
|
205
|
+
'isPrivate',
|
206
|
+
'files',
|
207
|
+
'fileStats',
|
208
|
+
'hashString',
|
209
|
+
'leftUntilDone',
|
210
|
+
'metadataPercentComplete',
|
211
|
+
'peers',
|
212
|
+
'peersFrom',
|
213
|
+
'peersConnected',
|
214
|
+
'peersGettingFromUs',
|
215
|
+
'peersSendingToUs',
|
216
|
+
'percentDone',
|
217
|
+
'queuePosition',
|
218
|
+
'rateDownload',
|
219
|
+
'rateUpload',
|
220
|
+
'secondsDownloading',
|
221
|
+
'secondsSeeding',
|
222
|
+
'recheckProgress',
|
223
|
+
'seedRatioMode',
|
224
|
+
'seedRatioLimit',
|
225
|
+
'seedIdleLimit',
|
226
|
+
'sizeWhenDone',
|
227
|
+
'status',
|
228
|
+
'trackers',
|
229
|
+
'downloadDir',
|
230
|
+
'downloadLimit',
|
231
|
+
'downloadLimited',
|
232
|
+
'uploadedEver',
|
233
|
+
'downloadedEver',
|
234
|
+
'corruptEver',
|
235
|
+
'uploadRatio',
|
236
|
+
'webseedsSendingToUs',
|
237
|
+
'haveUnchecked',
|
238
|
+
'haveValid',
|
239
|
+
'honorsSessionLimits',
|
240
|
+
'manualAnnounceTime',
|
241
|
+
'activityDate',
|
242
|
+
'desiredAvailable',
|
243
|
+
'labels',
|
244
|
+
'magnetLink',
|
245
|
+
'maxConnectedPeers',
|
246
|
+
'peer-limit',
|
247
|
+
'priorities',
|
248
|
+
'wanted',
|
249
|
+
'webseeds',
|
250
|
+
...additionalFields,
|
251
|
+
];
|
252
|
+
const args = { fields };
|
253
|
+
if (ids) {
|
254
|
+
args.ids = ids;
|
255
|
+
}
|
256
|
+
const res = await this.request('torrent-get', args);
|
257
|
+
return res.body;
|
258
|
+
}
|
259
|
+
// async getTorrent(id: TorrentIds): Promise<NormalizedTorrent> {
|
260
|
+
// const torrent: any = {};
|
261
|
+
// return torrent;
|
262
|
+
// }
|
263
|
+
async request(method, args = {}) {
|
264
|
+
var _a, _b, _c;
|
265
|
+
if (!this.sessionId && method !== 'session-get') {
|
266
|
+
await this.getSession();
|
267
|
+
}
|
268
|
+
const headers = {
|
269
|
+
'X-Transmission-Session-Id': this.sessionId,
|
270
|
+
};
|
271
|
+
if (this.config.username || this.config.password) {
|
272
|
+
const str = `${(_a = this.config.username) !== null && _a !== void 0 ? _a : ''}:${(_b = this.config.password) !== null && _b !== void 0 ? _b : ''}`;
|
273
|
+
headers.Authorization = 'Basic ' + Buffer.from(str).toString('base64');
|
274
|
+
}
|
275
|
+
const url = (0, url_join_1.urlJoin)(this.config.baseUrl, this.config.path);
|
276
|
+
try {
|
277
|
+
const res = await got_1.default.post(url, {
|
278
|
+
json: {
|
279
|
+
method,
|
280
|
+
arguments: args,
|
281
|
+
},
|
282
|
+
headers,
|
283
|
+
retry: 0,
|
284
|
+
// allow proxy agent
|
285
|
+
agent: this.config.agent,
|
286
|
+
timeout: this.config.timeout,
|
287
|
+
responseType: 'json',
|
288
|
+
});
|
289
|
+
return res;
|
290
|
+
}
|
291
|
+
catch (error) {
|
292
|
+
if (((_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.statusCode) === 409) {
|
293
|
+
this.sessionId = error.response.headers['x-transmission-session-id'];
|
294
|
+
// eslint-disable-next-line no-return-await
|
295
|
+
return await this.request(method, args);
|
296
|
+
}
|
297
|
+
throw error;
|
298
|
+
}
|
299
|
+
}
|
300
|
+
_normalizeTorrentData(torrent) {
|
301
|
+
var _a;
|
302
|
+
const dateAdded = new Date(torrent.addedDate * 1000).toISOString();
|
303
|
+
const dateCompleted = new Date(torrent.doneDate * 1000).toISOString();
|
304
|
+
// normalize state to enum
|
305
|
+
// https://github.com/transmission/transmission/blob/c11f2870fd18ff781ca06ce84b6d43541f3293dd/web/javascript/torrent.js#L18
|
306
|
+
let state = shared_torrent_1.TorrentState.unknown;
|
307
|
+
if (torrent.status === 6) {
|
308
|
+
state = shared_torrent_1.TorrentState.seeding;
|
309
|
+
}
|
310
|
+
else if (torrent.status === 4) {
|
311
|
+
state = shared_torrent_1.TorrentState.downloading;
|
312
|
+
}
|
313
|
+
else if (torrent.status === 0) {
|
314
|
+
state = shared_torrent_1.TorrentState.paused;
|
315
|
+
}
|
316
|
+
else if (torrent.status === 2) {
|
317
|
+
state = shared_torrent_1.TorrentState.checking;
|
318
|
+
}
|
319
|
+
else if (torrent.status === 3 || torrent.status === 5) {
|
320
|
+
state = shared_torrent_1.TorrentState.queued;
|
321
|
+
}
|
322
|
+
const result = {
|
323
|
+
id: torrent.id,
|
324
|
+
name: torrent.name,
|
325
|
+
state,
|
326
|
+
isCompleted: torrent.leftUntilDone < 1,
|
327
|
+
stateMessage: '',
|
328
|
+
progress: torrent.percentDone,
|
329
|
+
ratio: torrent.uploadRatio,
|
330
|
+
dateAdded,
|
331
|
+
dateCompleted,
|
332
|
+
label: ((_a = torrent.labels) === null || _a === void 0 ? void 0 : _a.length) ? torrent.labels[0] : undefined,
|
333
|
+
savePath: torrent.downloadDir,
|
334
|
+
uploadSpeed: torrent.rateUpload,
|
335
|
+
downloadSpeed: torrent.rateDownload,
|
336
|
+
eta: torrent.eta,
|
337
|
+
queuePosition: torrent.queuePosition,
|
338
|
+
connectedPeers: torrent.peersSendingToUs,
|
339
|
+
connectedSeeds: torrent.peersGettingFromUs,
|
340
|
+
totalPeers: torrent.peersConnected,
|
341
|
+
totalSeeds: torrent.peersConnected,
|
342
|
+
totalSelected: torrent.sizeWhenDone,
|
343
|
+
totalSize: torrent.totalSize,
|
344
|
+
totalUploaded: torrent.uploadedEver,
|
345
|
+
totalDownloaded: torrent.downloadedEver,
|
346
|
+
};
|
347
|
+
return result;
|
348
|
+
}
|
349
|
+
}
|
350
|
+
exports.Transmission = Transmission;
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ctrl/transmission",
|
3
|
-
"version": "2.
|
3
|
+
"version": "2.4.1",
|
4
4
|
"description": "TypeScript api wrapper for transmission using got",
|
5
5
|
"author": "Scott Cooper <scttcper@gmail.com>",
|
6
6
|
"license": "MIT",
|
@@ -16,35 +16,36 @@
|
|
16
16
|
],
|
17
17
|
"sideEffects": false,
|
18
18
|
"scripts": {
|
19
|
-
"lint": "eslint --ext .
|
20
|
-
"lint:fix": "eslint --fix --ext .
|
19
|
+
"lint": "eslint --ext .ts .",
|
20
|
+
"lint:fix": "eslint --fix --ext .ts .",
|
21
21
|
"prepare": "npm run build",
|
22
22
|
"build": "tsc -p tsconfig.build.json",
|
23
|
-
"build:docs": "typedoc
|
23
|
+
"build:docs": "typedoc",
|
24
24
|
"test": "jest --runInBand",
|
25
25
|
"test:watch": "jest --watch --runInBand",
|
26
|
-
"test:ci": "jest --
|
26
|
+
"test:ci": "jest --ci --runInBand --reporters=default --reporters=jest-junit --coverage"
|
27
27
|
},
|
28
28
|
"dependencies": {
|
29
|
-
"@ctrl/shared-torrent": "^3.0.
|
30
|
-
"got": "^11.8.
|
31
|
-
"@ctrl/url-join": "^1.0.
|
29
|
+
"@ctrl/shared-torrent": "^3.0.5",
|
30
|
+
"got": "^11.8.3",
|
31
|
+
"@ctrl/url-join": "^1.0.4"
|
32
32
|
},
|
33
33
|
"devDependencies": {
|
34
|
-
"@babel/plugin-transform-modules-commonjs": "7.
|
35
|
-
"@babel/preset-typescript": "7.
|
36
|
-
"@ctrl/eslint-config": "1.
|
37
|
-
"@jest/globals": "
|
38
|
-
"@types/
|
39
|
-
"@types/
|
40
|
-
"
|
41
|
-
"jest": "
|
42
|
-
"p-wait-for": "3.
|
43
|
-
"typedoc": "0.
|
44
|
-
"typescript": "4.
|
34
|
+
"@babel/plugin-transform-modules-commonjs": "7.16.0",
|
35
|
+
"@babel/preset-typescript": "7.16.0",
|
36
|
+
"@ctrl/eslint-config": "3.1.3",
|
37
|
+
"@jest/globals": "27.4.2",
|
38
|
+
"@types/node": "16.11.11",
|
39
|
+
"@types/url-join": "4.0.1",
|
40
|
+
"jest": "27.4.3",
|
41
|
+
"jest-junit": "13.0.0",
|
42
|
+
"p-wait-for": "3.2.0",
|
43
|
+
"typedoc": "0.22.10",
|
44
|
+
"typescript": "4.5.2"
|
45
45
|
},
|
46
46
|
"jest": {
|
47
|
-
"testEnvironment": "node"
|
47
|
+
"testEnvironment": "node",
|
48
|
+
"coverageProvider": "v8"
|
48
49
|
},
|
49
50
|
"babel": {
|
50
51
|
"presets": [
|