cross-seed 6.13.6 → 7.0.0-2

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 (247) hide show
  1. package/README.md +12 -13
  2. package/dist/Result.d.ts +27 -0
  3. package/dist/action.d.ts +34 -0
  4. package/dist/action.js +2 -1
  5. package/dist/action.js.map +1 -1
  6. package/dist/arr.d.ts +31 -0
  7. package/dist/arr.js +107 -39
  8. package/dist/arr.js.map +1 -1
  9. package/dist/auth.d.ts +3 -0
  10. package/dist/auth.js +9 -6
  11. package/dist/auth.js.map +1 -1
  12. package/dist/clients/Deluge.d.ts +153 -0
  13. package/dist/clients/Deluge.js +6 -6
  14. package/dist/clients/Deluge.js.map +1 -1
  15. package/dist/clients/QBittorrent.d.ts +218 -0
  16. package/dist/clients/RTorrent.d.ts +43 -0
  17. package/dist/clients/RTorrent.js +6 -3
  18. package/dist/clients/RTorrent.js.map +1 -1
  19. package/dist/clients/TorrentClient.d.ts +108 -0
  20. package/dist/clients/TorrentClient.js +136 -67
  21. package/dist/clients/TorrentClient.js.map +1 -1
  22. package/dist/clients/Transmission.d.ts +43 -0
  23. package/dist/clients/Transmission.js +2 -2
  24. package/dist/clients/Transmission.js.map +1 -1
  25. package/dist/cmd.d.ts +2 -0
  26. package/dist/cmd.js +42 -110
  27. package/dist/cmd.js.map +1 -1
  28. package/dist/configSchema.d.ts +1 -0
  29. package/dist/configSchema.js +1 -666
  30. package/dist/configSchema.js.map +1 -1
  31. package/dist/configuration.d.ts +63 -0
  32. package/dist/configuration.js +263 -24
  33. package/dist/configuration.js.map +1 -1
  34. package/dist/constants.d.ts +108 -0
  35. package/dist/constants.js +2 -32
  36. package/dist/constants.js.map +1 -1
  37. package/dist/dataFiles.d.ts +8 -0
  38. package/dist/dataFiles.js +21 -6
  39. package/dist/dataFiles.js.map +1 -1
  40. package/dist/db.d.ts +3 -0
  41. package/dist/dbConfig.d.ts +4 -0
  42. package/dist/dbConfig.js +67 -0
  43. package/dist/dbConfig.js.map +1 -0
  44. package/dist/decide.d.ts +25 -0
  45. package/dist/decide.js +4 -4
  46. package/dist/decide.js.map +1 -1
  47. package/dist/diff.d.ts +1 -0
  48. package/dist/errors.d.ts +3 -0
  49. package/dist/errors.js +0 -9
  50. package/dist/errors.js.map +1 -1
  51. package/dist/indexers.d.ts +105 -0
  52. package/dist/indexers.js +82 -14
  53. package/dist/indexers.js.map +1 -1
  54. package/dist/inject.d.ts +2 -0
  55. package/dist/jobs.d.ts +29 -0
  56. package/dist/jobs.js +14 -9
  57. package/dist/jobs.js.map +1 -1
  58. package/dist/logger.d.ts +29 -0
  59. package/dist/logger.js +18 -4
  60. package/dist/logger.js.map +1 -1
  61. package/dist/migrations/00-initialSchema.d.ts +9 -0
  62. package/dist/migrations/01-jobs.d.ts +9 -0
  63. package/dist/migrations/02-timestamps.d.ts +9 -0
  64. package/dist/migrations/03-rateLimits.d.ts +9 -0
  65. package/dist/migrations/04-auth.d.ts +9 -0
  66. package/dist/migrations/04-auth.js +1 -1
  67. package/dist/migrations/04-auth.js.map +1 -1
  68. package/dist/migrations/05-caps.d.ts +9 -0
  69. package/dist/migrations/06-uniqueDecisions.d.ts +9 -0
  70. package/dist/migrations/07-limits.d.ts +9 -0
  71. package/dist/migrations/08-rss.d.ts +9 -0
  72. package/dist/migrations/09-clientAndDataSearchees.d.ts +9 -0
  73. package/dist/migrations/10-indexerNameAudioBookCaps.d.ts +9 -0
  74. package/dist/migrations/11-trackers.d.ts +9 -0
  75. package/dist/migrations/12-user-auth.d.ts +9 -0
  76. package/dist/migrations/12-user-auth.js +22 -0
  77. package/dist/migrations/12-user-auth.js.map +1 -0
  78. package/dist/migrations/13-settings.d.ts +9 -0
  79. package/dist/migrations/13-settings.js +23 -0
  80. package/dist/migrations/13-settings.js.map +1 -0
  81. package/dist/migrations/14-indexer-enabled-flag.d.ts +9 -0
  82. package/dist/migrations/14-indexer-enabled-flag.js +12 -0
  83. package/dist/migrations/14-indexer-enabled-flag.js.map +1 -0
  84. package/dist/migrations/15-remove-url-unique-constraint.d.ts +9 -0
  85. package/dist/migrations/15-remove-url-unique-constraint.js +14 -0
  86. package/dist/migrations/15-remove-url-unique-constraint.js.map +1 -0
  87. package/dist/migrations/16-prune-inactive-indexers.d.ts +9 -0
  88. package/dist/migrations/16-prune-inactive-indexers.js +17 -0
  89. package/dist/migrations/16-prune-inactive-indexers.js.map +1 -0
  90. package/dist/migrations/migrations.d.ts +13 -0
  91. package/dist/migrations/migrations.js +10 -0
  92. package/dist/migrations/migrations.js.map +1 -1
  93. package/dist/parseTorrent.d.ts +53 -0
  94. package/dist/pipeline.d.ts +41 -0
  95. package/dist/pipeline.js +57 -10
  96. package/dist/pipeline.js.map +1 -1
  97. package/dist/preFilter.d.ts +25 -0
  98. package/dist/preFilter.js +15 -15
  99. package/dist/preFilter.js.map +1 -1
  100. package/dist/problems/linking.d.ts +2 -0
  101. package/dist/problems/linking.js +80 -0
  102. package/dist/problems/linking.js.map +1 -0
  103. package/dist/problems/path.d.ts +22 -0
  104. package/dist/problems/path.js +96 -0
  105. package/dist/problems/path.js.map +1 -0
  106. package/dist/problems.d.ts +13 -0
  107. package/dist/problems.js +48 -0
  108. package/dist/problems.js.map +1 -0
  109. package/dist/pushNotifier.d.ts +19 -0
  110. package/dist/routes/baseApi.d.ts +2 -0
  111. package/dist/routes/baseApi.js +354 -0
  112. package/dist/routes/baseApi.js.map +1 -0
  113. package/dist/routes/indexerApi.d.ts +6 -0
  114. package/dist/routes/indexerApi.js +165 -0
  115. package/dist/routes/indexerApi.js.map +1 -0
  116. package/dist/routes/staticFrontendPlugin.d.ts +4 -0
  117. package/dist/routes/staticFrontendPlugin.js +61 -0
  118. package/dist/routes/staticFrontendPlugin.js.map +1 -0
  119. package/dist/runtimeConfig.d.ts +6 -0
  120. package/dist/runtimeConfig.js +17 -1
  121. package/dist/runtimeConfig.js.map +1 -1
  122. package/dist/searchee.d.ts +108 -0
  123. package/dist/searchee.js +36 -5
  124. package/dist/searchee.js.map +1 -1
  125. package/dist/server.d.ts +4 -0
  126. package/dist/server.js +38 -429
  127. package/dist/server.js.map +1 -1
  128. package/dist/services/indexerService.d.ts +96 -0
  129. package/dist/services/indexerService.js +287 -0
  130. package/dist/services/indexerService.js.map +1 -0
  131. package/dist/sessionCookies.d.ts +5 -0
  132. package/dist/sessionCookies.js +27 -0
  133. package/dist/sessionCookies.js.map +1 -0
  134. package/dist/startup.d.ts +25 -0
  135. package/dist/startup.js +105 -151
  136. package/dist/startup.js.map +1 -1
  137. package/dist/torrent.d.ts +69 -0
  138. package/dist/torrent.js +17 -13
  139. package/dist/torrent.js.map +1 -1
  140. package/dist/torznab.d.ts +60 -0
  141. package/dist/torznab.js +14 -89
  142. package/dist/torznab.js.map +1 -1
  143. package/dist/trpc/fastifyAdapter.d.ts +2 -0
  144. package/dist/trpc/fastifyAdapter.js +9 -0
  145. package/dist/trpc/fastifyAdapter.js.map +1 -0
  146. package/dist/trpc/index.d.ts +49 -0
  147. package/dist/trpc/index.js +53 -0
  148. package/dist/trpc/index.js.map +1 -0
  149. package/dist/trpc/routers/auth.d.ts +43 -0
  150. package/dist/trpc/routers/auth.js +116 -0
  151. package/dist/trpc/routers/auth.js.map +1 -0
  152. package/dist/trpc/routers/clients.d.ts +21 -0
  153. package/dist/trpc/routers/clients.js +65 -0
  154. package/dist/trpc/routers/clients.js.map +1 -0
  155. package/dist/trpc/routers/health.d.ts +14 -0
  156. package/dist/trpc/routers/health.js +20 -0
  157. package/dist/trpc/routers/health.js.map +1 -0
  158. package/dist/trpc/routers/index.d.ts +391 -0
  159. package/dist/trpc/routers/index.js +23 -0
  160. package/dist/trpc/routers/index.js.map +1 -0
  161. package/dist/trpc/routers/indexers.d.ts +75 -0
  162. package/dist/trpc/routers/indexers.js +79 -0
  163. package/dist/trpc/routers/indexers.js.map +1 -0
  164. package/dist/trpc/routers/jobs.d.ts +33 -0
  165. package/dist/trpc/routers/jobs.js +84 -0
  166. package/dist/trpc/routers/jobs.js.map +1 -0
  167. package/dist/trpc/routers/logs.d.ts +27 -0
  168. package/dist/trpc/routers/logs.js +91 -0
  169. package/dist/trpc/routers/logs.js.map +1 -0
  170. package/dist/trpc/routers/searchees.d.ts +51 -0
  171. package/dist/trpc/routers/searchees.js +156 -0
  172. package/dist/trpc/routers/searchees.js.map +1 -0
  173. package/dist/trpc/routers/settings.d.ts +83 -0
  174. package/dist/trpc/routers/settings.js +92 -0
  175. package/dist/trpc/routers/settings.js.map +1 -0
  176. package/dist/trpc/routers/stats.d.ts +42 -0
  177. package/dist/trpc/routers/stats.js +102 -0
  178. package/dist/trpc/routers/stats.js.map +1 -0
  179. package/dist/userAuth.d.ts +21 -0
  180. package/dist/userAuth.js +86 -0
  181. package/dist/userAuth.js.map +1 -0
  182. package/dist/utils/authUtils.d.ts +10 -0
  183. package/dist/utils/authUtils.js +24 -0
  184. package/dist/utils/authUtils.js.map +1 -0
  185. package/dist/utils/logWatcher.d.ts +28 -0
  186. package/dist/utils/logWatcher.js +218 -0
  187. package/dist/utils/logWatcher.js.map +1 -0
  188. package/dist/utils/object.d.ts +1 -0
  189. package/dist/utils/object.js +4 -0
  190. package/dist/utils/object.js.map +1 -0
  191. package/dist/utils.d.ts +175 -0
  192. package/dist/utils.js +61 -38
  193. package/dist/utils.js.map +1 -1
  194. package/dist/webui/assets/FieldInfo-Bxj_j8SJ.js +1 -0
  195. package/dist/webui/assets/Page-C3rteCZt.js +1 -0
  196. package/dist/webui/assets/array-field-DVSC6nHP.js +1 -0
  197. package/dist/webui/assets/badge-DTZMtS0e.js +1 -0
  198. package/dist/webui/assets/check-Bu3ldi63.js +1 -0
  199. package/dist/webui/assets/chevron-down-CRy8M0kJ.js +1 -0
  200. package/dist/webui/assets/clients-CW8oEZoQ.js +1 -0
  201. package/dist/webui/assets/connect-YBNsnjWT.js +1 -0
  202. package/dist/webui/assets/debug-mz8-WYZj.js +1 -0
  203. package/dist/webui/assets/directories-BSK28RgR.js +1 -0
  204. package/dist/webui/assets/duration-field-C6xoSlJg.js +1 -0
  205. package/dist/webui/assets/general-lJJxZhH7.js +1 -0
  206. package/dist/webui/assets/health-CXbsVrie.js +1 -0
  207. package/dist/webui/assets/index-Bi48hI2z.js +54 -0
  208. package/dist/webui/assets/index-C-Ul7GNg.css +1 -0
  209. package/dist/webui/assets/index-C2cH1Gst.js +1 -0
  210. package/dist/webui/assets/index-Cc5bDmJr.js +1 -0
  211. package/dist/webui/assets/jobs-CxmNab9w.js +1 -0
  212. package/dist/webui/assets/library-vaj2W8sE.js +1 -0
  213. package/dist/webui/assets/loader-circle-M0gu1gZ-.js +1 -0
  214. package/dist/webui/assets/logs-Cu9RyKS0.js +1 -0
  215. package/dist/webui/assets/search-2R5sIdT8.js +1 -0
  216. package/dist/webui/assets/select-field-BCqNLDrJ.js +1 -0
  217. package/dist/webui/assets/select-zHgqMzLj.js +1 -0
  218. package/dist/webui/assets/settings-CMYjpTbZ.js +1 -0
  219. package/dist/webui/assets/submit-button-BtcnyggQ.js +1 -0
  220. package/dist/webui/assets/switch-G0W3uJVN.js +1 -0
  221. package/dist/webui/assets/switch-field-IBd9ORNq.js +1 -0
  222. package/dist/webui/assets/table-DvgJU7Gh.js +1 -0
  223. package/dist/webui/assets/test-tube-BIwmoM45.js +1 -0
  224. package/dist/webui/assets/text-field-DruSbGhy.js +1 -0
  225. package/dist/webui/assets/time-BSMZjmyW.js +1 -0
  226. package/dist/webui/assets/trackers-D-OpAe63.js +7 -0
  227. package/dist/webui/assets/use-form-validation-context-BkAfWAh0.js +1 -0
  228. package/dist/webui/assets/use-settings-form-submit-CDRh-E9U.js +2 -0
  229. package/dist/webui/assets/useQuery-A4Hv_4uX.js +1 -0
  230. package/dist/webui/index.html +13 -0
  231. package/node_modules/@cross-seed/shared/dist/configSchema.d.ts +261 -0
  232. package/node_modules/@cross-seed/shared/dist/configSchema.d.ts.map +1 -0
  233. package/node_modules/@cross-seed/shared/dist/configSchema.js +53 -0
  234. package/node_modules/@cross-seed/shared/dist/configSchema.js.map +1 -0
  235. package/node_modules/@cross-seed/shared/dist/constants.d.ts +122 -0
  236. package/node_modules/@cross-seed/shared/dist/constants.d.ts.map +1 -0
  237. package/node_modules/@cross-seed/shared/dist/constants.js +127 -0
  238. package/node_modules/@cross-seed/shared/dist/constants.js.map +1 -0
  239. package/node_modules/@cross-seed/shared/dist/tsconfig.tsbuildinfo +1 -0
  240. package/node_modules/@cross-seed/shared/dist/utils.d.ts +6 -0
  241. package/node_modules/@cross-seed/shared/dist/utils.d.ts.map +1 -0
  242. package/node_modules/@cross-seed/shared/dist/utils.js +9 -0
  243. package/node_modules/@cross-seed/shared/dist/utils.js.map +1 -0
  244. package/node_modules/@cross-seed/shared/package.json +22 -0
  245. package/package.json +35 -11
  246. package/dist/config.template.cjs +0 -353
  247. package/dist/config.template.cjs.map +0 -1
