cross-seed 6.13.6 → 7.0.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +12 -13
  2. package/dist/webui/assets/FieldInfo-Bxj_j8SJ.js +1 -0
  3. package/dist/webui/assets/Page-C3rteCZt.js +1 -0
  4. package/dist/webui/assets/array-field-DVSC6nHP.js +1 -0
  5. package/dist/webui/assets/badge-DTZMtS0e.js +1 -0
  6. package/dist/webui/assets/check-Bu3ldi63.js +1 -0
  7. package/dist/webui/assets/chevron-down-CRy8M0kJ.js +1 -0
  8. package/dist/webui/assets/clients-CW8oEZoQ.js +1 -0
  9. package/dist/webui/assets/connect-YBNsnjWT.js +1 -0
  10. package/dist/webui/assets/debug-mz8-WYZj.js +1 -0
  11. package/dist/webui/assets/directories-BSK28RgR.js +1 -0
  12. package/dist/webui/assets/duration-field-C6xoSlJg.js +1 -0
  13. package/dist/webui/assets/general-lJJxZhH7.js +1 -0
  14. package/dist/webui/assets/health-CXbsVrie.js +1 -0
  15. package/dist/webui/assets/index-Bi48hI2z.js +54 -0
  16. package/dist/webui/assets/index-C-Ul7GNg.css +1 -0
  17. package/dist/webui/assets/index-C2cH1Gst.js +1 -0
  18. package/dist/webui/assets/index-Cc5bDmJr.js +1 -0
  19. package/dist/webui/assets/jobs-CxmNab9w.js +1 -0
  20. package/dist/webui/assets/library-vaj2W8sE.js +1 -0
  21. package/dist/webui/assets/loader-circle-M0gu1gZ-.js +1 -0
  22. package/dist/webui/assets/logs-Cu9RyKS0.js +1 -0
  23. package/dist/webui/assets/search-2R5sIdT8.js +1 -0
  24. package/dist/webui/assets/select-field-BCqNLDrJ.js +1 -0
  25. package/dist/webui/assets/select-zHgqMzLj.js +1 -0
  26. package/dist/webui/assets/settings-CMYjpTbZ.js +1 -0
  27. package/dist/webui/assets/submit-button-BtcnyggQ.js +1 -0
  28. package/dist/webui/assets/switch-G0W3uJVN.js +1 -0
  29. package/dist/webui/assets/switch-field-IBd9ORNq.js +1 -0
  30. package/dist/webui/assets/table-DvgJU7Gh.js +1 -0
  31. package/dist/webui/assets/test-tube-BIwmoM45.js +1 -0
  32. package/dist/webui/assets/text-field-DruSbGhy.js +1 -0
  33. package/dist/webui/assets/time-BSMZjmyW.js +1 -0
  34. package/dist/webui/assets/trackers-D-OpAe63.js +7 -0
  35. package/dist/webui/assets/use-form-validation-context-BkAfWAh0.js +1 -0
  36. package/dist/webui/assets/use-settings-form-submit-CDRh-E9U.js +2 -0
  37. package/dist/webui/assets/useQuery-A4Hv_4uX.js +1 -0
  38. package/dist/webui/index.html +13 -0
  39. package/node_modules/@cross-seed/shared/dist/configSchema.d.ts +261 -0
  40. package/node_modules/@cross-seed/shared/dist/configSchema.d.ts.map +1 -0
  41. package/node_modules/@cross-seed/shared/dist/configSchema.js +53 -0
  42. package/node_modules/@cross-seed/shared/dist/configSchema.js.map +1 -0
  43. package/node_modules/@cross-seed/shared/dist/constants.d.ts +122 -0
  44. package/node_modules/@cross-seed/shared/dist/constants.d.ts.map +1 -0
  45. package/node_modules/@cross-seed/shared/dist/constants.js +127 -0
  46. package/node_modules/@cross-seed/shared/dist/constants.js.map +1 -0
  47. package/node_modules/@cross-seed/shared/dist/tsconfig.tsbuildinfo +1 -0
  48. package/node_modules/@cross-seed/shared/dist/utils.d.ts +6 -0
  49. package/node_modules/@cross-seed/shared/dist/utils.d.ts.map +1 -0
  50. package/node_modules/@cross-seed/shared/dist/utils.js +9 -0
  51. package/node_modules/@cross-seed/shared/dist/utils.js.map +1 -0
  52. package/node_modules/@cross-seed/shared/package.json +22 -0
  53. package/package.json +35 -11
  54. package/dist/Result.js +0 -64
  55. package/dist/Result.js.map +0 -1
  56. package/dist/action.js +0 -693
  57. package/dist/action.js.map +0 -1
  58. package/dist/arr.js +0 -199
  59. package/dist/arr.js.map +0 -1
  60. package/dist/auth.js +0 -25
  61. package/dist/auth.js.map +0 -1
  62. package/dist/clients/Deluge.js +0 -698
  63. package/dist/clients/Deluge.js.map +0 -1
  64. package/dist/clients/QBittorrent.js +0 -785
  65. package/dist/clients/QBittorrent.js.map +0 -1
  66. package/dist/clients/RTorrent.js +0 -654
  67. package/dist/clients/RTorrent.js.map +0 -1
  68. package/dist/clients/TorrentClient.js +0 -272
  69. package/dist/clients/TorrentClient.js.map +0 -1
  70. package/dist/clients/Transmission.js +0 -404
  71. package/dist/clients/Transmission.js.map +0 -1
  72. package/dist/cmd.js +0 -196
  73. package/dist/cmd.js.map +0 -1
  74. package/dist/config.template.cjs +0 -353
  75. package/dist/config.template.cjs.map +0 -1
  76. package/dist/configSchema.js +0 -667
  77. package/dist/configSchema.js.map +0 -1
  78. package/dist/configuration.js +0 -82
  79. package/dist/configuration.js.map +0 -1
  80. package/dist/constants.js +0 -281
  81. package/dist/constants.js.map +0 -1
  82. package/dist/dataFiles.js +0 -208
  83. package/dist/dataFiles.js.map +0 -1
  84. package/dist/db.js +0 -216
  85. package/dist/db.js.map +0 -1
  86. package/dist/decide.js +0 -553
  87. package/dist/decide.js.map +0 -1
  88. package/dist/diff.js +0 -24
  89. package/dist/diff.js.map +0 -1
  90. package/dist/errors.js +0 -16
  91. package/dist/errors.js.map +0 -1
  92. package/dist/indexers.js +0 -180
  93. package/dist/indexers.js.map +0 -1
  94. package/dist/inject.js +0 -594
  95. package/dist/inject.js.map +0 -1
  96. package/dist/jobs.js +0 -146
  97. package/dist/jobs.js.map +0 -1
  98. package/dist/logger.js +0 -143
  99. package/dist/logger.js.map +0 -1
  100. package/dist/migrations/00-initialSchema.js +0 -30
  101. package/dist/migrations/00-initialSchema.js.map +0 -1
  102. package/dist/migrations/01-jobs.js +0 -12
  103. package/dist/migrations/01-jobs.js.map +0 -1
  104. package/dist/migrations/02-timestamps.js +0 -21
  105. package/dist/migrations/02-timestamps.js.map +0 -1
  106. package/dist/migrations/03-rateLimits.js +0 -14
  107. package/dist/migrations/03-rateLimits.js.map +0 -1
  108. package/dist/migrations/04-auth.js +0 -13
  109. package/dist/migrations/04-auth.js.map +0 -1
  110. package/dist/migrations/05-caps.js +0 -16
  111. package/dist/migrations/05-caps.js.map +0 -1
  112. package/dist/migrations/06-uniqueDecisions.js +0 -29
  113. package/dist/migrations/06-uniqueDecisions.js.map +0 -1
  114. package/dist/migrations/07-limits.js +0 -12
  115. package/dist/migrations/07-limits.js.map +0 -1
  116. package/dist/migrations/08-rss.js +0 -15
  117. package/dist/migrations/08-rss.js.map +0 -1
  118. package/dist/migrations/09-clientAndDataSearchees.js +0 -34
  119. package/dist/migrations/09-clientAndDataSearchees.js.map +0 -1
  120. package/dist/migrations/10-indexerNameAudioBookCaps.js +0 -18
  121. package/dist/migrations/10-indexerNameAudioBookCaps.js.map +0 -1
  122. package/dist/migrations/11-trackers.js +0 -38
  123. package/dist/migrations/11-trackers.js.map +0 -1
  124. package/dist/migrations/migrations.js +0 -31
  125. package/dist/migrations/migrations.js.map +0 -1
  126. package/dist/parseTorrent.js +0 -128
  127. package/dist/parseTorrent.js.map +0 -1
  128. package/dist/pipeline.js +0 -527
  129. package/dist/pipeline.js.map +0 -1
  130. package/dist/preFilter.js +0 -250
  131. package/dist/preFilter.js.map +0 -1
  132. package/dist/pushNotifier.js +0 -137
  133. package/dist/pushNotifier.js.map +0 -1
  134. package/dist/runtimeConfig.js +0 -11
  135. package/dist/runtimeConfig.js.map +0 -1
  136. package/dist/searchee.js +0 -658
  137. package/dist/searchee.js.map +0 -1
  138. package/dist/server.js +0 -456
  139. package/dist/server.js.map +0 -1
  140. package/dist/startup.js +0 -203
  141. package/dist/startup.js.map +0 -1
  142. package/dist/torrent.js +0 -637
  143. package/dist/torrent.js.map +0 -1
  144. package/dist/torznab.js +0 -786
  145. package/dist/torznab.js.map +0 -1
  146. package/dist/utils.js +0 -637
  147. package/dist/utils.js.map +0 -1
