cross-seed 6.0.0-8 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.js +177 -71
- package/dist/action.js.map +1 -1
- package/dist/arr.js +62 -54
- package/dist/arr.js.map +1 -1
- package/dist/clients/Deluge.js +70 -46
- package/dist/clients/Deluge.js.map +1 -1
- package/dist/clients/QBittorrent.js +110 -68
- package/dist/clients/QBittorrent.js.map +1 -1
- package/dist/clients/RTorrent.js +46 -23
- package/dist/clients/RTorrent.js.map +1 -1
- package/dist/clients/TorrentClient.js +14 -1
- package/dist/clients/TorrentClient.js.map +1 -1
- package/dist/clients/Transmission.js +30 -10
- package/dist/clients/Transmission.js.map +1 -1
- package/dist/cmd.js +46 -23
- package/dist/cmd.js.map +1 -1
- package/dist/config.template.cjs +59 -59
- package/dist/config.template.cjs.map +1 -1
- package/dist/configSchema.js +90 -26
- package/dist/configSchema.js.map +1 -1
- package/dist/configuration.js +4 -1
- package/dist/configuration.js.map +1 -1
- package/dist/constants.js +77 -9
- package/dist/constants.js.map +1 -1
- package/dist/dataFiles.js +4 -5
- package/dist/dataFiles.js.map +1 -1
- package/dist/db.js +2 -1
- package/dist/db.js.map +1 -1
- package/dist/decide.js +279 -169
- package/dist/decide.js.map +1 -1
- package/dist/diff.js +13 -3
- package/dist/diff.js.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/indexers.js +94 -33
- package/dist/indexers.js.map +1 -1
- package/dist/inject.js +448 -0
- package/dist/inject.js.map +1 -0
- package/dist/jobs.js +13 -6
- package/dist/jobs.js.map +1 -1
- package/dist/logger.js +27 -9
- package/dist/logger.js.map +1 -1
- package/dist/migrations/00-initialSchema.js.map +1 -1
- package/dist/migrations/05-caps.js.map +1 -1
- package/dist/migrations/06-uniqueDecisions.js +29 -0
- package/dist/migrations/06-uniqueDecisions.js.map +1 -0
- package/dist/migrations/07-limits.js +12 -0
- package/dist/migrations/07-limits.js.map +1 -0
- package/dist/migrations/migrations.js +4 -0
- package/dist/migrations/migrations.js.map +1 -1
- package/dist/parseTorrent.js +6 -0
- package/dist/parseTorrent.js.map +1 -1
- package/dist/pipeline.js +224 -112
- package/dist/pipeline.js.map +1 -1
- package/dist/preFilter.js +122 -55
- package/dist/preFilter.js.map +1 -1
- package/dist/pushNotifier.js +7 -5
- package/dist/pushNotifier.js.map +1 -1
- package/dist/searchee.js +198 -17
- package/dist/searchee.js.map +1 -1
- package/dist/server.js +106 -54
- package/dist/server.js.map +1 -1
- package/dist/startup.js +16 -7
- package/dist/startup.js.map +1 -1
- package/dist/torrent.js +116 -50
- package/dist/torrent.js.map +1 -1
- package/dist/torznab.js +323 -153
- package/dist/torznab.js.map +1 -1
- package/dist/utils.js +229 -44
- package/dist/utils.js.map +1 -1
- package/package.json +11 -6
package/dist/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
|
18
|
-
|
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,
|
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"}
|
package/dist/errors.js.map
CHANGED
@@ -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;
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
31
|
-
.
|
32
|
-
|
33
|
-
|
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(
|
36
|
-
.
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
package/dist/indexers.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"indexers.js","sourceRoot":"","sources":["../src/indexers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,CAAN,IAAY,aAOX;AAPD,WAAY,aAAa;IACxB;;OAEG;IACH,0BAAS,CAAA;IACT,8CAA6B,CAAA;IAC7B,gDAA+B,CAAA;AAChC,CAAC,EAPW,aAAa,KAAb,aAAa,QAOxB;
|
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
|