cross-seed 6.0.0-8 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/action.js +177 -71
  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 +46 -23
  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 +30 -10
  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 +59 -59
  18. package/dist/config.template.cjs.map +1 -1
  19. package/dist/configSchema.js +90 -26
  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 +77 -9
  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 +279 -169
  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 -112
  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 +116 -50
  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 +229 -44
  69. package/dist/utils.js.map +1 -1
  70. package/package.json +11 -6
package/dist/diff.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { deepStrictEqual } from "assert";
1
2
  import { createSearcheeFromMetafile } from "./searchee.js";
2
3
  import { parseTorrentFromFilename } from "./torrent.js";
3
- import { deepStrictEqual } from "assert";
4
4
  function diff(thing1, thing2) {
5
5
  try {
6
6
  deepStrictEqual(thing1, thing2);
@@ -14,8 +14,18 @@ function diff(thing1, thing2) {
14
14
  export async function diffCmd(first, second) {
15
15
  const firstMeta = await parseTorrentFromFilename(first);
16
16
  const secondMeta = await parseTorrentFromFilename(second);
17
- const firstSearchee = createSearcheeFromMetafile(firstMeta);
18
- const secondSearchee = createSearcheeFromMetafile(secondMeta);
17
+ const firstSearcheeRes = createSearcheeFromMetafile(firstMeta);
18
+ if (firstSearcheeRes.isErr()) {
19
+ console.log(firstSearcheeRes.unwrapErr());
20
+ return;
21
+ }
22
+ const secondSearcheeRes = createSearcheeFromMetafile(secondMeta);
23
+ if (secondSearcheeRes.isErr()) {
24
+ console.log(secondSearcheeRes.unwrapErr());
25
+ return;
26
+ }
27
+ const firstSearchee = firstSearcheeRes.unwrap();
28
+ const secondSearchee = secondSearcheeRes.unwrap();
19
29
  delete firstSearchee.infoHash;
20
30
  delete secondSearchee.infoHash;
21
31
  diff(firstSearchee, secondSearchee);
package/dist/diff.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAEzC,SAAS,IAAI,CAAC,MAAM,EAAE,MAAM;IAC3B,IAAI,CAAC;QACJ,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAChB,CAAC;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAAa,EAAE,MAAc;IAC1D,MAAM,SAAS,GAAG,MAAM,wBAAwB,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,MAAM,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC1D,MAAM,aAAa,GAAG,0BAA0B,CAAC,SAAS,CAAC,CAAC;IAC5D,MAAM,cAAc,GAAG,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAC9D,OAAO,aAAa,CAAC,QAAQ,CAAC;IAC9B,OAAO,cAAc,CAAC,QAAQ,CAAC;IAC/B,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAExD,SAAS,IAAI,CAAC,MAAM,EAAE,MAAM;IAC3B,IAAI,CAAC;QACJ,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAChB,CAAC;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAAa,EAAE,MAAc;IAC1D,MAAM,SAAS,GAAG,MAAM,wBAAwB,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,MAAM,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC1D,MAAM,gBAAgB,GAAG,0BAA0B,CAAC,SAAS,CAAC,CAAC;IAC/D,IAAI,gBAAgB,CAAC,KAAK,EAAE,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,CAAC;QAC1C,OAAO;IACR,CAAC;IACD,MAAM,iBAAiB,GAAG,0BAA0B,CAAC,UAAU,CAAC,CAAC;IACjE,IAAI,iBAAiB,CAAC,KAAK,EAAE,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,SAAS,EAAE,CAAC,CAAC;QAC3C,OAAO;IACR,CAAC;IACD,MAAM,aAAa,GAAG,gBAAgB,CAAC,MAAM,EAAE,CAAC;IAChD,MAAM,cAAc,GAAG,iBAAiB,CAAC,MAAM,EAAE,CAAC;IAClD,OAAO,aAAa,CAAC,QAAQ,CAAC;IAC9B,OAAO,cAAc,CAAC,QAAQ,CAAC;IAC/B,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;AACrC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,OAAO,cAAe,SAAQ,KAAK;IACxC,YAAY,OAAgB,EAAE,OAAsB;QACnD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;IACD,KAAK;QACJ,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;IACF,CAAC;CACD;AAED,MAAM,UAAU,qBAAqB,CAAC,CAAC;IACtC,IAAI,CAAC,YAAY,cAAc,EAAE,CAAC;QACjC,CAAC,CAAC,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACR,CAAC;IACD,MAAM,CAAC,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,OAAO,cAAe,SAAQ,KAAK;IACxC,YAAY,OAAgB,EAAE,OAAsB;QACnD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;IAED,KAAK;QACJ,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;IACF,CAAC;CACD;AAED,MAAM,UAAU,qBAAqB,CAAC,CAAC;IACtC,IAAI,CAAC,YAAY,cAAc,EAAE,CAAC;QACjC,CAAC,CAAC,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACR,CAAC;IACD,MAAM,CAAC,CAAC;AACT,CAAC"}
package/dist/indexers.js CHANGED
@@ -10,43 +10,85 @@ export var IndexerStatus;
10
10
  IndexerStatus["RATE_LIMITED"] = "RATE_LIMITED";
11
11
  IndexerStatus["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
12
12
  })(IndexerStatus || (IndexerStatus = {}));
13
+ const allFields = {
14
+ id: "id",
15
+ url: "url",
16
+ apikey: "apikey",
17
+ active: "active",
18
+ status: "status",
19
+ retryAfter: "retry_after",
20
+ searchCap: "search_cap",
21
+ tvSearchCap: "tv_search_cap",
22
+ movieSearchCap: "movie_search_cap",
23
+ tvIdCaps: "tv_id_caps",
24
+ movieIdCaps: "movie_id_caps",
25
+ catCaps: "cat_caps",
26
+ limitsCaps: "limits_caps",
27
+ };
28
+ export const ALL_CAPS = {
29
+ limits: {
30
+ default: 100,
31
+ max: 100,
32
+ },
33
+ search: true,
34
+ categories: {
35
+ tv: true,
36
+ movie: true,
37
+ anime: true,
38
+ xxx: true,
39
+ audio: true,
40
+ book: true,
41
+ additional: true,
42
+ },
43
+ tvSearch: true,
44
+ movieSearch: true,
45
+ movieIdSearch: {
46
+ tvdbId: true,
47
+ tmdbId: true,
48
+ imdbId: true,
49
+ tvMazeId: true,
50
+ },
51
+ tvIdSearch: {
52
+ tvdbId: true,
53
+ tmdbId: true,
54
+ imdbId: true,
55
+ tvMazeId: true,
56
+ },
57
+ };
58
+ function deserialize(dbIndexer) {
59
+ const { tvIdCaps, movieIdCaps, catCaps, limitsCaps, ...rest } = dbIndexer;
60
+ return {
61
+ ...rest,
62
+ tvIdCaps: JSON.parse(tvIdCaps),
63
+ movieIdCaps: JSON.parse(movieIdCaps),
64
+ categories: JSON.parse(catCaps),
65
+ limits: JSON.parse(limitsCaps),
66
+ };
67
+ }
13
68
  export async function getAllIndexers() {
14
- return db("indexer").where({ active: true }).select({
15
- id: "id",
16
- url: "url",
17
- apikey: "apikey",
18
- active: "active",
19
- status: "status",
20
- retryAfter: "retry_after",
21
- searchCap: "search_cap",
22
- tvSearchCap: "tv_search_cap",
23
- movieSearchCap: "movie_search_cap",
24
- tvIdCaps: "tv_id_caps",
25
- movieIdCaps: "movie_id_caps",
26
- categories: "cat_caps",
27
- });
69
+ const rawIndexers = await db("indexer")
70
+ .where({ active: true })
71
+ .select(allFields);
72
+ return rawIndexers.map(deserialize);
28
73
  }
29
74
  export async function getEnabledIndexers() {
30
- return db("indexer")
31
- .where({ active: true, search_cap: true, status: null })
32
- .orWhere({ active: true, search_cap: true, status: IndexerStatus.OK })
33
- .orWhere((b) => b
75
+ const rawIndexers = await db("indexer")
76
+ .whereNot({
77
+ search_cap: null,
78
+ tv_search_cap: null,
79
+ movie_search_cap: null,
80
+ tv_id_caps: null,
81
+ movie_id_caps: null,
82
+ cat_caps: null,
83
+ limits_caps: null,
84
+ })
34
85
  .where({ active: true, search_cap: true })
35
- .where("retry_after", "<", Date.now()))
36
- .select({
37
- id: "id",
38
- url: "url",
39
- apikey: "apikey",
40
- active: "active",
41
- status: "status",
42
- retryAfter: "retry_after",
43
- searchCap: "search_cap",
44
- tvSearchCap: "tv_search_cap",
45
- movieSearchCap: "movie_search_cap",
46
- tvIdCaps: "tv_id_caps",
47
- movieIdCaps: "movie_id_caps",
48
- categories: "cat_caps",
49
- });
86
+ .where((i) => i
87
+ .where({ status: null })
88
+ .orWhere({ status: IndexerStatus.OK })
89
+ .orWhere("retry_after", "<", Date.now()))
90
+ .select(allFields);
91
+ return rawIndexers.map(deserialize);
50
92
  }
51
93
  export async function updateIndexerStatus(status, retryAfter, indexerIds) {
52
94
  if (indexerIds.length > 0) {
@@ -80,4 +122,23 @@ export async function updateSearchTimestamps(name, indexerIds) {
80
122
  });
81
123
  }
82
124
  }
125
+ export async function updateIndexerCapsById(indexerId, caps) {
126
+ await db("indexer")
127
+ .where({ id: indexerId })
128
+ .update({
129
+ search_cap: caps.search,
130
+ tv_search_cap: caps.tvSearch,
131
+ movie_search_cap: caps.movieSearch,
132
+ movie_id_caps: JSON.stringify(caps.movieIdSearch),
133
+ tv_id_caps: JSON.stringify(caps.tvIdSearch),
134
+ cat_caps: JSON.stringify(caps.categories),
135
+ limits_caps: JSON.stringify(caps.limits),
136
+ });
137
+ }
138
+ export async function clearIndexerFailures() {
139
+ await db("indexer").update({
140
+ status: null,
141
+ retry_after: null,
142
+ });
143
+ }
83
144
  //# sourceMappingURL=indexers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexers.js","sourceRoot":"","sources":["../src/indexers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"indexers.js","sourceRoot":"","sources":["../src/indexers.ts"],"names":[],"mappings":"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"}
package/dist/inject.js ADDED
@@ -0,0 +1,448 @@
1
+ import chalk from "chalk";
2
+ import { stat, unlink } from "fs/promises";
3
+ import ms from "ms";
4
+ import { copyFileSync, existsSync } from "fs";
5
+ import path, { basename, dirname } from "path";
6
+ import { linkAllFilesInMetafile, performAction } from "./action.js";
7
+ import { getClient, waitForTorrentToComplete, } from "./clients/TorrentClient.js";
8
+ import { appDir } from "./configuration.js";
9
+ import { Decision, InjectionResult, isAnyMatchedDecision, SaveResult, TORRENT_CACHE_FOLDER, UNKNOWN_TRACKER, } from "./constants.js";
10
+ import { assessCandidate } from "./decide.js";
11
+ import { Label, logger } from "./logger.js";
12
+ import { findAllSearchees } from "./pipeline.js";
13
+ import { sendResultsNotification } from "./pushNotifier.js";
14
+ import { resultOf, resultOfErr } from "./Result.js";
15
+ import { getRuntimeConfig } from "./runtimeConfig.js";
16
+ import { findAllTorrentFilesInDir, parseMetadataFromFilename, parseTorrentFromFilename, } from "./torrent.js";
17
+ import { areMediaTitlesSimilar, comparing, formatAsList, getLogString, isTruthy, MediaType, sanitizeInfoHash, } from "./utils.js";
18
+ function getTorrentFilePathLog(torrentFilePath) {
19
+ return chalk.bold.magenta(torrentFilePath.replace(/\[([a-z0-9]{40})].torrent$/i, (match, hash) => match.replace(hash, sanitizeInfoHash(hash))));
20
+ }
21
+ async function deleteTorrentFileIfSafe(torrentFilePath) {
22
+ const { tracker, name, mediaType } = parseMetadataFromFilename(basename(torrentFilePath));
23
+ // we are confident cross-seed created the torrent,
24
+ // or it is intended for use with cross-seed
25
+ const isSafeToDelete = tracker && name && mediaType;
26
+ if (!isSafeToDelete)
27
+ return;
28
+ const filePathLog = getTorrentFilePathLog(torrentFilePath);
29
+ logger.verbose({
30
+ label: Label.INJECT,
31
+ message: `Deleting ${filePathLog}`,
32
+ });
33
+ try {
34
+ await unlink(torrentFilePath);
35
+ }
36
+ catch (e) {
37
+ if (e.code !== "ENOENT") {
38
+ logger.error({
39
+ label: Label.INJECT,
40
+ message: `Failed to delete ${filePathLog}`,
41
+ });
42
+ logger.debug(e);
43
+ }
44
+ }
45
+ }
46
+ async function deleteTorrentFileIfComplete(torrentFilePath, infoHash) {
47
+ if (await waitForTorrentToComplete(infoHash)) {
48
+ await deleteTorrentFileIfSafe(torrentFilePath);
49
+ }
50
+ else {
51
+ logger.info({
52
+ label: Label.INJECT,
53
+ message: `Will not delete ${getTorrentFilePathLog(torrentFilePath)}: torrent is incomplete`,
54
+ });
55
+ }
56
+ }
57
+ async function whichSearcheesMatchTorrent(meta, searchees) {
58
+ const isSimilar = (searchee, meta) => areMediaTitlesSimilar(searchee.title, meta.title) ||
59
+ areMediaTitlesSimilar(searchee.title, meta.name) ||
60
+ areMediaTitlesSimilar(searchee.name, meta.name) ||
61
+ areMediaTitlesSimilar(searchee.name, meta.title) ||
62
+ meta.files.some((metaFile) => searchee.files.some((searcheeFile) => areMediaTitlesSimilar(searcheeFile.name, metaFile.name)));
63
+ let foundBlocked = false;
64
+ const matches = [];
65
+ for (const searchee of searchees) {
66
+ const { decision } = await assessCandidate(meta, searchee, []);
67
+ if (decision === Decision.BLOCKED_RELEASE) {
68
+ if (isSimilar(searchee, meta))
69
+ foundBlocked = true;
70
+ continue;
71
+ }
72
+ else if (!isAnyMatchedDecision(decision)) {
73
+ continue;
74
+ }
75
+ if (!isSimilar(searchee, meta)) {
76
+ logger.warn({
77
+ label: Label.INJECT,
78
+ message: `Skipping likely false positive for ${getLogString(meta, chalk.bold.white)} from ${getLogString(searchee, chalk.bold.white)}`,
79
+ });
80
+ continue;
81
+ }
82
+ matches.push({ searchee, decision });
83
+ }
84
+ /**
85
+ * full matches, then size only matches, then partial matches
86
+ * torrent, then data, then virtual
87
+ * prefer more files for partials
88
+ */
89
+ matches.sort(comparing((match) =>
90
+ // indexOf returns -1 for not found
91
+ -[Decision.MATCH_SIZE_ONLY, Decision.MATCH].indexOf(match.decision), (match) => !match.searchee.infoHash, (match) => !match.searchee.path, (match) => -match.searchee.files.length));
92
+ return { matches, foundBlocked };
93
+ }
94
+ async function injectInitialAction(meta, matches, tracker) {
95
+ let injectionResult = InjectionResult.FAILURE;
96
+ let matchedSearchee;
97
+ let matchedDecision;
98
+ let linkedNewFiles = false;
99
+ for (const { searchee, decision } of matches) {
100
+ if (injectionResult === InjectionResult.TORRENT_NOT_COMPLETE &&
101
+ !searchee.infoHash) {
102
+ continue; // Data/virtual searchee doesn't know if torrent is complete
103
+ }
104
+ const res = await performAction(meta, decision, searchee, tracker);
105
+ const result = res.actionResult;
106
+ if (res.linkedNewFiles) {
107
+ linkedNewFiles = true;
108
+ }
109
+ if (injectionResult === InjectionResult.SUCCESS ||
110
+ result === InjectionResult.FAILURE ||
111
+ result === SaveResult.SAVED) {
112
+ continue;
113
+ }
114
+ if (result === InjectionResult.ALREADY_EXISTS) {
115
+ injectionResult = result;
116
+ continue;
117
+ }
118
+ if (result === InjectionResult.TORRENT_NOT_COMPLETE) {
119
+ if (injectionResult !== InjectionResult.ALREADY_EXISTS) {
120
+ injectionResult = result;
121
+ matchedSearchee = searchee;
122
+ matchedDecision = decision;
123
+ }
124
+ continue;
125
+ }
126
+ injectionResult = InjectionResult.SUCCESS;
127
+ matchedSearchee = searchee;
128
+ matchedDecision = decision;
129
+ }
130
+ return {
131
+ injectionResult,
132
+ matchedSearchee,
133
+ matchedDecision,
134
+ linkedNewFiles,
135
+ };
136
+ }
137
+ function injectionFailed({ progress, injectionResult, summary, filePathLog, }) {
138
+ logger.error({
139
+ label: Label.INJECT,
140
+ message: `${progress} Failed to inject ${filePathLog} - ${chalk.red(injectionResult)}`,
141
+ });
142
+ summary.FAILED++;
143
+ }
144
+ async function injectFromStalledTorrent({ meta, matches, tracker, injectionResult, progress, filePathLog, }) {
145
+ let linkedNewFiles = false;
146
+ let inClient = (await getClient().isTorrentComplete(meta.infoHash)).isOk();
147
+ let injected = false;
148
+ for (const { searchee, decision } of matches) {
149
+ const linkedFilesRootResult = await linkAllFilesInMetafile(searchee, meta, tracker, decision, { onlyCompleted: false });
150
+ const linkResult = linkedFilesRootResult.isOk()
151
+ ? linkedFilesRootResult.unwrap()
152
+ : null;
153
+ if (linkResult && linkResult.linkedNewFiles) {
154
+ linkedNewFiles = true;
155
+ }
156
+ if (!inClient) {
157
+ if (linkedFilesRootResult.isOk()) {
158
+ const destinationDir = dirname(linkResult.contentPath);
159
+ const result = await getClient().inject(meta, searchee, Decision.MATCH_PARTIAL, // Should always be considered partial
160
+ destinationDir);
161
+ // result is only SUCCESS or FAILURE here but still log original injectionResult
162
+ if (result === InjectionResult.SUCCESS) {
163
+ logger.info({
164
+ label: Label.INJECT,
165
+ message: `${progress} Injected ${filePathLog} using stalled source, you will need to resume or remove from client - ${chalk.green(injectionResult)}`,
166
+ });
167
+ inClient = true;
168
+ injected = true;
169
+ }
170
+ else {
171
+ logger.warn({
172
+ label: Label.INJECT,
173
+ message: `${progress} Failed to inject ${filePathLog} using stalled source - ${chalk.yellow(injectionResult)}`,
174
+ });
175
+ }
176
+ }
177
+ else {
178
+ logger.warn({
179
+ label: Label.INJECT,
180
+ message: `${progress} Failed to link files for ${filePathLog}, ${linkedFilesRootResult.unwrapErr()} - ${chalk.yellow(injectionResult)}`,
181
+ });
182
+ }
183
+ }
184
+ }
185
+ if (inClient && !injected) {
186
+ if (linkedNewFiles) {
187
+ logger.info({
188
+ label: Label.INJECT,
189
+ message: `${progress} Rechecking ${filePathLog} as new files were linked - ${chalk.green(injectionResult)}`,
190
+ });
191
+ await getClient().recheckTorrent(meta.infoHash);
192
+ }
193
+ else {
194
+ logger.warn({
195
+ label: Label.INJECT,
196
+ message: `${progress} No new files linked for ${filePathLog}, resume or remove from client - ${chalk.yellow(injectionResult)}`,
197
+ });
198
+ }
199
+ }
200
+ }
201
+ async function injectionTorrentNotComplete(injectionAftermath) {
202
+ const { progress, torrentFilePath, injectionResult, summary, filePathLog } = injectionAftermath;
203
+ const { linkDir } = getRuntimeConfig();
204
+ if (!linkDir ||
205
+ (await stat(torrentFilePath)).mtimeMs >= Date.now() - ms("1 day")) {
206
+ // Normal case where source is likely still downloading
207
+ logger.warn({
208
+ label: Label.INJECT,
209
+ message: `${progress} Unable to inject ${filePathLog} - ${chalk.yellow(injectionResult)}`,
210
+ });
211
+ }
212
+ else {
213
+ // Since source is stalled, add to client paused so user can resume later if desired
214
+ // Try linking all possible matches as they may have different files
215
+ await injectFromStalledTorrent(injectionAftermath);
216
+ }
217
+ summary.INCOMPLETE_SEARCHEES++;
218
+ }
219
+ async function injectionAlreadyExists({ progress, torrentFilePath, injectionResult, summary, linkedNewFiles, meta, matches, filePathLog, }) {
220
+ const result = await getClient().isTorrentComplete(meta.infoHash);
221
+ let isComplete = result.isOk() ? result.unwrap() : false;
222
+ const anyFullMatch = matches.some((m) => m.decision === Decision.MATCH ||
223
+ m.decision === Decision.MATCH_SIZE_ONLY);
224
+ if (linkedNewFiles) {
225
+ logger.info({
226
+ label: Label.INJECT,
227
+ message: `${progress} Rechecking ${filePathLog} as new files were linked - ${chalk.green(injectionResult)}`,
228
+ });
229
+ await getClient().recheckTorrent(meta.infoHash);
230
+ }
231
+ else if (anyFullMatch && !isComplete) {
232
+ logger.info({
233
+ label: Label.INJECT,
234
+ message: `${progress} Rechecking ${filePathLog} as it's not complete but has all files - ${chalk.green(injectionResult)}`,
235
+ });
236
+ await getClient().recheckTorrent(meta.infoHash);
237
+ isComplete = true; // Prevent infinite recheck in rare case of corrupted cross seed
238
+ }
239
+ else {
240
+ logger.warn({
241
+ label: Label.INJECT,
242
+ message: `${progress} Unable to inject ${filePathLog} - ${chalk.yellow(injectionResult)}${isComplete ? "" : " (incomplete)"}`,
243
+ });
244
+ }
245
+ summary.ALREADY_EXISTS++;
246
+ summary.INCOMPLETE_CANDIDATES += isComplete ? 0 : 1;
247
+ if (isComplete) {
248
+ await deleteTorrentFileIfSafe(torrentFilePath);
249
+ }
250
+ else {
251
+ deleteTorrentFileIfComplete(torrentFilePath, meta.infoHash);
252
+ }
253
+ }
254
+ async function injectionSuccess({ progress, torrentFilePath, injectionResult, summary, matchedSearchee, matchedDecision, meta, tracker, filePathLog, }) {
255
+ logger.info({
256
+ label: Label.INJECT,
257
+ message: `${progress} Injected ${filePathLog} - ${chalk.green(injectionResult)}`,
258
+ });
259
+ sendResultsNotification(matchedSearchee, [
260
+ [
261
+ { decision: matchedDecision, metafile: meta },
262
+ tracker,
263
+ injectionResult,
264
+ ],
265
+ ]);
266
+ summary.INJECTED++;
267
+ if (matchedDecision === Decision.MATCH_PARTIAL) {
268
+ summary.PARTIAL_MATCHES++;
269
+ }
270
+ else {
271
+ summary.FULL_MATCHES++;
272
+ }
273
+ deleteTorrentFileIfComplete(torrentFilePath, meta.infoHash);
274
+ }
275
+ async function loadMetafile(torrentFilePath, progress, summary) {
276
+ const filePathLog = getTorrentFilePathLog(torrentFilePath);
277
+ let meta;
278
+ try {
279
+ meta = await parseTorrentFromFilename(torrentFilePath);
280
+ }
281
+ catch (e) {
282
+ logger.error({
283
+ label: Label.INJECT,
284
+ message: `${progress} Failed to parse ${filePathLog}`,
285
+ });
286
+ logger.debug(e);
287
+ return resultOfErr("FAILED_TO_PARSE");
288
+ }
289
+ const { tracker: trackerFromFilename } = parseMetadataFromFilename(basename(torrentFilePath));
290
+ summary.FOUND_BAD_FORMAT ||= !trackerFromFilename;
291
+ const tracker = trackerFromFilename ?? UNKNOWN_TRACKER;
292
+ return resultOf({ meta, tracker });
293
+ }
294
+ async function injectSavedTorrent(progress, torrentFilePath, summary, searchees) {
295
+ const metafileResult = await loadMetafile(torrentFilePath, progress, summary);
296
+ if (metafileResult.isErr())
297
+ return;
298
+ const { meta, tracker } = metafileResult.unwrap();
299
+ const filePathLog = getTorrentFilePathLog(torrentFilePath);
300
+ const metaLog = getLogString(meta, chalk.bold.white);
301
+ const { matches, foundBlocked } = await whichSearcheesMatchTorrent(meta, searchees);
302
+ if (!matches.length && foundBlocked) {
303
+ logger.info({
304
+ label: Label.INJECT,
305
+ message: `${progress} ${metaLog} ${chalk.yellow("possibly blocklisted")}: ${filePathLog}`,
306
+ });
307
+ summary.BLOCKED++;
308
+ await deleteTorrentFileIfSafe(torrentFilePath);
309
+ return;
310
+ }
311
+ else if (!matches.length) {
312
+ logger.info({
313
+ label: Label.INJECT,
314
+ message: `${progress} ${metaLog} ${chalk.red("has no matches")}: ${filePathLog}`,
315
+ });
316
+ summary.UNMATCHED++;
317
+ await deleteTorrentFileIfSafe(torrentFilePath);
318
+ return;
319
+ }
320
+ const { injectionResult, matchedSearchee, matchedDecision, linkedNewFiles, } = await injectInitialAction(meta, matches, tracker);
321
+ const injectionAftermath = {
322
+ progress,
323
+ torrentFilePath,
324
+ injectionResult,
325
+ summary,
326
+ meta,
327
+ tracker,
328
+ matches,
329
+ matchedSearchee,
330
+ matchedDecision,
331
+ linkedNewFiles,
332
+ filePathLog,
333
+ };
334
+ switch (injectionResult) {
335
+ case InjectionResult.SUCCESS:
336
+ await injectionSuccess(injectionAftermath);
337
+ break;
338
+ case InjectionResult.FAILURE:
339
+ injectionFailed(injectionAftermath);
340
+ break;
341
+ case InjectionResult.ALREADY_EXISTS:
342
+ await injectionAlreadyExists(injectionAftermath);
343
+ break;
344
+ case InjectionResult.TORRENT_NOT_COMPLETE:
345
+ await injectionTorrentNotComplete(injectionAftermath);
346
+ break;
347
+ }
348
+ }
349
+ function logInjectSummary(summary, flatLinking) {
350
+ const incompleteMsg = `${chalk.bold.yellow(summary.ALREADY_EXISTS)} existed in client${summary.INCOMPLETE_CANDIDATES
351
+ ? chalk.dim(` (${summary.INCOMPLETE_CANDIDATES} were incomplete)`)
352
+ : ""}`;
353
+ const resultMsg = formatAsList([
354
+ `Injected ${chalk.bold.green(summary.INJECTED)}/${chalk.bold.white(summary.TOTAL)} torrents`,
355
+ summary.FULL_MATCHES &&
356
+ `${chalk.bold.green(summary.FULL_MATCHES)} were full matches`,
357
+ summary.PARTIAL_MATCHES &&
358
+ `${chalk.bold.yellow(summary.PARTIAL_MATCHES)} were partial matches`,
359
+ summary.INCOMPLETE_SEARCHEES &&
360
+ `${chalk.bold.yellow(summary.INCOMPLETE_SEARCHEES)} had incomplete sources`,
361
+ summary.ALREADY_EXISTS && incompleteMsg,
362
+ summary.BLOCKED &&
363
+ `${chalk.bold.yellow(summary.BLOCKED)} were possibly blocklisted`,
364
+ summary.FAILED &&
365
+ `${chalk.bold.red(summary.FAILED)} failed to inject`,
366
+ summary.UNMATCHED &&
367
+ `${chalk.bold.red(summary.UNMATCHED)} had no matches`,
368
+ ].filter(isTruthy), { sort: false, type: "unit" });
369
+ logger.info({ label: Label.INJECT, message: chalk.cyan(resultMsg) });
370
+ if (summary.UNMATCHED > 0) {
371
+ logger.info({
372
+ label: Label.INJECT,
373
+ message: `Use "${chalk.bold.white("cross-seed diff")}" to get the reasons two torrents are not considered matches`,
374
+ });
375
+ }
376
+ if (summary.FOUND_BAD_FORMAT && !flatLinking) {
377
+ logger.warn({
378
+ label: Label.INJECT,
379
+ message: `Some torrents could be linked to linkDir/${UNKNOWN_TRACKER} - follow .torrent naming format in the docs to avoid this`,
380
+ });
381
+ }
382
+ logger.info({
383
+ label: Label.INJECT,
384
+ message: `Waiting on post-injection tasks to complete...`,
385
+ });
386
+ }
387
+ function createSummary(total) {
388
+ return {
389
+ TOTAL: total,
390
+ INJECTED: 0,
391
+ FULL_MATCHES: 0,
392
+ PARTIAL_MATCHES: 0,
393
+ BLOCKED: 0,
394
+ ALREADY_EXISTS: 0,
395
+ INCOMPLETE_CANDIDATES: 0,
396
+ INCOMPLETE_SEARCHEES: 0,
397
+ FAILED: 0,
398
+ UNMATCHED: 0,
399
+ FOUND_BAD_FORMAT: false,
400
+ };
401
+ }
402
+ export async function injectSavedTorrents() {
403
+ const { flatLinking, injectDir, outputDir } = getRuntimeConfig();
404
+ const targetDir = injectDir ?? outputDir;
405
+ const targetDirLog = chalk.bold.magenta(targetDir);
406
+ const torrentFilePaths = await findAllTorrentFilesInDir(targetDir);
407
+ if (torrentFilePaths.length === 0) {
408
+ logger.info({
409
+ label: Label.INJECT,
410
+ message: `No torrent files found to inject in ${targetDirLog}`,
411
+ });
412
+ return;
413
+ }
414
+ logger.info({
415
+ label: Label.INJECT,
416
+ message: `Found ${chalk.bold.white(torrentFilePaths.length)} torrent file(s) to inject in ${targetDirLog}`,
417
+ });
418
+ const summary = createSummary(torrentFilePaths.length);
419
+ const searchees = await findAllSearchees(Label.INJECT);
420
+ for (const [i, torrentFilePath] of torrentFilePaths.entries()) {
421
+ const progress = chalk.blue(`(${i + 1}/${torrentFilePaths.length})`);
422
+ await injectSavedTorrent(progress, torrentFilePath, summary, searchees);
423
+ }
424
+ logInjectSummary(summary, flatLinking);
425
+ }
426
+ export async function restoreFromTorrentCache() {
427
+ const { outputDir } = getRuntimeConfig();
428
+ const torrentFilePaths = await findAllTorrentFilesInDir(path.join(appDir(), TORRENT_CACHE_FOLDER));
429
+ if (torrentFilePaths.length === 0) {
430
+ console.log("No torrent files found to restore from cache");
431
+ return;
432
+ }
433
+ console.log(`Found ${chalk.bold.white(torrentFilePaths.length)} torrent file(s) to restore from cache, copying to outputDir...`);
434
+ let existed = 0;
435
+ for (const [i, torrentFilePath] of torrentFilePaths.entries()) {
436
+ const dest = path.join(outputDir, `[${MediaType.OTHER}][${UNKNOWN_TRACKER}]${basename(torrentFilePath)}`);
437
+ if (existsSync(dest)) {
438
+ existed++;
439
+ continue;
440
+ }
441
+ copyFileSync(torrentFilePath, dest);
442
+ if ((i + 1) % 100 === 0) {
443
+ console.log(`${chalk.blue(`(${i + 1}/${torrentFilePaths.length})`)} ${chalk.bold.magenta(dest)}`);
444
+ }
445
+ }
446
+ console.log(`Copied ${chalk.bold.green(torrentFilePaths.length - existed)}/${chalk.bold.white(torrentFilePaths.length)} torrent file(s) from cache to outputDir, run "${chalk.bold.white("cross-seed inject")}" to inject into client using your dataDirs`);
447
+ }
448
+ //# sourceMappingURL=inject.js.map