jav-manager 0.1.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.
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getCurlImpersonateBinaryPath = getCurlImpersonateBinaryPath;
7
+ exports.getCurlCaBundlePath = getCurlCaBundlePath;
8
+ exports.checkCurlImpersonateAvailable = checkCurlImpersonateAvailable;
9
+ exports.getAvailableTargets = getAvailableTargets;
10
+ const path_1 = __importDefault(require("path"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ function resolvePathMaybeRelative(filePath) {
13
+ if (!filePath) {
14
+ return filePath;
15
+ }
16
+ if (path_1.default.isAbsolute(filePath)) {
17
+ return filePath;
18
+ }
19
+ return path_1.default.resolve(process.cwd(), filePath);
20
+ }
21
+ function isExecutableFile(filePath) {
22
+ try {
23
+ const stat = fs_1.default.statSync(filePath);
24
+ return stat.isFile();
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ function findOnPath(fileName) {
31
+ const envPath = process.env.PATH ?? "";
32
+ const sep = process.platform === "win32" ? ";" : ":";
33
+ const entries = envPath
34
+ .split(sep)
35
+ .map((p) => p.trim())
36
+ .filter((p) => p.length > 0);
37
+ for (const dir of entries) {
38
+ const candidate = path_1.default.join(dir, fileName);
39
+ if (isExecutableFile(candidate)) {
40
+ return candidate;
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * Get the platform-specific curl-impersonate binary path
47
+ */
48
+ function getCurlImpersonateBinaryPath(target = "chrome116", configuredPath = null) {
49
+ if (configuredPath && configuredPath.trim()) {
50
+ return resolvePathMaybeRelative(configuredPath.trim());
51
+ }
52
+ const platform = process.platform;
53
+ const arch = process.arch;
54
+ // Map platform/arch to binary path in third_party/curl-impersonate/
55
+ const binaryMap = {
56
+ win32: {
57
+ x64: path_1.default.join("third_party", "curl-impersonate", "bin", `curl_${target}.exe`),
58
+ arm64: path_1.default.join("third_party", "curl-impersonate", "bin", `curl_${target}.exe`),
59
+ },
60
+ linux: {
61
+ x64: path_1.default.join("third_party", "curl-impersonate", "bin", `curl_${target}`),
62
+ arm64: path_1.default.join("third_party", "curl-impersonate", "bin", `curl_${target}`),
63
+ },
64
+ darwin: {
65
+ x64: path_1.default.join("third_party", "curl-impersonate", "bin", `curl_${target}`),
66
+ arm64: path_1.default.join("third_party", "curl-impersonate", "bin", `curl_${target}`),
67
+ },
68
+ };
69
+ const platformBinaries = binaryMap[platform];
70
+ if (!platformBinaries) {
71
+ return null;
72
+ }
73
+ const binaryPath = platformBinaries[arch] || platformBinaries.x64;
74
+ return resolvePathMaybeRelative(binaryPath);
75
+ }
76
+ /**
77
+ * Get the CA bundle path for curl-impersonate
78
+ */
79
+ function getCurlCaBundlePath(configuredPath = null) {
80
+ if (configuredPath && fs_1.default.existsSync(configuredPath)) {
81
+ return configuredPath;
82
+ }
83
+ const platform = process.platform;
84
+ const arch = process.arch;
85
+ // Map platform/arch to CA bundle path in native/curl-impersonate/
86
+ const caBundleMap = {
87
+ win32: {
88
+ x64: path_1.default.join("native", "curl-impersonate", "win-x64", "cacert.pem"),
89
+ arm64: path_1.default.join("native", "curl-impersonate", "win-arm64", "cacert.pem"),
90
+ },
91
+ linux: {
92
+ x64: path_1.default.join("native", "curl-impersonate", "linux-x64", "cacert.pem"),
93
+ arm64: path_1.default.join("native", "curl-impersonate", "linux-arm64", "cacert.pem"),
94
+ },
95
+ darwin: {
96
+ x64: path_1.default.join("native", "curl-impersonate", "osx-x64", "cacert.pem"),
97
+ arm64: path_1.default.join("native", "curl-impersonate", "osx-arm64", "cacert.pem"),
98
+ },
99
+ };
100
+ const platformCaBundles = caBundleMap[platform];
101
+ if (!platformCaBundles) {
102
+ return null;
103
+ }
104
+ const caBundlePath = platformCaBundles[arch] || platformCaBundles.x64;
105
+ if (fs_1.default.existsSync(caBundlePath)) {
106
+ return caBundlePath;
107
+ }
108
+ return null;
109
+ }
110
+ /**
111
+ * Check if curl-impersonate binary is available
112
+ */
113
+ function checkCurlImpersonateAvailable(target = "chrome116", configuredPath = null) {
114
+ const binaryPath = getCurlImpersonateBinaryPath(target, configuredPath);
115
+ if (!binaryPath) {
116
+ return { path: "", exists: false, target };
117
+ }
118
+ const existsOnDisk = fs_1.default.existsSync(binaryPath);
119
+ if (existsOnDisk) {
120
+ return { path: binaryPath, exists: true, target };
121
+ }
122
+ // Windows fallback: allow vendored Linux binaries (to be run via WSL) when the .exe doesn't exist.
123
+ if (process.platform === "win32" && binaryPath.toLowerCase().endsWith(".exe")) {
124
+ const alt = binaryPath.slice(0, -4);
125
+ if (alt && fs_1.default.existsSync(alt)) {
126
+ return { path: alt, exists: true, target };
127
+ }
128
+ }
129
+ // Allow PATH-installed curl_<target> binaries (useful when vendoring isn't available).
130
+ const fileName = process.platform === "win32" ? `curl_${target}.exe` : `curl_${target}`;
131
+ const found = findOnPath(fileName);
132
+ if (found) {
133
+ return { path: found, exists: true, target };
134
+ }
135
+ return {
136
+ path: binaryPath,
137
+ exists: false,
138
+ target,
139
+ };
140
+ }
141
+ /**
142
+ * Get available curl-impersonate targets
143
+ */
144
+ function getAvailableTargets() {
145
+ const targets = ["chrome116", "chrome120", "chrome131", "chrome136", "chrome144", "firefox133", "firefox135"];
146
+ const available = [];
147
+ for (const target of targets) {
148
+ const info = checkCurlImpersonateAvailable(target, null);
149
+ if (info.exists) {
150
+ available.push(target);
151
+ }
152
+ }
153
+ return available;
154
+ }
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpHelper = void 0;
4
+ class HttpHelper {
5
+ defaultHeaders = new Map();
6
+ setDefaultHeader(name, value) {
7
+ this.defaultHeaders.set(name, value);
8
+ }
9
+ removeDefaultHeader(name) {
10
+ this.defaultHeaders.delete(name);
11
+ }
12
+ setBasicAuth(userName, password) {
13
+ const credentials = Buffer.from(`${userName}:${password}`).toString("base64");
14
+ this.setDefaultHeader("Authorization", `Basic ${credentials}`);
15
+ }
16
+ async get(url, headers, timeoutMs) {
17
+ const response = await this.request("GET", url, { headers, timeoutMs });
18
+ if (response.status < 200 || response.status >= 300) {
19
+ throw new Error(`HTTP ${response.status}`);
20
+ }
21
+ return response.text;
22
+ }
23
+ async post(url, formData, headers, timeoutMs) {
24
+ const body = new URLSearchParams(formData).toString();
25
+ const response = await this.request("POST", url, {
26
+ headers: {
27
+ "Content-Type": "application/x-www-form-urlencoded",
28
+ ...(headers ?? {}),
29
+ },
30
+ body,
31
+ timeoutMs,
32
+ });
33
+ if (response.status < 200 || response.status >= 300) {
34
+ throw new Error(`HTTP ${response.status}`);
35
+ }
36
+ return response.text;
37
+ }
38
+ async postMultipart(url, formData, headers, timeoutMs) {
39
+ const data = new FormData();
40
+ for (const [key, value] of Object.entries(formData)) {
41
+ data.append(key, value ?? "");
42
+ }
43
+ const response = await this.request("POST", url, { headers, body: data, timeoutMs });
44
+ if (response.status < 200 || response.status >= 300) {
45
+ throw new Error(`HTTP ${response.status}`);
46
+ }
47
+ return response.text;
48
+ }
49
+ async request(method, url, options = {}) {
50
+ const headers = new Headers();
51
+ for (const [key, value] of this.defaultHeaders.entries()) {
52
+ headers.set(key, value);
53
+ }
54
+ if (options.headers) {
55
+ for (const [key, value] of Object.entries(options.headers)) {
56
+ headers.set(key, value);
57
+ }
58
+ }
59
+ const controller = new AbortController();
60
+ const timeout = options.timeoutMs ? setTimeout(() => controller.abort(), options.timeoutMs) : null;
61
+ try {
62
+ const response = await fetch(url, {
63
+ method,
64
+ headers,
65
+ body: options.body,
66
+ signal: controller.signal,
67
+ });
68
+ const text = await response.text();
69
+ return { status: response.status, text, headers: response.headers };
70
+ }
71
+ finally {
72
+ if (timeout) {
73
+ clearTimeout(timeout);
74
+ }
75
+ }
76
+ }
77
+ }
78
+ exports.HttpHelper = HttpHelper;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.openContainingFolder = openContainingFolder;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const open_1 = __importDefault(require("open"));
10
+ async function openContainingFolder(targetPath) {
11
+ if (!targetPath) {
12
+ return;
13
+ }
14
+ const resolved = path_1.default.resolve(targetPath);
15
+ const stat = fs_1.default.existsSync(resolved) ? fs_1.default.statSync(resolved) : null;
16
+ const folder = stat && stat.isDirectory() ? resolved : path_1.default.dirname(resolved);
17
+ await (0, open_1.default)(folder);
18
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mb = mb;
4
+ exports.tryParseToBytes = tryParseToBytes;
5
+ const unitMap = {
6
+ B: 1,
7
+ KB: 1024,
8
+ KIB: 1024,
9
+ K: 1024,
10
+ MB: 1024 * 1024,
11
+ MIB: 1024 * 1024,
12
+ M: 1024 * 1024,
13
+ GB: 1024 * 1024 * 1024,
14
+ GIB: 1024 * 1024 * 1024,
15
+ G: 1024 * 1024 * 1024,
16
+ TB: 1024 * 1024 * 1024 * 1024,
17
+ TIB: 1024 * 1024 * 1024 * 1024,
18
+ T: 1024 * 1024 * 1024 * 1024,
19
+ };
20
+ function mb(value) {
21
+ return value * 1024 * 1024;
22
+ }
23
+ function tryParseToBytes(text) {
24
+ if (!text) {
25
+ return null;
26
+ }
27
+ const input = text.trim();
28
+ if (!input) {
29
+ return null;
30
+ }
31
+ if (/^\d+$/.test(input)) {
32
+ return Number(input);
33
+ }
34
+ const match = input.match(/^\s*(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB|KIB|MIB|GIB|TIB|K|M|G|T)\s*$/i);
35
+ if (!match) {
36
+ return null;
37
+ }
38
+ const value = Number(match[1]);
39
+ if (Number.isNaN(value) || value < 0) {
40
+ return null;
41
+ }
42
+ const unit = match[2].toUpperCase();
43
+ const multiplier = unitMap[unit];
44
+ if (!multiplier) {
45
+ return null;
46
+ }
47
+ return Math.floor(value * multiplier);
48
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTelemetryPostUrl = getTelemetryPostUrl;
4
+ exports.getJavInfoPostUrl = getJavInfoPostUrl;
5
+ exports.getBaseEndpoint = getBaseEndpoint;
6
+ exports.normalizeBaseEndpointOrNull = normalizeBaseEndpointOrNull;
7
+ const DefaultBaseEndpoint = "https://jav-manager.techfetch.dev";
8
+ function getTelemetryPostUrl(endpoint) {
9
+ return `${getBaseEndpoint(endpoint)}/api/telemetry`;
10
+ }
11
+ function getJavInfoPostUrl(endpoint) {
12
+ return `${getBaseEndpoint(endpoint)}/api/javinfo`;
13
+ }
14
+ function getBaseEndpoint(endpoint) {
15
+ const normalized = normalizeBaseEndpointOrNull(endpoint);
16
+ return normalized || DefaultBaseEndpoint;
17
+ }
18
+ function normalizeBaseEndpointOrNull(endpoint) {
19
+ if (!endpoint || !String(endpoint).trim()) {
20
+ return null;
21
+ }
22
+ const raw = String(endpoint).trim();
23
+ let parsed;
24
+ try {
25
+ parsed = new URL(raw);
26
+ }
27
+ catch {
28
+ return raw.replace(/\/+$/, "");
29
+ }
30
+ let path = parsed.pathname.replace(/\/+$/, "");
31
+ if (path.toLowerCase().endsWith("/api/telemetry")) {
32
+ path = path.slice(0, -"/api/telemetry".length);
33
+ }
34
+ else if (path.toLowerCase().endsWith("/api/javinfo")) {
35
+ path = path.slice(0, -"/api/javinfo".length);
36
+ }
37
+ else if (path.toLowerCase().endsWith("/api")) {
38
+ path = path.slice(0, -"/api".length);
39
+ }
40
+ parsed.pathname = path.replace(/\/+$/, "") || "/";
41
+ parsed.search = "";
42
+ parsed.hash = "";
43
+ return parsed.toString().replace(/\/+$/, "");
44
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTorrentName = parseTorrentName;
4
+ exports.normalizeJavId = normalizeJavId;
5
+ exports.isValidJavId = isValidJavId;
6
+ const models_1 = require("../models");
7
+ const uncensoredRegex = /-(?:UC|U)(?=$|[^A-Za-z0-9])/i;
8
+ function parseTorrentName(torrentName) {
9
+ let uncensoredType = models_1.UncensoredMarkerType.None;
10
+ const hasSubtitle = false;
11
+ const match = torrentName.match(uncensoredRegex);
12
+ if (match) {
13
+ const marker = match[0].toUpperCase();
14
+ if (marker === "-UC") {
15
+ uncensoredType = models_1.UncensoredMarkerType.UC;
16
+ }
17
+ else if (marker === "-U") {
18
+ uncensoredType = models_1.UncensoredMarkerType.U;
19
+ }
20
+ }
21
+ if (uncensoredType === models_1.UncensoredMarkerType.None &&
22
+ (torrentName.toLowerCase().includes("无码") ||
23
+ torrentName.toLowerCase().includes("無碼") ||
24
+ torrentName.toLowerCase().includes("uncensored"))) {
25
+ uncensoredType = models_1.UncensoredMarkerType.U;
26
+ }
27
+ return { uncensoredType, hasSubtitle };
28
+ }
29
+ function normalizeJavId(javId) {
30
+ const normalized = javId.trim().replace(/_/g, "-").toUpperCase();
31
+ const noSpaces = normalized.replace(/\s+/g, "");
32
+ if (/^[A-Z0-9]+-\d+$/.test(noSpaces)) {
33
+ return noSpaces;
34
+ }
35
+ const extractable = normalized.replace(/[^A-Z0-9-]+/g, " ");
36
+ const match = extractable.match(/\b[A-Z0-9]+-\d+\b/);
37
+ if (match) {
38
+ return match[0];
39
+ }
40
+ return noSpaces;
41
+ }
42
+ function isValidJavId(javId) {
43
+ return /^[A-Z0-9]+-\d+$/i.test(javId);
44
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateWeight = calculateWeight;
4
+ exports.calculateAndSort = calculateAndSort;
5
+ const UC_SCORE = 5;
6
+ const SUB_SCORE = 3;
7
+ const HD_SCORE = 1;
8
+ const titleUncensoredRegex = /-(?:UC|U)(?=$|[^A-Za-z0-9])/i;
9
+ function calculateWeight(torrent) {
10
+ const hasUncensored = torrent.hasUncensoredMarker || titleUncensoredRegex.test(torrent.title);
11
+ let score = 0;
12
+ if (hasUncensored)
13
+ score += UC_SCORE;
14
+ if (torrent.hasSubtitle)
15
+ score += SUB_SCORE;
16
+ if (torrent.hasHd)
17
+ score += HD_SCORE;
18
+ // Keep derived field consistent with marker fallback, so downstream logic uses the same view.
19
+ torrent.hasUncensoredMarker = hasUncensored;
20
+ torrent.weightScore = score;
21
+ return score;
22
+ }
23
+ function calculateAndSort(torrents) {
24
+ for (const torrent of torrents) {
25
+ calculateWeight(torrent);
26
+ }
27
+ return torrents
28
+ .slice()
29
+ .sort((a, b) => {
30
+ if (a.weightScore !== b.weightScore) {
31
+ return b.weightScore - a.weightScore;
32
+ }
33
+ if (a.size !== b.size) {
34
+ return b.size - a.size;
35
+ }
36
+ return a.title.localeCompare(b.title);
37
+ });
38
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "jav-manager",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "TypeScript rewrite of JavManager",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "jav-manager": "bin/jav-manager.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.json",
18
+ "start": "node dist/index.js",
19
+ "cli": "node dist/index.js --no-gui",
20
+ "gui": "node dist/index.js",
21
+ "dev:cli": "ts-node src/index.ts --no-gui",
22
+ "dev:gui": "ts-node src/index.ts",
23
+ "lint": "tsc -p tsconfig.json --noEmit",
24
+ "test": "npm run build && node --test test/*.test.js"
25
+ },
26
+ "keywords": [],
27
+ "author": "",
28
+ "license": "MIT",
29
+ "type": "commonjs",
30
+ "dependencies": {
31
+ "cheerio": "^1.2.0",
32
+ "express": "^5.2.1",
33
+ "open": "^11.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/express": "^5.0.6",
37
+ "@types/node": "^25.2.0",
38
+ "ts-node": "^10.9.2",
39
+ "typescript": "^5.9.3"
40
+ }
41
+ }