cross-seed 5.9.1 → 6.0.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.
Files changed (64) hide show
  1. package/README.md +1 -1
  2. package/dist/Result.js +2 -0
  3. package/dist/Result.js.map +1 -1
  4. package/dist/action.js +117 -57
  5. package/dist/action.js.map +1 -1
  6. package/dist/auth.js +5 -5
  7. package/dist/auth.js.map +1 -1
  8. package/dist/clients/Deluge.js +68 -36
  9. package/dist/clients/Deluge.js.map +1 -1
  10. package/dist/clients/QBittorrent.js +40 -30
  11. package/dist/clients/QBittorrent.js.map +1 -1
  12. package/dist/clients/RTorrent.js +61 -23
  13. package/dist/clients/RTorrent.js.map +1 -1
  14. package/dist/clients/TorrentClient.js.map +1 -1
  15. package/dist/clients/Transmission.js +18 -8
  16. package/dist/clients/Transmission.js.map +1 -1
  17. package/dist/cmd.js +76 -75
  18. package/dist/cmd.js.map +1 -1
  19. package/dist/config.template.cjs +226 -142
  20. package/dist/config.template.cjs.map +1 -1
  21. package/dist/configSchema.js +184 -0
  22. package/dist/configSchema.js.map +1 -0
  23. package/dist/configuration.js +35 -9
  24. package/dist/configuration.js.map +1 -1
  25. package/dist/constants.js +27 -11
  26. package/dist/constants.js.map +1 -1
  27. package/dist/dataFiles.js +17 -8
  28. package/dist/dataFiles.js.map +1 -1
  29. package/dist/decide.js +100 -20
  30. package/dist/decide.js.map +1 -1
  31. package/dist/diff.js.map +1 -1
  32. package/dist/errors.js.map +1 -1
  33. package/dist/indexers.js +3 -3
  34. package/dist/indexers.js.map +1 -1
  35. package/dist/jobs.js +15 -7
  36. package/dist/jobs.js.map +1 -1
  37. package/dist/logger.js +9 -9
  38. package/dist/logger.js.map +1 -1
  39. package/dist/migrations/00-initialSchema.js.map +1 -1
  40. package/dist/migrations/02-timestamps.js +3 -1
  41. package/dist/migrations/02-timestamps.js.map +1 -1
  42. package/dist/parseTorrent.js +11 -2
  43. package/dist/parseTorrent.js.map +1 -1
  44. package/dist/pipeline.js +34 -25
  45. package/dist/pipeline.js.map +1 -1
  46. package/dist/preFilter.js +17 -6
  47. package/dist/preFilter.js.map +1 -1
  48. package/dist/pushNotifier.js +29 -10
  49. package/dist/pushNotifier.js.map +1 -1
  50. package/dist/runtimeConfig.js.map +1 -1
  51. package/dist/searchee.js +8 -1
  52. package/dist/searchee.js.map +1 -1
  53. package/dist/server.js.map +1 -1
  54. package/dist/startup.js +62 -23
  55. package/dist/startup.js.map +1 -1
  56. package/dist/torrent.js +14 -29
  57. package/dist/torrent.js.map +1 -1
  58. package/dist/torznab.js +56 -32
  59. package/dist/torznab.js.map +1 -1
  60. package/dist/utils.js +32 -5
  61. package/dist/utils.js.map +1 -1
  62. package/package.json +23 -13
  63. package/dist/config.template.docker.cjs +0 -236
  64. package/dist/config.template.docker.cjs.map +0 -1
@@ -1,231 +1,315 @@
1
1
  "use strict";
2
- // If you find yourself always using the same command-line flag, you can set
3
- // it here as a default.
2
+ // If you find yourself always using the same command-line flag, you can set it
3
+ // here as a default.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  module.exports = {
6
6
  /**
7
- * Pause at least this much in between each search. Higher is safer.
8
- * It is not recommended to set this to less than 2 seconds.
9
- */
10
- delay: 10,
7
+ * WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
8
+ *
9
+ * THE NEXT 8 OPTIONS CONTAIN POTENTIALLY SENSITIVE INFORMATION
10
+ * THERE IS A NOTE WHERE YOU WILL WANT TO START COPYING FROM
11
+ * IF YOU ARE TRYING TO SHARE YOUR CONFIGURATION SETTINGS!
12
+ *
13
+ * WARNING! WARNING! WARNING! WARNING! WARNING! WARNING! WARNING!
14
+ **/
11
15
  /**
12
16
  * List of Torznab URLs.
13
- * For Jackett, click "Copy RSS feed"
14
- * For Prowlarr, click on the indexer name and copy the Torznab Url, then append "?apikey=YOUR_PROWLARR_API_KEY"
15
- * Wrap each URL in quotation marks, and separate them with commas, and surround the entire set in brackets.
17
+ * For Jackett, click "Copy RSS feed".
18
+ * For Prowlarr, click on the indexer name and copy the Torznab Url, then
19
+ * append "?apikey=YOUR_PROWLARR_API_KEY". Wrap each URL in quotation marks
20
+ * and separate them with commas, and surround the entire set in brackets.
16
21
  */
