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