cross-seed 6.0.0-9 → 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.
Files changed (70) hide show
  1. package/dist/action.js +177 -68
  2. package/dist/action.js.map +1 -1
  3. package/dist/arr.js +62 -54
  4. package/dist/arr.js.map +1 -1
  5. package/dist/clients/Deluge.js +70 -46
  6. package/dist/clients/Deluge.js.map +1 -1
  7. package/dist/clients/QBittorrent.js +110 -68
  8. package/dist/clients/QBittorrent.js.map +1 -1
  9. package/dist/clients/RTorrent.js +45 -22
  10. package/dist/clients/RTorrent.js.map +1 -1
  11. package/dist/clients/TorrentClient.js +14 -1
  12. package/dist/clients/TorrentClient.js.map +1 -1
  13. package/dist/clients/Transmission.js +29 -9
  14. package/dist/clients/Transmission.js.map +1 -1
  15. package/dist/cmd.js +46 -23
  16. package/dist/cmd.js.map +1 -1
  17. package/dist/config.template.cjs +53 -57
  18. package/dist/config.template.cjs.map +1 -1
  19. package/dist/configSchema.js +89 -25
  20. package/dist/configSchema.js.map +1 -1
  21. package/dist/configuration.js +4 -1
  22. package/dist/configuration.js.map +1 -1
  23. package/dist/constants.js +69 -10
  24. package/dist/constants.js.map +1 -1
  25. package/dist/dataFiles.js +4 -5
  26. package/dist/dataFiles.js.map +1 -1
  27. package/dist/db.js +2 -1
  28. package/dist/db.js.map +1 -1
  29. package/dist/decide.js +231 -146
  30. package/dist/decide.js.map +1 -1
  31. package/dist/diff.js +13 -3
  32. package/dist/diff.js.map +1 -1
  33. package/dist/errors.js.map +1 -1
  34. package/dist/indexers.js +94 -33
  35. package/dist/indexers.js.map +1 -1
  36. package/dist/inject.js +448 -0
  37. package/dist/inject.js.map +1 -0
  38. package/dist/jobs.js +13 -6
  39. package/dist/jobs.js.map +1 -1
  40. package/dist/logger.js +27 -9
  41. package/dist/logger.js.map +1 -1
  42. package/dist/migrations/00-initialSchema.js.map +1 -1
  43. package/dist/migrations/05-caps.js.map +1 -1
  44. package/dist/migrations/06-uniqueDecisions.js +29 -0
  45. package/dist/migrations/06-uniqueDecisions.js.map +1 -0
  46. package/dist/migrations/07-limits.js +12 -0
  47. package/dist/migrations/07-limits.js.map +1 -0
  48. package/dist/migrations/migrations.js +4 -0
  49. package/dist/migrations/migrations.js.map +1 -1
  50. package/dist/parseTorrent.js +6 -0
  51. package/dist/parseTorrent.js.map +1 -1
  52. package/dist/pipeline.js +224 -108
  53. package/dist/pipeline.js.map +1 -1
  54. package/dist/preFilter.js +122 -55
  55. package/dist/preFilter.js.map +1 -1
  56. package/dist/pushNotifier.js +7 -5
  57. package/dist/pushNotifier.js.map +1 -1
  58. package/dist/searchee.js +198 -17
  59. package/dist/searchee.js.map +1 -1
  60. package/dist/server.js +106 -54
  61. package/dist/server.js.map +1 -1
  62. package/dist/startup.js +16 -7
  63. package/dist/startup.js.map +1 -1
  64. package/dist/torrent.js +100 -37
  65. package/dist/torrent.js.map +1 -1
  66. package/dist/torznab.js +323 -153
  67. package/dist/torznab.js.map +1 -1
  68. package/dist/utils.js +228 -43
  69. package/dist/utils.js.map +1 -1
  70. package/package.json +11 -6
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
package/dist/dataFiles.js CHANGED
@@ -1,15 +1,14 @@
1
1
  import { readdirSync, statSync } from "fs";
2
2
  import { basename, extname, join } from "path";
3
- import { IGNORED_FOLDERS_SUBSTRINGS, IGNORED_FOLDERS_REGEX, VIDEO_EXTENSIONS, } from "./constants.js";
3
+ import { IGNORED_FOLDERS_SUBSTRINGS, VIDEO_EXTENSIONS } from "./constants.js";
4
4
  import { getRuntimeConfig } from "./runtimeConfig.js";
5
5
  function shouldIgnorePathHeuristically(root, isDir) {
6
- const folderBaseName = basename(root);
6
+ const searchBasename = basename(root);
7
7
  if (isDir) {
8
- return (IGNORED_FOLDERS_SUBSTRINGS.includes(folderBaseName.toLowerCase()) ||
9
- IGNORED_FOLDERS_REGEX.test(folderBaseName));
8
+ return IGNORED_FOLDERS_SUBSTRINGS.includes(searchBasename.toLowerCase());
10
9
  }
11
10
  else {
12
- return !VIDEO_EXTENSIONS.includes(extname(folderBaseName));
11
+ return !VIDEO_EXTENSIONS.includes(extname(searchBasename));
13
12
  }
14
13
  }