17
22
  torznab: [],
18
23
  /**
19
- * To search with downloaded data, you can pass in directories to your downloaded torrent
20
- * data to find matches rather using the torrent files themselves for matching.
24
+ * Bind to a specific host address.
25
+ * Example: "127.0.0.1"
26
+ * Default is "0.0.0.0"
27
+ */
28
+ host: undefined,
29
+ /**
30
+ * The port you wish to listen on for daemon mode.
31
+ */
32
+ port: 2468,
33
+ /**
34
+ * cross-seed will send POST requests to this url with a JSON payload of
35
+ * { title, body }. Conforms to the caronc/apprise REST API.
36
+ */
37
+ notificationWebhookUrl: undefined,
38
+ /**
39
+ * The url of your rtorrent XMLRPC interface.
40
+ * Only relevant with action: "inject".
41
+ * Could be something like "http://username:password@localhost:1234/RPC2
42
+ */
43
+ rtorrentRpcUrl: undefined,
44
+ /**
45
+ * The url of your qBittorrent webui.
46
+ * Only relevant with action: "inject".
47
+ * Supply your username and password inside the url like so:
48
+ * "http://username:password@localhost:8080"
49
+ */
50
+ qbittorrentUrl: undefined,
51
+ /**
52
+ * The url of your Transmission RPC interface.
53
+ * Usually ends with "/transmission/rpc".
54
+ * Only relevant with action: "inject".
55
+ * Supply your username and password inside the url like so:
56
+ * "http://username:password@localhost:9091/transmission/rpc"
57
+ */
58
+ transmissionRpcUrl: undefined,
59
+ /**
60
+ * The url of your Deluge JSON-RPC interface.
61
+ * Usually ends with "/json".
62
+ * Only relevant with action: "inject".
63
+ * Supply your WebUI password as well like so:
64
+ * "http://:password@localhost:8112/json"
65
+ */
66
+ delugeRpcUrl: undefined,
67
+ /**
68
+ * END OF POTENTIALLY SENSITIVE CONFIGURATION OPTIONS
69
+ */
70
+ /**
71
+ * Pause at least this many seconds in between each search. Higher is safer
72
+ * for you and friendlier for trackers.
73
+ * Minimum value of 10.
74
+ */
75
+ delay: 30,
76
+ /**
77
+ * To search with already downloaded data, you can enter in the directories
78
+ * to your downloaded torrent data to find matches, rather than relying
79
+ * entirely on the .torrent files themselves for matching.
80
+ *
81
+ * If directories are entered, they must all be on the one line and they
82
+ * need to be surrounded by brackets.
83
+ * Windows users will need to use double backslash in all paths in this
84
+ * config.
21
85
  *
22
- * If enabled, this needs to be surrounded by brackets. Windows users will need to use
23
- * double backslash in all paths in this config.
24
- * e.g.
25
- * dataDirs: ["/path/here"],
26
- * dataDirs: ["/path/here", "/other/path/here"],
27
- * dataDirs: ["C:\\My Data\\Downloads"]
86
+ * example:
87
+ * dataDirs: ["/downloads/movies", "/downloads/packs"],
88
+ * or for windows users
89
+ * dataDirs: ["C:\\My Data\\Downloads\\Movies"],
28
90
  */
29
- dataDirs: undefined,
91
+ dataDirs: [],
30
92
  /**
31
- * Determines flexibility of naming during matching. "safe" will allow only perfect name matches
32
- * using the standard matching algorithm. "risky" uses filesize as its only comparison point.
33
- * Options: "safe", "risky"
93
+ * Determines flexibility of naming during matching.
94
+ * "safe" will allow only perfect name/size matches using the standard
95
+ * matching algorithm.
96
+ * "risky" uses filesize as its only comparison point.
97
+ * "partial" is like risky but allows matches if they are missing small
98
+ * files like .nfo/.srt.
99
+ * Options: "safe", "risky", "partial".
34
100
  */
35
101
  matchMode: "safe",
36
102
  /**
37
- * Defines what category torrents injected by data-based matching should use.
38
- * Default is "cross-seed-data"
103
+ * Defines what category torrents injected by data-based matching should
104
+ * use.
105
+ * Default is "cross-seed-data".
39
106
  */
40
107
  dataCategory: undefined,
