jav-manager 0.1.0 → 0.1.2

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 (41) hide show
  1. package/dist/data/curlImpersonateFetcher.js +29 -232
  2. package/dist/data/javdb.js +2 -104
  3. package/dist/jav_cache.json +1 -0
  4. package/dist/utils/curlBinaryResolver.js +103 -25
  5. package/package.json +4 -1
  6. package/scripts/postinstall.js +31 -0
  7. package/third_party/curl-impersonate/bin/curl-impersonate +0 -0
  8. package/third_party/curl-impersonate/bin/curl_chrome100 +33 -0
  9. package/third_party/curl-impersonate/bin/curl_chrome101 +33 -0
  10. package/third_party/curl-impersonate/bin/curl_chrome104 +33 -0
  11. package/third_party/curl-impersonate/bin/curl_chrome107 +33 -0
  12. package/third_party/curl-impersonate/bin/curl_chrome110 +33 -0
  13. package/third_party/curl-impersonate/bin/curl_chrome116 +33 -0
  14. package/third_party/curl-impersonate/bin/curl_chrome119 +34 -0
  15. package/third_party/curl-impersonate/bin/curl_chrome120 +34 -0
  16. package/third_party/curl-impersonate/bin/curl_chrome123 +37 -0
  17. package/third_party/curl-impersonate/bin/curl_chrome124 +40 -0
  18. package/third_party/curl-impersonate/bin/curl_chrome131 +39 -0
  19. package/third_party/curl-impersonate/bin/curl_chrome131_android +40 -0
  20. package/third_party/curl-impersonate/bin/curl_chrome133a +40 -0
  21. package/third_party/curl-impersonate/bin/curl_chrome136 +37 -0
  22. package/third_party/curl-impersonate/bin/curl_chrome142 +6 -0
  23. package/third_party/curl-impersonate/bin/curl_chrome99 +33 -0
  24. package/third_party/curl-impersonate/bin/curl_chrome99_android +33 -0
  25. package/third_party/curl-impersonate/bin/curl_edge101 +33 -0
  26. package/third_party/curl-impersonate/bin/curl_edge99 +33 -0
  27. package/third_party/curl-impersonate/bin/curl_firefox133 +36 -0
  28. package/third_party/curl-impersonate/bin/curl_firefox135 +37 -0
  29. package/third_party/curl-impersonate/bin/curl_firefox144 +6 -0
  30. package/third_party/curl-impersonate/bin/curl_safari153 +27 -0
  31. package/third_party/curl-impersonate/bin/curl_safari155 +28 -0
  32. package/third_party/curl-impersonate/bin/curl_safari170 +31 -0
  33. package/third_party/curl-impersonate/bin/curl_safari172_ios +31 -0
  34. package/third_party/curl-impersonate/bin/curl_safari180 +32 -0
  35. package/third_party/curl-impersonate/bin/curl_safari180_ios +32 -0
  36. package/third_party/curl-impersonate/bin/curl_safari184 +32 -0
  37. package/third_party/curl-impersonate/bin/curl_safari184_ios +32 -0
  38. package/third_party/curl-impersonate/bin/curl_safari260 +39 -0
  39. package/third_party/curl-impersonate/bin/curl_safari2601 +6 -0
  40. package/third_party/curl-impersonate/bin/curl_safari260_ios +37 -0
  41. package/third_party/curl-impersonate/bin/curl_tor145 +38 -0
@@ -69,14 +69,16 @@ function stripCacertArgs(args) {
69
69
  return result;
70
70
  }
71
71
  /**
72
- * HTTP fetcher that attempts to use curl-impersonate to bypass Cloudflare protection
73
- * Falls back to system curl with enhanced headers if curl-impersonate is unavailable
72
+ * HTTP fetcher using curl-impersonate to bypass Cloudflare protection.
73
+ * curl-impersonate is the only supported request method.
74
74
  */
