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.
Files changed (69) hide show
  1. package/dist/Result.js +17 -11
  2. package/dist/Result.js.map +1 -1
  3. package/dist/action.js +188 -74
  4. package/dist/action.js.map +1 -1
  5. package/dist/arr.js +197 -0
  6. package/dist/arr.js.map +1 -0
  7. package/dist/clients/Deluge.js +153 -81
  8. package/dist/clients/Deluge.js.map +1 -1
  9. package/dist/clients/QBittorrent.js +95 -64
  10. package/dist/clients/QBittorrent.js.map +1 -1
  11. package/dist/clients/RTorrent.js +31 -10
  12. package/dist/clients/RTorrent.js.map +1 -1
  13. package/dist/clients/TorrentClient.js.map +1 -1
  14. package/dist/clients/Transmission.js +30 -10
  15. package/dist/clients/Transmission.js.map +1 -1
  16. package/dist/cmd.js +32 -16
  17. package/dist/cmd.js.map +1 -1
  18. package/dist/config.template.cjs +83 -46
  19. package/dist/config.template.cjs.map +1 -1
  20. package/dist/configSchema.js +98 -13
  21. package/dist/configSchema.js.map +1 -1
  22. package/dist/configuration.js +3 -0
  23. package/dist/configuration.js.map +1 -1
  24. package/dist/constants.js +113 -6
  25. package/dist/constants.js.map +1 -1
  26. package/dist/dataFiles.js +4 -5
  27. package/dist/dataFiles.js.map +1 -1
  28. package/dist/decide.js +276 -160
  29. package/dist/decide.js.map +1 -1
  30. package/dist/diff.js +12 -2
  31. package/dist/diff.js.map +1 -1
  32. package/dist/errors.js +5 -2
  33. package/dist/errors.js.map +1 -1
  34. package/dist/indexers.js +31 -4
  35. package/dist/indexers.js.map +1 -1
  36. package/dist/inject.js +409 -0
  37. package/dist/inject.js.map +1 -0
  38. package/dist/jobs.js +8 -1
  39. package/dist/jobs.js.map +1 -1
  40. package/dist/logger.js +20 -5
  41. package/dist/logger.js.map +1 -1
  42. package/dist/migrations/05-caps.js +16 -0
  43. package/dist/migrations/05-caps.js.map +1 -0
  44. package/dist/migrations/06-uniqueDecisions.js +29 -0
  45. package/dist/migrations/06-uniqueDecisions.js.map +1 -0
  46. package/dist/migrations/migrations.js +11 -1
  47. package/dist/migrations/migrations.js.map +1 -1
  48. package/dist/parseTorrent.js +6 -0
  49. package/dist/parseTorrent.js.map +1 -1
  50. package/dist/pipeline.js +202 -95
  51. package/dist/pipeline.js.map +1 -1
  52. package/dist/preFilter.js +130 -52
  53. package/dist/preFilter.js.map +1 -1
  54. package/dist/pushNotifier.js +6 -4
  55. package/dist/pushNotifier.js.map +1 -1
  56. package/dist/runtimeConfig.js.map +1 -1
  57. package/dist/searchee.js +190 -19
  58. package/dist/searchee.js.map +1 -1
  59. package/dist/server.js +70 -32
  60. package/dist/server.js.map +1 -1
  61. package/dist/startup.js +12 -1
  62. package/dist/startup.js.map +1 -1
  63. package/dist/torrent.js +103 -40
  64. package/dist/torrent.js.map +1 -1
  65. package/dist/torznab.js +304 -90
  66. package/dist/torznab.js.map +1 -1
  67. package/dist/utils.js +215 -33
  68. package/dist/utils.js.map +1 -1
  69. 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["REVERSE_LOOKUP"] = "reverselookup";
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
- // redact torznab api keys
50
- ret = ret.replace(/apikey=[a-zA-Z0-9]+/g, `apikey=${redactionMsg}`);
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.colorize(), winston.format.printf(({ level, message, label, timestamp }) => {
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({
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
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([initialSchema, jobs, timestamps, rateLimits, auth]),
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;AAEhC,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,aAAa,EAAE,GAAG,EAAE,CACnB,OAAO,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;IACrE,gBAAgB,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI;IAC/C,YAAY,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS;CACtC,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"}
@@ -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));
@@ -1 +1 @@
1
- {"version":3,"file":"parseTorrent.js","sourceRoot":"","sources":["../src/parseTorrent.ts"],"names":[],"mappings":"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"}
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 { assessCandidate } from "./decide.js";
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, filterDupes, filterTimestamps } from "./preFilter.js";
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, getTorrentByFuzzyName, indexNewTorrents, loadTorrentDirLight, } from "./torrent.js";
17
- import { queryRssFeeds, searchTorznab } from "./torznab.js";
18
- import { filterAsync, isTruthy, stripExtension } from "./utils.js";
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 assessCandidate(result, searchee, hashesToExclude);
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.name })
31
+ .insert({ name: searchee.title })
31
32
  .onConflict("name")