41
108
  /**
42
- * If this is specified, cross-seed will create links to scanned files in the specified directory.
43
- * It will create a different link for every changed file name or directory structure.
109
+ * If this is specified, cross-seed will create links to matched files in
110
+ * the specified directory.
111
+ * It will create a different link for every changed file name or directory
112
+ * structure.
113
+ *
114
+ * Unlike dataDirs, this is just a quoted string WITHOUT []'s around it.
115
+ *
116
+ * IF YOU ARE USING HARDLINKS, THIS MUST BE UNDER THE SAME VOLUME AS YOUR
117
+ * DATADIRS. THIS PATH MUST ALSO BE ACCESSIBLE VIA YOUR TORRENT CLIENT
118
+ * USING THE SAME PATH.
44
119
  */
45
120
  linkDir: undefined,
46
121
  /**
47
- * cross-seed will use links of this type to inject data-based matches into your client.
48
- * Only relevant if dataDirs is specified.
49
- * Options: "symlink", "hardlink"
122
+ * cross-seed will use links of this type to inject data-based matches into
123
+ * your client.
124
+ * https://www.cross-seed.org/docs/basics/faq-troubleshooting#what-linktype-should-i-use
125
+ * Options: "symlink", "hardlink".
126
+ */
127
+ linkType: "hardlink",
128
+ /**
129
+ * Enabling this will link files using v5's flat folder style. This option
130
+ * is necessary if you prefer flat folders of files or use qBittorrent and
131
+ * Automatic Torrent Management.
132
+ *
133
+ * Otherwise each individual Torznab tracker's cross-seeds will have it's
134
+ * own folder with the tracker's name and it's links within it.
135
+ *
136
+ * Default: false.
50
137
  */
51
- linkType: "symlink",
138
+ legacyLinking: false,
52
139
  /**
53
- * Whether to skip recheck in Qbittorrent. If using "risky" matchMode it is HIGHLY
54
- * recommended to set this to false.
55
- * Only applies to data based matches.
140
+ * Whether to skip recheck in qBittorrent or Deluge. Not supported in rTorrent/Transmission.
56
141
  */
57
- skipRecheck: false,
142
+ skipRecheck: true,
58
143
  /**
59
- * Determines how deep into the specified dataDirs to go to generate new searchees.
60
- * Setting this to higher values will result in more searchees and more API hits to
61
- * your indexers.
144
+ * Determines how deep into the specified dataDirs to go to generate new
145
+ * searchees. Setting this to higher values will result in more searchees
146
+ * and more API hits to your indexers.
62
147
  */
63
- maxDataDepth: 2,
148
+ maxDataDepth: 1,
64
149
  /**
65
150
  * Directory containing .torrent files.
66
- * For qBittorrent, this is BT_Backup
67
- * For rtorrent, this is your session directory
68
- * as configured in your .rtorrent.rc file.
151
+ * For qBittorrent, this is BT_Backup.
152
+ * For rtorrent, this is your session directory as configured in your
153
+ * .rtorrent.rc file.
69
154
  * For Deluge, this is ~/.config/deluge/state.
70
- * For Transmission, this would be ~/.config/transmission/torrents
155
+ * For Transmission, this would be ~/.config/transmission/torrents.
71
156
  */
72
157
  torrentDir: "/path/to/torrent/file/dir",
73
158
  /**
74
- * Where to put the torrent files that cross-seed finds for you.
159
+ * Where to save the torrent files that cross-seed finds for you.
75
160
  */
76
161
  outputDir: ".",
77
162
  /**
78
- * Whether to search for all episode torrents, including those from season packs. This option overrides includeSingleEpisodes.
163
+ * Whether to search for all episode torrents, including those from season
164
+ * packs.
165
+ * This option overrides includeSingleEpisodes.
79
166
  */
80
167
  includeEpisodes: false,
81
168
  /**
82
- * Whether to include single episode torrents in the search (not from season packs).
169
+ * Whether to include single episode torrents in the search (not those from
170
+ * season packs).
83
171
  * Like `includeEpisodes` but slightly more restrictive.
84
172
  */
85
173
  includeSingleEpisodes: false,
