@ctrl/deluge 6.1.0 → 7.1.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
@@ -70,6 +70,15 @@ const res = await client.removeTorrent('torrent_id', true);
70
70
  console.log(res);
71
71
  ```
72
72
 
73
+ ##### export and create from state
74
+
75
+ If you're shutting down the server often (serverless?) you can export the state
76
+
77
+ ```ts
78
+ const state = client.exportState()
79
+ const client = Deluge.createFromState(config, state);
80
+ ```
81
+
73
82
  ### See Also
74
83
  transmission - https://github.com/scttcper/transmission
75
84
  qbittorrent - https://github.com/scttcper/qbittorrent
@@ -1,11 +1,20 @@
1
1
  import { ofetch } from 'ofetch';
2
- import type { AddTorrentOptions as NormalizedAddTorrentOptions, AllClientData, NormalizedTorrent, TorrentClient, TorrentSettings } from '@ctrl/shared-torrent';
2
+ import { Cookie } from 'tough-cookie';
3
+ import type { Jsonify } from 'type-fest';
4
+ import type { AddTorrentOptions as NormalizedAddTorrentOptions, AllClientData, NormalizedTorrent, TorrentClient, TorrentClientConfig, TorrentClientState } from '@ctrl/shared-torrent';
3
5
  import type { AddTorrentOptions, AddTorrentResponse, BooleanStatus, ConfigResponse, DefaultResponse, DelugeSettings, GetHostsResponse, GetHostStatusResponse, ListMethods, PluginInfo, PluginsListResponse, StringStatus, TorrentFiles, TorrentInfo, TorrentListResponse, TorrentOptions, TorrentStatus, Tracker, UploadResponse } from './types.js';
6
+ interface DelugeState extends TorrentClientState {
7
+ auth: {
8
+ cookie?: Cookie;
9
+ msgId: number;
10
+ };
11
+ }
4
12
  export declare class Deluge implements TorrentClient {
5
- config: TorrentSettings;
6
- private _msgId;
7
- private _cookie?;
8
- constructor(options?: Partial<TorrentSettings>);
13
+ static createFromState(config: Readonly<TorrentClientConfig>, state: Readonly<Jsonify<DelugeState>>): Deluge;
14
+ config: TorrentClientConfig;
15
+ state: DelugeState;
16
+ constructor(options?: Partial<TorrentClientConfig>);
17
+ exportState(): Jsonify<DelugeState>;
9
18
  resetSession(): void;
10
19
  getHosts(): Promise<GetHostsResponse>;
11
20
  /**
@@ -69,7 +78,7 @@ export declare class Deluge implements TorrentClient {
69
78
  /**
70
79
  *
71
80
  * @param torrentId torrent id from list torrents
72
- * @param removeData true will delete all data from disk
81
+ * @param removeData (default: false) If true, remove the data from disk
73
82
  */
74
83
  removeTorrent(torrentId: string, removeData?: boolean): Promise<BooleanStatus>;
75
84
  changePassword(password: string): Promise<BooleanStatus>;
@@ -108,3 +117,4 @@ export declare class Deluge implements TorrentClient {
108
117
  request<T extends object>(method: string, params?: any[], needsAuth?: boolean, autoConnect?: boolean): Promise<ReturnType<typeof ofetch.raw<T>>>;
109
118
  private _validateAuth;
110
119
  }
120
+ export {};
@@ -12,15 +12,37 @@ const defaults = {
12
12
  timeout: 5000,
13
13
  };
14
14
  export class Deluge {
15
+ static createFromState(config, state) {
16
+ const deluge = new Deluge(config);
17
+ deluge.state = {
18
+ ...state,
19
+ auth: state.auth
20
+ ? {
21
+ cookie: Cookie.fromJSON(state.auth.cookie),
22
+ msgId: state.auth.msgId,
23
+ }
24
+ : { msgId: 0 },
25
+ };
26
+ return deluge;
27
+ }
15
28
  config;
16
- _msgId = 0;
17
- _cookie;
29
+ state = { auth: { msgId: 0 } };
18
30
  constructor(options = {}) {
19
31
  this.config = { ...defaults, ...options };
20
32
  }
33
+ exportState() {
34
+ return JSON.parse(JSON.stringify({
35
+ ...this.state,
36
+ auth: this.state.auth
37
+ ? {
38
+ cookie: this.state.auth.cookie.toJSON(),
39
+ msgId: this.state.auth.msgId,
40
+ }
41
+ : { msgId: 0 },
42
+ }));
43
+ }
21
44
  resetSession() {
22
- this._cookie = undefined;
23
- this._msgId = 0;
45
+ this.state.auth = { msgId: 0 };
24
46
  }
25
47
  async getHosts() {
26
48
  const res = await this.request('web.get_hosts', [], true, false);
@@ -75,15 +97,15 @@ export class Deluge {
75
97
  */
76
98
  async checkSession() {
77
99
  // cookie is missing or expires in x seconds
78
- if (this._cookie) {
100
+ if (this.state.auth.cookie) {
79
101
  // eslint-disable-next-line new-cap
80
- if (this._cookie.TTL() < 5000) {
102
+ if (this.state.auth.cookie.TTL() < 5000) {
81
103
  this.resetSession();
82
104
  return false;
83
105
  }
84
106
  return true;
85
107
  }
86
- if (this._cookie) {
108
+ if (this.state.auth.cookie) {
87
109
  try {
88
110
  const check = await this.request('auth.check_session', undefined, false);
89
111
  const body = await check.json();
@@ -108,7 +130,7 @@ export class Deluge {
108
130
  if (!res.ok || !res.headers?.get('set-cookie')?.length) {
109
131
  throw new Error('Auth failed, incorrect password');
110
132
  }
111
- this._cookie = Cookie.parse(res.headers.get('set-cookie'));
133
+ this.state.auth.cookie = Cookie.parse(res.headers.get('set-cookie'));
112
134
  return true;
113
135
  }
114
136
  /**
@@ -167,8 +189,7 @@ export class Deluge {
167
189
  retry: 0,
168
190
  timeout: this.config.timeout,
169
191
  parseResponse: JSON.parse,
170
- // @ts-expect-error for some reason agent is not in the type
171
- agent: this.config.agent,
192
+ dispatcher: this.config.dispatcher,
172
193
  });
173
194
  return res;
174
195
  }
@@ -188,7 +209,8 @@ export class Deluge {
188
209
  }
189
210
  async addTorrent(torrent, config = {}) {
190
211
  let path;
191
- if (isUint8Array(torrent) || !torrent.startsWith('/tmp/')) {
212
+ const isUploaded = typeof torrent === 'string' && torrent.includes('delugeweb-');
213
+ if (isUint8Array(torrent) || !isUploaded) {
192
214
  const upload = await this.upload(torrent);
193
215
  if (!upload.success || !upload.files.length) {
194
216
  throw new Error('Failed to upload');
@@ -196,7 +218,10 @@ export class Deluge {
196
218
  path = upload.files[0];
197
219
  }
198
220
  else {
199
- /** Assume paths starting with /tmp/ are from {@link Deluge.addTorrent} */
221
+ /**
222
+ * Assume paths starting with /tmp/ are from {@link Deluge.upload}
223
+ * Example temp path: /run/deluged-temp/delugeweb-s0jy917j/ubuntu-20.10-desktop-amd64.iso.torrent
224
+ */
200
225
  path = torrent;
201
226
  }
202
227
  const options = {
@@ -278,9 +303,9 @@ export class Deluge {
278
303
  /**
279
304
  *
280
305
  * @param torrentId torrent id from list torrents
281
- * @param removeData true will delete all data from disk
306
+ * @param removeData (default: false) If true, remove the data from disk
282
307
  */
283
- async removeTorrent(torrentId, removeData = true) {
308
+ async removeTorrent(torrentId, removeData = false) {
284
309
  const req = await this.request('core.remove_torrent', [torrentId, removeData]);
285
310
  return req._data;
286
311
  }
@@ -295,7 +320,7 @@ export class Deluge {
295
320
  }
296
321
  // update current password to new password
297
322
  this.config.password = password;
298
- this._cookie = Cookie.parse(res.headers.get('set-cookie'));
323
+ this.state.auth.cookie = Cookie.parse(res.headers.get('set-cookie'));
299
324
  return body;
300
325
  }
301
326
  async getAllData() {
@@ -508,10 +533,9 @@ export class Deluge {
508
533
  const req = await this.request('core.disable_plugin', plugins);
509
534
  return req._data;
510
535
  }
511
- // eslint-disable-next-line @typescript-eslint/ban-types
512
536
  async request(method, params = [], needsAuth = true, autoConnect = true) {
513
- if (this._msgId === 4096) {
514
- this._msgId = 0;
537
+ if (this.state.auth.msgId === 4096) {
538
+ this.state.auth.msgId = 0;
515
539
  }
516
540
  if (needsAuth) {
517
541
  await this._validateAuth();
@@ -523,7 +547,7 @@ export class Deluge {
523
547
  }
524
548
  }
525
549
  const headers = {
526
- Cookie: this._cookie?.cookieString?.(),
550
+ Cookie: this.state.auth.cookie?.cookieString?.(),
527
551
  };
528
552
  const url = joinURL(this.config.baseUrl, this.config.path);
529
553
  const res = await ofetch.raw(url, {
@@ -531,15 +555,14 @@ export class Deluge {
531
555
  body: JSON.stringify({
532
556
  method,
533
557
  params,
534
- id: this._msgId++,
558
+ id: this.state.auth.msgId++,
535
559
  }),
536
560
  headers,
537
561
  retry: 0,
538
562
  timeout: this.config.timeout,
539
563
  responseType: 'json',
540
564
  parseResponse: JSON.parse,
541
- // @ts-expect-error for some reason agent is not in the type
542
- agent: this.config.agent,
565
+ dispatcher: this.config.dispatcher,
543
566
  });
544
567
  const err = res.body?.error ?? (typeof res.body === 'string' && res.body);
545
568
  if (err) {
@@ -1,3 +1,3 @@
1
- import { NormalizedTorrent } from '@ctrl/shared-torrent';
2
- import { Torrent } from './types.js';
1
+ import { type NormalizedTorrent } from '@ctrl/shared-torrent';
2
+ import type { Torrent } from './types.js';
3
3
  export declare function normalizeTorrentData(id: string, torrent: Torrent): NormalizedTorrent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl/deluge",
3
- "version": "6.1.0",
3
+ "version": "7.1.0",
4
4
  "description": "TypeScript api wrapper for deluge using got",
5
5
  "author": "Scott Cooper <scttcper@gmail.com>",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "lint:eslint": "eslint .",
24
24
  "lint:fix": "pnpm run '/^(lint:biome|lint:eslint):fix$/'",
25
25
  "lint:eslint:fix": "eslint . --fix",
26
- "lint:biome:fix": "biome check . --apply",
26
+ "lint:biome:fix": "biome check . --write",
27
27
  "prepare": "npm run build",
28
28
  "build": "tsc",
29
29
  "build:docs": "typedoc",
@@ -33,24 +33,24 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@ctrl/magnet-link": "^4.0.2",
36
- "@ctrl/shared-torrent": "^6.0.0",
36
+ "@ctrl/shared-torrent": "^6.2.1",
37
37
  "node-fetch-native": "^1.6.4",
38
- "ofetch": "^1.3.4",
39
- "tough-cookie": "^4.1.4",
40
- "ufo": "^1.5.3",
41
- "uint8array-extras": "^1.2.0"
38
+ "ofetch": "^1.4.1",
39
+ "tough-cookie": "^5.0.0",
40
+ "type-fest": "^4.30.2",
41
+ "ufo": "^1.5.4",
42
+ "uint8array-extras": "^1.4.0"
42
43
  },
43
44
  "devDependencies": {
44
- "@biomejs/biome": "1.8.3",
45
- "@ctrl/eslint-config-biome": "3.1.3",
46
- "@sindresorhus/tsconfig": "6.0.0",
47
- "@types/node": "20.14.9",
48
- "@types/tough-cookie": "4.0.5",
49
- "@vitest/coverage-v8": "1.6.0",
45
+ "@biomejs/biome": "1.9.4",
46
+ "@ctrl/eslint-config-biome": "4.3.1",
47
+ "@sindresorhus/tsconfig": "7.0.0",
48
+ "@types/node": "22.10.2",
49
+ "@vitest/coverage-v8": "2.1.8",
50
50
  "p-wait-for": "5.0.2",
51
- "typedoc": "0.26.3",
52
- "typescript": "5.5.3",
53
- "vitest": "1.6.0"
51
+ "typedoc": "0.27.5",
52
+ "typescript": "5.7.2",
53
+ "vitest": "2.1.8"
54
54
  },
55
55
  "publishConfig": {
56
56
  "access": "public",