cross-seed 6.0.0-3 → 6.0.0-31
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/Result.js +17 -11
- package/dist/Result.js.map +1 -1
- package/dist/action.js +188 -74
- package/dist/action.js.map +1 -1
- package/dist/arr.js +197 -0
- package/dist/arr.js.map +1 -0
- package/dist/clients/Deluge.js +153 -81
- package/dist/clients/Deluge.js.map +1 -1
- package/dist/clients/QBittorrent.js +95 -64
- package/dist/clients/QBittorrent.js.map +1 -1
- package/dist/clients/RTorrent.js +31 -10
- package/dist/clients/RTorrent.js.map +1 -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 +32 -16
- package/dist/cmd.js.map +1 -1
- package/dist/config.template.cjs +83 -46
- package/dist/config.template.cjs.map +1 -1
- package/dist/configSchema.js +98 -13
- package/dist/configSchema.js.map +1 -1
- package/dist/configuration.js +3 -0
- package/dist/configuration.js.map +1 -1
- package/dist/constants.js +113 -6
- package/dist/constants.js.map +1 -1
- package/dist/dataFiles.js +4 -5
- package/dist/dataFiles.js.map +1 -1
- package/dist/decide.js +276 -160
- package/dist/decide.js.map +1 -1
- package/dist/diff.js +12 -2
- package/dist/diff.js.map +1 -1
- package/dist/errors.js +5 -2
- package/dist/errors.js.map +1 -1
- package/dist/indexers.js +31 -4
- package/dist/indexers.js.map +1 -1
- package/dist/inject.js +409 -0
- package/dist/inject.js.map +1 -0
- package/dist/jobs.js +8 -1
- package/dist/jobs.js.map +1 -1
- package/dist/logger.js +20 -5
- package/dist/logger.js.map +1 -1
- package/dist/migrations/05-caps.js +16 -0
- package/dist/migrations/05-caps.js.map +1 -0
- package/dist/migrations/06-uniqueDecisions.js +29 -0
- package/dist/migrations/06-uniqueDecisions.js.map +1 -0
- package/dist/migrations/migrations.js +11 -1
- 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 +202 -95
- package/dist/pipeline.js.map +1 -1
- package/dist/preFilter.js +130 -52
- package/dist/preFilter.js.map +1 -1
- package/dist/pushNotifier.js +6 -4
- package/dist/pushNotifier.js.map +1 -1
- package/dist/runtimeConfig.js.map +1 -1
- package/dist/searchee.js +190 -19
- package/dist/searchee.js.map +1 -1
- package/dist/server.js +70 -32
- package/dist/server.js.map +1 -1
- package/dist/startup.js +12 -1
- package/dist/startup.js.map +1 -1
- package/dist/torrent.js +103 -40
- package/dist/torrent.js.map +1 -1
- package/dist/torznab.js +304 -90
- package/dist/torznab.js.map +1 -1
- package/dist/utils.js +215 -33
- package/dist/utils.js.map +1 -1
- package/package.json +3 -1
package/dist/logger.js
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import { join } from "path";
|
2
|
+
import stripAnsi from "strip-ansi";
|
2
3
|
import winston from "winston";
|
3
4
|
import DailyRotateFile from "winston-daily-rotate-file";
|
4
5
|
import { appDir, createAppDir } from "./configuration.js";
|
@@ -10,6 +11,7 @@ export var Label;
|
|
10
11
|
Label["TRANSMISSION"] = "transmission";
|
11
12
|
Label["DELUGE"] = "deluge";
|
12
13
|
Label["DECIDE"] = "decide";
|
14
|
+
Label["INJECT"] = "inject";
|
13
15
|
Label["PREFILTER"] = "prefilter";
|
14
16
|
Label["CONFIGDUMP"] = "configdump";
|
15
17
|
Label["TORZNAB"] = "torznab";
|
@@ -18,8 +20,12 @@ export var Label;
|
|
18
20
|
Label["SCHEDULER"] = "scheduler";
|
19
21
|
Label["SEARCH"] = "search";
|
20
22
|
Label["RSS"] = "rss";
|
23
|
+
Label["ANNOUNCE"] = "announce";
|
24
|
+
Label["WEBHOOK"] = "webhook";
|
21
25
|
Label["PERF"] = "perf";
|
22
|
-
Label["
|
26
|
+
Label["ARRS"] = "arrs";
|
27
|
+
Label["RADARR"] = "radarr";
|
28
|
+
Label["SONARR"] = "sonarr";
|
23
29
|
})(Label || (Label = {}));
|
24
30
|
export let logger;
|
25
31
|
const redactionMsg = "[REDACTED]";
|
@@ -46,8 +52,11 @@ function redactMessage(message, options) {
|
|
46
52
|
}
|
47
53
|
const runtimeConfig = options ?? getRuntimeConfig();
|
48
54
|
let ret = message;
|
49
|
-
|
50
|
-
ret = ret.replace(/
|
55
|
+
ret = ret.replace(/key=[a-zA-Z0-9]+/g, `key=${redactionMsg}`);
|
56
|
+
ret = ret.replace(/pass=[a-zA-Z0-9]+/g, `pass=${redactionMsg}`);
|
57
|
+
ret = ret.replace(/(?:(?:auto|download)[./]\d+[./])([a-zA-Z0-9]+)/g, (match, key) => match.replace(key, redactionMsg));
|
58
|
+
ret = ret.replace(/(?:\d+[./](?:auto|download)[./])([a-zA-Z0-9]+)/g, (match, key) => match.replace(key, redactionMsg));
|
59
|
+
ret = ret.replace(/apiKey: '.+'/g, `apiKey: ${redactionMsg}`);
|
51
60
|
ret = ret.replace(/\/notification\/crossSeed\/[a-zA-Z-0-9_-]+/g, `/notification/crossSeed/${redactionMsg}`);
|
52
61
|
for (const [key, urlStr] of Object.entries(runtimeConfig)) {
|
53
62
|
if (key.endsWith("Url") && urlStr) {
|
@@ -56,6 +65,12 @@ function redactMessage(message, options) {
|
|
56
65
|
}
|
57
66
|
return ret;
|
58
67
|
}
|
68
|
+
function stripAnsiChars(message) {
|
69
|
+
if (typeof message !== "string") {
|
70
|
+
return message;
|
71
|
+
}
|
72
|
+
return stripAnsi(message);
|
73
|
+
}
|
59
74
|
const logOnceCache = [];
|
60
75
|
export function logOnce(cacheKey, cb) {
|
61
76
|
if (!logOnceCache.includes(cacheKey)) {
|
@@ -69,8 +84,8 @@ export function initializeLogger(options) {
|
|
69
84
|
level: "info",
|
70
85
|
format: winston.format.combine(winston.format.timestamp({
|
71
86
|
format: "YYYY-MM-DD HH:mm:ss",
|
72
|
-
}), winston.format.errors({ stack: true }), winston.format.splat(), winston.format.
|
73
|
-
return `${timestamp} ${level}: ${label ? `[${label}] ` : ""}${redactMessage(message, options)}`;
|
87
|
+
}), winston.format.errors({ stack: true }), winston.format.splat(), winston.format.printf(({ level, message, label, timestamp, stack }) => {
|
88
|
+
return `${timestamp} ${level}: ${label ? `[${label}] ` : ""}${stripAnsiChars(redactMessage(stack ? stack : message, options))}`;
|
74
89
|
})),
|
75
90
|
transports: [
|
76
91
|
new DailyRotateFile({
|
package/dist/logger.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,eAAe,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAiB,MAAM,oBAAoB,CAAC;AAErE,MAAM,CAAN,IAAY,
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
async function up(knex) {
|
2
|
+
await knex.schema.alterTable("indexer", (table) => {
|
3
|
+
table.json("tv_id_caps");
|
4
|
+
table.json("movie_id_caps");
|
5
|
+
table.json("cat_caps");
|
6
|
+
});
|
7
|
+
}
|
8
|
+
async function down(knex) {
|
9
|
+
return knex.schema.table("indexer", function (table) {
|
10
|
+
table.dropColumn("tv_id_caps");
|
11
|
+
table.dropColumn("movie_id_caps");
|
12
|
+
table.dropColumn("cat_caps");
|
13
|
+
});
|
14
|
+
}
|
15
|
+
export default { name: "05-caps", up, down };
|
16
|
+
//# sourceMappingURL=05-caps.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"05-caps.js","sourceRoot":"","sources":["../../src/migrations/05-caps.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,EAAE,CAAC,IAAe;IAChC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QACjD,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI,CAAC,IAAe;IAClC,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,UAAU,KAAK;QAClD,KAAK,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QAC/B,KAAK,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;QAClC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACJ,CAAC;AACD,eAAe,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC"}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
async function up(knex) {
|
2
|
+
// Remove null info_hash as we may run out of memory if too many entries
|
3
|
+
await knex("decision").whereNull("info_hash").del();
|
4
|
+
// Remove duplicate decisions preserving the most recent one
|
5
|
+
await knex.raw(`
|
6
|
+
DELETE FROM decision
|
7
|
+
WHERE id NOT IN (
|
8
|
+
SELECT id
|
9
|
+
FROM (
|
10
|
+
SELECT id, ROW_NUMBER() OVER (PARTITION BY searchee_id, guid ORDER BY last_seen DESC) AS row_num
|
11
|
+
FROM decision
|
12
|
+
) AS subquery
|
13
|
+
WHERE row_num = 1
|
14
|
+
)
|
15
|
+
`);
|
16
|
+
// Add unique constraint to prevent duplicates and add fuzzy_size_factor
|
17
|
+
await knex.schema.alterTable("decision", (table) => {
|
18
|
+
table.float("fuzzy_size_factor").defaultTo(0.02);
|
19
|
+
table.unique(["searchee_id", "guid"]);
|
20
|
+
});
|
21
|
+
}
|
22
|
+
async function down(knex) {
|
23
|
+
return knex.schema.alterTable("decision", (table) => {
|
24
|
+
table.dropColumn("fuzzy_size_factor");
|
25
|
+
table.dropUnique(["searchee_id", "guid"]);
|
26
|
+
});
|
27
|
+
}
|
28
|
+
export default { name: "06-uniqueDecisions", up, down };
|
29
|
+
//# sourceMappingURL=06-uniqueDecisions.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"06-uniqueDecisions.js","sourceRoot":"","sources":["../../src/migrations/06-uniqueDecisions.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,EAAE,CAAC,IAAe;IAChC,wEAAwE;IACxE,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,CAAC;IAEpD,4DAA4D;IAC5D,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;KAUX,CAAC,CAAC;IAEN,wEAAwE;IACxE,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;QAClD,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACjD,KAAK,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI,CAAC,IAAe;IAClC,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;QACnD,KAAK,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;QACtC,KAAK,CAAC,UAAU,CAAC,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,eAAe,EAAE,IAAI,EAAE,oBAAoB,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC"}
|
@@ -3,8 +3,18 @@ import jobs from "./01-jobs.js";
|
|
3
3
|
import timestamps from "./02-timestamps.js";
|
4
4
|
import rateLimits from "./03-rateLimits.js";
|
5
5
|
import auth from "./04-auth.js";
|
6
|
+
import caps from "./05-caps.js";
|
7
|
+
import uniqueDecisions from "./06-uniqueDecisions.js";
|
6
8
|
export const migrations = {
|
7
|
-
getMigrations: () => Promise.resolve([
|
9
|
+
getMigrations: () => Promise.resolve([
|
10
|
+
initialSchema,
|
11
|
+
jobs,
|
12
|
+
timestamps,
|
13
|
+
rateLimits,
|
14
|
+
auth,
|
15
|
+
caps,
|
16
|
+
uniqueDecisions,
|
17
|
+
]),
|
8
18
|
getMigrationName: (migration) => migration.name,
|
9
19
|
getMigration: (migration) => migration,
|
10
20
|
};
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/migrations/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,uBAAuB,CAAC;AAClD,OAAO,IAAI,MAAM,cAAc,CAAC;AAChC,OAAO,UAAU,MAAM,oBAAoB,CAAC;AAC5C,OAAO,UAAU,MAAM,oBAAoB,CAAC;AAC5C,OAAO,IAAI,MAAM,cAAc,CAAC;
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/migrations/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,uBAAuB,CAAC;AAClD,OAAO,IAAI,MAAM,cAAc,CAAC;AAChC,OAAO,UAAU,MAAM,oBAAoB,CAAC;AAC5C,OAAO,UAAU,MAAM,oBAAoB,CAAC;AAC5C,OAAO,IAAI,MAAM,cAAc,CAAC;AAChC,OAAO,IAAI,MAAM,cAAc,CAAC;AAChC,OAAO,eAAe,MAAM,yBAAyB,CAAC;AAEtD,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,aAAa,EAAE,GAAG,EAAE,CACnB,OAAO,CAAC,OAAO,CAAC;QACf,aAAa;QACb,IAAI;QACJ,UAAU;QACV,UAAU;QACV,IAAI;QACJ,IAAI;QACJ,eAAe;KACf,CAAC;IACH,gBAAgB,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI;IAC/C,YAAY,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS;CACtC,CAAC"}
|
package/dist/parseTorrent.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import bencode from "bencode";
|
2
2
|
import { createHash } from "crypto";
|
3
3
|
import { join } from "path";
|
4
|
+
import { parseTitle } from "./searchee.js";
|
4
5
|
import { fallback } from "./utils.js";
|
5
6
|
function sumLength(sum, file) {
|
6
7
|
return sum + file.length;
|
@@ -18,6 +19,10 @@ export class Metafile {
|
|
18
19
|
infoHash;
|
19
20
|
length;
|
20
21
|
name;
|
22
|
+
/**
|
23
|
+
* Always name, exists for compatibility with Searchee
|
24
|
+
*/
|
25
|
+
title;
|
21
26
|
pieceLength;
|
22
27
|
files;
|
23
28
|
isSingleFileTorrent;
|
@@ -72,6 +77,7 @@ export class Metafile {
|
|
72
77
|
this.length = this.files.reduce(sumLength, 0);
|
73
78
|
this.isSingleFileTorrent = false;
|
74
79
|
}
|
80
|
+
this.title = parseTitle(this.name, this.files) ?? this.name;
|
75
81
|
}
|
76
82
|
static decode(buf) {
|
77
83
|
return new Metafile(bencode.decode(buf));
|
package/dist/parseTorrent.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"parseTorrent.js","sourceRoot":"","sources":["../src/parseTorrent.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
1
|
+
{"version":3,"file":"parseTorrent.js","sourceRoot":"","sources":["../src/parseTorrent.ts"],"names":[],"mappings":"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"}
|
package/dist/pipeline.js
CHANGED
@@ -3,46 +3,48 @@ import fs from "fs";
|
|
3
3
|
import { zip } from "lodash-es";
|
4
4
|
import ms from "ms";
|
5
5
|
import { performAction, performActions } from "./action.js";
|
6
|
-
import { Decision, InjectionResult, } from "./constants.js";
|
6
|
+
import { Decision, InjectionResult, isAnyMatchedDecision, SaveResult, } from "./constants.js";
|
7
7
|
import { findPotentialNestedRoots, findSearcheesFromAllDataDirs, } from "./dataFiles.js";
|
8
8
|
import { db } from "./db.js";
|
9
|
-
import {
|
9
|
+
import { assessCandidateCaching } from "./decide.js";
|
10
10
|
import { IndexerStatus, updateIndexerStatus, updateSearchTimestamps, } from "./indexers.js";
|
11
11
|
import { Label, logger } from "./logger.js";
|
12
|
-
import { filterByContent,
|
12
|
+
import { filterByContent, filterDupesFromSimilar, filterTimestamps, } from "./preFilter.js";
|
13
13
|
import { sendResultsNotification } from "./pushNotifier.js";
|
14
|
+
import { isOk } from "./Result.js";
|
14
15
|
import { getRuntimeConfig } from "./runtimeConfig.js";
|
15
16
|
import { createSearcheeFromMetafile, createSearcheeFromPath, createSearcheeFromTorrentFile, } from "./searchee.js";
|
16
|
-
import { getInfoHashesToExclude, getTorrentByCriteria,
|
17
|
-
import { queryRssFeeds, searchTorznab } from "./torznab.js";
|
18
|
-
import {
|
17
|
+
import { getInfoHashesToExclude, getTorrentByCriteria, getSimilarTorrentsByName, indexNewTorrents, loadTorrentDirLight, } from "./torrent.js";
|
18
|
+
import { getSearchString, queryRssFeeds, searchTorznab, } from "./torznab.js";
|
19
|
+
import { getLogString, isTruthy, wait } from "./utils.js";
|
19
20
|
async function assessCandidates(candidates, searchee, hashesToExclude) {
|
20
21
|
const assessments = [];
|
21
22
|
for (const result of candidates) {
|
22
|
-
const assessment = await
|
23
|
+
const assessment = await assessCandidateCaching(result, searchee, hashesToExclude);
|
23
24
|
assessments.push({ assessment, tracker: result.tracker });
|
24
25
|
}
|
25
26
|
return assessments;
|
26
27
|
}
|
27
|
-
async function findOnOtherSites(searchee, hashesToExclude) {
|
28
|
+
async function findOnOtherSites(searchee, hashesToExclude, cachedSearch, progress) {
|
28
29
|
// make sure searchee is in database
|
29
30
|
await db("searchee")
|
30
|
-
.insert({ name: searchee.
|
31
|
+
.insert({ name: searchee.title })
|
31
32
|
.onConflict("name")
|
32
33
|
.ignore();
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
catch (e) {
|
38
|
-
logger.error(`error searching for ${searchee.name}`);
|
39
|
-
logger.debug(e);
|
40
|
-
return { searchedIndexers: 0, matches: 0 };
|
41
|
-
}
|
34
|
+
const response = await searchTorznab(searchee, cachedSearch, progress);
|
35
|
+
const cachedIndexers = cachedSearch.indexerCandidates.length;
|
36
|
+
const searchedIndexers = response.length - cachedIndexers;
|
37
|
+
cachedSearch.indexerCandidates = response;
|
42
38
|
const results = response.flatMap((e) => e.candidates.map((candidate) => ({
|
43
39
|
...candidate,
|
44
40
|
indexerId: e.indexerId,
|
45
41
|
})));
|
42
|
+
if (response.length) {
|
43
|
+
logger.verbose({
|
44
|
+
label: Label.DECIDE,
|
45
|
+
message: `Assessing ${results.length} candidates for ${searchee.title} from ${searchedIndexers}|${cachedIndexers} indexers by search|cache`,
|
46
|
+
});
|
47
|
+
}
|
46
48
|
const assessments = await assessCandidates(results, searchee, hashesToExclude);
|
47
49
|
const { rateLimited, notRateLimited } = assessments.reduce((acc, cur, idx) => {
|
48
50
|
const candidate = results[idx];
|
@@ -55,141 +57,245 @@ async function findOnOtherSites(searchee, hashesToExclude) {
|
|
55
57
|
rateLimited: new Set(),
|
56
58
|
notRateLimited: new Set(response.map((r) => r.indexerId)),
|
57
59
|
});
|
58
|
-
const matches = assessments.filter((e) => e.assessment.decision
|
59
|
-
e.assessment.decision === Decision.MATCH_SIZE_ONLY ||
|
60
|
-
e.assessment.decision === Decision.MATCH_PARTIAL);
|
60
|
+
const matches = assessments.filter((e) => isAnyMatchedDecision(e.assessment.decision));
|
61
61
|
const actionResults = await performActions(searchee, matches);
|
62
|
-
|
63
|
-
// If the torrent is not complete, "cancel the search"
|
64
|
-
return { matches: 0, searchedIndexers: 0 };
|
65
|
-
}
|
66
|
-
await updateSearchTimestamps(searchee.name, Array.from(notRateLimited));
|
62
|
+
await updateSearchTimestamps(searchee.title, Array.from(notRateLimited));
|
67
63
|
await updateIndexerStatus(IndexerStatus.RATE_LIMITED, Date.now() + ms("1 hour"), Array.from(rateLimited));
|
68
64
|
const zipped = zip(matches.map((m) => m.assessment), matches.map((m) => m.tracker), actionResults);
|
69
|
-
sendResultsNotification(searchee, zipped
|
70
|
-
return { matches: matches.length
|
65
|
+
sendResultsNotification(searchee, zipped);
|
66
|
+
return { searchedIndexers, matches: matches.length };
|
71
67
|
}
|
72
|
-
async function findMatchesBatch(
|
68
|
+
async function findMatchesBatch(searchees, hashesToExclude) {
|
73
69
|
const { delay } = getRuntimeConfig();
|
74
70
|
let totalFound = 0;
|
75
|
-
|
71
|
+
let prevSearchTime = 0;
|
72
|
+
const cachedSearch = { q: null, indexerCandidates: [] };
|
73
|
+
for (const [i, searchee] of searchees.entries()) {
|
74
|
+
const progress = chalk.blue(`(${i + 1}/${searchees.length}) `);
|
76
75
|
try {
|
77
|
-
const
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
const
|
76
|
+
const sleepTime = delay * 1000 - (Date.now() - prevSearchTime);
|
77
|
+
if (sleepTime > 0) {
|
78
|
+
await wait(sleepTime);
|
79
|
+
}
|
80
|
+
const searchTime = Date.now();
|
81
|
+
const { searchedIndexers, matches } = await findOnOtherSites(searchee, hashesToExclude, cachedSearch, progress);
|
82
82
|
totalFound += matches;
|
83
|
-
// if all indexers were rate limited, don't sleep
|
83
|
+
// if all indexers were rate limited or cached, don't sleep
|
84
84
|
if (searchedIndexers === 0)
|
85
85
|
continue;
|
86
|
-
|
86
|
+
prevSearchTime = searchTime;
|
87
87
|
}
|
88
88
|
catch (e) {
|
89
|
-
|
89
|
+
const searcheeLog = getLogString(searchee, chalk.bold.white);
|
90
|
+
logger.error({
|
91
|
+
label: searchee.label,
|
92
|
+
message: `${progress}Error searching for ${searcheeLog}`,
|
93
|
+
});
|
90
94
|
logger.debug(e);
|
91
95
|
}
|
92
96
|
}
|
93
97
|
return totalFound;
|
94
98
|
}
|
95
99
|
export async function searchForLocalTorrentByCriteria(criteria) {
|
96
|
-
const { maxDataDepth } = getRuntimeConfig();
|
97
|
-
|
98
|
-
if (criteria.path) {
|
99
|
-
const
|
100
|
-
|
100
|
+
const { delay, maxDataDepth } = getRuntimeConfig();
|
101
|
+
const rawSearchees = [];
|
102
|
+
if (criteria.infoHash || !criteria.path) {
|
103
|
+
const res = createSearcheeFromMetafile(await getTorrentByCriteria(criteria));
|
104
|
+
if (res.isOk())
|
105
|
+
rawSearchees.push(res.unwrap());
|
101
106
|
}
|
102
107
|
else {
|
103
|
-
|
108
|
+
const searcheeResults = await Promise.all(findPotentialNestedRoots(criteria.path, maxDataDepth).map(createSearcheeFromPath));
|
109
|
+
rawSearchees.push(...searcheeResults.filter(isOk).map((r) => r.unwrap()));
|
104
110
|
}
|
111
|
+
const searchees = rawSearchees.map((searchee) => ({
|
112
|
+
...searchee,
|
113
|
+
label: Label.WEBHOOK,
|
114
|
+
}));
|
115
|
+
const includeEpisodes = searchees.length === 1;
|
105
116
|
const hashesToExclude = await getInfoHashesToExclude();
|
106
|
-
let
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
const
|
111
|
-
|
112
|
-
|
113
|
-
|
117
|
+
let totalFound = 0;
|
118
|
+
let filtered = 0;
|
119
|
+
const cachedSearch = { q: null, indexerCandidates: [] };
|
120
|
+
for (const [i, searchee] of searchees.entries()) {
|
121
|
+
const progress = chalk.blue(`(${i + 1}/${searchees.length}) `);
|
122
|
+
try {
|
123
|
+
if (!filterByContent(searchee, includeEpisodes)) {
|
124
|
+
filtered++;
|
125
|
+
continue;
|
126
|
+
}
|
127
|
+
const sleep = wait(delay * 1000);
|
128
|
+
const { matches, searchedIndexers } = await findOnOtherSites(searchee, hashesToExclude, cachedSearch, progress);
|
129
|
+
totalFound += matches;
|
130
|
+
// if all indexers were rate limited, don't sleep
|
131
|
+
if (searchedIndexers === 0 || i === searchees.length - 1)
|
132
|
+
continue;
|
133
|
+
await sleep;
|
134
|
+
}
|
135
|
+
catch (e) {
|
136
|
+
const searcheeLog = getLogString(searchee, chalk.bold.white);
|
137
|
+
logger.error({
|
138
|
+
label: searchee.label,
|
139
|
+
message: `${progress}Error searching for ${searcheeLog}`,
|
140
|
+
});
|
141
|
+
logger.debug(e);
|
142
|
+
}
|
143
|
+
}
|
144
|
+
if (filtered === searchees.length)
|
145
|
+
return null;
|
146
|
+
return totalFound;
|
114
147
|
}
|
115
|
-
export async function checkNewCandidateMatch(candidate) {
|
116
|
-
const
|
117
|
-
|
148
|
+
export async function checkNewCandidateMatch(candidate, searcheeLabel) {
|
149
|
+
const candidateLog = `${chalk.bold.white(candidate.name)} from ${candidate.tracker}`;
|
150
|
+
const { keys, metas } = await getSimilarTorrentsByName(candidate.name);
|
151
|
+
const method = keys.length ? `[${keys}]` : "Fuse fallback";
|
152
|
+
if (!metas.length) {
|
118
153
|
logger.verbose({
|
119
|
-
label:
|
120
|
-
message: `Did not find an existing entry for ${
|
154
|
+
label: searcheeLabel,
|
155
|
+
message: `Did not find an existing entry using ${method} for ${candidateLog}`,
|
121
156
|
});
|
122
|
-
return null;
|
157
|
+
return { decision: null, actionResult: null };
|
158
|
+
}
|
159
|
+
const searchees = filterDupesFromSimilar(metas
|
160
|
+
.map(createSearcheeFromMetafile)
|
161
|
+
.filter(isOk)
|
162
|
+
.map((r) => r.unwrap())
|
163
|
+
.map((searchee) => ({ ...searchee, label: searcheeLabel }))
|
164
|
+
.filter((searchee) => filterByContent(searchee)));
|
165
|
+
if (!searchees.length) {
|
166
|
+
logger.verbose({
|
167
|
+
label: searcheeLabel,
|
168
|
+
message: `No valid entries found using ${method} for ${candidateLog}`,
|
169
|
+
});
|
170
|
+
return { decision: null, actionResult: null };
|
123
171
|
}
|
172
|
+
logger.verbose({
|
173
|
+
label: searcheeLabel,
|
174
|
+
message: `Unique entries [${searchees.map((m) => m.title)}] using ${method} for ${candidateLog}`,
|
175
|
+
});
|
124
176
|
const hashesToExclude = await getInfoHashesToExclude();
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
177
|
+
let decision = null;
|
178
|
+
let actionResult = null;
|
179
|
+
searchees.sort((a, b) => b.files.length - a.files.length);
|
180
|
+
for (const searchee of searchees) {
|
181
|
+
await db("searchee")
|
182
|
+
.insert({ name: searchee.title })
|
183
|
+
.onConflict("name")
|
184
|
+
.ignore();
|
185
|
+
const assessment = await assessCandidateCaching(candidate, searchee, hashesToExclude);
|
186
|
+
if (!isAnyMatchedDecision(assessment.decision)) {
|
187
|
+
if (assessment.decision === Decision.SAME_INFO_HASH) {
|
188
|
+
decision = null;
|
189
|
+
break;
|
190
|
+
}
|
191
|
+
if (assessment.decision === Decision.INFO_HASH_ALREADY_EXISTS &&
|
192
|
+
(!decision || !isAnyMatchedDecision(decision))) {
|
193
|
+
decision = assessment.decision;
|
194
|
+
}
|
195
|
+
continue;
|
196
|
+
}
|
197
|
+
decision = assessment.decision;
|
198
|
+
({ actionResult } = await performAction(assessment.metafile, assessment.decision, searchee, candidate.tracker));
|
199
|
+
sendResultsNotification(searchee, [
|
200
|
+
[assessment, candidate.tracker, actionResult],
|
201
|
+
]);
|
202
|
+
if (actionResult === SaveResult.SAVED ||
|
203
|
+
actionResult === InjectionResult.SUCCESS ||
|
204
|
+
actionResult === InjectionResult.ALREADY_EXISTS) {
|
205
|
+
break;
|
206
|
+
}
|
138
207
|
}
|
139
|
-
|
140
|
-
await sendResultsNotification(searchee, [[assessment, candidate.tracker, result]], Label.REVERSE_LOOKUP);
|
141
|
-
return result;
|
208
|
+
return { decision, actionResult };
|
142
209
|
}
|
143
|
-
async function
|
144
|
-
const { torrents, dataDirs, torrentDir
|
145
|
-
|
210
|
+
export async function findAllSearchees(searcheeLabel) {
|
211
|
+
const { torrents, dataDirs, torrentDir } = getRuntimeConfig();
|
212
|
+
const rawSearchees = [];
|
146
213
|
if (Array.isArray(torrents)) {
|
147
214
|
const searcheeResults = await Promise.all(torrents.map(createSearcheeFromTorrentFile));
|
148
|
-
|
149
|
-
.filter((t) => t.isOk())
|
150
|
-
.map((t) => t.unwrapOrThrow());
|
215
|
+
rawSearchees.push(...searcheeResults.filter(isOk).map((r) => r.unwrap()));
|
151
216
|
}
|
152
217
|
else {
|
153
218
|
if (typeof torrentDir === "string") {
|
154
|
-
|
219
|
+
rawSearchees.push(...(await loadTorrentDirLight(torrentDir)));
|
155
220
|
}
|
156
221
|
if (Array.isArray(dataDirs)) {
|
157
222
|
const searcheeResults = await Promise.all(findSearcheesFromAllDataDirs().map(createSearcheeFromPath));
|
158
|
-
|
159
|
-
.filter((t) => t.isOk())
|
160
|
-
.map((t) => t.unwrapOrThrow()));
|
223
|
+
rawSearchees.push(...searcheeResults.filter(isOk).map((r) => r.unwrap()));
|
161
224
|
}
|
162
225
|
}
|
163
|
-
|
226
|
+
return rawSearchees.map((searchee) => ({
|
227
|
+
...searchee,
|
228
|
+
label: searcheeLabel,
|
229
|
+
}));
|
230
|
+
}
|
231
|
+
async function findSearchableTorrents() {
|
232
|
+
const { searchLimit } = getRuntimeConfig();
|
233
|
+
const realSearchees = await findAllSearchees(Label.SEARCH);
|
234
|
+
const hashesToExclude = realSearchees
|
164
235
|
.map((t) => t.infoHash)
|
165
236
|
.filter(isTruthy);
|
166
|
-
|
237
|
+
// Group the exact same search queries together for easy cache use later
|
238
|
+
const grouping = new Map();
|
239
|
+
for (const searchee of realSearchees.filter((s) => filterByContent(s))) {
|
240
|
+
const key = await getSearchString(searchee);
|
241
|
+
if (!grouping.has(key)) {
|
242
|
+
grouping.set(key, []);
|
243
|
+
}
|
244
|
+
grouping.get(key).push(searchee);
|
245
|
+
}
|
246
|
+
const keysToDelete = [];
|
247
|
+
for (const [key, groupedSearchees] of grouping) {
|
248
|
+
// If one searchee needs to be searched, use the candidates for all
|
249
|
+
const filteredSearchees = filterDupesFromSimilar(groupedSearchees);
|
250
|
+
const results = await Promise.all(filteredSearchees.map(filterTimestamps));
|
251
|
+
if (!results.some(isTruthy)) {
|
252
|
+
keysToDelete.push(key);
|
253
|
+
continue;
|
254
|
+
}
|
255
|
+
// Prefer infoHash
|
256
|
+
filteredSearchees.sort((a, b) => {
|
257
|
+
if (a.infoHash && !b.infoHash)
|
258
|
+
return -1;
|
259
|
+
if (!a.infoHash && b.infoHash)
|
260
|
+
return 1;
|
261
|
+
return 0;
|
262
|
+
});
|
263
|
+
// Sort by most number files (less chance of partial)
|
264
|
+
filteredSearchees.sort((a, b) => {
|
265
|
+
return b.files.length - a.files.length;
|
266
|
+
});
|
267
|
+
grouping.set(key, filteredSearchees);
|
268
|
+
}
|
269
|
+
for (const key of keysToDelete) {
|
270
|
+
grouping.delete(key);
|
271
|
+
}
|
272
|
+
let finalSearchees = Array.from(grouping.values()).flat();
|
167
273
|
logger.info({
|
168
274
|
label: Label.SEARCH,
|
169
|
-
message: `Found ${
|
275
|
+
message: `Found ${realSearchees.length} torrents, ${finalSearchees.length} suitable to search for matches using ${grouping.size} unique queries`,
|
170
276
|
});
|
171
|
-
if (searchLimit &&
|
277
|
+
if (searchLimit && finalSearchees.length > searchLimit) {
|
172
278
|
logger.info({
|
173
279
|
label: Label.SEARCH,
|
174
280
|
message: `Limited to ${searchLimit} searches`,
|
175
281
|
});
|
176
|
-
|
282
|
+
finalSearchees = finalSearchees.slice(0, searchLimit);
|
177
283
|
}
|
178
|
-
return {
|
284
|
+
return { searchees: finalSearchees, hashesToExclude };
|
179
285
|
}
|
180
286
|
export async function main() {
|
181
287
|
const { outputDir, linkDir } = getRuntimeConfig();
|
182
|
-
const {
|
288
|
+
const { searchees, hashesToExclude } = await findSearchableTorrents();
|
183
289
|
if (!fs.existsSync(outputDir)) {
|
184
290
|
fs.mkdirSync(outputDir, { recursive: true });
|
185
291
|
}
|
186
292
|
if (linkDir && !fs.existsSync(linkDir)) {
|
187
293
|
fs.mkdirSync(linkDir, { recursive: true });
|
188
294
|
}
|
189
|
-
const totalFound = await findMatchesBatch(
|
295
|
+
const totalFound = await findMatchesBatch(searchees, hashesToExclude);
|
190
296
|
logger.info({
|
191
297
|
label: Label.SEARCH,
|
192
|
-
message: chalk.cyan(`Found ${chalk.bold.white(totalFound)} cross seeds from ${chalk.bold.white(
|
298
|
+
message: chalk.cyan(`Found ${chalk.bold.white(totalFound)} cross seeds from ${chalk.bold.white(searchees.length)} original torrents`),
|
193
299
|
});
|
194
300
|
}
|
195
301
|
export async function scanRssFeeds() {
|
@@ -211,11 +317,12 @@ export async function scanRssFeeds() {
|
|
211
317
|
});
|
212
318
|
await indexNewTorrents();
|
213
319
|
for (const [i, candidate] of candidatesSinceLastTime.entries()) {
|
320
|
+
const candidateLog = `${chalk.bold.white(candidate.name)} from ${candidate.tracker}`;
|
214
321
|
logger.verbose({
|
215
322
|
label: Label.RSS,
|
216
|
-
message: `
|
323
|
+
message: `(${i + 1}/${candidatesSinceLastTime.length}) ${candidateLog}`,
|
217
324
|
});
|
218
|
-
await checkNewCandidateMatch(candidate);
|
325
|
+
await checkNewCandidateMatch(candidate, Label.RSS);
|
219
326
|
}
|
220
327
|
logger.info({ label: Label.RSS, message: "Scan complete" });
|
221
328
|
}
|