86
174
  /**
87
- * Include torrents which contain non-video files
175
+ * Include torrents which contain non-video files.
88
176
  * This option does not override includeEpisodes or includeSingleEpisodes.
89
177
  *
90
- * To search for everything except episodes, use (includeEpisodes: false, includeSingleEpisodes: false, includeNonVideos: true)
91
- * To search for everything including episodes, use (includeEpisodes: true, includeNonVideos: true)
92
- * To search for everything except season pack episodes (data-based)
93
- * use (includeEpisodes: false, includeSingleEpisodes: true, includeNonVideos: true)
178
+ * If this option is set to false, any folders or torrents containing ANY
179
+ * non-video files will automatically be excluded from cross-seed searches.
180
+ *
181
+ * For example, if you have .srt or .nfo files inside your folders/torrents
182
+ * you would set this as true.
183
+ * For full disc based folders (not .ISO) you may wish to set this as true.
184
+ * You may also want to set this as false to exclude things like music,
185
+ * games, or books.
186
+ *
187
+ * To search for everything except episodes, use:
188
+ *
189
+ * includeEpisodes: false
190
+ * includeSingleEpisodes: false
191
+ * includeNonVideos: true
192
+ *
193
+ * To search for everything including episodes, use:
194
+ *
195
+ * includeEpisodes: true
196
+ * includeNonVideos: true
197
+ *
198
+ * To search for everything except season pack episodes (data-based) use:
199
+ *
200
+ * includeEpisodes: false
201
+ * includeSingleEpisodes: true
202
+ * includeNonVideos: true
203
+ *
94
204
  */
95
205
  includeNonVideos: false,
96
206
  /**
97
- * fuzzy size match threshold
207
+ * You should NOT modify this unless you have good reason.
208
+ * The following option is the preliminary value to compare sizes of
209
+ * releases for further comparison.
210
+ *
98
211
  * decimal value (0.02 = 2%)
99
212
  */
100
213
  fuzzySizeThreshold: 0.02,
101
214
  /**
102
- * Exclude torrents first seen more than this long ago.
103
- * Format: https://github.com/vercel/ms
104
- * Examples:
105
- * "10min"
106
- * "2w"
107
- * "3 days"
215
+ * Time based options below use the following format:
216
+ * https://github.com/vercel/ms
108
217
  */
109
- excludeOlder: undefined,
110
218
  /**
111
- * Exclude torrents which have been searched
112
- * more recently than this long ago.
219
+ * Exclude torrents first seen by cross-seed more than this long ago.
113
220
  * Examples:
114
- * "10min"
115
- * "2w"
116
- * "3 days"
221
+ * "10 minutes"
222
+ * "1 day"
223
+ * "0" - this will search everything exactly once, never more.
117
224
  */
118
- excludeRecentSearch: undefined,
225
+ excludeOlder: "2 weeks",
119
226
  /**
120
- * With "inject" you need to set up one of the below clients.
121
- * Options: "save", "inject"
122
- */
123
- action: "save",
124
- /**
125
- * The url of your rtorrent XMLRPC interface.
126
- * Only relevant with action: "inject".
127
- * Could be something like "http://username:password@localhost:1234/RPC2
128
- */
129
- rtorrentRpcUrl: undefined,
130
- /**
131
- * The url of your qBittorrent webui.
132
- * Only relevant with action: "inject".
133
- * Supply your username and password inside the url like so:
134
- * "http://username:password@localhost:8080"
135
- */
136
- qbittorrentUrl: undefined,
137
- /**
138
- * The url of your Transmission RPC interface.
139
- * Usually ends with "/transmission/rpc".
140
- * Only relevant with action: "inject".
141
- * Supply your username and password inside the url like so:
142
- * "http://username:password@localhost:9091/transmission/rpc"
227
+ * Exclude torrents which have been searched more recently than this long
228
+ * ago.
229
+ * Doesn't exclude previously failed searches.
230
+ * Examples:
231
+ * "2 days"
232
+ * "1 year"
143
233
  */
144
- transmissionRpcUrl: undefined,
234
+ excludeRecentSearch: "3 days",
145
235
  /**
146
- * The url of your Deluge JSON-RPC interface.
147
- * Usually ends with "/json".
148
- * Only relevant with action: "inject".
149
- * Supply your WebUI password as well
150
- * "http://:password@localhost:8112/json"
236
+ * What action to take upon a match being found.
237
+ * Options: "save", "inject".
151
238
  */
152
- delugeRpcUrl: undefined,
239
+ action: "inject",
153
240
  /**
154
- * qBittorrent and Deluge specific
155
- * Whether to inject using the same labels/categories as the original torrent.
156
- * qBittorrent: This will apply the category's save path
157
- * Example: if you have a label/category called "Movies",
158
- * this will automatically inject cross-seeds to "Movies.cross-seed"
241
+ * qBittorrent and Deluge specific.
242
+ * Whether to inject using the same labels/categories as the original
243
+ * torrent.
244
+ * qBittorrent: This will apply the category's save path.
245
+ * Example: if you have a label/category called "Movies", this will
246
+ * automatically inject cross-seeds to "Movies.cross-seed".
159
247
  */
160
248
  duplicateCategories: false,