75
75
  class CurlImpersonateFetcher {
76
76
  config;
77
77
  available = null;
78
78
  binaryPath = null;
79
- systemCurlAvailable = null;
79
+ /** When true the resolved binary is the main `curl-impersonate` executable
80
+ * and we must pass `--impersonate <target>` to select the TLS profile. */
81
+ useImpersonateFlag = false;
80
82
  cookieJarFiles = new Map();
81
83
  constructor(config) {
82
84
  this.config = config;
@@ -96,58 +98,23 @@ class CurlImpersonateFetcher {
96
98
  const info = (0, curlBinaryResolver_1.checkCurlImpersonateAvailable)(target, this.config.libraryPath || null);
97
99
  this.available = info.exists;
98
100
  this.binaryPath = info.exists ? info.path : null;
101
+ this.useImpersonateFlag = info.useImpersonateFlag;
99
102
  return this.available;
100
103
  }
101
104
  /**
102
- * Check if system curl is available
103
- */
104
- isSystemCurlAvailable() {
105
- if (this.systemCurlAvailable !== null) {
106
- return this.systemCurlAvailable;
107
- }
108
- try {
109
- (0, child_process_1.execSync)("curl --version", { stdio: "ignore" });
110
- this.systemCurlAvailable = true;
111
- return true;
112
- }
113
- catch {
114
- this.systemCurlAvailable = false;
115
- return false;
116
- }
117
- }
118
- /**
119
- * Perform HTTP GET request using curl-impersonate or system curl
105
+ * Perform HTTP GET request using curl-impersonate.
106
+ * This is the only supported request method – no fallback.
120
107
  */
121
108
  async get(url, referer, cookieHeader, timeoutMs) {
122
- // Try curl-impersonate first if enabled and available
123
- if (this.isAvailable()) {
124
- try {
125
- const result = await this.getCurlImpersonate(url, referer, cookieHeader, timeoutMs);
126
- if (this.isSuccessStatus(result.status)) {
127
- return result;
128
- }
129
- // If curl-impersonate fails, fall back to system curl
130
- console.warn(`curl-impersonate returned status ${result.status}, falling back to system curl`);
131
- }
132
- catch (error) {
133
- console.warn(`curl-impersonate error: ${error instanceof Error ? error.message : "Unknown error"}, falling back to system curl`);
134
- }
135
- }
136
- // Try system curl with enhanced headers
137
- if (this.isSystemCurlAvailable()) {
138
- try {
139
- const result = await this.getSystemCurl(url, referer, cookieHeader, timeoutMs);
140
- if (this.isSuccessStatus(result.status)) {
141
- return result;
142
- }
143
- console.warn(`system curl returned status ${result.status}, falling back to enhanced fetch`);
144
- }
145
- catch (error) {
146
- console.warn(`system curl error: ${error instanceof Error ? error.message : "Unknown error"}, falling back to enhanced fetch`);
147
- }
109
+ // Ensure binary path is resolved (isAvailable caches the result).
110
+ if (!this.isAvailable()) {
111
+ return {
112
+ status: 0,
113
+ body: "",
114
+ error: "curl-impersonate binary not found",
115
+ };
148
116
  }
149
- // Fallback to enhanced fetch
150
- return this.getEnhancedFetch(url, referer, cookieHeader, timeoutMs);
117
+ return this.getCurlImpersonate(url, referer, cookieHeader, timeoutMs);
151
118
  }
152
119
  getCookieJarFilePath(url) {
153
120
  try {
@@ -181,19 +148,26 @@ class CurlImpersonateFetcher {
181
148
  */
182
149
  async getCurlImpersonate(url, referer, cookieHeader, timeoutMs) {
183
150
  const binaryPath = this.binaryPath;
184
- const rawArgs = this.buildCurlArgs(url, referer, cookieHeader, timeoutMs, true);
185
- const spawnPlanRaw = buildCurlSpawnCommand(binaryPath, rawArgs);
151
+ const rawArgs = this.buildCurlArgs(url, referer, cookieHeader, timeoutMs);
152
+ // When using the main `curl-impersonate` binary directly, prepend the
153
+ // --impersonate flag so it selects the correct TLS profile.
154
+ const target = this.config.target || "chrome116";
155
+ const impersonatePrefix = this.useImpersonateFlag
156
+ ? ["--impersonate", target]
157
+ : [];
158
+ const argsWithTarget = [...impersonatePrefix, ...rawArgs];
159
+ const spawnPlanRaw = buildCurlSpawnCommand(binaryPath, argsWithTarget);
186
160
  const cookieJarFile = this.getCookieJarFilePath(url);
187
161
  const argsWithJar = (() => {
188
162
  if (!cookieJarFile) {
189
- return rawArgs;
163
+ return argsWithTarget;
190
164
  }
191
165
  const jarPath = spawnPlanRaw.mode === "wsl" ? toWslPath(cookieJarFile) : cookieJarFile;
192
166
  if (!jarPath) {
193
- return rawArgs;
167
+ return argsWithTarget;
194
168
  }
195
169
  // Read & write cookies (helps Cloudflare/JavDB session cookies persist between requests).
196
- return [...rawArgs, "--cookie", jarPath, "--cookie-jar", jarPath];
170
+ return [...argsWithTarget, "--cookie", jarPath, "--cookie-jar", jarPath];
197
171
  })();
198
172
  const spawnPlan = spawnPlanRaw.mode === "wsl"
199
173
  ? { command: "wsl", args: ["--exec", toWslPath(binaryPath), ...stripCacertArgs(argsWithJar)], mode: "wsl" }
@@ -277,125 +251,10 @@ class CurlImpersonateFetcher {
277
251
  }
278
252
  });
279
253
  }
280
- /**
281
- * Perform HTTP GET request using system curl with enhanced headers
282
- */
283
- async getSystemCurl(url, referer, cookieHeader, timeoutMs) {
284
- const baseArgs = this.buildCurlArgs(url, referer, cookieHeader, timeoutMs, false);
285
- const cookieJarFile = this.getCookieJarFilePath(url);
286
- const args = cookieJarFile ? [...baseArgs, "--cookie", cookieJarFile, "--cookie-jar", cookieJarFile] : baseArgs;
287
- return new Promise((resolve) => {
288
- let child = null;
289
- let stdout = "";
290
- let stderr = "";
291
- let resolved = false;
292
- const timeout = Math.max(1000, timeoutMs);
293
- const timeoutTimer = setTimeout(() => {
294
- if (!resolved) {
295
- resolved = true;
296
- try {
297
- child?.kill();
298
- }
299
- catch {
300
- // ignore
301
- }
302
- resolve({
303
- status: 0,
304
- body: "",
305
- error: `Request timeout after ${timeout}ms`,
306
- });
307
- }
308
- }, timeout);
309
- try {
310
- child = (0, child_process_1.spawn)("curl", args, {
311
- stdio: ["ignore", "pipe", "pipe"],
312
- });
313
- child.stdout?.on("data", (data) => {
314
- stdout += data.toString("utf-8");
315
- });
316
- child.stderr?.on("data", (data) => {
317
- stderr += data.toString("utf-8");
318
- });
319
- child.on("close", (code) => {
320
- clearTimeout(timeoutTimer);
321
- if (resolved) {
322
- return;
323
- }
324
- resolved = true;
325
- if (code === 0) {
326
- const parsed = parseCurlWriteOutOutput(stdout);
327
- const error = this.isSuccessStatus(parsed.status) ? undefined : `HTTP ${parsed.status}`;
328
- resolve({ status: parsed.status, body: parsed.body, error });
329
- }
330
- else {
331
- const errorMsg = stderr.trim() || `curl exited with code ${code}`;
332
- resolve({
333
- status: 0,
334
- body: "",
335
- error: errorMsg,
336
- });
337
- }
338
- });
339
- child.on("error", (err) => {
340
- clearTimeout(timeoutTimer);
341
- if (resolved) {
342
- return;
343
- }
344
- resolved = true;
345
- resolve({
346
- status: 0,
347
- body: "",
348
- error: `Failed to spawn curl: ${err.message}`,
349
- });
350
- });
351
- }
352
- catch (err) {
353
- clearTimeout(timeoutTimer);
354
- if (resolved) {
355
- return;
356
- }
357
- resolved = true;
358
- resolve({
359
- status: 0,
360
- body: "",
361
- error: err instanceof Error ? err.message : "Unknown error",
362
- });
363
- }
364
- });
365
- }
366
- /**
367
- * Perform HTTP GET request using enhanced fetch with Cloudflare bypass headers
368
- */
369
- async getEnhancedFetch(url, referer, cookieHeader, timeoutMs) {
370
- const headers = this.buildEnhancedHeaders(referer, cookieHeader);
371
- const controller = new AbortController();
372
- const timeoutTimer = setTimeout(() => controller.abort(), timeoutMs);
373
- try {
374
- const response = await fetch(url, {
375
- method: "GET",
376
- headers,
377
- signal: controller.signal,
378
- });
379
- clearTimeout(timeoutTimer);
380
- const body = await response.text();
381
- return {
382
- status: response.status,
383
- body,
384
- };
385
- }
386
- catch (error) {
387
- clearTimeout(timeoutTimer);
388
- return {
389
- status: 0,
390
- body: "",
391
- error: error instanceof Error ? error.message : "Unknown error",
392
- };
393
- }
394
- }
395
254
  /**
396
255
  * Build curl command line arguments
397
256
  */
398
- buildCurlArgs(url, referer, cookieHeader, timeoutMs, useImpersonate) {
257
+ buildCurlArgs(url, referer, cookieHeader, timeoutMs) {
399
258
  const args = [];
400
259
  // Basic options
401
260
  args.push("--silent");
@@ -423,72 +282,10 @@ class CurlImpersonateFetcher {
423
282
  if (cookieHeader) {
424
283
  args.push("--cookie", cookieHeader);
425
284
  }
426
- // Add enhanced headers for system curl
427
- if (!useImpersonate) {
428
- const chromeVersion = "144.0.0.0";
429
- const chromeMajor = "144";
430
- args.push("--user-agent", `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`);
431
- args.push("--header", `Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7`);
432
- args.push("--header", `Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6`);
433
- args.push("--header", `Accept-Encoding: gzip, deflate, br`);
434
- args.push("--header", `Connection: keep-alive`);
435
- args.push("--header", `Upgrade-Insecure-Requests: 1`);
436
- args.push("--header", `Sec-Fetch-Dest: document`);
437
- args.push("--header", `Sec-Fetch-Mode: navigate`);
438
- args.push("--header", `Sec-Fetch-Site: ${referer ? "same-origin" : "none"}`);
439
- args.push("--header", `Sec-Fetch-User: ?1`);
440
- args.push("--header", `Cache-Control: max-age=0`);
441
- args.push("--header", `sec-ch-ua: "Google Chrome";v="${chromeMajor}", "Chromium";v="${chromeMajor}", "Not_A Brand";v="24"`);
442
- args.push("--header", `sec-ch-ua-mobile: ?0`);
443
- args.push("--header", `sec-ch-ua-platform: "Windows"`);
444
- args.push("--header", `DNT: 1`);
445
- }
446
285
  // URL
447
286
  args.push(url);
448
287
  return args;
449
288
  }
450
- /**
451
- * Build enhanced headers for Cloudflare bypass
452
- */
453
- buildEnhancedHeaders(referer, cookieHeader) {
454
- const chromeVersion = "144.0.0.0";
455
- const chromeMajor = "144";
456
- const headers = {
457
- // Standard browser headers
458
- "User-Agent": `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`,
459
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
460
- "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6",
461
- "Accept-Encoding": "gzip, deflate, br",
462
- "Connection": "keep-alive",
463
- "Upgrade-Insecure-Requests": "1",
464
- "Sec-Fetch-Dest": "document",
465
- "Sec-Fetch-Mode": "navigate",
466
- "Sec-Fetch-Site": referer ? "same-origin" : "none",
467
- "Sec-Fetch-User": "?1",
468
- "Cache-Control": "max-age=0",
469
- // Client Hints (important for Cloudflare)
470
- "sec-ch-ua": `"Google Chrome";v="${chromeMajor}", "Chromium";v="${chromeMajor}", "Not_A Brand";v="24"`,
471
- "sec-ch-ua-mobile": "?0",
472
- "sec-ch-ua-platform": `"Windows"`,
473
- // Additional client hints that help bypass detection
474
- "Sec-CH-UA-Arch": '"x86"',
475
- "Sec-CH-UA-Bitness": '"64"',
476
- "Sec-CH-UA-Full-Version": `"${chromeVersion}"`,
477
- "Sec-CH-UA-Model": '""',
478
- "Sec-CH-UA-Prefers-Color-Scheme": '"light"',
479
- "Sec-CH-UA-Prefers-Reduced-Motion": '"no-reduced-motion"',
480
- // Additional headers that may help
481
- DNT: "1",
482
- "Sec-Purpose": "prefetch",
483
- };
484
- if (referer) {
485
- headers.Referer = referer;
486
- }
487
- if (cookieHeader) {
488
- headers.Cookie = cookieHeader;
489
- }
490
- return headers;
491
- }
492
289
  /**
493
290
  * Check if status is successful
494
291
  */
@@ -44,12 +44,10 @@ const RetryBaseDelayMs = 1000;
44
44
  const PreferredLocale = "zh";
45
45
  class JavDbWebScraper {
46
46
  config;
47
- userAgents;
48
47
  cookieJar = new Map();
49
48
  curlFetcher;
50
49
  constructor(config) {
51
50
  this.config = config;
52
- this.userAgents = buildUserAgentCandidates(config);
53
51
  this.curlFetcher = new curlImpersonateFetcher_1.CurlImpersonateFetcher(config.curlImpersonate);
54
52
  }
55
53
  get serviceName() {
@@ -159,44 +157,10 @@ class JavDbWebScraper {
159
157
  }
160
158
  return { status: 0, body: "", error: lastError };
161
159
  }
162
- async sendRequest(url, referer, attemptIndex, timeoutMs) {
160
+ async sendRequest(url, referer, _attemptIndex, timeoutMs) {
163
161
  const cookieHeader = this.getCookieHeader(url);
164
162
  const timeout = timeoutMs ?? this.config.requestTimeout;
165
- // Try curl-impersonate first if enabled and available
166
- if (this.curlFetcher.isAvailable()) {
167
- try {
168
- const result = await this.curlFetcher.get(url, referer, cookieHeader, timeout);
169
- if (isSuccessStatus(result.status)) {
170
- return { status: result.status, body: result.body };
171
- }
172
- // If curl-impersonate fails, fall back to standard fetch
173
- console.warn(`curl-impersonate failed: ${result.error}, falling back to standard fetch`);
174
- }
175
- catch (error) {
176
- console.warn(`curl-impersonate error: ${error instanceof Error ? error.message : "Unknown error"}, falling back to standard fetch`);
177
- }
178
- }
179
- // Fallback to standard fetch
180
- const userAgent = this.userAgents[attemptIndex % this.userAgents.length] ?? defaultUserAgent;
181
- const headers = buildChromeHeaders(userAgent, referer);
182
- if (cookieHeader) {
183
- headers.Cookie = cookieHeader;
184
- }
185
- const controller = new AbortController();
186
- const timeoutTimer = setTimeout(() => controller.abort(), timeout);
187
- try {
188
- const response = await fetch(url, {
189
- method: "GET",
190
- headers,
191
- signal: controller.signal,
192
- });
193
- const body = await response.text();
194
- this.captureCookies(url, response.headers);
195
- return { status: response.status, body };
196
- }
197
- finally {
198
- clearTimeout(timeoutTimer);
199
- }
163
+ return this.curlFetcher.get(url, referer, cookieHeader, timeout);
200
164
  }
201
165
  seedCookiesForUrl(baseUrl) {
202
166
  if (!baseUrl) {
@@ -218,21 +182,6 @@ class JavDbWebScraper {
218
182
  .map(([key, value]) => `${key}=${value}`)
219
183
  .join("; ");
220
184
  }
221
- captureCookies(url, headers) {
222
- const host = new URL(url).host;
223
- const jar = this.cookieJar.get(host) ?? new Map();
224
- const setCookies = typeof headers.getSetCookie === "function"
225
- ? headers.getSetCookie()
226
- : (headers.get("set-cookie") ? [headers.get("set-cookie")] : []);
227
- for (const raw of setCookies) {
228
- const [pair] = raw.split(";", 1);
229
- const [name, value] = pair.split("=", 2);
230
- if (name && value) {
231
- jar.set(name.trim(), value.trim());
232
- }
233
- }
234
- this.cookieJar.set(host, jar);
235
- }
236
185
  }
237
186
  exports.JavDbWebScraper = JavDbWebScraper;
238
187
  function buildBaseUrls(config) {
@@ -241,56 +190,6 @@ function buildBaseUrls(config) {
241
190
  .filter((url) => url.length > 0);
242
191
  return Array.from(new Set(urls));
243
192
  }
244
- function buildUserAgentCandidates(config) {
245
- const candidates = [];
246
- if (config.userAgent?.trim()) {
247
- candidates.push(config.userAgent.trim());
248
- }
249
- candidates.push(defaultUserAgent);
250
- candidates.push("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36");
251
- candidates.push("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36");
252
- return Array.from(new Set(candidates));
253
- }
254
- function buildChromeHeaders(userAgent, referer) {
255
- const chromeMajor = parseChromeMajorVersion(userAgent);
256
- const platform = getPlatformFromUserAgent(userAgent);
257
- const mobile = userAgent.toLowerCase().includes("mobile") ? "?1" : "?0";
258
- const headers = {
259
- Connection: "keep-alive",
260
- "Cache-Control": "max-age=0",
261
- "sec-ch-ua": `"Google Chrome";v="${chromeMajor}", "Chromium";v="${chromeMajor}", "Not_A Brand";v="24"`,
262
- "sec-ch-ua-mobile": mobile,
263
- "sec-ch-ua-platform": `"${platform}"`,
264
- DNT: "1",
265
- "Upgrade-Insecure-Requests": "1",
266
- "User-Agent": userAgent,
267
- Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
268
- "Sec-Fetch-Site": referer ? "same-origin" : "none",
269
- "Sec-Fetch-Mode": "navigate",
270
- "Sec-Fetch-User": "?1",
271
- "Sec-Fetch-Dest": "document",
272
- "Accept-Encoding": "gzip, deflate, br",
273
- "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7",
274
- };
275
- if (referer) {
276
- headers.Referer = referer;
277
- }
278
- return headers;
279
- }
280
- function parseChromeMajorVersion(userAgent) {
281
- const match = userAgent.match(/Chrome\/(\d+)/);
282
- return match?.[1] ?? "131";
283
- }
284
- function getPlatformFromUserAgent(userAgent) {
285
- const ua = userAgent.toLowerCase();
286
- if (ua.includes("windows"))
287
- return "Windows";
288
- if (ua.includes("mac os x") || ua.includes("macintosh"))
289
- return "macOS";
290
- if (ua.includes("linux"))
291
- return "Linux";
292
- return "Windows";
293
- }
294
193
  function isSuccessStatus(status) {
295
194
  return status >= 200 && status < 300;
296
195
  }
@@ -643,4 +542,3 @@ function emptySearchResult(javId) {
643
542
  cachedAt: undefined,
644
543
  };
645
544
  }
646
- const defaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36";
@@ -0,0 +1 @@
1
+ {"items":{"MIAA-710":{"javId":"MIAA-710","title":"MIAA-710 性欲強過頭的我女友居然搞外遇!? 當作處罰一面禁止做愛一面吞精挑逗一周 禁欲解禁後、和好做愛持續中出中出。 白桃花 顯示原標題 【FANZA限定】性欲が強すぎる僕の彼女がまさかの浮気!? 罰としてSEX我慢させながら1週間ごっくんで焦らしまくって禁欲解禁後、仲直りSEXで何度も中出ししまくった。 白桃はな 生写真3枚付き","coverUrl":"https://c0.jdbstatic.com/covers/8v/8Vq5A9.jpg","releaseDate":"2022-10-19","duration":0,"director":"麒麟","maker":"MOODYZ","publisher":"","series":"","actors":["白桃はな","TECH"],"categories":["中出","單體作品","苗條","口交","吞精","美少女電影","無碼破解"],"torrents":[{"title":"MIAA-710","magnetLink":"magnet:?xt=urn:btih:76eae498f6ccf49c9749ec8ecf2f876e11c6be3a&dn=[javdb.com]MIAA-710","size":5647881994,"hasSubtitle":true,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710-C.torrent","magnetLink":"magnet:?xt=urn:btih:dbce95a340dd108398a34f1abe3758c48dd64648&dn=[javdb.com]MIAA-710-C.torrent","size":5368709120,"hasSubtitle":true,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710-U.torrent.无码破解","magnetLink":"magnet:?xt=urn:btih:62d6bbd5edf534d04e58fb7376ab6fb3d4c2cea2&dn=[javdb.com]MIAA-710-U.torrent.无码破解","size":6893422510,"hasSubtitle":false,"hasUncensoredMarker":true,"uncensoredMarkerType":"U","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710","magnetLink":"magnet:?xt=urn:btih:f389875882578a00143798dcbb09d5b22bd94932&dn=[javdb.com]MIAA-710","size":5433133629,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"miaa-710.torrent","magnetLink":"magnet:?xt=urn:btih:0633d1e82351e9f7cdeff212755746e2eff0597c&dn=[javdb.com]miaa-710.torrent","size":5336496865,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710","magnetLink":"magnet:?xt=urn:btih:54b0c7957079ade34996810b678a2c1559d9e8a0&dn=[javdb.com]MIAA-710","size":1825361100,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":false,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710","magnetLink":"magnet:?xt=urn:btih:56cd13a9f152838dd6775ffd339f6b97a355c79c&dn=[javdb.com]MIAA-710","size":5433133629,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710","magnetLink":"magnet:?xt=urn:btih:7c7c7d904f7ec846b6a55619d04c83580b6267e5&dn=[javdb.com]MIAA-710","size":5486820720,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710","magnetLink":"magnet:?xt=urn:btih:0ee52547c8f4fb6acf892cc938f25cfb30d8afb4&dn=[javdb.com]MIAA-710","size":5443871047,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"@miaa710","magnetLink":"magnet:?xt=urn:btih:5dfd38d63f18993b440ad9ba4fe7e31a81a39f0c&dn=[javdb.com]@miaa710","size":4048006676,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710-uncensored-HD","magnetLink":"magnet:?xt=urn:btih:8483028491f91c4fb4a5a45c1bebd4f4f7f804cb&dn=[javdb.com]MIAA-710-uncensored-HD","size":3543348019,"hasSubtitle":false,"hasUncensoredMarker":true,"uncensoredMarkerType":"U","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"HD_MIAA-710","magnetLink":"magnet:?xt=urn:btih:3f6708da3efaa19ddb29c812e5e91b5df6cd91a2&dn=[javdb.com]HD_MIAA-710","size":2426656522,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710","magnetLink":"magnet:?xt=urn:btih:f3dd616df127bd25b4a19c236a33f0fbbe11697e&dn=[javdb.com]MIAA-710","size":2319282339,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":false,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"[Mosaic Removed Uncensored ]MIAA-710 性欲が強すぎる僕の彼女がまさかの浮気!? 罰としてSEX我慢させながら1週間ごっくんで焦らしまくって禁欲解禁後、仲直りSEXで何度も中出ししまくった。 白桃はな.TS","magnetLink":"magnet:?xt=urn:btih:349f34484c14dd5a015ba89c19da88acd753a995&dn=[javdb.com][Mosaic Removed Uncensored ]MIAA-710 性欲が強すぎる僕の彼女がまさかの浮気!? 罰としてSEX我慢させながら1週間ごっくんで焦らしまくって禁欲解禁後、仲直りSEXで何度も中出ししまくった。 白桃はな.TS","size":2244120412,"hasSubtitle":false,"hasUncensoredMarker":true,"uncensoredMarkerType":"U","hasHd":false,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710","magnetLink":"magnet:?xt=urn:btih:b05c2c1d65190308841b68048ece59296d85a727&dn=[javdb.com]MIAA-710","size":1879048192,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":false,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"MIAA-710","magnetLink":"magnet:?xt=urn:btih:10a473daf0c829f337c3287209408ba9090c1ed4&dn=[javdb.com]MIAA-710","size":1320702443,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":false,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0},{"title":"miaa-710","magnetLink":"magnet:?xt=urn:btih:6382195c130804eacc743b4531ba2e1b3090e423&dn=[javdb.com]miaa-710","size":5529770393,"hasSubtitle":false,"hasUncensoredMarker":false,"uncensoredMarkerType":"None","hasHd":true,"seeders":0,"leechers":0,"sourceSite":"JavDB","dlSpeed":0,"eta":0,"weightScore":0}],"detailUrl":"https://javdb.com/v/8Vq5A9?locale=zh","dataSource":"Local","cachedAt":"2026-02-08T08:34:51.358Z"}}}
@@ -4,12 +4,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getCurlImpersonateBinaryPath = getCurlImpersonateBinaryPath;
7
+ exports.getCurlImpersonateMainBinaryPath = getCurlImpersonateMainBinaryPath;
7
8
  exports.getCurlCaBundlePath = getCurlCaBundlePath;
8
9
  exports.checkCurlImpersonateAvailable = checkCurlImpersonateAvailable;
9
10
  exports.getAvailableTargets = getAvailableTargets;
10
11
  const path_1 = __importDefault(require("path"));
11
12
  const fs_1 = __importDefault(require("fs"));
12
- function resolvePathMaybeRelative(filePath) {
13
+ /**
14
+ * Package root directory (works for both src/ and dist/ layouts).
15
+ * __dirname is either src/utils/ or dist/utils/ – two levels up is the package root.
16
+ */
17
+ const packageRoot = path_1.default.resolve(__dirname, "..", "..");
18
+ function resolveRelativeToPackageRoot(filePath) {
19
+ if (!filePath) {
20
+ return filePath;
21
+ }
22
+ if (path_1.default.isAbsolute(filePath)) {
23
+ return filePath;
24
+ }
25
+ return path_1.default.resolve(packageRoot, filePath);
26
+ }
27
+ function resolveRelativeToCwd(filePath) {
13
28
  if (!filePath) {
14
29
  return filePath;
15
30
  }
@@ -43,15 +58,18 @@ function findOnPath(fileName) {
43
58
  return null;
44
59
  }
45
60
  /**
46
- * Get the platform-specific curl-impersonate binary path
61
+ * Get the platform-specific curl-impersonate target wrapper script path.
62
+ * Resolves vendored paths relative to the **package root** (not CWD) so that
63
+ * the binary is found regardless of where the user invokes the command.
47
64
  */
48
65
  function getCurlImpersonateBinaryPath(target = "chrome116", configuredPath = null) {
66
+ // User-configured path is resolved relative to CWD (explicit override).
49
67
  if (configuredPath && configuredPath.trim()) {
50
- return resolvePathMaybeRelative(configuredPath.trim());
68
+ return resolveRelativeToCwd(configuredPath.trim());
51
69
  }
52
70
  const platform = process.platform;
53
71
  const arch = process.arch;
54
- // Map platform/arch to binary path in third_party/curl-impersonate/
72
+ // Map platform/arch to target wrapper script in third_party/curl-impersonate/
55
73
  const binaryMap = {
56
74
  win32: {
57
75
  x64: path_1.default.join("third_party", "curl-impersonate", "bin", `curl_${target}.exe`),
@@ -71,7 +89,27 @@ function getCurlImpersonateBinaryPath(target = "chrome116", configuredPath = nul
71
89
  return null;
72
90
  }
73
91
  const binaryPath = platformBinaries[arch] || platformBinaries.x64;
74
- return resolvePathMaybeRelative(binaryPath);
92
+ return resolveRelativeToPackageRoot(binaryPath);
93
+ }
94
+ /**
95
+ * Get the path to the main `curl-impersonate` executable (not a target wrapper).
96
+ * Checks vendored location first, then falls back to PATH lookup.
97
+ */
98
+ function getCurlImpersonateMainBinaryPath() {
99
+ const ext = process.platform === "win32" ? ".exe" : "";
100
+ const vendoredRelative = path_1.default.join("third_party", "curl-impersonate", "bin", `curl-impersonate${ext}`);
101
+ const vendored = resolveRelativeToPackageRoot(vendoredRelative);
102
+ if (isExecutableFile(vendored)) {
103
+ return vendored;
104
+ }
105
+ // Windows fallback: vendored Linux binary (to be run via WSL).
106
+ if (process.platform === "win32") {
107
+ const alt = resolveRelativeToPackageRoot(path_1.default.join("third_party", "curl-impersonate", "bin", "curl-impersonate"));
108
+ if (isExecutableFile(alt)) {
109
+ return alt;
110
+ }
111
+ }
112
+ return findOnPath(`curl-impersonate${ext}`);
75
113
  }
76
114
  /**
77
115
  * Get the CA bundle path for curl-impersonate
@@ -101,41 +139,81 @@ function getCurlCaBundlePath(configuredPath = null) {
101
139
  if (!platformCaBundles) {
102
140
  return null;
103
141
  }
104
- const caBundlePath = platformCaBundles[arch] || platformCaBundles.x64;
105
- if (fs_1.default.existsSync(caBundlePath)) {
106
- return caBundlePath;
142
+ const relative = platformCaBundles[arch] || platformCaBundles.x64;
143
+ const resolved = resolveRelativeToPackageRoot(relative);
144
+ if (fs_1.default.existsSync(resolved)) {
145
+ return resolved;
107
146
  }
108
147
  return null;
109
148
  }
110
149
  /**
111
- * Check if curl-impersonate binary is available
150
+ * Check if curl-impersonate binary is available.
151
+ *
152
+ * On Linux/macOS the main `curl-impersonate` ELF binary is preferred because
153
+ * it is a standalone executable that does not depend on bash; the `curl_<target>`
154
+ * wrapper scripts are shell scripts requiring `#!/usr/bin/env bash` which may
155
+ * not be available in all environments.
156
+ *
157
+ * Resolution order (Linux/macOS):
158
+ * 1. User-configured path
159
+ * 2. Vendored main `curl-impersonate` binary (package root) – with `--impersonate` flag
160
+ * 3. `curl-impersonate` on PATH – with `--impersonate` flag
161
+ * 4. Vendored wrapper script `curl_<target>` (package root)
162
+ * 5. `curl_<target>` on PATH
163
+ *
164
+ * Resolution order (Windows):
165
+ * 1. User-configured path
166
+ * 2. Vendored wrapper `curl_<target>.exe` / non-`.exe` WSL fallback (package root)
167
+ * 3. `curl_<target>.exe` on PATH
168
+ * 4. Main `curl-impersonate` binary (vendored or PATH) – with `--impersonate` flag
112
169
  */
113
170
  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 };
171
+ // 1. User-configured path (resolved relative to CWD).
172
+ if (configuredPath && configuredPath.trim()) {
173
+ const binaryPath = getCurlImpersonateBinaryPath(target, configuredPath);
174
+ if (binaryPath && fs_1.default.existsSync(binaryPath)) {
175
+ return { path: binaryPath, exists: true, target, useImpersonateFlag: false };
176
+ }
177
+ }
178
+ // On non-Windows, prefer the main binary (standalone ELF, no bash dependency).
179
+ if (process.platform !== "win32") {
180
+ const mainBinary = getCurlImpersonateMainBinaryPath();
181
+ if (mainBinary) {
182
+ return { path: mainBinary, exists: true, target, useImpersonateFlag: true };
127
183
  }
128
184
  }
129
- // Allow PATH-installed curl_<target> binaries (useful when vendoring isn't available).
185
+ // Vendored wrapper script lookup.
186
+ const binaryPath = getCurlImpersonateBinaryPath(target, null);
187
+ if (binaryPath) {
188
+ if (fs_1.default.existsSync(binaryPath)) {
189
+ return { path: binaryPath, exists: true, target, useImpersonateFlag: false };
190
+ }
191
+ // Windows fallback: allow vendored Linux binaries (to be run via WSL) when the .exe doesn't exist.
192
+ if (process.platform === "win32" && binaryPath.toLowerCase().endsWith(".exe")) {
193
+ const alt = binaryPath.slice(0, -4);
194
+ if (alt && fs_1.default.existsSync(alt)) {
195
+ return { path: alt, exists: true, target, useImpersonateFlag: false };
196
+ }
197
+ }
198
+ }
199
+ // Allow PATH-installed curl_<target> binaries.
130
200
  const fileName = process.platform === "win32" ? `curl_${target}.exe` : `curl_${target}`;
131
201
  const found = findOnPath(fileName);
132
202
  if (found) {
133
- return { path: found, exists: true, target };
203
+ return { path: found, exists: true, target, useImpersonateFlag: false };
204
+ }
205
+ // Windows: main binary fallback (vendored or PATH) as last resort.
206
+ if (process.platform === "win32") {
207
+ const mainBinary = getCurlImpersonateMainBinaryPath();
208
+ if (mainBinary) {
209
+ return { path: mainBinary, exists: true, target, useImpersonateFlag: true };
210
+ }
134
211
  }
135
212
  return {
136
- path: binaryPath,
213
+ path: binaryPath ?? "",
137
214
  exists: false,
138
215
  target,
216
+ useImpersonateFlag: false,
139
217
  };
140
218
  }
141
219
  /**