15
14
  export function findPotentialNestedRoots(root, depth, isDirHint) {
@@ -1 +1 @@
1
- {"version":3,"file":"dataFiles.js","sourceRoot":"","sources":["../src/dataFiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC/C,OAAO,EACN,0BAA0B,EAC1B,qBAAqB,EACrB,gBAAgB,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,SAAS,6BAA6B,CAAC,IAAY,EAAE,KAAc;IAClE,MAAM,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,CACN,0BAA0B,CAAC,QAAQ,CAAC,cAAc,CAAC,WAAW,EAAE,CAAC;YACjE,qBAAqB,CAAC,IAAI,CAAC,cAAc,CAAC,CAC1C,CAAC;IACH,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;IAC5D,CAAC;AACF,CAAC;AACD,MAAM,UAAU,wBAAwB,CACvC,IAAY,EACZ,KAAa,EACb,SAAmB;IAEnB,MAAM,KAAK,GACV,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACpE,IAAI,KAAK,IAAI,CAAC,IAAI,6BAA6B,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,EAAE,CAAC;IACX,CAAC;IACD,wCAAwC;SACnC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,MAAM,cAAc,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CACxD,wBAAwB,CACvB,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EACvB,KAAK,GAAG,CAAC,EACT,MAAM,CAAC,WAAW,EAAE,CACpB,CACD,CAAC;QACF,OAAO,CAAC,IAAI,EAAE,GAAG,cAAc,CAAC,CAAC;IAClC,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;AACF,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC3C,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAAC;IACtD,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CACnC,WAAW,CAAC,OAAO,CAAC;SAClB,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SACtC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,wBAAwB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,CACjE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"dataFiles.js","sourceRoot":"","sources":["../src/dataFiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC/C,OAAO,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,SAAS,6BAA6B,CAAC,IAAY,EAAE,KAAc;IAClE,MAAM,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,0BAA0B,CAAC,QAAQ,CACzC,cAAc,CAAC,WAAW,EAAE,CAC5B,CAAC;IACH,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;IAC5D,CAAC;AACF,CAAC;AAED,MAAM,UAAU,wBAAwB,CACvC,IAAY,EACZ,KAAa,EACb,SAAmB;IAEnB,MAAM,KAAK,GACV,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACpE,IAAI,KAAK,IAAI,CAAC,IAAI,6BAA6B,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,EAAE,CAAC;IACX,CAAC;IACD,wCAAwC;SACnC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,cAAc,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,MAAM,cAAc,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CACxD,wBAAwB,CACvB,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EACvB,KAAK,GAAG,CAAC,EACT,MAAM,CAAC,WAAW,EAAE,CACpB,CACD,CAAC;QACF,OAAO,CAAC,IAAI,EAAE,GAAG,cAAc,CAAC,CAAC;IAClC,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;AACF,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC3C,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAAC;IACtD,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CACnC,WAAW,CAAC,OAAO,CAAC;SAClB,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SACtC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,wBAAwB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,CACjE,CAAC;AACH,CAAC"}
package/dist/db.js CHANGED
@@ -1,8 +1,9 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2
+ import Sqlite from "better-sqlite3";
1
3
  import Knex from "knex";
2
4
  import { join } from "path";
3
5
  import { appDir } from "./configuration.js";
4
6
  import { migrations } from "./migrations/migrations.js";
5
- import Sqlite from "better-sqlite3";
6
7
  const filename = join(appDir(), "cross-seed.db");
7
8
  const rawSqliteHandle = new Sqlite(filename);
8
9
  rawSqliteHandle.pragma("journal_mode = WAL");
package/dist/db.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,MAAM,MAAM,gBAAgB,CAAC;AAEpC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC;AACjD,MAAM,eAAe,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC7C,eAAe,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAC7C,eAAe,CAAC,KAAK,EAAE,CAAC;AAExB,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC;IAC3B,MAAM,EAAE,gBAAgB;IACxB,UAAU,EAAE,EAAE,QAAQ,EAAE;IACxB,UAAU,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE;IAC3C,gBAAgB,EAAE,IAAI;CACtB,CAAC,CAAC"}
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,OAAO,MAAM,MAAM,gBAAgB,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAExD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC;AACjD,MAAM,eAAe,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC7C,eAAe,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAC7C,eAAe,CAAC,KAAK,EAAE,CAAC;AAExB,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC;IAC3B,MAAM,EAAE,gBAAgB;IACxB,UAAU,EAAE,EAAE,QAAQ,EAAE;IACxB,UAAU,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE;IAC3C,gBAAgB,EAAE,IAAI;CACtB,CAAC,CAAC"}
package/dist/decide.js CHANGED
@@ -1,22 +1,19 @@
1
- import { existsSync, statSync, utimesSync, writeFileSync } from "fs";
1
+ import { existsSync, statSync, unlinkSync, utimesSync, writeFileSync, } from "fs";
2
+ import ms from "ms";
2
3
  import path from "path";
3
4
  import { appDir } from "./configuration.js";
4
- import { Decision, isDecisionAnyMatch, MatchMode, RELEASE_GROUP_REGEX, REPACK_PROPER_REGEX, RES_STRICT_REGEX, TORRENT_CACHE_FOLDER, } from "./constants.js";
5
+ import { ANIME_GROUP_REGEX, Decision, isAnyMatchedDecision, isStaticDecision, MatchMode, parseSource, REPACK_PROPER_REGEX, RES_STRICT_REGEX, SEASON_REGEX, TORRENT_CACHE_FOLDER, } from "./constants.js";
5
6
  import { db } from "./db.js";
6
7
  import { Label, logger } from "./logger.js";
7
- import { findBlockedStringInReleaseMaybe } from "./preFilter.js";
8
+ import { Metafile } from "./parseTorrent.js";
9
+ import { findBlockedStringInReleaseMaybe, isSingleEpisode, } from "./preFilter.js";
10
+ import { resultOf, resultOfErr } from "./Result.js";
8
11
  import { getRuntimeConfig } from "./runtimeConfig.js";
12
+ import { getReleaseGroup, } from "./searchee.js";
9
13
  import { parseTorrentFromFilename, snatch, SnatchError } from "./torrent.js";
10
- import { humanReadableSize } from "./utils.js";
11
- const createReasonLogger = (Title, tracker, name) => (decision, cached, searchee, candidate) => {
12
- if (cached)
13
- return;
14
- function logReason(reason) {
15
- logger.verbose({
16
- label: Label.DECIDE,
17
- message: `${name} - no match for ${tracker} torrent ${Title} - ${reason}`,
18
- });
19
- }
14
+ import { extractInt, getFuzzySizeFactor, getLogString, getMediaType, getMinSizeRatio, sanitizeInfoHash, stripExtension, wait, } from "./utils.js";
15
+ function logDecision(searchee, candidate, decision, metafile, tracker) {
16
+ const { matchMode } = getRuntimeConfig();
20
17
  let reason;
21
18
  switch (decision) {
22
19
  case Decision.MATCH_PARTIAL:
@@ -26,14 +23,16 @@ const createReasonLogger = (Title, tracker, name) => (decision, cached, searchee
26
23
  case Decision.MATCH:
27
24
  return;
28
25
  case Decision.FUZZY_SIZE_MISMATCH:
26
+ reason = `the total sizes are outside of the fuzzySizeThreshold range: ${Math.abs((candidate.size - searchee.length) / searchee.length).toFixed(3)} > ${getFuzzySizeFactor()}`;
27
+ break;
29
28
  case Decision.SIZE_MISMATCH:
29
+ reason = `some files are missing or have different sizes${compareFileTreesPartial(metafile, searchee) ? ` (will match in partial match mode)` : ""}`;
30
+ break;
30
31
  case Decision.PARTIAL_SIZE_MISMATCH:
31
- reason = `its size does not match by ${decision}${candidate.size
32
- ? `: ${candidate.size >= searchee.length ? "+" : ""}${humanReadableSize(candidate.size - searchee.length)}`
33
- : ""}`;
32
+ reason = `too many files are missing or have different sizes: torrent progress would be ${(getPartialSizeRatio(metafile, searchee) * 100).toFixed(3)}%`;
34
33
  break;
35
34
  case Decision.RESOLUTION_MISMATCH:
36
- reason = `its resolution does not match: ${searchee.name.match(RES_STRICT_REGEX)?.groups?.res} -> ${candidate.name.match(RES_STRICT_REGEX)?.groups?.res}`;
35
+ reason = `its resolution does not match: ${searchee.title.match(RES_STRICT_REGEX)?.groups?.res} -> ${candidate.name.match(RES_STRICT_REGEX)?.groups?.res}`;
37
36
  break;
38
37
  case Decision.NO_DOWNLOAD_LINK:
39
38
  reason = "it doesn't have a download link";
@@ -47,23 +46,25 @@ const createReasonLogger = (Title, tracker, name) => (decision, cached, searchee
47
46
  case Decision.MAGNET_LINK:
48
47
  reason = "the torrent is a magnet link";
49
48
  break;
49
+ case Decision.SAME_INFO_HASH:
50
+ reason = "the info hash is the same";
51
+ break;
50
52
  case Decision.INFO_HASH_ALREADY_EXISTS:
51
53
  reason = "the info hash matches a torrent you already have";
52
54
  break;
53
55
  case Decision.FILE_TREE_MISMATCH:
54
- reason = "it has a different file tree";
56
+ reason = `it has a different file tree${matchMode === MatchMode.SAFE ? " (will match in risky or partial match mode)" : ""}`;
55
57
  break;
56
58
  case Decision.RELEASE_GROUP_MISMATCH:
57
- reason = `it has a different release group: ${searchee.name
58
- .match(RELEASE_GROUP_REGEX)
59
- ?.groups?.group?.trim()} -> ${candidate.name
60
- .match(RELEASE_GROUP_REGEX)
61
- ?.groups?.group?.trim()}`;
59
+ reason = `it has a different release group: ${getReleaseGroup(stripExtension(searchee.title))} -> ${getReleaseGroup(stripExtension(candidate.name))}`;
62
60
  break;
63
61
  case Decision.PROPER_REPACK_MISMATCH:
64
- reason = `one is a different subsequent release: ${searchee.name.match(REPACK_PROPER_REGEX)?.groups?.type ??
62
+ reason = `one is a different subsequent release: ${searchee.title.match(REPACK_PROPER_REGEX)?.groups?.type ??
65
63
  "INITIAL"} -> ${candidate.name.match(REPACK_PROPER_REGEX)?.groups?.type ?? "INITIAL"}`;
66
64
  break;
65
+ case Decision.SOURCE_MISMATCH:
66
+ reason = `it has a different source: ${parseSource(searchee.title)} -> ${parseSource(candidate.name)}`;
67
+ break;
67
68
  case Decision.BLOCKED_RELEASE:
68
69
  reason = `it matches the blocklist: "${findBlockedStringInReleaseMaybe(searchee, getRuntimeConfig().blockList)}"`;
69
70
  break;
@@ -71,8 +72,11 @@ const createReasonLogger = (Title, tracker, name) => (decision, cached, searchee
71
72
  reason = decision;
72
73
  break;
73
74
  }
74
- logReason(reason);
75
- };
75
+ logger.verbose({
76
+ label: Label.DECIDE,
77
+ message: `${getLogString(searchee)} - no match for ${tracker} torrent ${candidate.name}${metafile ? ` [${sanitizeInfoHash(metafile.infoHash)}]` : ""} - ${reason}`,
78
+ });
79
+ }
76
80
  export function compareFileTrees(candidate, searchee) {
77
81
  const cmp = (elOfA, elOfB) => {
78
82
  const lengthsAreEqual = elOfB.length === elOfA.length;
@@ -82,19 +86,21 @@ export function compareFileTrees(candidate, searchee) {
82
86
  return candidate.files.every((elOfA) => searchee.files.some((elOfB) => cmp(elOfA, elOfB)));
83
87
  }
84
88
  export function compareFileTreesIgnoringNames(candidate, searchee) {
89
+ const availableFiles = searchee.files.slice();
85
90
  for (const candidateFile of candidate.files) {
86
- let matchedSearcheeFiles = searchee.files.filter((searcheeFile) => searcheeFile.length === candidateFile.length);
91
+ let matchedSearcheeFiles = availableFiles.filter((searcheeFile) => searcheeFile.length === candidateFile.length);
87
92
  if (matchedSearcheeFiles.length > 1) {
88
93
  matchedSearcheeFiles = matchedSearcheeFiles.filter((searcheeFile) => searcheeFile.name === candidateFile.name);
89
94
  }
90
95
  if (matchedSearcheeFiles.length === 0) {
91
96
  return false;
92
97
  }
98
+ const index = availableFiles.indexOf(matchedSearcheeFiles[0]);
99
+ availableFiles.splice(index, 1);
93
100
  }
94
101
  return true;
95
102
  }
96
- export function comparePartialSizeOnly(candidate, searchee) {
97
- const { fuzzySizeThreshold } = getRuntimeConfig();
103
+ export function getPartialSizeRatio(candidate, searchee) {
98
104
  let matchedSizes = 0;
99
105
  for (const candidateFile of candidate.files) {
100
106
  const searcheeHasFileSize = searchee.files.some((searcheeFile) => searcheeFile.length === candidateFile.length);
@@ -102,33 +108,35 @@ export function comparePartialSizeOnly(candidate, searchee) {
102
108
  matchedSizes += candidateFile.length;
103
109
  }
104
110
  }
105
- return matchedSizes / candidate.length >= 1 - fuzzySizeThreshold;
111
+ return matchedSizes / candidate.length;
106
112
  }
107
113
  export function compareFileTreesPartial(candidate, searchee) {
108
- const { fuzzySizeThreshold } = getRuntimeConfig();
109
114
  let matchedSizes = 0;
115
+ const availableFiles = searchee.files.slice();
110
116
  for (const candidateFile of candidate.files) {
111
- let matchedSearcheeFiles = searchee.files.filter((searcheeFile) => searcheeFile.length === candidateFile.length);
117
+ let matchedSearcheeFiles = availableFiles.filter((searcheeFile) => searcheeFile.length === candidateFile.length);
112
118
  if (matchedSearcheeFiles.length > 1) {
113
119
  matchedSearcheeFiles = matchedSearcheeFiles.filter((searcheeFile) => searcheeFile.name === candidateFile.name);
114
120
  }
115
121
  if (matchedSearcheeFiles.length) {
116
122
  matchedSizes += candidateFile.length;
123
+ const index = availableFiles.indexOf(matchedSearcheeFiles[0]);
124
+ availableFiles.splice(index, 1);
117
125
  }
118
126
  }
119
127
  const totalPieces = Math.ceil(candidate.length / candidate.pieceLength);
120
128
  const availablePieces = Math.floor(matchedSizes / candidate.pieceLength);
121
- return availablePieces / totalPieces >= 1 - fuzzySizeThreshold;
129
+ return availablePieces / totalPieces >= getMinSizeRatio();
122
130
  }
123
131
  function fuzzySizeDoesMatch(resultSize, searchee) {
124
- const { fuzzySizeThreshold } = getRuntimeConfig();
132
+ const fuzzySizeFactor = getFuzzySizeFactor();
125
133
  const { length } = searchee;
126
- const lowerBound = length - fuzzySizeThreshold * length;
127
- const upperBound = length + fuzzySizeThreshold * length;
134
+ const lowerBound = length - fuzzySizeFactor * length;
135
+ const upperBound = length + fuzzySizeFactor * length;
128
136
  return resultSize >= lowerBound && resultSize <= upperBound;
129
137
  }
130
- function resolutionDoesMatch(searcheeName, candidateName, matchMode) {
131
- const searcheeRes = searcheeName
138
+ function resolutionDoesMatch(searcheeTitle, candidateName) {
139
+ const searcheeRes = searcheeTitle
132
140
  .match(RES_STRICT_REGEX)
133
141
  ?.groups?.res?.trim()
134
142
  ?.toLowerCase();
@@ -136,88 +144,116 @@ function resolutionDoesMatch(searcheeName, candidateName, matchMode) {
136
144
  .match(RES_STRICT_REGEX)
137
145
  ?.groups?.res?.trim()
138
146
  ?.toLowerCase();
139
- if (searcheeRes === candidateRes) {
147
+ if (!searcheeRes || !candidateRes)
140
148
  return true;
141
- }
142
- // if we are unsure, pass in risky or partial mode but fail in safe mode
143
- if (!searcheeRes || !candidateRes) {
144
- return matchMode !== MatchMode.SAFE;
145
- }
146
- return searcheeRes.startsWith(candidateRes);
149
+ return extractInt(searcheeRes) === extractInt(candidateRes);
147
150
  }
148
- function releaseVersionDoesMatch(searcheeName, candidateName, matchMode) {
149
- const searcheeVersionType = searcheeName.match(REPACK_PROPER_REGEX);
150
- const candidateVersionType = candidateName.match(REPACK_PROPER_REGEX);
151
- const searcheeTypeStr = searcheeVersionType?.groups?.type
152
- ?.trim()
153
- ?.toLowerCase();
154
- const candidateTypeStr = candidateVersionType?.groups?.type
155
- ?.trim()
156
- ?.toLowerCase();
157
- if (searcheeTypeStr !== candidateTypeStr ||
158
- searcheeVersionType?.groups?.arrtype) {
159
- return matchMode !== MatchMode.SAFE;
151
+ function releaseGroupDoesMatch(searcheeTitle, candidateName) {
152
+ const searcheeReleaseGroup = getReleaseGroup(stripExtension(searcheeTitle))?.toLowerCase();
153
+ const candidateReleaseGroup = getReleaseGroup(stripExtension(candidateName))?.toLowerCase();
154
+ if (!searcheeReleaseGroup || !candidateReleaseGroup) {
155
+ return true; // Pass if missing -GRP
160
156
  }
161
- return true;
162
- }
163
- function releaseGroupDoesMatch(searcheeName, candidateName, matchMode) {
164
- const searcheeReleaseGroup = searcheeName
165
- .match(RELEASE_GROUP_REGEX)
157
+ if (searcheeReleaseGroup.startsWith(candidateReleaseGroup) ||
158
+ candidateReleaseGroup.startsWith(searcheeReleaseGroup)) {
159
+ return true; // -GRP matches
160
+ }
161
+ // Anime naming can cause weird things to match as release groups
162
+ const searcheeAnimeGroup = searcheeTitle
163
+ .match(ANIME_GROUP_REGEX)
166
164
  ?.groups?.group?.trim()
167
165
  ?.toLowerCase();
168
- const candidateReleaseGroup = candidateName
169
- .match(RELEASE_GROUP_REGEX)
166
+ const candidateAnimeGroup = candidateName
167
+ .match(ANIME_GROUP_REGEX)
170
168
  ?.groups?.group?.trim()
171
169
  ?.toLowerCase();
172
- if (searcheeReleaseGroup === candidateReleaseGroup) {
173
- return true;
170
+ if (!searcheeAnimeGroup && !candidateAnimeGroup) {
171
+ return false;
174
172
  }
175
- // if we are unsure, pass in risky or partial mode but fail in safe mode
176
- if (!searcheeReleaseGroup || !candidateReleaseGroup) {
177
- return matchMode !== MatchMode.SAFE;
173
+ // Most checks will never get here, below are rare edge cases
174
+ if (searcheeAnimeGroup === candidateAnimeGroup) {
175
+ return true;
178
176
  }
179
- return searcheeReleaseGroup.startsWith(candidateReleaseGroup);
180
- }
181
- async function assessCandidateHelper(candidate, searchee, hashesToExclude) {
182
- const { matchMode, blockList } = getRuntimeConfig();
183
- const { name, size, link } = candidate;
184
- if (!releaseGroupDoesMatch(searchee.name, name, matchMode)) {
185
- return { decision: Decision.RELEASE_GROUP_MISMATCH };
177
+ if (searcheeAnimeGroup && searcheeAnimeGroup === candidateReleaseGroup) {
178
+ return true;
186
179
  }
187
- if (!releaseVersionDoesMatch(searchee.name, name, matchMode)) {
188
- return { decision: Decision.PROPER_REPACK_MISMATCH };
180
+ if (candidateAnimeGroup && searcheeReleaseGroup === candidateAnimeGroup) {
181
+ return true;
189
182
  }
190
- if (size) {
191
- if (!fuzzySizeDoesMatch(size, searchee)) {
192
- return { decision: Decision.FUZZY_SIZE_MISMATCH };
183
+ return false;
184
+ }
185
+ function sourceDoesMatch(searcheeTitle, candidateName) {
186
+ const searcheeSource = parseSource(searcheeTitle);
187
+ const candidateSource = parseSource(candidateName);
188
+ if (!searcheeSource || !candidateSource)
189
+ return true;
190
+ return searcheeSource === candidateSource;
191
+ }
192
+ export async function assessCandidate(metaOrCandidate, searchee, hashesToExclude) {
193
+ const { blockList, includeSingleEpisodes, matchMode } = getRuntimeConfig();
194
+ // When metaOrCandidate is a Metafile, skip straight to the
195
+ // main matching algorithms as we don't need pre-download filtering.
196
+ const isCandidate = !(metaOrCandidate instanceof Metafile);
197
+ const name = metaOrCandidate.name;
198
+ const size = isCandidate ? metaOrCandidate.size : metaOrCandidate.length;
199
+ if (isCandidate) {
200
+ if (!releaseGroupDoesMatch(searchee.title, name)) {
201
+ return { decision: Decision.RELEASE_GROUP_MISMATCH };
193
202
  }
194
- }
195
- else {
196
- if (!resolutionDoesMatch(searchee.name, name, matchMode)) {
203
+ if (!resolutionDoesMatch(searchee.title, name)) {
197
204
  return { decision: Decision.RESOLUTION_MISMATCH };
198
205
  }
199
- }
200
- if (!link) {
201
- return { decision: Decision.NO_DOWNLOAD_LINK };
206
+ if (!sourceDoesMatch(searchee.title, name)) {
207
+ return { decision: Decision.SOURCE_MISMATCH };
208
+ }
209
+ if (size && !fuzzySizeDoesMatch(size, searchee)) {
210
+ return { decision: Decision.FUZZY_SIZE_MISMATCH };
211
+ }
212
+ if (!metaOrCandidate.link) {
213
+ return { decision: Decision.NO_DOWNLOAD_LINK };
214
+ }
202
215
  }
203
216
  if (findBlockedStringInReleaseMaybe(searchee, blockList)) {
204
217
  return { decision: Decision.BLOCKED_RELEASE };
205
218
  }
206
- const result = await snatch(candidate);
207
- if (result.isErr()) {
208
- const err = result.unwrapErr();
209
- return err === SnatchError.RATE_LIMITED
210
- ? { decision: Decision.RATE_LIMITED }
211
- : err === SnatchError.MAGNET_LINK
212
- ? { decision: Decision.MAGNET_LINK }
213
- : { decision: Decision.DOWNLOAD_FAILED };
219
+ let metafile;
220
+ if (isCandidate) {
221
+ let res = await snatch(metaOrCandidate);
222
+ if (res.isErr()) {
223
+ const e = res.unwrapErr();
224
+ if ([Label.ANNOUNCE, Label.RSS].includes(searchee.label) &&
225
+ ![SnatchError.RATE_LIMITED, SnatchError.MAGNET_LINK].includes(e)) {
226
+ await wait(ms("30 seconds"));
227
+ res = await snatch(metaOrCandidate);
228
+ }
229
+ if (res.isErr()) {
230
+ const err = res.unwrapErr();
231
+ return err === SnatchError.MAGNET_LINK
232
+ ? { decision: Decision.MAGNET_LINK }
233
+ : err === SnatchError.RATE_LIMITED
234
+ ? { decision: Decision.RATE_LIMITED }
235
+ : { decision: Decision.DOWNLOAD_FAILED };
236
+ }
237
+ }
238
+ metafile = res.unwrap();
239
+ cacheTorrentFile(metafile);
240
+ metaOrCandidate.size = metafile.length; // Trackers can be wrong
241
+ }
242
+ else {
243
+ metafile = metaOrCandidate;
244
+ }
245
+ if (searchee.infoHash === metafile.infoHash) {
246
+ return { decision: Decision.SAME_INFO_HASH, metafile };
214
247
  }
215
- const metafile = result.unwrap();
216
- cacheTorrentFile(metafile);
217
- candidate.size = metafile.length; // Trackers can be wrong
218
248
  if (hashesToExclude.includes(metafile.infoHash)) {
219
249
  return { decision: Decision.INFO_HASH_ALREADY_EXISTS, metafile };
220
250
  }
251
+ // Prevent candidate episodes from matching searchee season packs
252
+ if (!includeSingleEpisodes &&
253
+ SEASON_REGEX.test(searchee.title) &&
254
+ isSingleEpisode(metafile, getMediaType(metafile))) {
255
+ return { decision: Decision.FILE_TREE_MISMATCH, metafile };
256
+ }
221
257
  const perfectMatch = compareFileTrees(metafile, searchee);
222
258
  if (perfectMatch) {
223
259
  return { decision: Decision.MATCH, metafile };
@@ -227,7 +263,7 @@ async function assessCandidateHelper(candidate, searchee, hashesToExclude) {
227
263
  return { decision: Decision.MATCH_SIZE_ONLY, metafile };
228
264
  }
229
265
  if (matchMode === MatchMode.PARTIAL) {
230
- const partialSizeMatch = comparePartialSizeOnly(metafile, searchee);
266
+ const partialSizeMatch = getPartialSizeRatio(metafile, searchee) >= getMinSizeRatio();
231
267
  if (!partialSizeMatch) {
232
268
  return { decision: Decision.PARTIAL_SIZE_MISMATCH, metafile };
233
269
  }
@@ -248,8 +284,22 @@ function existsInTorrentCache(infoHash) {
248
284
  utimesSync(torrentPath, new Date(), statSync(torrentPath).mtime);
249
285
  return true;
250
286
  }
251
- async function getCachedTorrentFile(infoHash) {
252
- return parseTorrentFromFilename(path.join(appDir(), TORRENT_CACHE_FOLDER, `${infoHash}.cached.torrent`));
287
+ async function getCachedTorrentFile(infoHash, options = { deleteOnFail: true }) {
288
+ const torrentPath = path.join(appDir(), TORRENT_CACHE_FOLDER, `${infoHash}.cached.torrent`);
289
+ try {
290
+ return resultOf(await parseTorrentFromFilename(torrentPath));
291
+ }
292
+ catch (e) {
293
+ logger.error({
294
+ label: Label.DECIDE,
295
+ message: `Failed to parse cached torrent ${sanitizeInfoHash(infoHash)}${options.deleteOnFail ? " - deleting" : ""}`,
296
+ });
297
+ logger.debug(e);
298
+ if (options.deleteOnFail) {
299
+ unlinkSync(torrentPath);
300
+ }
301
+ return resultOfErr(e);
302
+ }
253
303
  }
254
304
  function cacheTorrentFile(meta) {
255
305
  const torrentPath = path.join(appDir(), TORRENT_CACHE_FOLDER, `${meta.infoHash}.cached.torrent`);
@@ -257,77 +307,112 @@ function cacheTorrentFile(meta) {
257
307
  return;
258
308
  writeFileSync(torrentPath, meta.encode());
259
309
  }
260
- async function assessAndSaveResults(result, searchee, guid, infoHashesToExclude) {
261
- const assessment = await assessCandidateHelper(result, searchee, infoHashesToExclude);
310
+ async function assessAndSaveResults(metaOrCandidate, searchee, guid, infoHashesToExclude, firstSeen) {
311
+ const assessment = await assessCandidate(metaOrCandidate, searchee, infoHashesToExclude);
262
312
  await db.transaction(async (trx) => {
263
- const now = Date.now();
264
313
  const { id } = await trx("searchee")
265
314
  .select("id")
266
- .where({ name: searchee.name })
315
+ .where({ name: searchee.title })
267
316
  .first();
268
- await trx("decision").insert({
317
+ await trx("decision")
318
+ .insert({
269
319
  searchee_id: id,
270
320
  guid: guid,
271
- decision: assessment.decision,
272
321
  info_hash: assessment.metafile?.infoHash ?? null,
273
- last_seen: now,
274
- first_seen: now,
275
- });
322
+ decision: assessment.decision,
323
+ first_seen: firstSeen,
324
+ last_seen: Date.now(),
325
+ fuzzy_size_factor: getFuzzySizeFactor(),
326
+ })
327
+ .onConflict(["searchee_id", "guid"])
328
+ .merge();
276
329
  });
277
330
  return assessment;
278
331
  }
279
- async function assessCandidateCaching(candidate, searchee, infoHashesToExclude) {
332
+ /**
333
+ * Some trackers have alt titles which get their own guid but resolve to same torrent
334
+ * @param guid The guid of the candidate
335
+ * @returns The info hash of the torrent if found
336
+ */
337
+ async function fuzzyGuidLookup(guid) {
338
+ if (!guid.includes(".tv/torrent/"))
339
+ return;
340
+ const torrentIdStr = guid.match(/\.tv\/torrent\/(\d+)\/group/)?.[1];
341
+ if (!torrentIdStr)
342
+ return;
343
+ return (await db("decision")
344
+ .select({ infoHash: "info_hash" })
345
+ .where("guid", "like", `%.tv/torrent/${torrentIdStr}/group%`)
346
+ .whereNotNull("info_hash")
347
+ .first())?.infoHash;
348
+ }
349
+ export async function assessCandidateCaching(candidate, searchee, infoHashesToExclude) {
280
350
  const { guid, name, tracker } = candidate;
281
- const logReason = createReasonLogger(name, tracker, searchee.name);
282
351
  const cacheEntry = await db("decision")
283
352
  .select({
284
- decision: "decision.decision",
285
- infoHash: "decision.info_hash",
286
353
  id: "decision.id",
354
+ infoHash: "decision.info_hash",
355
+ decision: "decision.decision",
356
+ firstSeen: "decision.first_seen",
357
+ fuzzySizeFactor: "decision.fuzzy_size_factor",
287
358
  })
288
359
  .join("searchee", "decision.searchee_id", "searchee.id")
289
- .where({ name: searchee.name, guid })
360
+ .where({ name: searchee.title, guid })
290
361
  .first();
362
+ const metaInfoHash = (await db("decision")
363
+ .select({ infoHash: "info_hash" })
364
+ .where({ guid })
365
+ .whereNotNull("info_hash")
366
+ .first())?.infoHash ?? (await fuzzyGuidLookup(guid));
367
+ const metaOrCandidate = metaInfoHash
368
+ ? existsInTorrentCache(metaInfoHash)
369
+ ? (await getCachedTorrentFile(metaInfoHash)).orElse(candidate)
370
+ : candidate
371
+ : candidate;
372
+ if (metaOrCandidate instanceof Metafile) {
373
+ logger.verbose({
374
+ label: Label.DECIDE,
375
+ message: `Using cached torrent ${sanitizeInfoHash(metaInfoHash)} for ${tracker} assessment ${name}`,
376
+ });
377
+ candidate.size = metaOrCandidate.length; // Trackers can be wrong
378
+ }
291
379
  let assessment;
292
- if (!cacheEntry?.decision ||
293
- cacheEntry.decision === Decision.DOWNLOAD_FAILED ||
294
- cacheEntry.decision === Decision.RATE_LIMITED) {
295
- assessment = await assessAndSaveResults(candidate, searchee, guid, infoHashesToExclude);
296
- logReason(assessment.decision, false, searchee, candidate);
380
+ if (!cacheEntry?.decision) {
381
+ // New candidate, could be metafile from cache
382
+ assessment = await assessAndSaveResults(metaOrCandidate, searchee, guid, infoHashesToExclude, Date.now());
297
383
  }
298
- else if (isDecisionAnyMatch(cacheEntry.decision) &&
384
+ else if (isAnyMatchedDecision(cacheEntry.decision) &&
299
385
  infoHashesToExclude.includes(cacheEntry.infoHash)) {
300
- // has been added since the last run
386
+ // Already injected fast path, preserve match decision
301
387
  assessment = { decision: Decision.INFO_HASH_ALREADY_EXISTS };
302
- await db("decision")
303
- .where({ id: cacheEntry.id })
304
- .update({ decision: Decision.INFO_HASH_ALREADY_EXISTS });
305
- }
306
- else if (isDecisionAnyMatch(cacheEntry.decision) &&
307
- cacheEntry.infoHash &&
308
- existsInTorrentCache(cacheEntry.infoHash)) {
309
- // cached match
310
- assessment = {
311
- decision: cacheEntry.decision,
312
- metafile: await getCachedTorrentFile(cacheEntry.infoHash),
313
- };
388
+ await db("decision").where({ id: cacheEntry.id }).update({
389
+ last_seen: Date.now(),
390
+ });
314
391
  }
315
- else if (isDecisionAnyMatch(cacheEntry.decision)) {
316
- assessment = await assessAndSaveResults(candidate, searchee, guid, infoHashesToExclude);
317
- logReason(assessment.decision, false, searchee, candidate);
392
+ else if (isStaticDecision(cacheEntry.decision)) {
393
+ // These decisions will never change unless we update their logic
394
+ assessment = { decision: cacheEntry.decision };
395
+ await db("decision").where({ id: cacheEntry.id }).update({
396
+ last_seen: Date.now(),
397
+ });
318
398
  }
319
399
  else {
320
- // cached rejection
321
- assessment = { decision: cacheEntry.decision };
322
- logReason(cacheEntry.decision, true, searchee, candidate);
400
+ // Re-assess decisions using Metafile if cached
401
+ if (cacheEntry.decision !== Decision.FUZZY_SIZE_MISMATCH ||
402
+ cacheEntry.fuzzySizeFactor < getFuzzySizeFactor()) {
403
+ assessment = await assessAndSaveResults(metaOrCandidate, searchee, guid, infoHashesToExclude, cacheEntry.firstSeen);
404
+ }
405
+ else {
406
+ assessment = { decision: Decision.FUZZY_SIZE_MISMATCH };
407
+ await db("decision").where({ id: cacheEntry.id }).update({
408
+ last_seen: Date.now(),
409
+ });
410
+ }
323
411
  }
324
- // if previously known
325
- if (cacheEntry) {
326
- await db("decision")
327
- .where({ id: cacheEntry.id })
328
- .update({ last_seen: Date.now() });
412
+ const wasCached = cacheEntry?.decision === assessment.decision;
413
+ if (!wasCached) {
414
+ logDecision(searchee, candidate, assessment.decision, assessment.metafile, tracker);
329
415
  }
330
416
  return assessment;
331
417
  }
332
- export { assessCandidateCaching as assessCandidate };
333
418
  //# sourceMappingURL=decide.js.map