161
249
  /**
162
- * cross-seed will send POST requests to this url
163
- * with a JSON payload of { title, body }.
164
- * Conforms to the caronc/apprise REST API.
165
- */
166
- notificationWebhookUrl: undefined,
167
- /**
168
- * Listen on a custom port.
169
- */
170
- port: 2468,
171
- /**
172
- * Bind to a specific host address.
173
- * Example: "127.0.0.1"
174
- * Default is "0.0.0.0"
175
- */
176
- host: undefined,
177
- /**
178
- * Whether to require authentication for API.
179
- * Run the command `cross-seed api-key` to find your api key.
180
- * Keys can be provided in an X-Api-Key HTTP header or a query param.
181
- */
182
- apiAuth: true,
183
- /**
184
- * Run rss scans on a schedule. Format: https://github.com/vercel/ms
250
+ * Run rss scans on a schedule.
185
251
  * Set to undefined or null to disable. Minimum of 10 minutes.
186
252
  * Examples:
187
- * "10min"
188
- * "2w"
189
- * "3 days"
253
+ * "10 minutes"
254
+ * "1 hour"
190
255
  */
191
- rssCadence: undefined,
256
+ rssCadence: "30 minutes",
192
257
  /**
193
- * Run searches on a schedule. Format: https://github.com/vercel/ms
258
+ * Run searches on a schedule.
194
259
  * Set to undefined or null to disable. Minimum of 1 day.
195
- * If you have RSS enabled, you won't need this to run often (2+ weeks recommended)
196
260
  * Examples:
197
- * "10min"
198
- * "2w"
261
+ * "2 weeks"
199
262
  * "3 days"
200
263
  */
201
- searchCadence: undefined,
264
+ searchCadence: "1 day",
202
265
  /**
203
266
  * Fail snatch requests that haven't responded after this long.
204
267
  * Set to null for an infinite timeout.
205
- * Format: https://github.com/vercel/ms
206
268
  * Examples:
207
- * "30sec"
208
- * "10s"
209
- * "1min"
269
+ * "30 seconds"
210
270
  * null
211
271
  */
212
272
  snatchTimeout: undefined,
213
273
  /**
214
274
  * Fail search requests that haven't responded after this long.
215
275
  * Set to null for an infinite timeout.
216
- * Format: https://github.com/vercel/ms
217
276
  * Examples:
218
- * "30sec"
219
- * "10s"
220
- * "1min"
277
+ * "30 seconds"
221
278
  * null
222
279
  */
223
280
  searchTimeout: undefined,
224
281
  /**
225
- * The number of searches to be done before it stops.
226
- * Combine this with "excludeRecentSearch" and "searchCadence" to smooth long-term API usage patterns.
227
- * Default is no limit.
282
+ * The number of searches to make in one run/batch.
283
+ * If more than this many searches are queued,
284
+ * "searchCadence" will determine how long until the next batch.
285
+ *
286
+ * Combine this with "excludeRecentSearch" and "searchCadence" to smooth
287
+ * long-term API usage patterns.
288
+ *
289
+ * Set to null for no limit.
290
+ */
291
+ searchLimit: 100,
292
+ /**
293
+ * The list of infohashes or strings which are contained in torrents that
294
+ * you want to be excluded from cross-seed. This is the same format as
295
+ * torznab, surround the entire set of quoted strings in square brackets
296
+ * You can use any combination which must be entered on the one line.
297
+ * Leave as undefined to disable.
298
+ *
299
+ * examples:
300
+ *
301
+ * blockList: ["-excludedGroup", "-excludedGroup2"],
302
+ * blocklist: ["x265"],
303
+ * blocklist: ["Release.Name"],
304
+ * blocklist: ["3317e6485454354751555555366a8308c1e92093"],
305
+ */
306
+ blockList: undefined,
307
+ /**
308
+ * Provide your own API key here to override the autogenerated one.
309
+ * Not recommended - prefer using the autogenerated API key via
310
+ * `cross-seed api-key`.
311
+ * Must be 24+ characters.
228
312
  */
229
- searchLimit: undefined,
313
+ apiKey: undefined,
230
314
  };
