@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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ### Install
6
6
 
7
- ```console
7
+ ```sh
8
8
  npm install @ctrl/transmission
9
9
  ```
10
10
 
package/dist/index.d.ts CHANGED
@@ -1,58 +1,2 @@
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
- }
1
+ export * from './transmission';
2
+ export * from './types';
package/dist/index.js CHANGED
@@ -1,350 +1,14 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
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.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 = 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
- * (1) an integer referring to a torrent id
44
- * (2) a list of torrent id numbers, sha1 hash strings, or both
45
- * (3) a string, "recently-active", for recently-active torrents
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: FileStats[];
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": "2.3.0",
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 .js,.ts, .",
20
- "lint:fix": "eslint --fix --ext .js,.ts, .",
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 --coverage --no-cache --runInBand"
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.2",
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.14.0",
35
- "@babel/preset-typescript": "7.13.0",
36
- "@ctrl/eslint-config": "2.0.8",
37
- "@jest/globals": "27.0.3",
38
- "@types/jest": "26.0.23",
39
- "@types/node": "15.12.2",
40
- "@types/url-join": "4.0.0",
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.21.0-beta.2",
45
- "typescript": "4.3.2"
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": [