cross-seed 6.0.0-8 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.js +177 -71
- package/dist/action.js.map +1 -1
- package/dist/arr.js +62 -54
- package/dist/arr.js.map +1 -1
- package/dist/clients/Deluge.js +70 -46
- package/dist/clients/Deluge.js.map +1 -1
- package/dist/clients/QBittorrent.js +110 -68
- package/dist/clients/QBittorrent.js.map +1 -1
- package/dist/clients/RTorrent.js +46 -23
- package/dist/clients/RTorrent.js.map +1 -1
- package/dist/clients/TorrentClient.js +14 -1
- package/dist/clients/TorrentClient.js.map +1 -1
- package/dist/clients/Transmission.js +30 -10
- package/dist/clients/Transmission.js.map +1 -1
- package/dist/cmd.js +46 -23
- package/dist/cmd.js.map +1 -1
- package/dist/config.template.cjs +59 -59
- package/dist/config.template.cjs.map +1 -1
- package/dist/configSchema.js +90 -26
- package/dist/configSchema.js.map +1 -1
- package/dist/configuration.js +4 -1
- package/dist/configuration.js.map +1 -1
- package/dist/constants.js +77 -9
- package/dist/constants.js.map +1 -1
- package/dist/dataFiles.js +4 -5
- package/dist/dataFiles.js.map +1 -1
- package/dist/db.js +2 -1
- package/dist/db.js.map +1 -1
- package/dist/decide.js +279 -169
- package/dist/decide.js.map +1 -1
- package/dist/diff.js +13 -3
- package/dist/diff.js.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/indexers.js +94 -33
- package/dist/indexers.js.map +1 -1
- package/dist/inject.js +448 -0
- package/dist/inject.js.map +1 -0
- package/dist/jobs.js +13 -6
- package/dist/jobs.js.map +1 -1
- package/dist/logger.js +27 -9
- package/dist/logger.js.map +1 -1
- package/dist/migrations/00-initialSchema.js.map +1 -1
- package/dist/migrations/05-caps.js.map +1 -1
- package/dist/migrations/06-uniqueDecisions.js +29 -0
- package/dist/migrations/06-uniqueDecisions.js.map +1 -0
- package/dist/migrations/07-limits.js +12 -0
- package/dist/migrations/07-limits.js.map +1 -0
- package/dist/migrations/migrations.js +4 -0
- package/dist/migrations/migrations.js.map +1 -1
- package/dist/parseTorrent.js +6 -0
- package/dist/parseTorrent.js.map +1 -1
- package/dist/pipeline.js +224 -112
- package/dist/pipeline.js.map +1 -1
- package/dist/preFilter.js +122 -55
- package/dist/preFilter.js.map +1 -1
- package/dist/pushNotifier.js +7 -5
- package/dist/pushNotifier.js.map +1 -1
- package/dist/searchee.js +198 -17
- package/dist/searchee.js.map +1 -1
- package/dist/server.js +106 -54
- package/dist/server.js.map +1 -1
- package/dist/startup.js +16 -7
- package/dist/startup.js.map +1 -1
- package/dist/torrent.js +116 -50
- package/dist/torrent.js.map +1 -1
- package/dist/torznab.js +323 -153
- package/dist/torznab.js.map +1 -1
- package/dist/utils.js +229 -44
- package/dist/utils.js.map +1 -1
- package/package.json +11 -6
package/dist/server.js
CHANGED
@@ -1,12 +1,35 @@
|
|
1
|
+
import chalk from "chalk";
|
2
|
+
import { existsSync } from "fs";
|
1
3
|
import http from "http";
|
2
4
|
import { pick } from "lodash-es";
|
3
5
|
import { parse as qsParse } from "querystring";
|
4
6
|
import { inspect } from "util";
|
7
|
+
import { z } from "zod";
|
5
8
|
import { checkApiKey } from "./auth.js";
|
9
|
+
import { Decision, InjectionResult, SaveResult, } from "./constants.js";
|
6
10
|
import { Label, logger } from "./logger.js";
|
7
11
|
import { checkNewCandidateMatch, searchForLocalTorrentByCriteria, } from "./pipeline.js";
|
8
|
-
import {
|
12
|
+
import { getRuntimeConfig } from "./runtimeConfig.js";
|
9
13
|
import { indexNewTorrents } from "./torrent.js";
|
14
|
+
import { formatAsList, sanitizeInfoHash } from "./utils.js";
|
15
|
+
const ANNOUNCE_SCHEMA = z
|
16
|
+
.object({
|
17
|
+
name: z.string().refine((name) => name.trim().length > 0),
|
18
|
+
guid: z.string().url(),
|
19
|
+
link: z.string().url(),
|
20
|
+
tracker: z.string().refine((tracker) => tracker.trim().length > 0),
|
21
|
+
})
|
22
|
+
.strict()
|
23
|
+
.required()
|
24
|
+
.refine((data) => data.guid === data.link);
|
25
|
+
const WEBHOOK_SCHEMA = z
|
26
|
+
.object({
|
27
|
+
infoHash: z.string().length(40),
|
28
|
+
path: z.string().refine((path) => !path || existsSync(path)),
|
29
|
+
})
|
30
|
+
.strict()
|
31
|
+
.partial()
|
32
|
+
.refine((data) => Object.keys(data).length === 1);
|
10
33
|
function getData(req) {
|
11
34
|
return new Promise((resolve) => {
|
12
35
|
const chunks = [];
|
@@ -64,26 +87,31 @@ async function search(req, res) {
|
|
64
87
|
}
|
65
88
|
catch (e) {
|
66
89
|
logger.error({
|
67
|
-
label: Label.
|
90
|
+
label: Label.WEBHOOK,
|
68
91
|
message: e.message,
|
69
92
|
});
|
70
93
|
res.writeHead(400);
|
71
94
|
res.end(e.message);
|
72
95
|
return;
|
73
96
|
}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
97
|
+
let criteria = pick(data, ["infoHash", "path"]);
|
98
|
+
try {
|
99
|
+
criteria = WEBHOOK_SCHEMA.parse(criteria);
|
100
|
+
}
|
101
|
+
catch {
|
102
|
+
const message = `A valid infoHash or an accessible path must be provided (infoHash is recommended: see https://www.cross-seed.org/docs/basics/daemon#set-up-automatic-searches-for-finished-downloads): ${inspect(criteria)}`;
|
103
|
+
logger.error({ label: Label.WEBHOOK, message });
|
78
104
|
res.writeHead(400);
|
79
105
|
res.end(message);
|
80
106
|
return;
|
81
107
|
}
|
82
|
-
const criteriaStr =
|
108
|
+
const criteriaStr = criteria.infoHash
|
109
|
+
? inspect(criteria).replace(criteria.infoHash, sanitizeInfoHash(criteria.infoHash))
|
110
|
+
: inspect(criteria);
|
83
111
|
res.writeHead(204);
|
84
112
|
res.end();
|
85
113
|
logger.info({
|
86
|
-
label: Label.
|
114
|
+
label: Label.WEBHOOK,
|
87
115
|
message: `Received search request: ${criteriaStr}`,
|
88
116
|
});
|
89
117
|
await indexNewTorrents();
|
@@ -94,23 +122,54 @@ async function search(req, res) {
|
|
94
122
|
}
|
95
123
|
if (numFound === null) {
|
96
124
|
logger.info({
|
97
|
-
label: Label.
|
98
|
-
message: `Did not search for ${criteriaStr}`,
|
125
|
+
label: Label.WEBHOOK,
|
126
|
+
message: `Did not search for ${criteriaStr} (check verbose logs for preFilter reason)`,
|
99
127
|
});
|
100
128
|
}
|
101
129
|
else {
|
102
130
|
logger.info({
|
103
|
-
label: Label.
|
131
|
+
label: Label.WEBHOOK,
|
104
132
|
message: `Found ${numFound} torrents for ${criteriaStr}`,
|
105
133
|
});
|
106
134
|
}
|
107
135
|
}
|
108
136
|
catch (e) {
|
109
|
-
logger.error(
|
137
|
+
logger.error({
|
138
|
+
label: Label.WEBHOOK,
|
139
|
+
message: e.message,
|
140
|
+
});
|
110
141
|
logger.debug(e);
|
111
142
|
}
|
112
143
|
}
|
144
|
+
function determineResponse(result) {
|
145
|
+
const injected = result.actionResult === InjectionResult.SUCCESS;
|
146
|
+
const added = injected ||
|
147
|
+
result.actionResult === InjectionResult.FAILURE ||
|
148
|
+
result.actionResult === SaveResult.SAVED;
|
149
|
+
const exists = result.decision === Decision.INFO_HASH_ALREADY_EXISTS ||
|
150
|
+
result.actionResult === InjectionResult.ALREADY_EXISTS;
|
151
|
+
const incomplete = result.actionResult === InjectionResult.TORRENT_NOT_COMPLETE;
|
152
|
+
let status;
|
153
|
+
let state;
|
154
|
+
if (added) {
|
155
|
+
status = 200;
|
156
|
+
state = injected ? "Injected" : "Saved";
|
157
|
+
}
|
158
|
+
else if (exists) {
|
159
|
+
status = 200;
|
160
|
+
state = "Already exists";
|
161
|
+
}
|
162
|
+
else if (incomplete) {
|
163
|
+
status = 202;
|
164
|
+
state = "Saved";
|
165
|
+
}
|
166
|
+
else {
|
167
|
+
throw new Error(`Unexpected result: ${result.decision} | ${result.actionResult}`);
|
168
|
+
}
|
169
|
+
return { status, state };
|
170
|
+
}
|
113
171
|
async function announce(req, res) {
|
172
|
+
const { torrentDir } = getRuntimeConfig();
|
114
173
|
const dataStr = await getData(req);
|
115
174
|
let data;
|
116
175
|
try {
|
@@ -118,54 +177,54 @@ async function announce(req, res) {
|
|
118
177
|
}
|
119
178
|
catch (e) {
|
120
179
|
logger.error({
|
121
|
-
label: Label.
|
180
|
+
label: Label.ANNOUNCE,
|
122
181
|
message: e.message,
|
123
182
|
});
|
124
183
|
res.writeHead(400);
|
125
184
|
res.end(e.message);
|
126
185
|
return;
|
127
186
|
}
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
const message =
|
133
|
-
logger.error({
|
134
|
-
label: Label.SERVER,
|
135
|
-
message,
|
136
|
-
});
|
187
|
+
try {
|
188
|
+
data = ANNOUNCE_SCHEMA.parse(data);
|
189
|
+
}
|
190
|
+
catch ({ errors }) {
|
191
|
+
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)}`;
|
192
|
+
logger.error({ label: Label.ANNOUNCE, message });
|
137
193
|
res.writeHead(400);
|
138
194
|
res.end(message);
|
139
195
|
return;
|
140
196
|
}
|
141
197
|
logger.verbose({
|
142
|
-
label: Label.
|
198
|
+
label: Label.ANNOUNCE,
|
143
199
|
message: `Received announce from ${data.tracker}: ${data.name}`,
|
144
200
|
});
|
145
201
|
const candidate = data;
|
202
|
+
const candidateLog = `${chalk.bold.white(candidate.name)} from ${candidate.tracker}`;
|
146
203
|
try {
|
147
|
-
|
148
|
-
|
149
|
-
const isOk = result === InjectionResult.SUCCESS || result === SaveResult.SAVED;
|
150
|
-
if (!isOk) {
|
151
|
-
if (result === InjectionResult.TORRENT_NOT_COMPLETE) {
|
152
|
-
res.writeHead(202);
|
153
|
-
}
|
154
|
-
else {
|
155
|
-
res.writeHead(204);
|
156
|
-
}
|
204
|
+
if (!torrentDir) {
|
205
|
+
throw new Error("Announce requires torrentDir");
|
157
206
|
}
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
207
|
+
await indexNewTorrents();
|
208
|
+
const result = await checkNewCandidateMatch(candidate, Label.ANNOUNCE);
|
209
|
+
if (!result.decision) {
|
210
|
+
res.writeHead(204);
|
211
|
+
res.end();
|
212
|
+
return;
|
164
213
|
}
|
214
|
+
const { status, state } = determineResponse(result);
|
215
|
+
logger.info({
|
216
|
+
label: Label.ANNOUNCE,
|
217
|
+
message: `${state} ${candidateLog} (status: ${status})`,
|
218
|
+
});
|
219
|
+
res.writeHead(status);
|
165
220
|
res.end();
|
166
221
|
}
|
167
222
|
catch (e) {
|
168
|
-
logger.error(
|
223
|
+
logger.error({
|
224
|
+
label: Label.ANNOUNCE,
|
225
|
+
message: e.message,
|
226
|
+
});
|
227
|
+
logger.debug(e);
|
169
228
|
res.writeHead(500);
|
170
229
|
res.end(e.message);
|
171
230
|
}
|
@@ -178,24 +237,17 @@ async function handleRequest(req, res) {
|
|
178
237
|
res.end("Methods allowed: POST");
|
179
238
|
return;
|
180
239
|
}
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
label: Label.SERVER,
|
185
|
-
message: "POST /api/webhook",
|
186
|
-
});
|
240
|
+
const endpoint = req.url.split("?")[0];
|
241
|
+
switch (endpoint) {
|
242
|
+
case "/api/webhook":
|
187
243
|
return search(req, res);
|
188
|
-
|
189
|
-
case "/api/announce": {
|
190
|
-
logger.verbose({
|
191
|
-
label: Label.SERVER,
|
192
|
-
message: "POST /api/announce",
|
193
|
-
});
|
244
|
+
case "/api/announce":
|
194
245
|
return announce(req, res);
|
195
|
-
}
|
196
246
|
default: {
|
247
|
+
const message = `Unknown endpoint: ${endpoint}`;
|
248
|
+
logger.error({ label: Label.SERVER, message });
|
197
249
|
res.writeHead(404);
|
198
|
-
res.end(
|
250
|
+
res.end(message);
|
199
251
|
return;
|
200
252
|
}
|
201
253
|
}
|
package/dist/server.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAyC,MAAM,MAAM,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAEN,sBAAsB,EACtB,+BAA+B,GAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/startup.js
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
+
import { access, constants, stat } from "fs/promises";
|
1
2
|
import { sep } from "path";
|
3
|
+
import { inspect } from "util";
|
4
|
+
import { validateUArrLs } from "./arr.js";
|
5
|
+
import { getClient } from "./clients/TorrentClient.js";
|
6
|
+
import { Action } from "./constants.js";
|
2
7
|
import { CrossSeedError } from "./errors.js";
|
3
8
|
import { Label, logger } from "./logger.js";
|
4
|
-
import { Action } from "./constants.js";
|
5
|
-
import { validateTorznabUrls } from "./torznab.js";
|
6
|
-
import { getClient } from "./clients/TorrentClient.js";
|
7
9
|
import { getRuntimeConfig } from "./runtimeConfig.js";
|
8
|
-
import {
|
9
|
-
import { stat, access, constants } from "fs/promises";
|
10
|
-
import { validateUArrLs } from "./arr.js";
|
10
|
+
import { validateTorznabUrls } from "./torznab.js";
|
11
11
|
/**
|
12
12
|
* validates existence, permission, and that a path is a directory
|
13
13
|
* @param path string of path to validate
|
@@ -41,7 +41,7 @@ async function verifyPath(path, optionName, permissions) {
|
|
41
41
|
* @returns true (if paths are valid)
|
42
42
|
*/
|
43
43
|
async function checkConfigPaths() {
|
44
|
-
const { action,
|
44
|
+
const { action, dataDirs, injectDir, linkDir, outputDir, rtorrentRpcUrl, torrentDir, } = getRuntimeConfig();
|
45
45
|
const READ_ONLY = constants.R_OK;
|
46
46
|
const READ_AND_WRITE = constants.R_OK | constants.W_OK;
|
47
47
|
let pathFailure = 0;
|
@@ -63,6 +63,15 @@ async function checkConfigPaths() {
|
|
63
63
|
}
|
64
64
|
}
|
65
65
|
}
|
66
|
+
if (injectDir) {
|
67
|
+
logger.warn({
|
68
|
+
label: Label.INJECT,
|
69
|
+
message: `Manually injecting torrents performs minimal filtering which slightly increases chances of false positives, see the docs for more info`,
|
70
|
+
});
|
71
|
+
if (!(await verifyPath(injectDir, "injectDir", READ_AND_WRITE))) {
|
72
|
+
pathFailure++;
|
73
|
+
}
|
74
|
+
}
|
66
75
|
if (pathFailure) {
|
67
76
|
throw new CrossSeedError(`\tYour configuration is invalid, please see the ${pathFailure > 1 ? "errors" : "error"} above for details.`);
|
68
77
|
}
|
package/dist/startup.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"startup.js","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
1
|
+
{"version":3,"file":"startup.js","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"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"}
|
package/dist/torrent.js
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
import { distance } from "fastest-levenshtein";
|
2
|
+
import fs from "fs";
|
1
3
|
import { readdir, readFile, writeFile } from "fs/promises";
|
2
4
|
import Fuse from "fuse.js";
|
3
5
|
import { extname, join, resolve } from "path";
|
4
6
|
import { inspect } from "util";
|
5
|
-
import { USER_AGENT } from "./constants.js";
|
7
|
+
import { LEVENSHTEIN_DIVISOR, SAVED_TORRENTS_INFO_REGEX, USER_AGENT, } from "./constants.js";
|
6
8
|
import { db } from "./db.js";
|
7
9
|
import { logger, logOnce } from "./logger.js";
|
8
10
|
import { Metafile } from "./parseTorrent.js";
|
9
11
|
import { resultOf, resultOfErr } from "./Result.js";
|
10
12
|
import { getRuntimeConfig } from "./runtimeConfig.js";
|
11
|
-
import { createSearcheeFromTorrentFile } from "./searchee.js";
|
12
|
-
import {
|
13
|
+
import { createSearcheeFromTorrentFile, getAnimeKeys, getEpisodeKey, getMovieKey, getSeasonKey, } from "./searchee.js";
|
14
|
+
import { createKeyTitle, MediaType, stripExtension } from "./utils.js";
|
13
15
|
export var SnatchError;
|
14
16
|
(function (SnatchError) {
|
15
17
|
SnatchError["ABORTED"] = "ABORTED";
|
@@ -29,29 +31,32 @@ function isMagnetRedirectError(error) {
|
|
29
31
|
// undici
|
30
32
|
Boolean(error.cause?.message.includes("URL scheme must be a HTTP(S) scheme")));
|
31
33
|
}
|
32
|
-
export async function snatch(
|
33
|
-
const abortController = new AbortController();
|
34
|
+
export async function snatch(candidate) {
|
34
35
|
const { snatchTimeout } = getRuntimeConfig();
|
35
|
-
|
36
|
-
|
37
|
-
}
|
36
|
+
const url = candidate.link;
|
37
|
+
const tracker = candidate.tracker;
|
38
38
|
let response;
|
39
39
|
try {
|
40
40
|
response = await fetch(url, {
|
41
41
|
headers: { "User-Agent": USER_AGENT },
|
42
|
-
signal:
|
42
|
+
signal: typeof snatchTimeout === "number"
|
43
|
+
? AbortSignal.timeout(snatchTimeout)
|
44
|
+
: undefined,
|
43
45
|
});
|
44
46
|
}
|
45
47
|
catch (e) {
|
46
|
-
if (e.name === "AbortError") {
|
47
|
-
logger.error(`
|
48
|
+
if (e.name === "AbortError" || e.name === "TimeoutError") {
|
49
|
+
logger.error(`Snatch timed out from ${tracker} for ${candidate.name}`);
|
50
|
+
logger.debug(`${candidate.name}: ${url}`);
|
48
51
|
return resultOfErr(SnatchError.ABORTED);
|
49
52
|
}
|
50
53
|
else if (isMagnetRedirectError(e)) {
|
51
|
-
logger.
|
54
|
+
logger.verbose(`Unsupported: magnet link detected from ${tracker} for ${candidate.name}`);
|
55
|
+
logger.debug(`${candidate.name}: ${url}`);
|
52
56
|
return resultOfErr(SnatchError.MAGNET_LINK);
|
53
57
|
}
|
54
|
-
logger.error(`
|
58
|
+
logger.error(`Failed to access ${tracker} for ${candidate.name}`);
|
59
|
+
logger.debug(`${candidate.name}: ${url}`);
|
55
60
|
logger.debug(e);
|
56
61
|
return resultOfErr(SnatchError.UNKNOWN_ERROR);
|
57
62
|
}
|
@@ -59,36 +64,52 @@ export async function snatch(url, tracker) {
|
|
59
64
|
return resultOfErr(SnatchError.RATE_LIMITED);
|
60
65
|
}
|
61
66
|
else if (!response.ok) {
|
62
|
-
logger.error(`
|
63
|
-
|
67
|
+
logger.error(`Error downloading torrent from ${tracker} for ${candidate.name}: ${response.status} ${response.statusText}`);
|
68
|
+
const responseText = await response.clone().text();
|
69
|
+
logger.debug(`${candidate.name}: ${url} - Response: "${responseText.slice(0, 100)}${responseText.length > 100 ? "..." : ""}"`);
|
64
70
|
return resultOfErr(SnatchError.UNKNOWN_ERROR);
|
65
71
|
}
|
66
72
|
else if (response.headers.get("Content-Type") === "application/rss+xml") {
|
67
73
|
const responseText = await response.clone().text();
|
68
|
-
|
69
|
-
|
70
|
-
}
|
71
|
-
logger.error(`invalid torrent contents from ${tracker}: ${url}`);
|
72
|
-
logger.debug(`contents: "${responseText.slice(0, 100)}${responseText.length > 100 ? "..." : ""}"`);
|
74
|
+
logger.error(`Invalid torrent contents from ${tracker} for ${candidate.name}`);
|
75
|
+
logger.debug(`${candidate.name}: ${url} - Contents: "${responseText.slice(0, 100)}${responseText.length > 100 ? "..." : ""}"`);
|
73
76
|
return resultOfErr(SnatchError.INVALID_CONTENTS);
|
74
77
|
}
|
75
78
|
try {
|
76
79
|
return resultOf(Metafile.decode(Buffer.from(new Uint8Array(await response.arrayBuffer()))));
|
77
80
|
}
|
78
81
|
catch (e) {
|
79
|
-
logger.error(`
|
82
|
+
logger.error(`Invalid torrent contents from ${tracker} for ${candidate.name}`);
|
80
83
|
const contentType = response.headers.get("Content-Type");
|
81
84
|
const contentLength = response.headers.get("Content-Length");
|
82
|
-
logger.debug(
|
83
|
-
logger.debug(
|
85
|
+
logger.debug(`${candidate.name}: ${url} - Content-Type: ${contentType} - Content-Length: ${contentLength}`);
|
86
|
+
logger.debug(e);
|
84
87
|
return resultOfErr(SnatchError.INVALID_CONTENTS);
|
85
88
|
}
|
86
89
|
}
|
87
90
|
export async function saveTorrentFile(tracker, tag, meta) {
|
88
91
|
const { outputDir } = getRuntimeConfig();
|
89
92
|
const buf = meta.encode();
|
90
|
-
|
91
|
-
|
93
|
+
// Be sure to update parseInfoFromSavedTorrent if changing the format
|
94
|
+
const filePath = join(outputDir, `[${tag}][${tracker}]${stripExtension(meta.getFileSystemSafeName())}[${meta.infoHash}].torrent`);
|
95
|
+
if (fs.existsSync(filePath)) {
|
96
|
+
fs.utimesSync(filePath, new Date(), fs.statSync(filePath).mtime);
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
await writeFile(filePath, buf, { mode: 0o644 });
|
100
|
+
}
|
101
|
+
export function parseMetadataFromFilename(filename) {
|
102
|
+
const match = filename.match(SAVED_TORRENTS_INFO_REGEX);
|
103
|
+
if (!match) {
|
104
|
+
return {};
|
105
|
+
}
|
106
|
+
const mediaType = match.groups.mediaType;
|
107
|
+
if (!Object.values(MediaType).includes(mediaType)) {
|
108
|
+
return {};
|
109
|
+
}
|
110
|
+
const tracker = match.groups.tracker;
|
111
|
+
const name = match.groups.name;
|
112
|
+
return { name, mediaType, tracker };
|
92
113
|
}
|
93
114
|
export async function findAllTorrentFilesInDir(torrentDir) {
|
94
115
|
return (await readdir(torrentDir))
|
@@ -100,8 +121,8 @@ export async function indexNewTorrents() {
|
|
100
121
|
const { torrentDir } = getRuntimeConfig();
|
101
122
|
if (typeof torrentDir !== "string")
|
102
123
|
return;
|
103
|
-
const dirContents = await findAllTorrentFilesInDir(torrentDir);
|
104
|
-
//
|
124
|
+
const dirContents = new Set(await findAllTorrentFilesInDir(torrentDir));
|
125
|
+
// Index new torrents in the torrentDir
|
105
126
|
for (const filepath of dirContents) {
|
106
127
|
const doesAlreadyExist = await db("torrent")
|
107
128
|
.select("id")
|
@@ -129,9 +150,14 @@ export async function indexNewTorrents() {
|
|
129
150
|
.ignore();
|
130
151
|
}
|
131
152
|
}
|
132
|
-
|
133
|
-
|
134
|
-
|
153
|
+
const dbFiles = await db("torrent").select({ filePath: "file_path" });
|
154
|
+
const dbFilePaths = dbFiles.map((row) => row.filePath);
|
155
|
+
const filesToDelete = dbFilePaths.filter((filePath) => !dirContents.has(filePath));
|
156
|
+
const batchSize = 1000;
|
157
|
+
for (let i = 0; i < filesToDelete.length; i += batchSize) {
|
158
|
+
const batch = filesToDelete.slice(i, i + batchSize);
|
159
|
+
await db("torrent").whereIn("file_path", batch).del();
|
160
|
+
}
|
135
161
|
}
|
136
162
|
export async function getInfoHashesToExclude() {
|
137
163
|
return (await db("torrent").select({ infoHash: "info_hash" })).map((t) => t.infoHash);
|
@@ -147,18 +173,69 @@ export async function loadTorrentDirLight(torrentDir) {
|
|
147
173
|
}
|
148
174
|
return searchees;
|
149
175
|
}
|
176
|
+
function getKeysFromName(name) {
|
177
|
+
const stem = stripExtension(name);
|
178
|
+
const episodeKey = getEpisodeKey(stem);
|
179
|
+
if (episodeKey) {
|
180
|
+
const keyTitles = [episodeKey.keyTitle];
|
181
|
+
const element = `${episodeKey.season ? `${episodeKey.season}.` : ""}${episodeKey.episode}`;
|
182
|
+
return { keyTitles, element, useFallback: false };
|
183
|
+
}
|
184
|
+
const seasonKey = getSeasonKey(stem);
|
185
|
+
if (seasonKey) {
|
186
|
+
const keyTitles = [seasonKey.keyTitle];
|
187
|
+
const element = seasonKey.season;
|
188
|
+
return { keyTitles, element, useFallback: false };
|
189
|
+
}
|
190
|
+
const movieKey = getMovieKey(stem);
|
191
|
+
if (movieKey) {
|
192
|
+
const keyTitles = [movieKey.keyTitle];
|
193
|
+
return { keyTitles, useFallback: false };
|
194
|
+
}
|
195
|
+
const animeKeys = getAnimeKeys(stem);
|
196
|
+
if (animeKeys) {
|
197
|
+
const keyTitles = animeKeys.keyTitles;
|
198
|
+
const element = animeKeys.release;
|
199
|
+
return { keyTitles, element, useFallback: true };
|
200
|
+
}
|
201
|
+
return { keyTitles: [], useFallback: true };
|
202
|
+
}
|
203
|
+
export async function getSimilarTorrentsByName(name) {
|
204
|
+
const { keyTitles, element, useFallback } = getKeysFromName(name);
|
205
|
+
const metas = useFallback ? await getTorrentByFuzzyName(name) : [];
|
206
|
+
if (!keyTitles.length) {
|
207
|
+
return { keys: [], metas };
|
208
|
+
}
|
209
|
+
const candidateMaxDistance = Math.floor(Math.max(...keyTitles.map((keyTitle) => keyTitle.length)) /
|
210
|
+
LEVENSHTEIN_DIVISOR);
|
211
|
+
const allEntries = await db("torrent").select("name", "file_path");
|
212
|
+
const filteredEntries = allEntries.filter((dbName) => {
|
213
|
+
const entry = getKeysFromName(dbName.name);
|
214
|
+
if (entry.element !== element)
|
215
|
+
return false;
|
216
|
+
if (!entry.keyTitles.length)
|
217
|
+
return false;
|
218
|
+
const maxDistance = Math.max(candidateMaxDistance, Math.floor(Math.max(...entry.keyTitles.map((keyTitle) => keyTitle.length)) / LEVENSHTEIN_DIVISOR));
|
219
|
+
return entry.keyTitles.some((dbKeyTitle) => {
|
220
|
+
return keyTitles.some((keyTitle) => distance(keyTitle, dbKeyTitle) <= maxDistance);
|
221
|
+
});
|
222
|
+
});
|
223
|
+
const keys = element
|
224
|
+
? keyTitles.map((keyTitle) => `${keyTitle}.${element}`)
|
225
|
+
: keyTitles;
|
226
|
+
metas.push(...(await Promise.all(filteredEntries.map(async (dbName) => {
|
227
|
+
return parseTorrentFromFilename(dbName.file_path);
|
228
|
+
}))));
|
229
|
+
return { keys, metas };
|
230
|
+
}
|
150
231
|
export async function getTorrentByFuzzyName(name) {
|
151
232
|
const allNames = await db("torrent").select("name", "file_path");
|
152
|
-
const fullMatch =
|
153
|
-
.replace(/[^a-z0-9]/gi, "")
|
154
|
-
.toLowerCase();
|
233
|
+
const fullMatch = createKeyTitle(name);
|
155
234
|
// Attempt to filter torrents in DB to match incoming torrent before fuzzy check
|
156
235
|
let filteredNames = [];
|
157
236
|
if (fullMatch) {
|
158
237
|
filteredNames = allNames.filter((dbName) => {
|
159
|
-
const dbMatch =
|
160
|
-
.replace(/[^a-z0-9]/gi, "")
|
161
|
-
.toLowerCase();
|
238
|
+
const dbMatch = createKeyTitle(dbName.name);
|
162
239
|
if (!dbMatch)
|
163
240
|
return false;
|
164
241
|
return fullMatch === dbMatch;
|
@@ -172,24 +249,13 @@ export async function getTorrentByFuzzyName(name) {
|
|
172
249
|
distance: 6,
|
173
250
|
threshold: 0.25,
|
174
251
|
}).search(name);
|
175
|
-
// Valid matches exist
|
176
252
|
if (potentialMatches.length === 0)
|
177
|
-
return
|
178
|
-
|
179
|
-
return parseTorrentFromFilename(firstMatch.item.file_path);
|
253
|
+
return [];
|
254
|
+
return [await parseTorrentFromFilename(potentialMatches[0].item.file_path)];
|
180
255
|
}
|
181
256
|
export async function getTorrentByCriteria(criteria) {
|
182
257
|
const findResult = await db("torrent")
|
183
|
-
.where((b) => {
|
184
|
-
// there is always at least one criterion
|
185
|
-
if (criteria.infoHash) {
|
186
|
-
b = b.where({ info_hash: criteria.infoHash });
|
187
|
-
}
|
188
|
-
if (criteria.name) {
|
189
|
-
b = b.where({ name: criteria.name });
|
190
|
-
}
|
191
|
-
return b;
|
192
|
-
})
|
258
|
+
.where((b) => b.where({ info_hash: criteria.infoHash }))
|
193
259
|
.first();
|
194
260
|
if (findResult === undefined) {
|
195
261
|
const message = `torrentDir does not have any torrent with criteria ${inspect(criteria)}`;
|