32
33
  .ignore();
33
- let response;
34
- try {
35
- response = await searchTorznab(searchee);
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 === Decision.MATCH ||
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
- if (actionResults.includes(InjectionResult.TORRENT_NOT_COMPLETE)) {
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, Label.SEARCH);
70
- return { matches: matches.length, searchedIndexers: response.length };
65
+ sendResultsNotification(searchee, zipped);
66
+ return { searchedIndexers, matches: matches.length };
71
67
  }
72
- async function findMatchesBatch(samples, hashesToExclude) {
68
+ async function findMatchesBatch(searchees, hashesToExclude) {
73
69
  const { delay } = getRuntimeConfig();
74
70
  let totalFound = 0;
75
- for (const [i, sample] of samples.entries()) {
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 sleep = new Promise((r) => setTimeout(r, delay * 1000));
78
- const progress = chalk.blue(`[${i + 1}/${samples.length}]`);
79
- const name = stripExtension(sample.name);
80
- logger.info("%s %s %s", progress, chalk.dim("Searching for"), name);
81
- const { matches, searchedIndexers } = await findOnOtherSites(sample, hashesToExclude);
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
- await sleep;
86
+ prevSearchTime = searchTime;
87
87
  }
88
88
  catch (e) {
89
- logger.error(`error searching for ${sample.name}`);
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
- let searchees;
98
- if (criteria.path) {
99
- const searcheeResults = await Promise.all(findPotentialNestedRoots(criteria.path, maxDataDepth).map(createSearcheeFromPath));
100
- searchees = searcheeResults.map((t) => t.unwrapOrThrow());
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
- searchees = [await getTorrentByCriteria(criteria)];
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 matches = 0;
107
- for (let i = 0; i < searchees.length; i++) {
108
- if (!filterByContent(searchees[i]))
109
- return null;
110
- const foundOnOtherSites = await findOnOtherSites(searchees[i], hashesToExclude);
111
- matches += foundOnOtherSites.matches;
112
- }
113
- return matches;
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 meta = await getTorrentByFuzzyName(candidate.name);
117
- if (meta === null) {
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: Label.REVERSE_LOOKUP,
120
- message: `Did not find an existing entry for ${candidate.name}`,
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
- if (!filterByContent(meta))
126
- return null;
127
- const searchee = createSearcheeFromMetafile(meta);
128
- // make sure searchee is in database
129
- await db("searchee")
130
- .insert({ name: searchee.name })
131
- .onConflict("name")
132
- .ignore();
133
- const assessment = await assessCandidate(candidate, searchee, hashesToExclude);
134
- if (assessment.decision !== Decision.MATCH &&
135
- assessment.decision !== Decision.MATCH_SIZE_ONLY &&
136
- assessment.decision !== Decision.MATCH_PARTIAL) {
137
- return null;
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
- const result = await performAction(assessment.metafile, assessment.decision, searchee, candidate.tracker);
140
- await sendResultsNotification(searchee, [[assessment, candidate.tracker, result]], Label.REVERSE_LOOKUP);
141
- return result;
208
+ return { decision, actionResult };
142
209
  }
143
- async function findSearchableTorrents() {
144
- const { torrents, dataDirs, torrentDir, searchLimit } = getRuntimeConfig();
145
- let allSearchees = [];
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
- allSearchees = searcheeResults
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
- allSearchees.push(...(await loadTorrentDirLight(torrentDir)));
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
- allSearchees.push(...searcheeResults
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
- const hashesToExclude = allSearchees
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
- let filteredTorrents = await filterAsync(filterDupes(allSearchees).filter(filterByContent), filterTimestamps);
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 ${allSearchees.length} torrents, ${filteredTorrents.length} suitable to search for matches`,
275
+ message: `Found ${realSearchees.length} torrents, ${finalSearchees.length} suitable to search for matches using ${grouping.size} unique queries`,
170
276
  });
171
- if (searchLimit && filteredTorrents.length > 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
- filteredTorrents = filteredTorrents.slice(0, searchLimit);
282
+ finalSearchees = finalSearchees.slice(0, searchLimit);
177
283
  }
178
- return { samples: filteredTorrents, hashesToExclude };
284
+ return { searchees: finalSearchees, hashesToExclude };
179
285
  }
180
286
  export async function main() {
181
287
  const { outputDir, linkDir } = getRuntimeConfig();
182
- const { samples, hashesToExclude } = await findSearchableTorrents();
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(samples, hashesToExclude);
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(samples.length)} original torrents`),
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: `Processing release ${i + 1}/${candidatesSinceLastTime.length}`,
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
  }