@@ -0,0 +1,80 @@
1
+ import { testLinking } from "../action.js";
2
+ import { Label, logger } from "../logger.js";
3
+ import { getRuntimeConfig } from "../runtimeConfig.js";
4
+ import { buildPathProblem, diagnoseDirForProblems, } from "./path.js";
5
+ async function collectDataLinkingProblemDescriptors(linkDirs, dataDirs) {
6
+ const problems = [];
7
+ if (!linkDirs.length || !dataDirs.length)
8
+ return problems;
9
+ const linkDirStats = await Promise.all(linkDirs.map(async (dir, index) => {
10
+ const { descriptor, verification } = await diagnoseDirForProblems(dir, `linkDir${index}`, "linkDirs", { read: true, write: true });
11
+ return {
12
+ path: dir,
13
+ problem: descriptor,
14
+ ok: verification.ok,
15
+ dev: verification.ok ? verification.stats.dev : null,
16
+ };
17
+ }));
18
+ for (const { problem } of linkDirStats) {
19
+ if (problem)
20
+ problems.push(problem);
21
+ }
22
+ const validLinkDirs = linkDirStats
23
+ .filter(({ ok, dev }) => ok && typeof dev === "number")
24
+ .map(({ path, dev }) => ({ path, dev: dev }));
25
+ if (!validLinkDirs.length)
26
+ return problems;
27
+ for (const [index, dataDir] of dataDirs.entries()) {
28
+ const { descriptor, verification } = await diagnoseDirForProblems(dataDir, `dataDir${index}`, "dataDirs", { read: true });
29
+ if (!verification.ok) {
30
+ if (descriptor) {
31
+ problems.push(descriptor);
32
+ }
33
+ continue;
34
+ }
35
+ try {
36
+ const dataDev = verification.stats.dev;
37
+ const matchingLinkDir = validLinkDirs.find((linkDir) => linkDir.dev === dataDev);
38
+ if (!matchingLinkDir) {
39
+ problems.push({
40
+ category: "linkDirs",
41
+ name: "linkDir",
42
+ path: validLinkDirs.map((dir) => dir.path).join(", "),
43
+ issue: "cross-platform-linking",
44
+ message: "No linkDir shares a filesystem with this dataDir, so linking will fail. Add a linkDir on the same device.",
45
+ severity: "error",
46
+ });
47
+ continue;
48
+ }
49
+ const result = await testLinking(dataDir, `healthCheckSrc.cross-seed`, `healthCheckDest.cross-seed`);
50
+ if (!result) {
51
+ problems.push({
52
+ category: "linkDirs",
53
+ name: "linkDir",
54
+ path: matchingLinkDir.path,
55
+ issue: "linking-failed",
56
+ message: "Linking test failed. Check filesystem permissions and mounts.",
57
+ severity: "warning",
58
+ });
59
+ }
60
+ }
61
+ catch (error) {
62
+ logger.debug({ label: Label.INJECT, message: error });
63
+ problems.push({
64
+ category: "linkDirs",
65
+ name: "linkDir",
66
+ path: dataDir,
67
+ issue: "linking-failed",
68
+ message: "Linking test threw an error. See logs for details.",
69
+ severity: "error",
70
+ });
71
+ }
72
+ }
73
+ return problems;
74
+ }
75
+ export async function collectDataLinkingProblems() {
76
+ const { linkDirs, dataDirs } = getRuntimeConfig();
77
+ const descriptors = await collectDataLinkingProblemDescriptors(linkDirs ?? [], dataDirs ?? []);
78
+ return descriptors.map(buildPathProblem);
79
+ }
80
+ //# sourceMappingURL=linking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linking.js","sourceRoot":"","sources":["../../src/problems/linking.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { Problem } from "../problems.js";
2
+ import { DirVerificationResult } from "../utils.js";
3
+ export type PathIssue = "missing" | "not-directory" | "unreadable" | "unwritable" | "cross-platform-linking" | "linking-failed";
4
+ export type PathProblemCategory = "torrentDir" | "outputDir" | "injectDir" | "linkDirs" | "dataDirs";
5
+ export interface PathProblemDescriptor {
6
+ category: PathProblemCategory;
7
+ name: string;
8
+ path: string;
9
+ issue: PathIssue;
10
+ message: string;
11
+ severity: Problem["severity"];
12
+ }
13
+ export interface DirectoryAssessment {
14
+ descriptor: PathProblemDescriptor | null;
15
+ verification: DirVerificationResult;
16
+ }
17
+ export declare function buildPathProblem(descriptor: PathProblemDescriptor): Problem;
18
+ export declare function diagnoseDirForProblems(path: string, name: string, category: PathProblemCategory, options: {
19
+ read?: boolean;
20
+ write?: boolean;
21
+ }): Promise<DirectoryAssessment>;
22
+ export declare function collectPathProblems(): Promise<Problem[]>;
@@ -0,0 +1,96 @@
1
+ import { constants } from "fs/promises";
2
+ import { getRuntimeConfig } from "../runtimeConfig.js";
3
+ import { verifyDir } from "../utils.js";
4
+ export function buildPathProblem(descriptor) {
5
+ const { category, name, path, issue, message, severity } = descriptor;
6
+ return {
7
+ id: `path:${category}:${issue}:${path}`,
8
+ severity,
9
+ summary: `${name}: ${message}`,
10
+ details: `Path: ${path}`,
11
+ metadata: { category, path, issue },
12
+ };
13
+ }
14
+ export async function diagnoseDirForProblems(path, name, category, options) {
15
+ const mode = (options.read ? constants.R_OK : 0) |
16
+ (options.write ? constants.W_OK : 0);
17
+ const verification = await verifyDir(path, name, mode);
18
+ if (verification.ok) {
19
+ return { descriptor: null, verification };
20
+ }
21
+ let descriptor;
22
+ switch (verification.reason) {
23
+ case "missing":
24
+ descriptor = {
25
+ category,
26
+ name,
27
+ path,
28
+ issue: "missing",
29
+ message: "Directory does not exist.",
30
+ severity: "error",
31
+ };
32
+ break;
33
+ case "not-directory":
34
+ descriptor = {
35
+ category,
36
+ name,
37
+ path,
38
+ issue: "not-directory",
39
+ message: "Path is not a directory.",
40
+ severity: "error",
41
+ };
42
+ break;
43
+ case "unreadable":
44
+ descriptor = {
45
+ category,
46
+ name,
47
+ path,
48
+ issue: "unreadable",
49
+ message: "cross-seed cannot read from this directory.",
50
+ severity: "error",
51
+ };
52
+ break;
53
+ case "unwritable":
54
+ descriptor = {
55
+ category,
56
+ name,
57
+ path,
58
+ issue: "unwritable",
59
+ message: "cross-seed cannot write to this directory.",
60
+ severity: "error",
61
+ };
62
+ break;
63
+ default:
64
+ descriptor = {
65
+ category,
66
+ name,
67
+ path,
68
+ issue: "unreadable",
69
+ message: "cross-seed cannot access this directory.",
70
+ severity: "error",
71
+ };
72
+ break;
73
+ }
74
+ return { descriptor, verification };
75
+ }
76
+ export async function collectPathProblems() {
77
+ const { torrentDir, outputDir, injectDir } = getRuntimeConfig();
78
+ const descriptors = [];
79
+ if (torrentDir) {
80
+ const { descriptor } = await diagnoseDirForProblems(torrentDir, "torrentDir", "torrentDir", { read: true });
81
+ if (descriptor)
82
+ descriptors.push(descriptor);
83
+ }
84
+ if (outputDir) {
85
+ const { descriptor } = await diagnoseDirForProblems(outputDir, "outputDir", "outputDir", { read: true, write: true });
86
+ if (descriptor)
87
+ descriptors.push(descriptor);
88
+ }
89
+ if (injectDir) {
90
+ const { descriptor } = await diagnoseDirForProblems(injectDir, "injectDir", "injectDir", { read: true, write: true });
91
+ if (descriptor)
92
+ descriptors.push(descriptor);
93
+ }
94
+ return descriptors.map(buildPathProblem);
95
+ }
96
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/problems/path.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export type ProblemSeverity = "error" | "warning" | "info";
2
+ export interface ProblemMetadata {
3
+ [key: string]: unknown;
4
+ }
5
+ export interface Problem {
6
+ id: string;
7
+ severity: ProblemSeverity;
8
+ summary: string;
9
+ details?: string;
10
+ metadata?: ProblemMetadata;
11
+ }
12
+ export type ProblemProvider = () => Promise<Problem[]> | Problem[];
13
+ export declare function collectProblems(): Promise<Problem[]>;
@@ -0,0 +1,48 @@
1
+ import { collectArrProblems } from "./arr.js";
2
+ import { collectClientLinkingProblems, collectClientProblems, } from "./clients/TorrentClient.js";
3
+ import { collectIndexerProblems } from "./indexers.js";
4
+ import { collectDataLinkingProblems } from "./problems/linking.js";
5
+ import { collectPathProblems } from "./problems/path.js";
6
+ import { collectRecommendationProblems } from "./runtimeConfig.js";
7
+ import { collectSearcheeProblems } from "./searchee.js";
8
+ const registeredProblemProviders = [
9
+ { id: "indexers", provider: collectIndexerProblems },
10
+ { id: "clients", provider: collectClientProblems },
11
+ { id: "client-linking", provider: collectClientLinkingProblems },
12
+ { id: "arr", provider: collectArrProblems },
13
+ { id: "paths", provider: collectPathProblems },
14
+ { id: "linking", provider: collectDataLinkingProblems },
15
+ { id: "searchees", provider: collectSearcheeProblems },
16
+ { id: "recommendations", provider: collectRecommendationProblems },
17
+ ];
18
+ function getProviderName(registration, index) {
19
+ return registration.id || registration.provider.name || `provider-${index}`;
20
+ }
21
+ export async function collectProblems() {
22
+ const results = await Promise.allSettled(registeredProblemProviders.map((registration) => registration.provider()));
23
+ const problems = [];
24
+ results.forEach((result, index) => {
25
+ const registration = registeredProblemProviders[index];
26
+ if (result.status === "fulfilled") {
27
+ for (const problem of result.value) {
28
+ if (problem)
29
+ problems.push(problem);
30
+ }
31
+ return;
32
+ }
33
+ const error = result.reason;
34
+ const message = error instanceof Error ? error.message : String(error ?? "unknown");
35
+ const providerName = getProviderName(registration, index);
36
+ problems.push({
37
+ id: `problem-provider-error:${providerName}`,
38
+ severity: "error",
39
+ summary: "Problem provider failed to collect problems.",
40
+ details: message,
41
+ metadata: {
42
+ provider: providerName,
43
+ },
44
+ });
45
+ });
46
+ return problems;
47
+ }
48
+ //# sourceMappingURL=problems.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"problems.js","sourceRoot":"","sources":["../src/problems.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EACN,4BAA4B,EAC5B,qBAAqB,GACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAuBxD,MAAM,0BAA0B,GAAgC;IAC/D,EAAE,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,sBAAsB,EAAE;IACpD,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,qBAAqB,EAAE;IAClD,EAAE,EAAE,EAAE,gBAAgB,EAAE,QAAQ,EAAE,4BAA4B,EAAE;IAChE,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IAC3C,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE;IAC9C,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,0BAA0B,EAAE;IACvD,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,uBAAuB,EAAE;IACtD,EAAE,EAAE,EAAE,iBAAiB,EAAE,QAAQ,EAAE,6BAA6B,EAAE;CAClE,CAAC;AAEF,SAAS,eAAe,CACvB,YAAuC,EACvC,KAAa;IAEb,OAAO,YAAY,CAAC,EAAE,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,IAAI,YAAY,KAAK,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACpC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACvC,0BAA0B,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAC/C,YAAY,CAAC,QAAQ,EAAE,CACvB,CACD,CAAC;IAEF,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QACjC,MAAM,YAAY,GAAG,0BAA0B,CAAC,KAAK,CAAC,CAAC;QACvD,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YACnC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACpC,IAAI,OAAO;oBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrC,CAAC;YACD,OAAO;QACR,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,MAAM,OAAO,GACZ,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC;QAErE,MAAM,YAAY,GAAG,eAAe,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;QAC1D,QAAQ,CAAC,IAAI,CAAC;YACb,EAAE,EAAE,0BAA0B,YAAY,EAAE;YAC5C,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,8CAA8C;YACvD,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE;gBACT,QAAQ,EAAE,YAAY;aACtB;SACD,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC;AACjB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { ActionResult } from "./constants.js";
2
+ import { ResultAssessment } from "./decide.js";
3
+ import { SearcheeWithLabel } from "./searchee.js";
4
+ export declare let pushNotifier: PushNotifier;
5
+ type TrackerName = string;
6
+ interface PushNotification {
7
+ title?: string;
8
+ body: string;
9
+ extra?: Record<string, unknown>;
10
+ }
11
+ export declare class PushNotifier {
12
+ urls: string[];
13
+ constructor(urls: string[]);
14
+ notify({ title, body, ...rest }: PushNotification): Promise<void[]>;
15
+ }
16
+ export declare function sendResultsNotification(searchee: SearcheeWithLabel, results: [ResultAssessment, TrackerName, ActionResult][]): void;
17
+ export declare function initializePushNotifier(): void;
18
+ export declare function sendTestNotification(): Promise<void>;
19
+ export {};
@@ -0,0 +1,2 @@
1
+ import { FastifyInstance } from "fastify";
2
+ export declare function baseApiPlugin(app: FastifyInstance): Promise<void>;
@@ -0,0 +1,354 @@
1
+ import chalk from "chalk";
2
+ import { existsSync } from "fs";
3
+ import { parse as qsParse } from "querystring";
4
+ import { inspect } from "util";
5
+ import { z } from "zod";
6
+ import { Decision, InjectionResult, SaveResult, } from "../constants.js";
7
+ import { checkJobs, getJobLastRun, getJobs, JobName } from "../jobs.js";
8
+ import { Label, logger } from "../logger.js";
9
+ import { checkNewCandidateMatch, searchForLocalTorrentByCriteria, } from "../pipeline.js";
10
+ import { getRuntimeConfig } from "../runtimeConfig.js";
11
+ import { indexTorrentsAndDataDirs } from "../torrent.js";
12
+ import { formatAsList, humanReadableDate, sanitizeInfoHash } from "../utils.js";
13
+ import { authorize } from "../utils/authUtils.js";
14
+ const ANNOUNCE_SCHEMA = z
15
+ .object({
16
+ name: z
17
+ .string()
18
+ .transform((name) => name.trim())
19
+ .refine((name) => name.length > 0),
20
+ guid: z.string().url(),
21
+ link: z.string().url(),
22
+ tracker: z
23
+ .string()
24
+ .transform((tracker) => tracker.trim())
25
+ .refine((tracker) => tracker.length > 0),
26
+ cookie: z
27
+ .string()
28
+ .nullish()
29
+ .transform((cookie) => cookie?.trim() || undefined),
30
+ })
31
+ .strict()
32
+ .required()
33
+ .refine((data) => data.guid === data.link);
34
+ const WEBHOOK_SCHEMA = z
35
+ .object({
36
+ infoHash: z.string().length(40),
37
+ path: z.string().refine((path) => path && existsSync(path)),
38
+ ignoreCrossSeeds: z
39
+ .boolean()
40
+ .or(z.string().transform((v) => v === "true")),
41
+ ignoreExcludeRecentSearch: z
42
+ .boolean()
43
+ .or(z.string().transform((v) => v === "true")),
44
+ ignoreExcludeOlder: z
45
+ .boolean()
46
+ .or(z.string().transform((v) => v === "true")),
47
+ ignoreBlockList: z
48
+ .boolean()
49
+ .or(z.string().transform((v) => v === "true")),
50
+ includeSingleEpisodes: z
51
+ .boolean()
52
+ .or(z.string().transform((v) => v === "true")),
53
+ includeNonVideos: z
54
+ .boolean()
55
+ .or(z.string().transform((v) => v === "true")),
56
+ })
57
+ .strict()
58
+ .partial()
59
+ .refine((data) => !data.infoHash !== !data.path);
60
+ const JOB_SCHEMA = z
61
+ .object({
62
+ name: z.string(),
63
+ ignoreExcludeRecentSearch: z
64
+ .boolean()
65
+ .or(z.string().transform((v) => v === "true")),
66
+ ignoreExcludeOlder: z
67
+ .boolean()
68
+ .or(z.string().transform((v) => v === "true")),
69
+ })
70
+ .strict()
71
+ .partial()
72
+ .refine((data) => Object.values(JobName).includes(data.name));
73
+ function parseData(data) {
74
+ let parsed;
75
+ try {
76
+ parsed = JSON.parse(data);
77
+ }
78
+ catch (_) {
79
+ parsed = qsParse(data);
80
+ }
81
+ // transformations
82
+ try {
83
+ if ("infoHash" in parsed) {
84
+ parsed.infoHash = parsed.infoHash.toLowerCase();
85
+ }
86
+ if ("size" in parsed && typeof parsed.size === "string") {
87
+ parsed.size = Number(parsed.size);
88
+ }
89
+ }
90
+ catch (e) {
91
+ throw new Error(`Unable to parse request body: "${data}"`);
92
+ }
93
+ return parsed;
94
+ }
95
+ function determineResponse(result) {
96
+ const injected = result.actionResult === InjectionResult.SUCCESS;
97
+ const added = injected ||
98
+ result.actionResult === InjectionResult.FAILURE ||
99
+ result.actionResult === SaveResult.SAVED;
100
+ const exists = result.decision === Decision.INFO_HASH_ALREADY_EXISTS ||
101
+ result.actionResult === InjectionResult.ALREADY_EXISTS;
102
+ const incomplete = result.actionResult === InjectionResult.TORRENT_NOT_COMPLETE;
103
+ let status;
104
+ let state;
105
+ if (added) {
106
+ status = 200;
107
+ state = injected ? "Injected" : "Saved";
108
+ }
109
+ else if (exists) {
110
+ status = 200;
111
+ state = "Already exists";
112
+ }
113
+ else if (incomplete) {
114
+ status = 202;
115
+ state = "Saved";
116
+ }
117
+ else {
118
+ throw new Error(`Unexpected result: ${result.decision} | ${result.actionResult}`);
119
+ }
120
+ return { status, state };
121
+ }
122
+ export async function baseApiPlugin(app) {
123
+ // Add query string parsing support
124
+ app.addContentTypeParser("application/x-www-form-urlencoded", { parseAs: "string" }, function (req, body, done) {
125
+ try {
126
+ const parsed = parseData(body);
127
+ done(null, parsed);
128
+ }
129
+ catch (error) {
130
+ done(error, undefined);
131
+ }
132
+ });
133
+ // Add JSON body parsing support
134
+ app.addContentTypeParser("application/json", { parseAs: "string" }, function (req, body, done) {
135
+ try {
136
+ const parsed = parseData(body);
137
+ done(null, parsed);
138
+ }
139
+ catch (error) {
140
+ done(error, undefined);
141
+ }
142
+ });
143
+ /**
144
+ * Trigger a search for a torrent
145
+ */
146
+ app.post("/webhook", async (request, reply) => {
147
+ if (!(await authorize(request, reply)))
148
+ return;
149
+ const injectJob = getJobs().find((job) => job.name === JobName.INJECT);
150
+ if (injectJob) {
151
+ injectJob.runAheadOfSchedule = true;
152
+ void checkJobs({ isFirstRun: false, useQueue: true });
153
+ }
154
+ await indexTorrentsAndDataDirs();
155
+ let data;
156
+ try {
157
+ data = request.body;
158
+ }
159
+ catch (e) {
160
+ const message = e.message;
161
+ logger.error({
162
+ label: Label.WEBHOOK,
163
+ message,
164
+ });
165
+ return reply.code(400).send(message);
166
+ }
167
+ try {
168
+ data = WEBHOOK_SCHEMA.parse(data);
169
+ }
170
+ catch {
171
+ const message = `A valid infoHash or an accessible path must be provided (infoHash is recommended: see https://www.cross-seed.org/docs/reference/api#post-apiwebhook): ${inspect(data)}`;
172
+ logger.error({ label: Label.WEBHOOK, message });
173
+ return reply.code(400).send(message);
174
+ }
175
+ void reply.code(204).send();
176
+ const criteriaStr = data.infoHash
177
+ ? inspect(data).replace(data.infoHash, sanitizeInfoHash(data.infoHash))
178
+ : inspect(data);
179
+ logger.info({
180
+ label: Label.WEBHOOK,
181
+ message: `Received search request: ${criteriaStr}`,
182
+ });
183
+ const configOverride = {
184
+ includeSingleEpisodes: data.includeSingleEpisodes,
185
+ includeNonVideos: data.includeNonVideos,
186
+ excludeRecentSearch: data.ignoreExcludeRecentSearch ? 1 : undefined,
187
+ excludeOlder: data.ignoreExcludeOlder
188
+ ? Number.MAX_SAFE_INTEGER
189
+ : undefined,
190
+ blockList: data.ignoreBlockList ? [] : undefined,
191
+ };
192
+ try {
193
+ let numFound = null;
194
+ if (data) {
195
+ numFound = await searchForLocalTorrentByCriteria(data, {
196
+ configOverride,
197
+ ignoreCrossSeeds: data.ignoreCrossSeeds ?? true,
198
+ });
199
+ }
200
+ if (numFound !== null) {
201
+ logger.info({
202
+ label: Label.WEBHOOK,
203
+ message: `Found ${numFound} torrents for ${criteriaStr}`,
204
+ });
205
+ }
206
+ }
207
+ catch (e) {
208
+ logger.error({
209
+ label: Label.WEBHOOK,
210
+ message: e.message,
211
+ });
212
+ logger.debug(e);
213
+ }
214
+ });
215
+ /**
216
+ * Reverse lookup for a torrent
217
+ */
218
+ app.post("/announce", async (request, reply) => {
219
+ if (!(await authorize(request, reply)))
220
+ return;
221
+ const { dataDirs, torrentDir, useClientTorrents } = getRuntimeConfig();
222
+ let data;
223
+ try {
224
+ data = request.body;
225
+ }
226
+ catch (e) {
227
+ const message = e.message;
228
+ logger.error({
229
+ label: Label.ANNOUNCE,
230
+ message,
231
+ });
232
+ return reply.code(400).send(message);
233
+ }
234
+ try {
235
+ data = ANNOUNCE_SCHEMA.parse(data);
236
+ }
237
+ catch ({ errors }) {
238
+ const message = `Missing required params (https://www.cross-seed.org/docs/v6-migration#autobrr-update): {${formatAsList(errors.map(({ path }) => path.join(".")), { sort: true, type: "unit" })}} in ${inspect(data)}\n${inspect(errors)}`;
239
+ logger.error({ label: Label.ANNOUNCE, message });
240
+ return reply.code(400).send(message);
241
+ }
242
+ logger.verbose({
243
+ label: Label.ANNOUNCE,
244
+ message: `Received announce from ${data.tracker}: ${data.name}`,
245
+ });
246
+ const candidate = data;
247
+ const candidateLog = `${chalk.bold.white(candidate.name)} from ${candidate.tracker}`;
248
+ try {
249
+ if (!useClientTorrents && !torrentDir && !dataDirs.length) {
250
+ throw new Error(`Announce requires at least one of useClientTorrents, torrentDir, or dataDirs to be set`);
251
+ }
252
+ await indexTorrentsAndDataDirs();
253
+ const result = await checkNewCandidateMatch(candidate, Label.ANNOUNCE);
254
+ if (!result.decision) {
255
+ return await reply.code(204).send();
256
+ }
257
+ const { status, state } = determineResponse(result);
258
+ if (result.actionResult !== InjectionResult.SUCCESS) {
259
+ logger.info({
260
+ label: Label.ANNOUNCE,
261
+ message: `${state} ${candidateLog} (status: ${status})`,
262
+ });
263
+ }
264
+ return await reply.code(status).send();
265
+ }
266
+ catch (e) {
267
+ logger.error({
268
+ label: Label.ANNOUNCE,
269
+ message: e.message,
270
+ });
271
+ logger.debug(e);
272
+ return reply.code(500).send(e.message);
273
+ }
274
+ });
275
+ /**
276
+ * Run a job ahead of schedule if eligible
277
+ */
278
+ app.post("/job", async (request, reply) => {
279
+ if (!(await authorize(request, reply)))
280
+ return;
281
+ let data;
282
+ try {
283
+ data = request.body;
284
+ }
285
+ catch (e) {
286
+ const message = e.message;
287
+ logger.error({
288
+ label: Label.SERVER,
289
+ message,
290
+ });
291
+ return reply.code(400).send(message);
292
+ }
293
+ try {
294
+ data = JOB_SCHEMA.parse(data);
295
+ }
296
+ catch {
297
+ const message = `Job name must be one of ${formatAsList(Object.values(JobName), { sort: true, style: "narrow", type: "unit" })} - received: ${inspect(data)}`;
298
+ logger.error({ label: Label.SERVER, message });
299
+ return reply.code(400).send(message);
300
+ }
301
+ logger.info({
302
+ label: Label.SERVER,
303
+ message: `Received job request: ${inspect(data)}`,
304
+ });
305
+ const job = getJobs().find((j) => j.name === data.name);
306
+ if (!job) {
307
+ const message = `${data.name}: unable to run, disabled in config`;
308
+ logger.error({ label: Label.SCHEDULER, message });
309
+ return reply.code(404).send(message);
310
+ }
311
+ if (job.isActive) {
312
+ const message = `${job.name}: already running`;
313
+ logger.error({ label: Label.SCHEDULER, message });
314
+ return reply.code(409).send(message);
315
+ }
316
+ const lastRun = (await getJobLastRun(job.name)) ?? 0;
317
+ if (Date.now() < lastRun) {
318
+ const message = `${job.name}: not eligible to run ahead of schedule, next scheduled run is at ${humanReadableDate(lastRun + job.cadence)} (triggering an early run is allowed after ${humanReadableDate(lastRun)})`;
319
+ logger.error({ label: Label.SCHEDULER, message });
320
+ return reply.code(409).send(message);
321
+ }
322
+ job.runAheadOfSchedule = true;
323
+ if (job.name === JobName.SEARCH || job.name === JobName.RSS) {
324
+ job.delayNextRun = true;
325
+ }
326
+ job.configOverride = {
327
+ excludeRecentSearch: data.ignoreExcludeRecentSearch ? 1 : undefined,
328
+ excludeOlder: data.ignoreExcludeOlder
329
+ ? Number.MAX_SAFE_INTEGER
330
+ : undefined,
331
+ };
332
+ void checkJobs({ isFirstRun: false, useQueue: true });
333
+ const message = `${job.name}: running ahead of schedule`;
334
+ logger.info({ label: Label.SCHEDULER, message });
335
+ return reply.code(200).send(message);
336
+ });
337
+ /**
338
+ * current: sends "200 OK"
339
+ * future: respond with current state and job status details via API
340
+ * uses: potential usage of this in dashbrr
341
+ */
342
+ app.get("/status", async (request, reply) => {
343
+ if (!(await authorize(request, reply)))
344
+ return;
345
+ return reply.code(200).send("OK");
346
+ });
347
+ /**
348
+ * cross-seed health check
349
+ */
350
+ app.get("/ping", async (request, reply) => {
351
+ return reply.code(200).send("OK");
352
+ });
353
+ }
354
+ //# sourceMappingURL=baseApi.js.map