231
315
  //# sourceMappingURL=config.template.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.template.cjs","sourceRoot":"","sources":["../src/config.template.cjs"],"names":[],"mappings":";AAAA,4EAA4E;AAC5E,wBAAwB;;AAExB,MAAM,CAAC,OAAO,GAAG;IAChB;;;OAGG;IACH,KAAK,EAAE,EAAE;IAET;;;;;OAKG;IACH,OAAO,EAAE,EAAE;IAEX;;;;;;;;;;OAUG;IACH,QAAQ,EAAE,SAAS;IAEnB;;;;OAIG;IACH,SAAS,EAAE,MAAM;IAEjB;;;OAGG;IACH,YAAY,EAAE,SAAS;IAEvB;;;OAGG;IACH,OAAO,EAAE,SAAS;IAElB;;;;OAIG;IACH,QAAQ,EAAE,SAAS;IAEnB;;;;OAIG;IACH,WAAW,EAAE,KAAK;IAElB;;;;OAIG;IACH,YAAY,EAAE,CAAC;IAEf;;;;;;;OAOG;IACH,UAAU,EAAE,2BAA2B;IAEvC;;OAEG;IACH,SAAS,EAAE,GAAG;IAEd;;OAEG;IACH,eAAe,EAAE,KAAK;IAEtB;;;OAGG;IACH,qBAAqB,EAAE,KAAK;IAE5B;;;;;;;;OAQG;IACH,gBAAgB,EAAE,KAAK;IAEvB;;;OAGG;IACH,kBAAkB,EAAE,IAAI;IAExB;;;;;;;OAOG;IACH,YAAY,EAAE,SAAS;IAEvB;;;;;;;OAOG;IACH,mBAAmB,EAAE,SAAS;IAE9B;;;OAGG;IACH,MAAM,EAAE,MAAM;IAEd;;;;OAIG;IACH,cAAc,EAAE,SAAS;IAEzB;;;;;OAKG;IACH,cAAc,EAAE,SAAS;IAEzB;;;;;;OAMG;IACH,kBAAkB,EAAE,SAAS;IAE7B;;;;;;OAMG;IACH,YAAY,EAAE,SAAS;IAEvB;;;;;;OAMG;IACH,mBAAmB,EAAE,KAAK;IAE1B;;;;OAIG;IACH,sBAAsB,EAAE,SAAS;IAEjC;;OAEG;IACH,IAAI,EAAE,IAAI;IAEV;;;;OAIG;IACH,IAAI,EAAE,SAAS;IAEf;;;;OAIG;IACH,OAAO,EAAE,IAAI;IAEb;;;;;;;OAOG;IACH,UAAU,EAAE,SAAS;IAErB;;;;;;;;OAQG;IACH,aAAa,EAAE,SAAS;IAExB;;;;;;;;;OASG;IACH,aAAa,EAAE,SAAS;IAExB;;;;;;;;;OASG;IACH,aAAa,EAAE,SAAS;IAExB;;;;OAIG;IACH,WAAW,EAAE,SAAS;CACtB,CAAC"}