@@ -1,698 +0,0 @@
1
- import { readdir } from "fs/promises";
2
- import ms from "ms";
3
- import { basename } from "path";
4
- import { inspect } from "util";
5
- import { InjectionResult, TORRENT_CATEGORY_SUFFIX, TORRENT_TAG, USER_AGENT, } from "../constants.js";
6
- import { db } from "../db.js";
7
- import { CrossSeedError } from "../errors.js";
8
- import { Label, logger } from "../logger.js";
9
- import { resultOf, resultOfErr } from "../Result.js";
10
- import { getRuntimeConfig } from "../runtimeConfig.js";
11
- import { createSearcheeFromDB, parseTitle, updateSearcheeClientDB, } from "../searchee.js";
12
- import { extractCredentialsFromUrl, getLogString, humanReadableSize, sanitizeInfoHash, wait, } from "../utils.js";
13
- import { shouldResumeFromNonRelevantFiles, clientSearcheeModified, getMaxRemainingBytes, getResumeStopTime, organizeTrackers, resumeErrSleepTime, resumeSleepTime, shouldRecheck, } from "./TorrentClient.js";
14
- var DelugeErrorCode;
15
- (function (DelugeErrorCode) {
16
- DelugeErrorCode[DelugeErrorCode["NO_AUTH"] = 1] = "NO_AUTH";
17
- DelugeErrorCode[DelugeErrorCode["BAD_METHOD"] = 2] = "BAD_METHOD";
18
- DelugeErrorCode[DelugeErrorCode["CALL_ERR"] = 3] = "CALL_ERR";
19
- DelugeErrorCode[DelugeErrorCode["RPC_FAIL"] = 4] = "RPC_FAIL";
20
- DelugeErrorCode[DelugeErrorCode["BAD_JSON"] = 5] = "BAD_JSON";
21
- })(DelugeErrorCode || (DelugeErrorCode = {}));
22
- export default class Deluge {
23
- url;
24
- clientHost;
25
- clientPriority;
26
- clientType = Label.DELUGE;
27
- readonly;
28
- label;
29
- delugeCookie = null;
30
- delugeLabel = TORRENT_TAG;
31
- delugeLabelSuffix = TORRENT_CATEGORY_SUFFIX;
32
- isLabelEnabled;
33
- delugeRequestId = 0;
34
- constructor(url, clientHost, priority, readonly) {
35
- this.url = url;
36
- this.clientHost = clientHost;
37
- this.clientPriority = priority;
38
- this.readonly = readonly;
39
- this.label = `${this.clientType}@${this.clientHost}`;
40
- }
41
- /**
42
- * validates the login and host for deluge webui
43
- */
44
- async validateConfig() {
45
- const { torrentDir } = getRuntimeConfig();
46
- await this.authenticate();
47
- this.isLabelEnabled = await this.labelEnabled();
48
- logger.info({
49
- label: this.label,
50
- message: `Logged in successfully${this.readonly ? " (readonly)" : ""}`,
51
- });
52
- if (!torrentDir)
53
- return;
54
- if (!(await readdir(torrentDir)).some((f) => f.endsWith(".state"))) {
55
- throw new CrossSeedError(`[${this.label}] Invalid torrentDir, if no torrents are in client set to null for now: https://www.cross-seed.org/docs/basics/options#torrentdir`);
56
- }
57
- }
58
- /**
59
- * connects and authenticates to the webui
60
- */
61
- async authenticate() {
62
- const { href, password } = extractCredentialsFromUrl(this.url).unwrapOrThrow(new CrossSeedError(`[${this.label}] delugeRpcUrl must be percent-encoded`));
63
- if (!password) {
64
- throw new CrossSeedError(`[${this.label}] You need to define a password in the delugeRpcUrl. (e.g. http://:<PASSWORD>@localhost:8112)`);
65
- }
66
- try {
67
- const authResponse = (await this.call("auth.login", [password], 0)).unwrapOrThrow(new Error(`[${this.label}] failed to connect for authentication`));
68
- if (!authResponse) {
69
- throw new CrossSeedError(`[${this.label}] Reached Deluge, but failed to authenticate: ${href}`);
70
- }
71
- }
72
- catch (networkError) {
73
- throw new CrossSeedError(networkError);
74
- }
75
- const isConnectedResponse = await this.call("web.connected", [], 0);
76
- if (isConnectedResponse.isOk() && !isConnectedResponse.unwrap()) {
77
- logger.warn({
78
- label: this.label,
79
- message: "Deluge WebUI disconnected from daemon...attempting to reconnect.",
80
- });
81
- const webuiHostList = (await this.call("web.get_hosts", [], 0)).unwrapOrThrow(new Error(`[${this.label}] failed to get host-list for reconnect`));
82
- const connectResponse = await this.call("web.connect", [webuiHostList[0][0]], 0);
83
- if (connectResponse.isOk() && connectResponse.unwrap()) {
84
- logger.info({
85
- label: this.label,
86
- message: "Deluge WebUI connected to the daemon.",
87
- });
88
- }
89
- else {
90
- throw new CrossSeedError(`[${this.label}] Unable to connect WebUI to Deluge daemon. Connect to the WebUI to resolve this.`);
91
- }
92
- }
93
- }
94
- /**
95
- * ensures authentication and sends JSON-RPC calls to deluge
96
- * @param method RPC method to send (usually prefaced with module name)
97
- * @param params parameters for the method (usually in an array)
98
- * @param retries specify a retry count (optional)
99
- * @return a promised Result of the specified ResultType or an ErrorType
100
- */
101
- async call(method, params, retries = 1) {
102
- const msg = `Calling method ${method} with params ${inspect(params, { depth: null, compact: true })}`;
103
- const message = msg.length > 1000 ? `${msg.slice(0, 1000)}...` : msg;
104
- logger.verbose({ label: this.label, message });
105
- const { href } = extractCredentialsFromUrl(this.url).unwrapOrThrow(new CrossSeedError(`[${this.label}] delugeRpcUrl must be percent-encoded`));
106
- const headers = new Headers({
107
- "Content-Type": "application/json",
108
- "User-Agent": USER_AGENT,
109
- });
110
- if (this.delugeCookie)
111
- headers.set("Cookie", this.delugeCookie);
112
- let response, json;
113
- try {
114
- response = await fetch(href, {
115
- body: JSON.stringify({
116
- method,
117
- params,
118
- id: this.delugeRequestId++,
119
- }),
120
- method: "POST",
121
- headers,
122
- signal: AbortSignal.timeout(ms("10 seconds")),
123
- });
124
- }
125
- catch (networkError) {
126
- if (networkError.name === "AbortError" ||
127
- networkError.name === "TimeoutError") {
128
- throw new Error(`[${this.label}] Deluge method ${method} timed out after 10 seconds`);
129
- }
130
- throw new Error(`[${this.label}] Failed to connect to Deluge at ${href}`, {
131
- cause: networkError,
132
- });
133
- }
134
- try {
135
- json = (await response.json());
136
- }
137
- catch (jsonParseError) {
138
- throw new Error(`[${this.label}] Deluge method ${method} response was non-JSON ${jsonParseError}`);
139
- }
140
- if (json.error?.code === DelugeErrorCode.NO_AUTH && retries > 0) {
141
- this.delugeCookie = null;
142
- await this.authenticate();
143
- if (this.delugeCookie) {
144
- return this.call(method, params, 0);
145
- }
146
- else {
147
- throw new Error(`[${this.label}] Connection lost with Deluge. Re-authentication failed.`);
148
- }
149
- }
150
- this.handleResponseHeaders(response.headers);
151
- if (json.error) {
152
- return resultOfErr(json.error);
153
- }
154
- return resultOf(json.result);
155
- }
156
- /**
157
- * parses the set-cookie header and updates stored value
158
- * @param headers the headers from a request
159
- */
160
- handleResponseHeaders(headers) {
161
- if (headers.has("Set-Cookie")) {
162
- this.delugeCookie = headers.get("Set-Cookie").split(";")[0];
163
- }
164
- }
165
- /**
166
- * checks enabled plugins for "Label"
167
- * @return boolean declaring whether the "Label" plugin is enabled
168
- */
169
- async labelEnabled() {
170
- const enabledPlugins = await this.call("core.get_enabled_plugins", []);
171
- if (enabledPlugins.isOk()) {
172
- return enabledPlugins.unwrap().includes("Label");
173
- }
174
- else {
175
- return false;
176
- }
177
- }
178
- /**
179
- * checks the status of an infohash in the client and resumes if/when criteria is met
180
- * @param meta MetaFile containing torrent being resumed
181
- * @param decision decision by which the newTorrent was matched
182
- * @param options options object for extra flags
183
- * @param options.checkOnce boolean to only check for resuming once
184
- * @param options.meta metafile object containing the torrent data
185
- */
186
- async resumeInjection(meta, decision, options) {
187
- const infoHash = meta.infoHash;
188
- let sleepTime = resumeSleepTime;
189
- const stopTime = getResumeStopTime();
190
- let stop = false;
191
- while (Date.now() < stopTime) {
192
- if (options.checkOnce) {
193
- if (stop)
194
- return;
195
- stop = true;
196
- }
197
- await wait(sleepTime);
198
- let torrentInfo;
199
- let torrentLog;
200
- try {
201
- torrentInfo = await this.getTorrentInfo(infoHash);
202
- if (torrentInfo.state === "Checking") {
203
- continue;
204
- }
205
- torrentLog = `${torrentInfo.name} [${sanitizeInfoHash(infoHash)}]`;
206
- if (torrentInfo.state !== "Paused") {
207
- logger.warn({
208
- label: this.label,
209
- message: `Will not resume ${torrentLog}: state is ${torrentInfo.state}`,
210
- });
211
- return;
212
- }
213
- const maxRemainingBytes = getMaxRemainingBytes(meta, decision, {
214
- torrentLog,
215
- label: this.label,
216
- });
217
- if (torrentInfo.total_remaining > maxRemainingBytes) {
218
- if (!shouldResumeFromNonRelevantFiles(meta, torrentInfo.total_remaining, decision, { torrentLog, label: this.label })) {
219
- logger.warn({
220
- label: this.label,
221
- message: `autoResumeMaxDownload will not resume ${torrentLog}: remainingSize ${humanReadableSize(torrentInfo.total_remaining, { binary: true })} > ${humanReadableSize(maxRemainingBytes, { binary: true })} limit`,
222
- });
223
- return;
224
- }
225
- }
226
- }
227
- catch (e) {
228
- sleepTime = resumeErrSleepTime; // Dropping connections or restart
229
- continue;
230
- }
231
- logger.info({
232
- label: this.label,
233
- message: `Resuming ${torrentLog}: ${humanReadableSize(torrentInfo.total_remaining, { binary: true })} remaining`,
234
- });
235
- await this.call("core.resume_torrent", [[infoHash]]);
236
- return;
237
- }
238
- logger.warn({
239
- label: this.label,
240
- message: `Will not resume torrent ${infoHash}: timeout`,
241
- });
242
- }
243
- /**
244
- * generates the label for injection based on searchee and torrentInfo
245
- * @param searchee Searchee that contains the originating torrent
246
- * @param torrentInfo TorrentInfo from the searchee
247
- * @return string with the label for the newTorrent
248
- */
249
- calculateLabel(searchee, torrentInfo) {
250
- const { linkCategory, duplicateCategories } = getRuntimeConfig();
251
- if (!searchee.infoHash || !torrentInfo.label) {
252
- return this.delugeLabel;
253
- }
254
- const ogLabel = torrentInfo.label;
255
- if (!duplicateCategories) {
256
- return ogLabel;
257
- }
258
- const shouldSuffixLabel = !ogLabel.endsWith(this.delugeLabelSuffix) && // no .cross-seed
259
- ogLabel !== linkCategory; // not data
260
- return !searchee.infoHash
261
- ? linkCategory ?? ""
262
- : shouldSuffixLabel
263
- ? `${ogLabel}${this.delugeLabelSuffix}`
264
- : ogLabel;
265
- }
266
- /**
267
- * if Label plugin is loaded, adds (if necessary)
268
- * and sets the label based on torrent hash.
269
- * @param newTorrent the searchee of the newTorrent
270
- * @param label the destination label for the newTorrent/searchee
271
- */
272
- async setLabel(newTorrent, label) {
273
- let setResult;
274
- if (!this.isLabelEnabled)
275
- return;
276
- try {
277
- const getCurrentLabels = await this.call("label.get_labels", []);
278
- if (getCurrentLabels.isErr()) {
279
- this.isLabelEnabled = false;
280
- throw new Error("Labels have been disabled.");
281
- }
282
- if (getCurrentLabels.unwrap().includes(label)) {
283
- setResult = await this.call("label.set_torrent", [
284
- newTorrent.infoHash,
285
- label,
286
- ]);
287
- }
288
- else {
289
- await this.call("label.add", [label]);
290
- await wait(300);
291
- setResult = await this.call("label.set_torrent", [
292
- newTorrent.infoHash,
293
- label,
294
- ]);
295
- }
296
- if (setResult.isErr()) {
297
- throw new Error(setResult.unwrapErr().message);
298
- }
299
- }
300
- catch (e) {
301
- logger.warn({
302
- label: this.label,
303
- message: `Failed to label ${getLogString(newTorrent)} as ${label}: ${e.message}`,
304
- });
305
- logger.debug(e);
306
- }
307
- }
308
- /**
309
- * injects a torrent into deluge client
310
- * @param newTorrent injected candidate torrent
311
- * @param searchee originating torrent (searchee)
312
- * @param decision decision by which the newTorrent was matched
313
- * @param options.onlyCompleted boolean to only inject completed torrents
314
- * @param options.destinationDir location of the linked files (optional)
315
- * @return InjectionResult of the newTorrent's injection
316
- */
317
- async inject(newTorrent, searchee, decision, options) {
318
- try {
319
- const existsRes = await this.isTorrentInClient(newTorrent.infoHash);
320
- if (existsRes.isErr())
321
- return InjectionResult.FAILURE;
322
- if (existsRes.unwrap())
323
- return InjectionResult.ALREADY_EXISTS;
324
- let torrentInfo;
325
- if (options.onlyCompleted && searchee.infoHash) {
326
- torrentInfo = await this.getTorrentInfo(searchee.infoHash);
327
- if (!torrentInfo.complete)
328
- return InjectionResult.TORRENT_NOT_COMPLETE;
329
- }
330
- if (!options.destinationDir &&
331
- (!searchee.infoHash || !torrentInfo)) {
332
- logger.debug({
333
- label: this.label,
334
- message: `Injection failure: ${getLogString(searchee)} was missing critical data.`,
335
- });
336
- return InjectionResult.FAILURE;
337
- }
338
- const torrentFileName = `${newTorrent.getFileSystemSafeName()}.cross-seed.torrent`;
339
- const encodedTorrentData = newTorrent.encode().toString("base64");
340
- const destinationDir = options.destinationDir
341
- ? options.destinationDir
342
- : torrentInfo.save_path;
343
- const toRecheck = shouldRecheck(newTorrent, decision);
344
- const params = this.formatData(torrentFileName, encodedTorrentData, destinationDir, toRecheck);
345
- const addResponse = await this.call("core.add_torrent_file", params);
346
- if (addResponse.isErr()) {
347
- const addResponseError = addResponse.unwrapErr();
348
- if (addResponseError.message.includes("already")) {
349
- return InjectionResult.ALREADY_EXISTS;
350
- }
351
- else if (addResponseError) {
352
- logger.debug({
353
- label: this.label,
354
- message: `Injection failed: ${addResponseError.message}`,
355
- });
356
- return InjectionResult.FAILURE;
357
- }
358
- else {
359
- logger.debug({
360
- label: this.label,
361
- message: `Unknown injection failure: ${getLogString(newTorrent)}`,
362
- });
363
- return InjectionResult.FAILURE;
364
- }
365
- }
366
- if (addResponse.isOk()) {
367
- await this.setLabel(newTorrent, this.calculateLabel(searchee, torrentInfo));
368
- if (toRecheck) {
369
- // when paused, libtorrent doesnt start rechecking
370
- // leaves torrent ready to download - ~99%
371
- await wait(1000);
372
- await this.recheckTorrent(newTorrent.infoHash);
373
- void this.resumeInjection(newTorrent, decision, {
374
- checkOnce: false,
375
- });
376
- }
377
- }
378
- }
379
- catch (error) {
380
- logger.error({
381
- label: this.label,
382
- message: `Injection failed: ${error}`,
383
- });
384
- logger.debug(error);
385
- return InjectionResult.FAILURE;
386
- }
387
- return InjectionResult.SUCCESS;
388
- }
389
- async recheckTorrent(infoHash) {
390
- // Pause first as it may resume after recheck automatically
391
- await this.call("core.pause_torrent", [[infoHash]]);
392
- await this.call("core.force_recheck", [[infoHash]]);
393
- }
394
- /**
395
- * formats the json for rpc calls to inject
396
- * @param filename filename for the injecting torrent file
397
- * @param filedump string with encoded torrent file
398
- * @param destinationDir path to the torrent data
399
- * @param toRecheck boolean to recheck the torrent
400
- */
401
- formatData(filename, filedump, destinationDir, toRecheck) {
402
- return [
403
- filename,
404
- filedump,
405
- {
406
- add_paused: toRecheck,
407
- seed_mode: !toRecheck,
408
- download_location: destinationDir,
409
- },
410
- ];
411
- }
412
- /**
413
- * returns directory of an infohash in deluge as a string
414
- * @param meta SearcheeWithInfoHash or Metafile for torrent to lookup in client
415
- * @param options options object for extra flags
416
- * @param options.onlyCompleted boolean to only return a completed torrent
417
- * @return Result containing either a string with path or reason it was not provided
418
- */
419
- async getDownloadDir(meta, options) {
420
- let response;
421
- const params = [["save_path", "progress"], { hash: meta.infoHash }];
422
- try {
423
- response = await this.call("web.update_ui", params);
424
- }
425
- catch (e) {
426
- return resultOfErr("UNKNOWN_ERROR");
427
- }
428
- if (!response.isOk()) {
429
- return resultOfErr("UNKNOWN_ERROR");
430
- }
431
- const torrentResponse = response.unwrap().torrents;
432
- if (!torrentResponse) {
433
- return resultOfErr("UNKNOWN_ERROR");
434
- }
435
- const torrent = torrentResponse[meta.infoHash];
436
- if (!torrent) {
437
- return resultOfErr("NOT_FOUND");
438
- }
439
- if (options.onlyCompleted && torrent.progress !== 100) {
440
- return resultOfErr("TORRENT_NOT_COMPLETE");
441
- }
442
- return resultOf(torrent.save_path);
443
- }
444
- /**
445
- * returns map of hashes and download directories for all torrents
446
- * @param options.metas array of SearcheeWithInfoHash or Metafile for torrents to lookup in client
447
- * @param options.onlyCompleted boolean to only return completed torrents
448
- * @return Promise of a Map with hashes and download directories
449
- */
450
- async getAllDownloadDirs(options) {
451
- const dirs = new Map();
452
- let response;
453
- const params = [["save_path", "progress"], {}];
454
- try {
455
- response = await this.call("web.update_ui", params);
456
- }
457
- catch (e) {
458
- return dirs;
459
- }
460
- if (!response.isOk()) {
461
- return dirs;
462
- }
463
- const torrentResponse = response.unwrap().torrents;
464
- if (!torrentResponse) {
465
- return dirs;
466
- }
467
- for (const [hash, torrent] of Object.entries(torrentResponse)) {
468
- if (options.onlyCompleted && torrent.progress !== 100)
469
- continue;
470
- dirs.set(hash, torrent.save_path);
471
- }
472
- return dirs;
473
- }
474
- /**
475
- * checks if a torrent exists in deluge
476
- * @param inputHash the infoHash of the torrent to check
477
- * @return Result containing either a boolean or reason it was not provided
478
- */
479
- async isTorrentInClient(inputHash) {
480
- const infoHash = inputHash.toLowerCase();
481
- try {
482
- const torrentsRes = await this.call("web.update_ui", [[], {}]);
483
- if (torrentsRes.isErr()) {
484
- const err = torrentsRes.unwrapErr();
485
- throw new Error(`${err.code ? err.code + ": " : ""}${err.message}`);
486
- }
487
- const torrents = torrentsRes.unwrap().torrents;
488
- if (!torrents)
489
- throw new Error("No torrents found");
490
- for (const hash of Object.keys(torrents)) {
491
- if (hash.toLowerCase() === infoHash)
492
- return resultOf(true);
493
- }
494
- return resultOf(false);
495
- }
496
- catch (e) {
497
- return resultOfErr(e);
498
- }
499
- }
500
- /**
501
- * checks if a torrent is complete in deluge
502
- * @param infoHash the infoHash of the torrent to check
503
- * @return Result containing either a boolean or reason it was not provided
504
- */
505
- async isTorrentComplete(infoHash) {
506
- try {
507
- const torrentInfo = await this.getTorrentInfo(infoHash, {
508
- useVerbose: true,
509
- });
510
- return torrentInfo.complete ? resultOf(true) : resultOf(false);
511
- }
512
- catch (e) {
513
- return resultOfErr("NOT_FOUND");
514
- }
515
- }
516
- /**
517
- * checks if a torrent is checking in deluge
518
- * @param infoHash the infoHash of the torrent to check
519
- * @return Result containing either a boolean or reason it was not provided
520
- */
521
- async isTorrentChecking(infoHash) {
522
- try {
523
- const torrentInfo = await this.getTorrentInfo(infoHash, {
524
- useVerbose: true,
525
- });
526
- return resultOf(torrentInfo.state === "Checking");
527
- }
528
- catch (e) {
529
- return resultOfErr("NOT_FOUND");
530
- }
531
- }
532
- /**
533
- * @return All torrents in the client
534
- */
535
- async getAllTorrents() {
536
- const params = [["hash", "label"], {}];
537
- const response = await this.call("web.update_ui", params);
538
- if (!response.isOk()) {
539
- return [];
540
- }
541
- const torrents = response.unwrap().torrents;
542
- if (!torrents) {
543
- return [];
544
- }
545
- return Object.entries(torrents).map(([hash, torrent]) => ({
546
- infoHash: hash,
547
- category: torrent.label ?? "",
548
- }));
549
- }
550
- /**
551
- * Get all searchees from the client and update the db
552
- * @param options.newSearcheesOnly only return searchees that are not in the db
553
- * @param options.refresh undefined uses the cache, [] refreshes all searchees, or a list of infoHashes to refresh
554
- * @return an object containing all searchees and new searchees (refreshed searchees are considered new)
555
- */
556
- async getClientSearchees(options) {
557
- const searchees = [];
558
- const newSearchees = [];
559
- const infoHashes = new Set();
560
- const torrentsRes = await this.call("web.update_ui", [
561
- ["name", "label", "save_path", "total_size", "files", "trackers"],
562
- {},
563
- ]);
564
- if (torrentsRes.isErr()) {
565
- logger.error({
566
- label: this.label,
567
- message: "Failed to get torrents from client",
568
- });
569
- logger.debug(torrentsRes.unwrapErr());
570
- return { searchees, newSearchees };
571
- }
572
- const torrents = torrentsRes.unwrap().torrents;
573
- if (!torrents || !Object.keys(torrents).length) {
574
- logger.verbose({
575
- label: this.label,
576
- message: "No torrents found in client",
577
- });
578
- return { searchees, newSearchees };
579
- }
580
- for (const [hash, torrent] of Object.entries(torrents)) {
581
- const infoHash = hash.toLowerCase();
582
- infoHashes.add(infoHash);
583
- const dbTorrent = await db("client_searchee")
584
- .where("info_hash", infoHash)
585
- .where("client_host", this.clientHost)
586
- .first();
587
- const name = torrent.name;
588
- const savePath = torrent.save_path;
589
- const category = torrent.label ?? "";
590
- const modified = clientSearcheeModified(this.label, dbTorrent, name, savePath, {
591
- category,
592
- });
593
- const refresh = options?.refresh === undefined
594
- ? false
595
- : options.refresh.length === 0
596
- ? true
597
- : options.refresh.includes(infoHash);
598
- if (!modified && !refresh) {
599
- if (!options?.newSearcheesOnly) {
600
- searchees.push(createSearcheeFromDB(dbTorrent));
601
- }
602
- continue;
603
- }
604
- const files = torrent.files.map((file) => ({
605
- name: basename(file.path),
606
- path: file.path,
607
- length: file.size,
608
- }));
609
- if (!files.length) {
610
- logger.verbose({
611
- label: this.label,
612
- message: `No files found for ${torrent.name} [${sanitizeInfoHash(infoHash)}]: skipping`,
613
- });
614
- continue;
615
- }
616
- const trackers = organizeTrackers(torrent.trackers);
617
- const title = parseTitle(name, files) ?? name;
618
- const length = torrent.total_size;
619
- const searchee = {
620
- infoHash,
621
- name,
622
- title,
623
- files,
624
- length,
625
- clientHost: this.clientHost,
626
- savePath,
627
- category,
628
- trackers,
629
- };
630
- newSearchees.push(searchee);
631
- searchees.push(searchee);
632
- }
633
- await updateSearcheeClientDB(this.clientHost, newSearchees, infoHashes);
634
- return { searchees, newSearchees };
635
- }
636
- /**
637
- * returns information needed to complete/validate injection
638
- * @return Promise of TorrentInfo type
639
- * @param infoHash infohash to query for in the client
640
- * @param options options object for extra flags
641
- * @param options.useVerbose use verbose instead of error logging
642
- * @return Promise of TorrentInfo type
643
- */
644
- async getTorrentInfo(infoHash, options) {
645
- let torrent;
646
- try {
647
- const params = [
648
- [
649
- "name",
650
- "state",
651
- "progress",
652
- "save_path",
653
- "label",
654
- "total_remaining",
655
- ],
656
- { hash: infoHash },
657
- ];
658
- const response = (await this.call("web.update_ui", params)).unwrapOrThrow(new Error("failed to fetch the torrent list"));
659
- if (response.torrents) {
660
- torrent = response.torrents?.[infoHash];
661
- }
662
- else {
663
- throw new Error("Client returned unexpected response (object missing)");
664
- }
665
- if (torrent === undefined) {
666
- throw new Error(`Torrent not found in client (${infoHash})`);
667
- }
668
- const completedTorrent = (torrent.state === "Paused" &&
669
- (torrent.progress === 100 || !torrent.total_remaining)) ||
670
- torrent.state === "Seeding" ||
671
- torrent.progress === 100 ||
672
- !torrent.total_remaining;
673
- const torrentLabel = this.isLabelEnabled && torrent.label.length != 0
674
- ? torrent.label
675
- : undefined;
676
- return {
677
- name: torrent.name,
678
- complete: completedTorrent,
679
- state: torrent.state,
680
- save_path: torrent.save_path,
681
- label: torrentLabel,
682
- total_remaining: torrent.total_remaining,
683
- };
684
- }
685
- catch (e) {
686
- const log = options?.useVerbose ? logger.verbose : logger.error;
687
- log({
688
- label: this.label,
689
- message: `Failed to fetch torrent data for ${infoHash}: ${e.message}`,
690
- });
691
- logger.debug(e);
692
- throw new Error(`[${this.label}] web.update_ui: failed to fetch data from client`, {
693
- cause: e,
694
- });
695
- }
696
- }
697
- }
698
- //# sourceMappingURL=Deluge.js.map