1
+ {"version":3,"file":"config.template.cjs","sourceRoot":"","sources":["../src/config.template.cjs"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,qBAAqB;;AAErB,MAAM,CAAC,OAAO,GAAG;IAChB;;;;;;;;QAQI;IAEJ;;;;;;OAMG;IACH,OAAO,EAAE,EAAE;IAEX;;;;OAIG;IACH,IAAI,EAAE,SAAS;IAEf;;OAEG;IACH,IAAI,EAAE,IAAI;IAEV;;;OAGG;IACH,sBAAsB,EAAE,SAAS;IAEjC;;;;OAIG;IACH,cAAc,EAAE,SAAS;IAEzB;;;;;OAKG;IACH,cAAc,EAAE,SAAS;IAEzB;;;;;;OAMG;IACH,kBAAkB,EAAE,SAAS;IAE7B;;;;;;OAMG;IACH,YAAY,EAAE,SAAS;IAEvB;;OAEG;IAEH;;;;OAIG;IACH,KAAK,EAAE,EAAE;IAET;;;;;;;;;;;;;;OAcG;IACH,QAAQ,EAAE,EAAE;IAEZ;;;;;;;;OAQG;IACH,SAAS,EAAE,MAAM;IAEjB;;;;OAIG;IACH,YAAY,EAAE,SAAS;IAEvB;;;;;;;;;;;OAWG;IACH,OAAO,EAAE,SAAS;IAElB;;;;;OAKG;IACH,QAAQ,EAAE,UAAU;IAEpB;;;;;;;;;OASG;IACH,aAAa,EAAE,KAAK;IAEpB;;OAEG;IACH,WAAW,EAAE,IAAI;IAEjB;;;;OAIG;IACH,YAAY,EAAE,CAAC;IAEf;;;;;;;OAOG;IACH,UAAU,EAAE,2BAA2B;IAEvC;;OAEG;IACH,SAAS,EAAE,GAAG;IAEd;;;;OAIG;IACH,eAAe,EAAE,KAAK;IAEtB;;;;OAIG;IACH,qBAAqB,EAAE,KAAK;IAE5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,gBAAgB,EAAE,KAAK;IAEvB;;;;;;OAMG;IACH,kBAAkB,EAAE,IAAI;IAExB;;;OAGG;IAEH;;;;;;OAMG;IACH,YAAY,EAAE,SAAS;IAEvB;;;;;;;OAOG;IACH,mBAAmB,EAAE,QAAQ;IAE7B;;;OAGG;IACH,MAAM,EAAE,QAAQ;IAEhB;;;;;;;OAOG;IACH,mBAAmB,EAAE,KAAK;IAE1B;;;;;;OAMG;IACH,UAAU,EAAE,YAAY;IAExB;;;;;;OAMG;IACH,aAAa,EAAE,OAAO;IAEtB;;;;;;OAMG;IACH,aAAa,EAAE,SAAS;IAExB;;;;;;OAMG;IACH,aAAa,EAAE,SAAS;IAExB;;;;;;;;;OASG;IACH,WAAW,EAAE,GAAG;IAEhB;;;;;;;;;;;;;OAaG;IACH,SAAS,EAAE,SAAS;IAEpB;;;;;OAKG;IACH,MAAM,EAAE,SAAS;CACjB,CAAC"}
@@ -0,0 +1,184 @@
1
+ import ms from "ms";
2
+ import { sep } from "path";
3
+ import { z } from "zod";
4
+ import { Action, LinkType, MatchMode } from "./constants.js";
5
+ import { logger } from "./logger.js";
6
+ /**
7
+ * error messages and map returned upon Zod validation failure
8
+ */
9
+ const ZodErrorMessages = {
10
+ vercel: "format does not follow vercel's `ms` style ( https://github.com/vercel/ms#examples )",
11
+ emptyString: "cannot have an empty string. If you want to unset it, use null or undefined.",
12
+ delay: "delay is in seconds, you can't travel back in time.",
13
+ fuzzySizeThreshold: "fuzzySizeThreshold must be between 0 and 1.",
14
+ injectUrl: "You need to specify rtorrentRpcUrl, transmissionRpcUrl, qbittorrentUrl, or delugeRpcUrl when using 'inject'",
15
+ recheckWarn: "It is strongly recommended to not skip rechecking for risky or partial matching mode.",
16
+ windowsPath: `Your path is not formatted properly for Windows. Please use "\\\\" or "/" for directory separators.`,
17
+ qBitLegacyLinking: "Using Automatic Torrent Management in qBittorrent without legacyLinking enabled can result in injection path failures.",
18
+ needsLinkDir: "You need to set a linkDir for risky or partial matching to work.",
19
+ };
20
+ /**
21
+ * custom zod error map for logging
22
+ * @param error ZodIssue messaging object
23
+ * @param ctx ZodError map
24
+ * @returns (the custom error for config display)
25
+ */
26
+ export function customizeErrorMessage(error, ctx) {
27
+ switch (error.code) {
28
+ case z.ZodIssueCode.invalid_union:
29
+ return {
30
+ message: error.unionErrors
31
+ .reduce((acc, error) => {
32
+ error.errors.forEach((x) => acc.push(x.message));
33
+ return acc;
34
+ }, [])
35
+ .join("; "),
36
+ };
37
+ }
38
+ return { message: ctx.defaultError };
39
+ }
40
+ /**
41
+ * adds an issue in Zod's error mapped formatting
42
+ * @param setting the value of the setting
43
+ * @param errorMessage the error message to append on a newline
44
+ * @param ctx ZodError map
45
+ */
46
+ function addZodIssue(setting, errorMessage, ctx) {
47
+ ctx.addIssue({
48
+ code: "custom",
49
+ message: `Setting: "${setting}"\n\t${errorMessage}`,
50
+ });
51
+ }
52
+ /**
53
+ * helper function for ms time validation
54
+ * @return transformed duration (string -> milliseconds)
55
+ */
56
+ function transformDurationString(durationStr, ctx) {
57
+ const duration = ms(durationStr);
58
+ if (isNaN(duration)) {
59
+ // adds the error to the Zod Issues
60
+ addZodIssue(durationStr, ZodErrorMessages.vercel, ctx);
61
+ }
62
+ return duration;
63
+ }
64
+ /**
65
+ * helper function for directory validation
66
+ * @return path if valid formatting
67
+ */
68
+ function checkValidPathFormat(path, ctx) {
69
+ if (sep === "\\" && !path.includes("\\") && !path.includes("/")) {
70
+ addZodIssue(path, ZodErrorMessages.windowsPath, ctx);
71
+ }
72
+ return path;
73
+ }
74
+ /**
75
+ * an object of the zod schema
76
+ * each are named after what they are intended to validate
77
+ */
78
+ export const VALIDATION_SCHEMA = z
79
+ .object({
80
+ delay: z.number().nonnegative({
81
+ message: ZodErrorMessages.delay,
82
+ }),
83
+ torznab: z.array(z.string().url()),
84
+ dataDirs: z
85
+ .array(z
86
+ .string()
87
+ .transform((value, ctx) => value && value.length > 0
88
+ ? checkValidPathFormat(value, ctx)
89
+ : null))
90
+ .nullish(),
91
+ matchMode: z.nativeEnum(MatchMode),
92
+ dataCategory: z.string().nullish(),
93
+ linkDir: z.string().transform(checkValidPathFormat).nullish(),
94
+ linkType: z.nativeEnum(LinkType),
95
+ legacyLinking: z
96
+ .boolean()
97
+ .nullish()
98
+ .transform((value) => (typeof value === "boolean" ? value : false)),
99
+ skipRecheck: z.boolean(),
100
+ maxDataDepth: z.number().gte(1),
101
+ torrentDir: z.string().nullish(),
102
+ outputDir: z.string(),
103
+ includeEpisodes: z.boolean(),
104
+ includeSingleEpisodes: z.boolean(),
105
+ includeNonVideos: z.boolean(),
106
+ fuzzySizeThreshold: z.number().positive().lte(1, {
107
+ message: ZodErrorMessages.fuzzySizeThreshold,
108
+ }),
109
+ excludeOlder: z
110
+ .string()
111
+ .min(1, { message: ZodErrorMessages.emptyString })
112
+ .transform(transformDurationString)
113
+ .nullish(),
114
+ excludeRecentSearch: z
115
+ .string()
116
+ .min(1, { message: ZodErrorMessages.emptyString })
117
+ .transform(transformDurationString)
118
+ .nullish(),
119
+ action: z.nativeEnum(Action),
120
+ qbittorrentUrl: z.string().url().nullish(),
121
+ rtorrentRpcUrl: z.string().url().nullish(),
122
+ transmissionRpcUrl: z.string().url().nullish(),
123
+ delugeRpcUrl: z.string().url().nullish(),
124
+ duplicateCategories: z.boolean(),
125
+ notificationWebhookUrl: z.string().url().nullish(),
126
+ port: z
127
+ .number()
128
+ .positive()
129
+ .lte(65535)
130
+ .or(z.literal(false).transform(() => null))
131
+ .nullish(),
132
+ host: z.string().ip().nullish(),
133
+ rssCadence: z
134
+ .string()
135
+ .min(1, { message: ZodErrorMessages.emptyString })
136
+ .transform(transformDurationString)
137
+ .nullish(),
138
+ searchCadence: z
139
+ .string()
140
+ .min(1, { message: ZodErrorMessages.emptyString })
141
+ .transform(transformDurationString)
142
+ .nullish(),
143
+ snatchTimeout: z
144
+ .string()
145
+ .min(1, { message: ZodErrorMessages.emptyString })
146
+ .transform(transformDurationString)
147
+ .nullish(),
148
+ searchTimeout: z
149
+ .string()
150
+ .min(1, { message: ZodErrorMessages.emptyString })
151
+ .transform(transformDurationString)
152
+ .nullish(),
153
+ searchLimit: z.number().nonnegative().nullish(),
154
+ verbose: z.boolean(),
155
+ torrents: z.array(z.string()).optional(),
156
+ blockList: z
157
+ .array(z.string())
158
+ .nullish()
159
+ .transform((value) => (Array.isArray(value) ? value : [])),
160
+ apiKey: z.string().min(24).nullish(),
161
+ })
162
+ .strict()
163
+ .refine((config) => {
164
+ if (config.action === Action.INJECT &&
165
+ config.qbittorrentUrl &&
166
+ !config.legacyLinking &&
167
+ config.linkDir) {
168
+ logger.warn(ZodErrorMessages.qBitLegacyLinking);
169
+ }
170
+ return true;
171
+ })
172
+ .refine((config) => !(config.action === Action.INJECT &&
173
+ !config.rtorrentRpcUrl &&
174
+ !config.qbittorrentUrl &&
175
+ !config.transmissionRpcUrl &&
176
+ !config.delugeRpcUrl), ZodErrorMessages.injectUrl)
177
+ .refine((config) => {
178
+ if (config.skipRecheck && config.matchMode !== MatchMode.SAFE) {
179
+ logger.warn(ZodErrorMessages.recheckWarn);
180
+ }
181
+ return true;
182
+ })
183
+ .refine((config) => config.matchMode === MatchMode.SAFE || config.linkDir, ZodErrorMessages.needsLinkDir);
184
+ //# sourceMappingURL=configSchema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configSchema.js","sourceRoot":"","sources":["../src/configSchema.ts"],"names":[],"mappings":"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"}