@skrillex1224/playwright-toolkit 2.1.286 → 2.1.287

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/index.cjs CHANGED
@@ -40,11 +40,9 @@ __export(constants_exports, {
40
40
  ActorInfo: () => ActorInfo,
41
41
  Code: () => Code,
42
42
  Device: () => Device,
43
- Mode: () => Mode,
44
43
  PresetOfLiveViewKey: () => PresetOfLiveViewKey,
45
44
  Status: () => Status,
46
- normalizeDevice: () => normalizeDevice,
47
- normalizeMode: () => normalizeMode
45
+ normalizeDevice: () => normalizeDevice
48
46
  });
49
47
  var Code = {
50
48
  Success: 0,
@@ -64,10 +62,6 @@ var Device = Object.freeze({
64
62
  Desktop: "desktop",
65
63
  Mobile: "mobile"
66
64
  });
67
- var Mode = Object.freeze({
68
- Default: "default",
69
- CloakBrowser: "cloakbrowser"
70
- });
71
65
  var normalizeDevice = (value, fallback = Device.Desktop) => {
72
66
  const normalizedFallback = String(fallback || "").trim().toLowerCase() === Device.Mobile ? Device.Mobile : Device.Desktop;
73
67
  const raw = String(value || "").trim().toLowerCase();
@@ -75,13 +69,6 @@ var normalizeDevice = (value, fallback = Device.Desktop) => {
75
69
  if (raw === Device.Desktop) return Device.Desktop;
76
70
  return normalizedFallback;
77
71
  };
78
- var normalizeMode = (value, fallback = Mode.Default) => {
79
- const normalizedFallback = String(fallback || "").trim().toLowerCase() === Mode.CloakBrowser ? Mode.CloakBrowser : Mode.Default;
80
- const raw = String(value || "").trim().toLowerCase();
81
- if (raw === Mode.CloakBrowser) return Mode.CloakBrowser;
82
- if (raw === Mode.Default) return Mode.Default;
83
- return normalizedFallback;
84
- };
85
72
  var createActorInfo = (info) => {
86
73
  const normalizeDomain = (value) => {
87
74
  if (!value) return "";
@@ -149,6 +136,7 @@ var createActorInfo = (info) => {
149
136
  const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path4 }) => {
150
137
  const safeProtocol = String(protocol2).trim();
151
138
  const safeDomain = normalizeDomain(domain2);
139
+ if (!safeDomain) return "";
152
140
  const safePath = normalizePath(path4);
153
141
  return `${safeProtocol}://${safeDomain}${safePath}`;
154
142
  };
@@ -356,6 +344,18 @@ var ActorInfo = {
356
344
  prefix: "",
357
345
  xurl: []
358
346
  }
347
+ }),
348
+ // 通用网页抓取 Actor:入口 URL 来自 query,因此这里只声明统一的 Actor 元信息。
349
+ webpage: createActorInfo({
350
+ key: "webpage",
351
+ name: "\u901A\u7528\u7F51\u9875",
352
+ domain: "",
353
+ path: "/",
354
+ share: {
355
+ mode: "dom",
356
+ prefix: "",
357
+ xurl: []
358
+ }
359
359
  })
360
360
  };
361
361
 
@@ -780,7 +780,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
780
780
  if (safeTargetHeight <= viewportHeight + 1) {
781
781
  return 0;
782
782
  }
783
- const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
783
+ const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
784
784
  const isVisible = (el, style, rect) => {
785
785
  if (!el || !style || !rect) return false;
786
786
  if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
@@ -820,7 +820,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
820
820
  return true;
821
821
  });
822
822
  topLevelCandidates.forEach(({ el, position, edge }) => {
823
- if (!hasOwn2(el.dataset, "pkAffixedAdjusted")) {
823
+ if (!hasOwn(el.dataset, "pkAffixedAdjusted")) {
824
824
  el.dataset.pkAffixedAdjusted = "1";
825
825
  el.dataset.pkOrigPosition = el.style.getPropertyValue("position") || "";
826
826
  el.dataset.pkOrigPositionPriority = el.style.getPropertyPriority("position") || "";
@@ -851,7 +851,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
851
851
  };
852
852
  var restoreAffixedElementsForExpandedScreenshot = async (page) => {
853
853
  await page.evaluate((className) => {
854
- const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
854
+ const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
855
855
  const expansionKeys = [
856
856
  "pkOrigOverflow",
857
857
  "pkOrigHeight",
@@ -859,28 +859,28 @@ var restoreAffixedElementsForExpandedScreenshot = async (page) => {
859
859
  "pkOrigMaxHeight"
860
860
  ];
861
861
  document.querySelectorAll('[data-pk-affixed-adjusted="1"]').forEach((el) => {
862
- if (hasOwn2(el.dataset, "pkOrigPosition")) {
862
+ if (hasOwn(el.dataset, "pkOrigPosition")) {
863
863
  el.style.setProperty("position", el.dataset.pkOrigPosition || "", el.dataset.pkOrigPositionPriority || "");
864
864
  delete el.dataset.pkOrigPosition;
865
865
  delete el.dataset.pkOrigPositionPriority;
866
866
  }
867
- if (hasOwn2(el.dataset, "pkOrigTop")) {
867
+ if (hasOwn(el.dataset, "pkOrigTop")) {
868
868
  el.style.setProperty("top", el.dataset.pkOrigTop || "", el.dataset.pkOrigTopPriority || "");
869
869
  delete el.dataset.pkOrigTop;
870
870
  delete el.dataset.pkOrigTopPriority;
871
871
  }
872
- if (hasOwn2(el.dataset, "pkOrigBottom")) {
872
+ if (hasOwn(el.dataset, "pkOrigBottom")) {
873
873
  el.style.setProperty("bottom", el.dataset.pkOrigBottom || "", el.dataset.pkOrigBottomPriority || "");
874
874
  delete el.dataset.pkOrigBottom;
875
875
  delete el.dataset.pkOrigBottomPriority;
876
876
  }
877
- if (hasOwn2(el.dataset, "pkOrigTranslate")) {
877
+ if (hasOwn(el.dataset, "pkOrigTranslate")) {
878
878
  el.style.setProperty("translate", el.dataset.pkOrigTranslate || "", el.dataset.pkOrigTranslatePriority || "");
879
879
  delete el.dataset.pkOrigTranslate;
880
880
  delete el.dataset.pkOrigTranslatePriority;
881
881
  }
882
882
  delete el.dataset.pkAffixedAdjusted;
883
- const stillExpanded = expansionKeys.some((key) => hasOwn2(el.dataset, key));
883
+ const stillExpanded = expansionKeys.some((key) => hasOwn(el.dataset, key));
884
884
  if (!stillExpanded) {
885
885
  el.classList.remove(className);
886
886
  }
@@ -923,7 +923,7 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
923
923
  viewportResized
924
924
  };
925
925
  };
926
- var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
926
+ var restoreExpandedFullPageScreenshot = async (page, state = {}) => {
927
927
  await page.evaluate((className) => {
928
928
  const targets = new Set([
929
929
  ...document.querySelectorAll(`.${className}`),
@@ -947,8 +947,8 @@ var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
947
947
  el.classList.remove(className);
948
948
  });
949
949
  }, EXPANDED_SCROLLABLE_CLASS);
950
- if (state2?.originalViewport && state2?.viewportResized) {
951
- await page.setViewportSize(state2.originalViewport);
950
+ if (state?.originalViewport && state?.viewportResized) {
951
+ await page.setViewportSize(state.originalViewport);
952
952
  }
953
953
  };
954
954
  var capturePageScreenshot = async (page, options = {}) => {
@@ -1005,21 +1005,21 @@ var capturePageScreenshot = async (page, options = {}) => {
1005
1005
  }
1006
1006
  };
1007
1007
  var captureExpandedFullPageScreenshot = async (page, options = {}) => {
1008
- const state2 = await prepareExpandedFullPageScreenshot(page, options);
1008
+ const state = await prepareExpandedFullPageScreenshot(page, options);
1009
1009
  try {
1010
1010
  return await capturePageScreenshot(page, {
1011
1011
  fullPage: true,
1012
1012
  type: options.type || "png",
1013
1013
  quality: options.quality,
1014
1014
  timeout: options.timeout,
1015
- maxClipHeight: state2.targetHeight
1015
+ maxClipHeight: state.targetHeight
1016
1016
  });
1017
1017
  } finally {
1018
1018
  await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
1019
1019
  logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
1020
1020
  });
1021
1021
  if (options.restore) {
1022
- await restoreExpandedFullPageScreenshot(page, state2);
1022
+ await restoreExpandedFullPageScreenshot(page, state);
1023
1023
  }
1024
1024
  }
1025
1025
  };
@@ -1196,6 +1196,19 @@ var ensureLogPath = () => {
1196
1196
  const label = runId ? `proxy-meter-${runId}-${suffix}.json` : `proxy-meter-${process.pid}-${suffix}.json`;
1197
1197
  return import_path.default.join(baseDir, label);
1198
1198
  };
1199
+ var ensureStdioLogPath = (logPath) => `${logPath}.stdio.log`;
1200
+ var writeChildOutput = (stream, output, prefix) => {
1201
+ if (!stream || !output) return;
1202
+ output.on("data", (chunk) => {
1203
+ const text = chunk?.toString?.() || String(chunk || "");
1204
+ if (!text) return;
1205
+ for (const line of text.split(/\r?\n/)) {
1206
+ if (!line) continue;
1207
+ stream.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${prefix} ${line}
1208
+ `);
1209
+ }
1210
+ });
1211
+ };
1199
1212
  var readSnapshot = (logPath) => {
1200
1213
  if (!logPath || !(0, import_fs.existsSync)(logPath)) return null;
1201
1214
  try {
@@ -1363,6 +1376,7 @@ var startProxyMeter = (options = {}) => {
1363
1376
  observedDomainResourceTypes = /* @__PURE__ */ new Map();
1364
1377
  const port = pickFreePort();
1365
1378
  const logPath = ensureLogPath();
1379
+ const stdioLogPath = ensureStdioLogPath(logPath);
1366
1380
  const scriptPath = resolveScriptPath();
1367
1381
  const debugMode = Boolean(options.debugMode);
1368
1382
  const debugMaxEvents = Math.max(10, toSafeInt(options.debugMaxEvents) || DEFAULT_DEBUG_MAX_EVENTS);
@@ -1375,19 +1389,28 @@ var startProxyMeter = (options = {}) => {
1375
1389
  PROXY_METER_DEBUG: debugMode ? "1" : "0",
1376
1390
  PROXY_METER_DEBUG_MAX_EVENTS: String(debugMaxEvents)
1377
1391
  };
1392
+ const stdioLog = (0, import_fs.createWriteStream)(stdioLogPath, { flags: "a" });
1393
+ stdioLog.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [proxy-meter-runtime] start script=${scriptPath} port=${port} snapshot=${logPath}
1394
+ `);
1378
1395
  const child = (0, import_child_process.spawn)(process.execPath, [scriptPath], {
1379
1396
  env,
1380
- stdio: ["ignore", "ignore", "ignore"]
1397
+ stdio: ["ignore", "pipe", "pipe"]
1381
1398
  });
1382
- child.once("exit", (code) => {
1399
+ writeChildOutput(stdioLog, child.stdout, "stdout");
1400
+ writeChildOutput(stdioLog, child.stderr, "stderr");
1401
+ child.once("exit", (code, signal) => {
1402
+ stdioLog.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [proxy-meter-runtime] exit code=${code ?? ""} signal=${signal ?? ""}
1403
+ `);
1404
+ stdioLog.end();
1383
1405
  if (code && code !== 0) {
1384
- logger2.warn(`[proxy-meter] exited with code ${code}`);
1406
+ logger2.warn(`[proxy-meter] exited with code ${code}; stdio=${stdioLogPath}`);
1385
1407
  }
1386
1408
  });
1387
1409
  runtime = {
1388
1410
  proc: child,
1389
1411
  port,
1390
1412
  logPath,
1413
+ stdioLogPath,
1391
1414
  startedAt: Date.now()
1392
1415
  };
1393
1416
  registerCleanup();
@@ -1915,8 +1938,8 @@ var normalizeBrowserProfile = (value) => {
1915
1938
  payload: buildBrowserProfilePayload(core, observed)
1916
1939
  };
1917
1940
  };
1918
- var rememberRuntimeState = (state2) => {
1919
- rememberedRuntimeState = deepClone(state2);
1941
+ var rememberRuntimeState = (state) => {
1942
+ rememberedRuntimeState = deepClone(state);
1920
1943
  return rememberedRuntimeState;
1921
1944
  };
1922
1945
  var normalizeRuntimeState = (source = {}, actor = "") => {
@@ -1975,7 +1998,7 @@ var RuntimeEnv = {
1975
1998
  } else {
1976
1999
  delete normalizedRuntime.browser_profile;
1977
2000
  }
1978
- const state2 = {
2001
+ const state = {
1979
2002
  actor: resolvedActor,
1980
2003
  device,
1981
2004
  runtime: normalizedRuntime,
@@ -1989,73 +2012,73 @@ var RuntimeEnv = {
1989
2012
  browserProfileCore: browserProfile.core,
1990
2013
  browserProfileObserved: browserProfile.observed
1991
2014
  };
1992
- rememberRuntimeState(state2);
1993
- return state2;
2015
+ rememberRuntimeState(state);
2016
+ return state;
1994
2017
  },
1995
2018
  // buildEnvPatch 只构造允许回写到后端 env 的字段集合。
1996
2019
  buildEnvPatch(source = {}, actor = "") {
1997
- const state2 = normalizeRuntimeState(source, actor);
1998
- const browserProfile = buildBrowserProfilePayload(state2.browserProfileCore, state2.browserProfileObserved);
2020
+ const state = normalizeRuntimeState(source, actor);
2021
+ const browserProfile = buildBrowserProfilePayload(state.browserProfileCore, state.browserProfileObserved);
1999
2022
  const envPatch = {
2000
- ...Array.isArray(state2.cookies) && state2.cookies.length > 0 ? { cookies: state2.cookies } : {},
2001
- ...Object.keys(state2.localStorage || {}).length > 0 ? { local_storage: state2.localStorage } : {},
2002
- ...Object.keys(state2.sessionStorage || {}).length > 0 ? { session_storage: state2.sessionStorage } : {},
2023
+ ...Array.isArray(state.cookies) && state.cookies.length > 0 ? { cookies: state.cookies } : {},
2024
+ ...Object.keys(state.localStorage || {}).length > 0 ? { local_storage: state.localStorage } : {},
2025
+ ...Object.keys(state.sessionStorage || {}).length > 0 ? { session_storage: state.sessionStorage } : {},
2003
2026
  ...Object.keys(browserProfile).length > 0 ? { browser_profile: browserProfile } : {}
2004
2027
  };
2005
2028
  return Object.keys(envPatch).length > 0 ? envPatch : null;
2006
2029
  },
2007
2030
  // hasLoginState 只判断 runtime 是否存在有效载荷,不再区分具体字段来源。
2008
2031
  hasLoginState(source = {}, actor = "") {
2009
- const state2 = normalizeRuntimeState(source, actor);
2010
- return isPlainObject(state2.runtime) && Object.keys(state2.runtime || {}).length > 0;
2032
+ const state = normalizeRuntimeState(source, actor);
2033
+ return isPlainObject(state.runtime) && Object.keys(state.runtime || {}).length > 0;
2011
2034
  },
2012
2035
  rememberState(source = {}) {
2013
- const state2 = normalizeRuntimeState(source);
2014
- rememberRuntimeState(state2);
2036
+ const state = normalizeRuntimeState(source);
2037
+ rememberRuntimeState(state);
2015
2038
  return RuntimeEnv.peekRememberedState();
2016
2039
  },
2017
2040
  peekRememberedState() {
2018
2041
  return rememberedRuntimeState ? deepClone(rememberedRuntimeState) : null;
2019
2042
  },
2020
2043
  getBrowserProfileCore(source = {}, actor = "") {
2021
- const state2 = normalizeRuntimeState(source, actor);
2022
- return deepClone(state2.browserProfileCore || {});
2044
+ const state = normalizeRuntimeState(source, actor);
2045
+ return deepClone(state.browserProfileCore || {});
2023
2046
  },
2024
2047
  setBrowserProfileCore(source = {}, core = {}, actor = "") {
2025
- const state2 = normalizeRuntimeState(source, actor);
2048
+ const state = normalizeRuntimeState(source, actor);
2026
2049
  const normalizedCore = normalizeBrowserProfileCore({
2027
2050
  ...core,
2028
- device: normalizeKnownDevice(core?.device) || state2.device
2051
+ device: normalizeKnownDevice(core?.device) || state.device
2029
2052
  });
2030
- state2.browserProfileCore = normalizedCore;
2031
- state2.browserProfile = buildBrowserProfilePayload(normalizedCore, state2.browserProfileObserved);
2032
- if (Object.keys(state2.browserProfile).length > 0) {
2033
- state2.runtime.browser_profile = state2.browserProfile;
2053
+ state.browserProfileCore = normalizedCore;
2054
+ state.browserProfile = buildBrowserProfilePayload(normalizedCore, state.browserProfileObserved);
2055
+ if (Object.keys(state.browserProfile).length > 0) {
2056
+ state.runtime.browser_profile = state.browserProfile;
2034
2057
  } else {
2035
- delete state2.runtime.browser_profile;
2058
+ delete state.runtime.browser_profile;
2036
2059
  }
2037
- rememberRuntimeState(state2);
2038
- return state2;
2060
+ rememberRuntimeState(state);
2061
+ return state;
2039
2062
  },
2040
2063
  // applyToPage 只负责把登录态相关字段注入页面:
2041
2064
  // cookies / localStorage / sessionStorage。
2042
2065
  // 指纹、时区、UA、viewport 的回放发生在 launch.js 启动阶段,不在这里做。
2043
2066
  async applyToPage(page, source = {}, options = {}) {
2044
2067
  if (!page) return;
2045
- let state2 = normalizeRuntimeState(source, options?.actor || "");
2068
+ let state = normalizeRuntimeState(source, options?.actor || "");
2046
2069
  if (typeof options?.preapply === "function") {
2047
- state2 = await options.preapply(state2) || state2;
2048
- rememberRuntimeState(state2);
2070
+ state = await options.preapply(state) || state;
2071
+ rememberRuntimeState(state);
2049
2072
  }
2050
2073
  Object.defineProperty(page, PageRuntimeStateKey, {
2051
2074
  configurable: true,
2052
2075
  enumerable: false,
2053
2076
  writable: true,
2054
- value: state2
2077
+ value: state
2055
2078
  });
2056
- const localStorage = state2.localStorage || {};
2057
- const sessionStorage = state2.sessionStorage || {};
2058
- const cookies = (state2.cookies || []).map((cookie) => {
2079
+ const localStorage = state.localStorage || {};
2080
+ const sessionStorage = state.sessionStorage || {};
2081
+ const cookies = (state.cookies || []).map((cookie) => {
2059
2082
  const normalized = { ...cookie };
2060
2083
  if (!normalized.path) {
2061
2084
  normalized.path = "/";
@@ -2093,8 +2116,8 @@ var RuntimeEnv = {
2093
2116
  },
2094
2117
  // captureEnvPatch 在任务结束时采集最新环境快照,用于 pushSuccess / pushFailed 自动回写。
2095
2118
  async captureEnvPatch(page, source = {}, options = {}) {
2096
- const state2 = normalizeRuntimeState(source, options?.actor || "");
2097
- const baseline = RuntimeEnv.buildEnvPatch(state2) || {};
2119
+ const state = normalizeRuntimeState(source, options?.actor || "");
2120
+ const baseline = RuntimeEnv.buildEnvPatch(state) || {};
2098
2121
  if (!page || typeof page.evaluate !== "function" || typeof page.context !== "function") {
2099
2122
  return Object.keys(baseline).length > 0 ? baseline : null;
2100
2123
  }
@@ -2113,7 +2136,7 @@ var RuntimeEnv = {
2113
2136
  cookies
2114
2137
  },
2115
2138
  {
2116
- browserProfileCore: state2.browserProfileCore
2139
+ browserProfileCore: state.browserProfileCore
2117
2140
  }
2118
2141
  );
2119
2142
  return RuntimeEnv.mergeEnvPatches(baseline, capturedPatch);
@@ -2127,11 +2150,11 @@ var RuntimeEnv = {
2127
2150
  var logger3 = createInternalLogger("ApifyKit");
2128
2151
  var resolveRuntimeContext = (input) => {
2129
2152
  const rememberedState = RuntimeEnv.peekRememberedState();
2130
- const state2 = rememberedState || RuntimeEnv.parseInput(input || {});
2131
- const envPatch = RuntimeEnv.buildEnvPatch(state2) || null;
2153
+ const state = rememberedState || RuntimeEnv.parseInput(input || {});
2154
+ const envPatch = RuntimeEnv.buildEnvPatch(state) || null;
2132
2155
  return {
2133
- actor: state2.actor,
2134
- runtime: state2.runtime,
2156
+ actor: state.actor,
2157
+ runtime: state.runtime,
2135
2158
  envPatch
2136
2159
  };
2137
2160
  };
@@ -2490,99 +2513,8 @@ var Utils = {
2490
2513
  }
2491
2514
  };
2492
2515
 
2493
- // src/internals/context.js
2494
- var state = {
2495
- mode: Mode.Default
2496
- };
2497
- var normalizeStrategies = (strategies) => strategies && typeof strategies === "object" ? strategies : {};
2498
- var ToolkitContext = {
2499
- get mode() {
2500
- return state.mode;
2501
- },
2502
- setMode(mode = Mode.Default) {
2503
- state.mode = normalizeMode(mode, Mode.Default);
2504
- return state.mode;
2505
- }
2506
- };
2507
- var getToolkitMode = () => state.mode;
2508
- var setToolkitMode = (mode = Mode.Default) => ToolkitContext.setMode(mode);
2509
- var resolveModeStrategy = (strategies = {}, mode = getToolkitMode(), fallbackMode = Mode.Default) => {
2510
- const normalizedStrategies = normalizeStrategies(strategies);
2511
- const normalizedMode = normalizeMode(mode, fallbackMode);
2512
- const delegate = normalizedStrategies[normalizedMode] ?? normalizedStrategies[fallbackMode] ?? Object.values(normalizedStrategies).find(Boolean) ?? null;
2513
- return {
2514
- mode: normalizedMode,
2515
- delegate
2516
- };
2517
- };
2518
-
2519
- // src/internals/delegate.js
2520
- var normalizeArrayMethodDefinition = (definition) => typeof definition === "string" ? { name: definition, enumerable: true } : {
2521
- name: String(definition?.name || "").trim(),
2522
- enumerable: definition?.enumerable !== false
2523
- };
2524
- var normalizeMethodDefinitions = (methods = {}) => {
2525
- if (Array.isArray(methods)) {
2526
- return methods.map(normalizeArrayMethodDefinition).filter((method) => method.name);
2527
- }
2528
- if (!methods || typeof methods !== "object") {
2529
- return [];
2530
- }
2531
- return Object.entries(methods).map(([name, definition]) => ({
2532
- name: String(name || "").trim(),
2533
- enumerable: definition?.enumerable !== false
2534
- })).filter((method) => method.name);
2535
- };
2536
- var normalizeDelegateResolution = (resolution) => {
2537
- if (resolution && typeof resolution === "object" && ("delegate" in resolution || "label" in resolution)) {
2538
- return {
2539
- delegate: resolution.delegate ?? null,
2540
- label: String(resolution.label || "current delegate").trim() || "current delegate"
2541
- };
2542
- }
2543
- return {
2544
- delegate: resolution,
2545
- label: "current delegate"
2546
- };
2547
- };
2548
- var createMethodDescriptor = (namespace, methodName, enumerable, resolveDelegate) => ({
2549
- enumerable,
2550
- value: (...args) => {
2551
- const { delegate, label } = normalizeDelegateResolution(resolveDelegate(methodName, args));
2552
- if (typeof delegate?.[methodName] !== "function") {
2553
- throw new Error(`${namespace}.${methodName} is not available in ${label}`);
2554
- }
2555
- return delegate[methodName](...args);
2556
- }
2557
- });
2558
- var withDelegatedProperties = (target = {}, {
2559
- namespace = "Delegate",
2560
- methods = {},
2561
- resolveDelegate = () => null
2562
- } = {}) => {
2563
- const descriptors = Object.fromEntries(
2564
- normalizeMethodDefinitions(methods).map((method) => [
2565
- method.name,
2566
- createMethodDescriptor(namespace, method.name, method.enumerable, resolveDelegate)
2567
- ])
2568
- );
2569
- return Object.defineProperties(target, descriptors);
2570
- };
2571
- var createDelegatedFacade = (namespace, strategies = {}, methods = {}) => {
2572
- return withDelegatedProperties({}, {
2573
- namespace,
2574
- methods,
2575
- resolveDelegate: () => {
2576
- const { mode, delegate } = resolveModeStrategy(strategies);
2577
- return {
2578
- delegate,
2579
- label: `${mode} mode`
2580
- };
2581
- }
2582
- });
2583
- };
2584
-
2585
- // src/internals/anti-cheat/default.js
2516
+ // src/anti-cheat.js
2517
+ var logger5 = createInternalLogger("AntiCheat");
2586
2518
  var BASE_CONFIG = Object.freeze({
2587
2519
  locale: "zh-CN",
2588
2520
  acceptLanguage: "zh-CN,zh;q=0.9",
@@ -2617,7 +2549,7 @@ function buildFingerprintOptions({ locale = BASE_CONFIG.locale, browserMajorVers
2617
2549
  }
2618
2550
  return options;
2619
2551
  }
2620
- var DefaultAntiCheat = {
2552
+ var AntiCheat = {
2621
2553
  /**
2622
2554
  * 获取统一的基础配置
2623
2555
  */
@@ -2654,54 +2586,6 @@ var DefaultAntiCheat = {
2654
2586
  }
2655
2587
  };
2656
2588
 
2657
- // src/internals/anti-cheat/cloakbrowser.js
2658
- var CLOAK_BROWSER_BASE_CONFIG = Object.freeze({
2659
- locale: "",
2660
- acceptLanguage: "",
2661
- timezoneId: "",
2662
- timezoneOffset: null,
2663
- geolocation: null
2664
- });
2665
- var normalizeHeaders = (headers) => headers && typeof headers === "object" ? headers : {};
2666
- var CloakBrowserAntiCheat = {
2667
- /**
2668
- * CloakBrowser 自身会负责浏览器指纹,toolkit 在该模式下尽量不再注入额外反检测配置。
2669
- */
2670
- getBaseConfig() {
2671
- return { ...CLOAK_BROWSER_BASE_CONFIG };
2672
- },
2673
- getFingerprintGeneratorOptions() {
2674
- return {};
2675
- },
2676
- getLaunchArgs() {
2677
- return [];
2678
- },
2679
- getTlsFingerprintOptions() {
2680
- return {};
2681
- },
2682
- applyLocaleHeaders(headers, acceptLanguage = "") {
2683
- const normalizedHeaders = normalizeHeaders(headers);
2684
- if (acceptLanguage && !normalizedHeaders["accept-language"]) {
2685
- normalizedHeaders["accept-language"] = acceptLanguage;
2686
- }
2687
- return normalizedHeaders;
2688
- }
2689
- };
2690
-
2691
- // src/anti-cheat.js
2692
- var antiCheatStrategies = {
2693
- [Mode.Default]: DefaultAntiCheat,
2694
- [Mode.CloakBrowser]: CloakBrowserAntiCheat
2695
- };
2696
- var antiCheatFacadeMethods = Object.freeze({
2697
- getBaseConfig: { enumerable: true },
2698
- getFingerprintGeneratorOptions: { enumerable: true },
2699
- getLaunchArgs: { enumerable: true },
2700
- getTlsFingerprintOptions: { enumerable: true },
2701
- applyLocaleHeaders: { enumerable: true }
2702
- });
2703
- var AntiCheat = createDelegatedFacade("AntiCheat", antiCheatStrategies, antiCheatFacadeMethods);
2704
-
2705
2589
  // src/device-input.js
2706
2590
  var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
2707
2591
  var assertPage = (page, method) => {
@@ -3101,12 +2985,12 @@ var resolveDescriptor = (descriptor, device) => {
3101
2985
  }
3102
2986
  return resolved;
3103
2987
  };
3104
- var attachRuntimeState = (page, state2) => {
2988
+ var attachRuntimeState = (page, state) => {
3105
2989
  Object.defineProperty(page, PageRuntimeStateKey, {
3106
2990
  configurable: true,
3107
2991
  enumerable: false,
3108
2992
  writable: true,
3109
- value: state2
2993
+ value: state
3110
2994
  });
3111
2995
  };
3112
2996
  var restoreRuntimeState = (page, snapshot) => {
@@ -3252,1240 +3136,1136 @@ var DeviceView = {
3252
3136
  }
3253
3137
  };
3254
3138
 
3255
- // src/internals/humanize/cloakbrowser.js
3256
- var import_delay3 = __toESM(require("delay"), 1);
3257
-
3258
- // src/internals/humanize/shared.js
3139
+ // src/internals/humanize/desktop.js
3259
3140
  var import_delay2 = __toESM(require("delay"), 1);
3260
- var jitterMs = (base, jitterPercent = 0.3) => {
3261
- const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
3262
- return Math.max(10, Math.round(Number(base || 0) + jitter));
3263
- };
3264
- var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
3265
- if (target == null) return null;
3266
- let element = target;
3267
- if (typeof target === "string") {
3268
- element = await page.$(target);
3269
- }
3270
- if (!element) {
3271
- if (throwOnMissing) {
3272
- throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
3273
- }
3274
- return null;
3275
- }
3276
- return element;
3277
- };
3278
- var waitJitter = (base, jitterPercent = 0.3) => (0, import_delay2.default)(jitterMs(base, jitterPercent));
3279
-
3280
- // src/internals/humanize/mobile.js
3281
- var logger5 = createInternalLogger("Humanize.Mobile");
3282
- var initializedPages = /* @__PURE__ */ new WeakSet();
3283
- var DEFAULT_TAP_TIMEOUT_MS = 2500;
3284
- var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3285
- var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3286
- var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3287
- var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3288
- var describeTarget = (target) => {
3289
- if (target == null) return "Current Position";
3290
- return typeof target === "string" ? target : "ElementHandle";
3291
- };
3292
- var clipBoxToViewport = (box, viewport) => {
3293
- if (!box || !viewport) return box;
3294
- const left = clamp(box.x, 0, viewport.width);
3295
- const top = clamp(box.y, 0, viewport.height);
3296
- const right = clamp(box.x + box.width, 0, viewport.width);
3297
- const bottom = clamp(box.y + box.height, 0, viewport.height);
3298
- if (right - left > 1 && bottom - top > 1) {
3299
- return {
3300
- x: left,
3301
- y: top,
3302
- width: right - left,
3303
- height: bottom - top
3304
- };
3305
- }
3306
- return box;
3307
- };
3308
- var centerPointInBox = (box) => ({
3309
- x: box.x + box.width / 2,
3310
- y: box.y + box.height / 2
3311
- });
3312
- var withTimeout = async (operation, timeoutMs, label) => {
3313
- const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3314
- let timeoutId = null;
3315
- try {
3316
- return await Promise.race([
3317
- Promise.resolve().then(operation),
3318
- new Promise((_, reject) => {
3319
- timeoutId = setTimeout(() => {
3320
- reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
3321
- }, safeTimeoutMs);
3322
- })
3323
- ]);
3324
- } finally {
3325
- if (timeoutId) clearTimeout(timeoutId);
3141
+ var import_ghost_cursor_playwright = require("ghost-cursor-playwright");
3142
+ var logger6 = createInternalLogger("Humanize");
3143
+ var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
3144
+ function $GetCursor(page) {
3145
+ const cursor = $CursorWeakMap.get(page);
3146
+ if (!cursor) {
3147
+ throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
3326
3148
  }
3327
- };
3328
- var checkElementVisibility = async (element) => {
3329
- return element.evaluate((el) => {
3330
- const targetStyle = window.getComputedStyle(el);
3331
- if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3332
- return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
3333
- }
3334
- const rect = el.getBoundingClientRect();
3335
- if (!rect || rect.width <= 0 || rect.height <= 0) {
3336
- return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6", direction: "down" };
3337
- }
3338
- const viewW = window.innerWidth;
3339
- const viewH = window.innerHeight;
3340
- const centerY = rect.top + rect.height / 2;
3341
- let clipLeft = 0;
3342
- let clipRight = viewW;
3343
- let clipTop = 0;
3344
- let clipBottom = viewH;
3345
- let isFixed = false;
3346
- let positioning = null;
3347
- for (let node = el; node && node !== document.body; node = node.parentElement) {
3348
- const style = window.getComputedStyle(node);
3349
- if (style && (style.position === "fixed" || style.position === "sticky")) {
3350
- isFixed = true;
3351
- positioning = style.position;
3352
- break;
3353
- }
3149
+ return cursor;
3150
+ }
3151
+ var Humanize = {
3152
+ /**
3153
+ * 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
3154
+ * @param {number} base - 基础延迟 (ms)
3155
+ * @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
3156
+ * @returns {number} 抖动后的毫秒数
3157
+ */
3158
+ jitterMs(base, jitterPercent = 0.3) {
3159
+ const jitter = base * jitterPercent * (Math.random() * 2 - 1);
3160
+ return Math.max(10, Math.round(base + jitter));
3161
+ },
3162
+ /**
3163
+ * 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
3164
+ *
3165
+ * @param {import('playwright').Page} page
3166
+ * @returns {Promise<void>}
3167
+ */
3168
+ async initializeCursor(page) {
3169
+ if ($CursorWeakMap.has(page)) {
3170
+ logger6.debug("initializeCursor: cursor already exists, skipping");
3171
+ return;
3354
3172
  }
3355
- for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
3356
- const style = window.getComputedStyle(node);
3357
- if (!style) continue;
3358
- const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
3359
- const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
3360
- if (!clipsX && !clipsY) continue;
3361
- const nodeRect = node.getBoundingClientRect();
3362
- if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
3363
- if (clipsX) {
3364
- clipLeft = Math.max(clipLeft, nodeRect.left);
3365
- clipRight = Math.min(clipRight, nodeRect.right);
3366
- }
3367
- if (clipsY) {
3368
- clipTop = Math.max(clipTop, nodeRect.top);
3369
- clipBottom = Math.min(clipBottom, nodeRect.bottom);
3173
+ logger6.start("initializeCursor", "creating cursor");
3174
+ const cursor = await (0, import_ghost_cursor_playwright.createCursor)(page);
3175
+ $CursorWeakMap.set(page, cursor);
3176
+ logger6.success("initializeCursor", "cursor initialized");
3177
+ },
3178
+ /**
3179
+ * 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
3180
+ *
3181
+ * @param {import('playwright').Page} page
3182
+ * @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
3183
+ */
3184
+ async humanMove(page, target) {
3185
+ const cursor = $GetCursor(page);
3186
+ logger6.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
3187
+ try {
3188
+ if (typeof target === "string") {
3189
+ const element = await page.$(target);
3190
+ if (!element) {
3191
+ logger6.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
3192
+ return false;
3193
+ }
3194
+ const box = await element.boundingBox();
3195
+ if (!box) {
3196
+ logger6.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
3197
+ return false;
3198
+ }
3199
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
3200
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
3201
+ await cursor.actions.move({ x, y });
3202
+ } else if (target && typeof target.x === "number" && typeof target.y === "number") {
3203
+ await cursor.actions.move(target);
3204
+ } else if (target && typeof target.boundingBox === "function") {
3205
+ const box = await target.boundingBox();
3206
+ if (box) {
3207
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
3208
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
3209
+ await cursor.actions.move({ x, y });
3210
+ }
3370
3211
  }
3212
+ logger6.success("humanMove");
3213
+ return true;
3214
+ } catch (error) {
3215
+ logger6.fail("humanMove", error);
3216
+ throw error;
3371
3217
  }
3372
- const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
3373
- const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
3374
- const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
3375
- const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
3376
- const visibleWidth = visibleRight - visibleLeft;
3377
- const visibleHeight = visibleBottom - visibleTop;
3378
- const cx = visibleLeft + visibleWidth / 2;
3379
- const cy = visibleTop + visibleHeight / 2;
3380
- if (visibleWidth <= 1 || visibleHeight <= 1) {
3381
- return {
3382
- code: "OUT_OF_VIEWPORT",
3383
- reason: "\u4E0D\u5728\u89C6\u53E3\u5185",
3384
- direction: centerY < clipTop ? "up" : "down",
3385
- cy: centerY,
3386
- viewH,
3387
- isFixed,
3388
- positioning
3389
- };
3390
- }
3391
- const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3392
- const commonAncestor = (a, b) => {
3393
- for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3394
- if (current.contains(b)) return current;
3395
- }
3396
- return null;
3397
- };
3398
- const sameTapTarget = (pointElement) => {
3399
- if (!pointElement) return false;
3400
- if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3401
- return true;
3402
- }
3403
- const common = commonAncestor(el, pointElement);
3404
- if (!common) return false;
3405
- const commonRect = common.getBoundingClientRect?.();
3406
- if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3407
- const commonArea = commonRect.width * commonRect.height;
3408
- const targetArea = Math.max(1, rect.width * rect.height);
3409
- const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3410
- if (commonArea > maxSharedRegionArea) return false;
3411
- if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3412
- return false;
3413
- }
3414
- return common.contains(el) && common.contains(pointElement);
3415
- };
3416
- const describeElement = (node) => {
3417
- if (!node) return null;
3418
- const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
3419
- const rect2 = node.getBoundingClientRect?.();
3420
- const style = window.getComputedStyle(node);
3421
- return {
3422
- tag: node.tagName,
3423
- id: node.id || "",
3424
- className,
3425
- isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
3426
- positioning: style?.position || "",
3427
- top: rect2 ? rect2.top : null,
3428
- bottom: rect2 ? rect2.bottom : null,
3429
- left: rect2 ? rect2.left : null,
3430
- right: rect2 ? rect2.right : null
3431
- };
3432
- };
3433
- const samplePoints = [
3434
- { x: cx, y: cy },
3435
- { x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3436
- { x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3437
- { x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
3438
- { x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
3439
- ];
3440
- let obstruction = null;
3441
- for (const point of samplePoints) {
3442
- const pointElement = document.elementFromPoint(point.x, point.y);
3443
- if (sameTapTarget(pointElement)) {
3444
- return { code: "VISIBLE", isFixed, positioning };
3445
- }
3446
- obstruction = obstruction || describeElement(pointElement);
3447
- }
3448
- if (obstruction) {
3449
- return {
3450
- code: "OBSTRUCTED",
3451
- reason: "\u88AB\u906E\u6321",
3452
- direction: cy > viewH / 2 ? "down" : "up",
3453
- obstruction,
3454
- cy,
3455
- viewH,
3456
- isFixed,
3457
- positioning
3458
- };
3459
- }
3460
- return { code: "VISIBLE", isFixed, positioning };
3461
- });
3462
- };
3463
- var activateElementFallback = async (element, point = null, options = {}) => {
3464
- return element.evaluate((el, { innerOptions }) => {
3465
- const isEditable = (node) => {
3466
- if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3467
- if (node.isContentEditable) return true;
3468
- if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3469
- if (node instanceof HTMLInputElement) {
3470
- return !node.disabled && !node.readOnly && typeof node.select === "function";
3471
- }
3472
- return false;
3473
- };
3474
- const findEditable = (node) => {
3475
- for (let current = node; current && current !== document.body; current = current.parentElement) {
3476
- if (isEditable(current)) return current;
3477
- }
3478
- return null;
3479
- };
3480
- const editable = findEditable(el);
3481
- if (editable && typeof editable.focus === "function") {
3482
- editable.focus({ preventScroll: true });
3483
- if (innerOptions.editableOnly) {
3484
- return { activated: true, method: "focus", tag: editable.tagName || "" };
3218
+ },
3219
+ /**
3220
+ * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
3221
+ * 返回 restore 方法,用于将滚动容器恢复到原位置
3222
+ *
3223
+ * @param {import('playwright').Page} page
3224
+ * @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
3225
+ * @param {Object} [options]
3226
+ * @param {number} [options.maxSteps=20] - 最大滚动步数
3227
+ * @param {number} [options.minStep=260] - 单次滚动最小步长
3228
+ * @param {number} [options.maxStep=800] - 单次滚动最大步长
3229
+ * @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
3230
+ */
3231
+ async humanScroll(page, target, options = {}) {
3232
+ const {
3233
+ maxSteps = 20,
3234
+ minStep = 260,
3235
+ maxStep = 800,
3236
+ maxDurationMs = maxSteps * 220 + 800
3237
+ } = options;
3238
+ const targetDesc = typeof target === "string" ? target : "ElementHandle";
3239
+ logger6.start("humanScroll", `target=${targetDesc}`);
3240
+ let element;
3241
+ if (typeof target === "string") {
3242
+ element = await page.$(target);
3243
+ if (!element) {
3244
+ logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
3245
+ return { element: null, didScroll: false };
3485
3246
  }
3247
+ } else {
3248
+ element = target;
3486
3249
  }
3487
- if (innerOptions.editableOnly) {
3488
- return { activated: false, method: "none", tag: el?.tagName || "" };
3489
- }
3490
- if (typeof el.focus === "function") {
3491
- el.focus({ preventScroll: true });
3492
- }
3493
- if (typeof el.click === "function") {
3494
- el.click();
3495
- return { activated: true, method: "dom-click", tag: el.tagName || "" };
3496
- }
3497
- if (typeof el.dispatchEvent === "function") {
3498
- el.dispatchEvent(new MouseEvent("click", {
3499
- bubbles: true,
3500
- cancelable: true,
3501
- view: window
3502
- }));
3503
- return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
3504
- }
3505
- return {
3506
- activated: Boolean(editable),
3507
- method: editable ? "focus" : "none",
3508
- tag: el?.tagName || ""
3509
- };
3510
- }, {
3511
- innerOptions: options || {}
3512
- });
3513
- };
3514
- var getScrollableRect = async (element) => {
3515
- return element.evaluate((el) => {
3516
- const isScrollable = (node) => {
3517
- const style = window.getComputedStyle(node);
3518
- if (!style) return false;
3519
- const overflowY = style.overflowY;
3520
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3521
- return node.scrollHeight > node.clientHeight + 1;
3522
- };
3523
- let current = el;
3524
- while (current && current !== document.body) {
3525
- if (isScrollable(current)) {
3526
- const rect = current.getBoundingClientRect();
3527
- if (rect && rect.width > 0 && rect.height > 0) {
3250
+ const cursor = $GetCursor(page);
3251
+ let didScroll = false;
3252
+ const checkVisibility = async () => {
3253
+ return await element.evaluate((el) => {
3254
+ const rect = el.getBoundingClientRect();
3255
+ if (!rect || rect.width === 0 || rect.height === 0) {
3256
+ return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
3257
+ }
3258
+ const cx = rect.left + rect.width / 2;
3259
+ const cy = rect.top + rect.height / 2;
3260
+ const viewH = window.innerHeight;
3261
+ const viewW = window.innerWidth;
3262
+ let isFixed = false;
3263
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
3264
+ const style = window.getComputedStyle(node);
3265
+ if (style && style.position === "fixed") {
3266
+ isFixed = true;
3267
+ break;
3268
+ }
3269
+ }
3270
+ if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
3271
+ const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
3272
+ return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH, isFixed };
3273
+ }
3274
+ const pointElement = document.elementFromPoint(cx, cy);
3275
+ if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
3528
3276
  return {
3529
- x: rect.x,
3530
- y: rect.y,
3531
- width: rect.width,
3532
- height: rect.height
3277
+ code: "OBSTRUCTED",
3278
+ reason: "\u88AB\u906E\u6321",
3279
+ obstruction: {
3280
+ tag: pointElement.tagName,
3281
+ id: pointElement.id,
3282
+ className: pointElement.className
3283
+ },
3284
+ cy,
3285
+ // Return Center Y for smart direction calculation
3286
+ viewH,
3287
+ isFixed
3533
3288
  };
3534
3289
  }
3535
- }
3536
- current = current.parentElement;
3537
- }
3538
- return null;
3539
- });
3540
- };
3541
- var scrollScrollableAncestor = async (element, deltaY) => {
3542
- return element.evaluate((el, amount) => {
3543
- const isScrollable = (node) => {
3544
- const style = window.getComputedStyle(node);
3545
- if (!style) return false;
3546
- const overflowY = style.overflowY;
3547
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3548
- return node.scrollHeight > node.clientHeight + 1;
3290
+ return { code: "VISIBLE", isFixed };
3291
+ });
3549
3292
  };
3550
- let current = el;
3551
- while (current && current !== document.body) {
3552
- if (isScrollable(current)) {
3553
- const beforeTop2 = current.scrollTop;
3554
- current.scrollTop = beforeTop2 + amount;
3555
- return {
3556
- scroller: true,
3557
- moved: current.scrollTop !== beforeTop2,
3558
- scrollTop: current.scrollTop
3293
+ const getScrollableRect2 = async () => {
3294
+ return await element.evaluate((el) => {
3295
+ const isScrollable = (node) => {
3296
+ const style = window.getComputedStyle(node);
3297
+ if (!style) return false;
3298
+ const overflowY = style.overflowY;
3299
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3300
+ return node.scrollHeight > node.clientHeight + 1;
3559
3301
  };
3560
- }
3561
- current = current.parentElement;
3562
- }
3563
- const beforeTop = window.scrollY;
3564
- window.scrollBy(0, amount);
3565
- return {
3566
- scroller: null,
3567
- moved: window.scrollY !== beforeTop,
3568
- scrollTop: window.scrollY
3569
- };
3570
- }, deltaY);
3571
- };
3572
- var scrollAwayFromObstruction = async (element, status) => {
3573
- return element.evaluate((el, innerStatus) => {
3574
- const isScrollable = (node) => {
3575
- const style = window.getComputedStyle(node);
3576
- if (!style) return false;
3577
- const overflowY = style.overflowY;
3578
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3579
- return node.scrollHeight > node.clientHeight + 1;
3580
- };
3581
- let scroller = el;
3582
- while (scroller && scroller !== document.body) {
3583
- if (isScrollable(scroller)) break;
3584
- scroller = scroller.parentElement;
3585
- }
3586
- if (!scroller || scroller === document.body) {
3587
- return { moved: false, scrollTop: 0, deltaY: 0 };
3588
- }
3589
- const rect = el.getBoundingClientRect();
3590
- const scrollerRect = scroller.getBoundingClientRect();
3591
- const obstruction = innerStatus?.obstruction || {};
3592
- const obstructionTop = Number(obstruction.top);
3593
- const obstructionBottom = Number(obstruction.bottom);
3594
- const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
3595
- const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
3596
- const padding = 18;
3597
- let deltaY = 0;
3598
- if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
3599
- deltaY = rect.bottom - obstructionTop + padding;
3600
- } else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
3601
- deltaY = rect.top - obstructionBottom - padding;
3602
- } else {
3603
- const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
3604
- deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
3605
- }
3606
- const beforeTop = scroller.scrollTop;
3607
- scroller.scrollTop = beforeTop + deltaY;
3608
- return {
3609
- moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
3610
- scrollTop: scroller.scrollTop,
3611
- deltaY
3612
- };
3613
- }, status);
3614
- };
3615
- var getElementViewportSnapshot = async (element) => {
3616
- return element.evaluate((el) => {
3617
- const rect = el.getBoundingClientRect();
3618
- return {
3619
- top: rect.top,
3620
- bottom: rect.bottom,
3621
- left: rect.left,
3622
- right: rect.right,
3623
- width: rect.width,
3624
- height: rect.height,
3625
- scrollX: window.scrollX,
3626
- scrollY: window.scrollY
3302
+ let current = el;
3303
+ while (current && current !== document.body) {
3304
+ if (isScrollable(current)) {
3305
+ const rect = current.getBoundingClientRect();
3306
+ if (rect && rect.width > 0 && rect.height > 0) {
3307
+ return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
3308
+ }
3309
+ }
3310
+ current = current.parentElement;
3311
+ }
3312
+ return null;
3313
+ });
3627
3314
  };
3628
- });
3629
- };
3630
- var isTargetImmobileAfterScroll = (before, after) => {
3631
- if (!before || !after) return false;
3632
- const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
3633
- const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
3634
- const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
3635
- const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
3636
- const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
3637
- const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
3638
- if (!rectMoved && !pageMoved) return true;
3639
- if (pageMoved && !rectMoved) return true;
3640
- if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
3641
- return true;
3642
- }
3643
- return false;
3644
- };
3645
- var restoreWindowFromSnapshot = async (page, before, after) => {
3646
- if (!before || !after) return;
3647
- if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
3648
- return;
3649
- }
3650
- await page.evaluate(
3651
- (state2) => window.scrollTo(state2.x, state2.y),
3652
- { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
3653
- ).catch(() => {
3654
- });
3655
- };
3656
- var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
3657
- const viewport = resolveViewport(page);
3658
- const rawRect = options.rect || null;
3659
- const rect = rawRect ? {
3660
- x: clamp(rawRect.x, 0, viewport.width),
3661
- y: clamp(rawRect.y, 0, viewport.height),
3662
- width: clamp(rawRect.width, 0, viewport.width),
3663
- height: clamp(rawRect.height, 0, viewport.height)
3664
- } : null;
3665
- const area = rect && rect.width > 24 && rect.height > 48 ? {
3666
- left: rect.x,
3667
- right: Math.min(viewport.width, rect.x + rect.width),
3668
- top: rect.y,
3669
- bottom: Math.min(viewport.height, rect.y + rect.height)
3670
- } : {
3671
- left: 0,
3672
- right: viewport.width,
3673
- top: 0,
3674
- bottom: viewport.height
3675
- };
3676
- const areaWidth = Math.max(1, area.right - area.left);
3677
- const areaHeight = Math.max(1, area.bottom - area.top);
3678
- const maxDistance = rect ? Math.max(60, areaHeight * 0.72) : Math.max(120, viewport.height * 0.72);
3679
- const distance = clamp(Math.abs(deltaY), Math.min(80, maxDistance), maxDistance);
3680
- const direction = deltaY >= 0 ? 1 : -1;
3681
- const startX = clamp(
3682
- area.left + areaWidth * (0.45 + Math.random() * 0.1),
3683
- 24,
3684
- viewport.width - 24
3685
- );
3686
- const startY = direction > 0 ? clamp(area.top + areaHeight * (0.72 + Math.random() * 0.1), area.top + 24, area.bottom - 16) : clamp(area.top + areaHeight * (0.28 + Math.random() * 0.1), area.top + 16, area.bottom - 24);
3687
- const endY = clamp(startY - direction * distance, Math.max(16, area.top + 12), Math.min(viewport.height - 16, area.bottom - 12));
3688
- const steps = Math.max(4, Math.round(Number(options.steps || 6)));
3689
- const durationMs = jitterMs(options.durationMs || 320, 0.35);
3690
- let client = null;
3691
- const scrollBefore = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3692
- try {
3693
- if (page.context && typeof page.context().newCDPSession === "function") {
3694
- client = await page.context().newCDPSession(page);
3315
+ const startTime = Date.now();
3316
+ try {
3317
+ for (let i = 0; i < maxSteps; i++) {
3318
+ if (Date.now() - startTime > maxDurationMs) {
3319
+ logger6.warn(`humanScroll | \u8D85\u65F6\u4FDD\u62A4\u89E6\u53D1 (${maxDurationMs}ms)`);
3320
+ return { element, didScroll };
3321
+ }
3322
+ const status = await checkVisibility();
3323
+ if (status.code === "VISIBLE") {
3324
+ if (status.isFixed) {
3325
+ logger6.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3326
+ } else {
3327
+ logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
3328
+ }
3329
+ logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3330
+ return { element, didScroll };
3331
+ }
3332
+ logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
3333
+ if (status.code === "OBSTRUCTED" && status.obstruction) {
3334
+ logger6.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
3335
+ }
3336
+ const scrollRect = await getScrollableRect2();
3337
+ if (!scrollRect && status.isFixed) {
3338
+ logger6.warn("humanScroll | fixed \u5BB9\u5668\u5185\u4E14\u65E0\u53EF\u6EDA\u52A8\u7956\u5148\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3339
+ return { element, didScroll };
3340
+ }
3341
+ const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
3342
+ const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
3343
+ let deltaY = 0;
3344
+ if (status.code === "OUT_OF_VIEWPORT") {
3345
+ if (status.direction === "down") {
3346
+ deltaY = stepMin + Math.random() * (stepMax - stepMin);
3347
+ } else if (status.direction === "up") {
3348
+ deltaY = -(stepMin + Math.random() * (stepMax - stepMin));
3349
+ } else {
3350
+ deltaY = 100;
3351
+ }
3352
+ } else if (status.code === "OBSTRUCTED") {
3353
+ const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3354
+ const isBottomHalf = status.cy > halfY;
3355
+ const direction = isBottomHalf ? 1 : -1;
3356
+ deltaY = direction * (stepMin + Math.random() * 50);
3357
+ }
3358
+ if (i === 0) {
3359
+ const viewSize = page.viewportSize();
3360
+ if (scrollRect) {
3361
+ const safeX = scrollRect.x + scrollRect.width * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.width * 0.4);
3362
+ const safeY = scrollRect.y + scrollRect.height * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.height * 0.4);
3363
+ await cursor.actions.move({ x: safeX, y: safeY });
3364
+ } else if (viewSize) {
3365
+ const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 80;
3366
+ const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 80;
3367
+ await cursor.actions.move({ x: safeX, y: safeY });
3368
+ }
3369
+ }
3370
+ await page.mouse.wheel(0, deltaY);
3371
+ didScroll = true;
3372
+ await (0, import_delay2.default)(this.jitterMs(20 + Math.random() * 40, 0.2));
3373
+ }
3374
+ logger6.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
3375
+ return { element, didScroll };
3376
+ } catch (error) {
3377
+ logger6.fail("humanScroll", error);
3378
+ throw error;
3695
3379
  }
3696
- if (!client) throw new Error("CDP session unavailable");
3697
- await client.send("Input.synthesizeScrollGesture", {
3698
- x: startX,
3699
- y: startY,
3700
- yDistance: -direction * distance,
3701
- xOverscroll: (Math.random() - 0.5) * 4,
3702
- yOverscroll: (Math.random() - 0.5) * 8,
3703
- speed: 700 + Math.round(Math.random() * 350),
3704
- gestureSourceType: "touch"
3705
- });
3706
- await waitJitter(160, 0.35);
3707
- const scrollAfterSynth = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3708
- if (scrollBefore && scrollAfterSynth && (Math.abs(scrollAfterSynth.y - scrollBefore.y) > 2 || Math.abs(scrollAfterSynth.x - scrollBefore.x) > 2)) {
3380
+ },
3381
+ /**
3382
+ * 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
3383
+ *
3384
+ * @param {import('playwright').Page} page
3385
+ * @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
3386
+ * @param {Object} [options]
3387
+ * @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
3388
+ * @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
3389
+ * @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
3390
+ */
3391
+ async humanClick(page, target, options = {}) {
3392
+ const cursor = $GetCursor(page);
3393
+ const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
3394
+ const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
3395
+ logger6.start("humanClick", `target=${targetDesc}`);
3396
+ const restoreOnce = async () => {
3397
+ if (restoreOnce.restored) return;
3398
+ restoreOnce.restored = true;
3399
+ if (typeof restoreOnce.do !== "function") return;
3400
+ try {
3401
+ await (0, import_delay2.default)(this.jitterMs(1e3));
3402
+ await restoreOnce.do();
3403
+ } catch (restoreError) {
3404
+ logger6.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
3405
+ }
3406
+ };
3407
+ try {
3408
+ if (target == null) {
3409
+ await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
3410
+ await cursor.actions.click();
3411
+ logger6.success("humanClick", "Clicked current position");
3412
+ return true;
3413
+ }
3414
+ let element;
3415
+ if (typeof target === "string") {
3416
+ element = await page.$(target);
3417
+ if (!element) {
3418
+ if (throwOnMissing) {
3419
+ throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
3420
+ }
3421
+ logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
3422
+ return false;
3423
+ }
3424
+ } else {
3425
+ element = target;
3426
+ }
3427
+ if (scrollIfNeeded) {
3428
+ const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
3429
+ restoreOnce.do = didScroll && restore ? restoreFn : null;
3430
+ }
3431
+ const box = await element.boundingBox();
3432
+ if (!box) {
3433
+ await restoreOnce();
3434
+ if (throwOnMissing) {
3435
+ throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
3436
+ }
3437
+ logger6.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
3438
+ return false;
3439
+ }
3440
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
3441
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
3442
+ await cursor.actions.move({ x, y });
3443
+ await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
3444
+ await cursor.actions.click();
3445
+ await restoreOnce();
3446
+ logger6.success("humanClick");
3709
3447
  return true;
3448
+ } catch (error) {
3449
+ await restoreOnce();
3450
+ logger6.fail("humanClick", error);
3451
+ throw error;
3710
3452
  }
3711
- await client.send("Input.dispatchTouchEvent", {
3712
- type: "touchStart",
3713
- touchPoints: [{ x: startX, y: startY, id: 1 }]
3714
- });
3715
- for (let i = 1; i <= steps; i += 1) {
3716
- const progress = i / steps;
3717
- const eased = 1 - Math.pow(1 - progress, 2);
3718
- const x = startX + (Math.random() - 0.5) * 3;
3719
- const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 5;
3720
- await waitJitter(durationMs / steps, 0.25);
3721
- await client.send("Input.dispatchTouchEvent", {
3722
- type: "touchMove",
3723
- touchPoints: [{ x, y, id: 1 }]
3724
- });
3725
- }
3726
- await client.send("Input.dispatchTouchEvent", {
3727
- type: "touchEnd",
3728
- touchPoints: []
3729
- });
3730
- await waitJitter(120, 0.35);
3731
- const scrollAfterTouch = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3732
- if (scrollBefore && scrollAfterTouch && Math.abs(scrollAfterTouch.y - scrollBefore.y) <= 2 && Math.abs(scrollAfterTouch.x - scrollBefore.x) <= 2) {
3733
- await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
3734
- await waitJitter(120, 0.35);
3453
+ },
3454
+ /**
3455
+ * 随机延迟一段毫秒数(带 ±30% 抖动)
3456
+ * @param {number} baseMs - 基础延迟毫秒数
3457
+ * @param {number} [jitterPercent=0.3] - 抖动百分比
3458
+ */
3459
+ async randomSleep(baseMs, jitterPercent = 0.3) {
3460
+ const ms = this.jitterMs(baseMs, jitterPercent);
3461
+ logger6.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
3462
+ await (0, import_delay2.default)(ms);
3463
+ logger6.success("randomSleep");
3464
+ },
3465
+ /**
3466
+ * 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
3467
+ * @param {import('playwright').Page} page
3468
+ * @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
3469
+ */
3470
+ async simulateGaze(page, baseDurationMs = 2500) {
3471
+ const cursor = $GetCursor(page);
3472
+ const durationMs = this.jitterMs(baseDurationMs, 0.4);
3473
+ logger6.start("simulateGaze", `duration=${durationMs}ms`);
3474
+ const startTime = Date.now();
3475
+ const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
3476
+ while (Date.now() - startTime < durationMs) {
3477
+ const x = 100 + Math.random() * (viewportSize.width - 200);
3478
+ const y = 100 + Math.random() * (viewportSize.height - 200);
3479
+ await cursor.actions.move({ x, y });
3480
+ await (0, import_delay2.default)(this.jitterMs(600, 0.5));
3735
3481
  }
3736
- return true;
3737
- } catch (error) {
3738
- logger5.debug(`touch swipe fallback: ${error?.message || error}`);
3482
+ logger6.success("simulateGaze");
3483
+ },
3484
+ /**
3485
+ * 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
3486
+ * @param {import('playwright').Page} page
3487
+ * @param {string} selector - 输入框选择器
3488
+ * @param {string} text - 要输入的文本
3489
+ * @param {Object} [options]
3490
+ * @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
3491
+ * @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
3492
+ * @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
3493
+ */
3494
+ async humanType(page, selector, text, options = {}) {
3495
+ logger6.start("humanType", `selector=${selector}, textLen=${text.length}`);
3496
+ const {
3497
+ baseDelay = 180,
3498
+ pauseProbability = 0.08,
3499
+ pauseBase = 800
3500
+ } = options;
3739
3501
  try {
3740
- await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
3741
- await waitJitter(120, 0.35);
3742
- return true;
3743
- } catch {
3744
- await page.mouse.wheel(0, deltaY);
3745
- await waitJitter(120, 0.35);
3746
- return true;
3747
- }
3748
- } finally {
3749
- if (client && typeof client.detach === "function") {
3750
- try {
3751
- await client.detach();
3752
- } catch {
3502
+ const locator = page.locator(selector);
3503
+ await Humanize.humanClick(page, locator);
3504
+ await (0, import_delay2.default)(this.jitterMs(200, 0.4));
3505
+ for (let i = 0; i < text.length; i++) {
3506
+ const char = text[i];
3507
+ let charDelay;
3508
+ if (char === " ") {
3509
+ charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
3510
+ } else if (/[,.!?;:,。!?;:]/.test(char)) {
3511
+ charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
3512
+ } else {
3513
+ charDelay = this.jitterMs(baseDelay, 0.4);
3514
+ }
3515
+ await page.keyboard.type(char);
3516
+ await (0, import_delay2.default)(charDelay);
3517
+ if (Math.random() < pauseProbability && i < text.length - 1) {
3518
+ const pauseTime = this.jitterMs(pauseBase, 0.5);
3519
+ logger6.debug(`\u505C\u987F ${pauseTime}ms...`);
3520
+ await (0, import_delay2.default)(pauseTime);
3521
+ }
3753
3522
  }
3523
+ logger6.success("humanType");
3524
+ } catch (error) {
3525
+ logger6.fail("humanType", error);
3526
+ throw error;
3754
3527
  }
3755
- }
3756
- };
3757
- var tapPoint = async (page, point, options = {}) => {
3758
- const {
3759
- timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
3760
- mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
3761
- allowMouseFallback = true
3762
- } = options;
3763
- if (page.touchscreen && typeof page.touchscreen.tap === "function") {
3764
- try {
3765
- await withTimeout(
3766
- () => page.touchscreen.tap(point.x, point.y),
3767
- timeoutMs,
3768
- "touchscreen.tap"
3769
- );
3770
- return { method: "touchscreen" };
3528
+ },
3529
+ /**
3530
+ * 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
3531
+ * @param {import('playwright').Page} page
3532
+ * @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
3533
+ * @param {string|Object} [maybeKey] - 按键或选项
3534
+ * @param {Object} [options]
3535
+ */
3536
+ async humanPress(page, targetOrKey, maybeKey, options = {}) {
3537
+ const hasTarget = typeof maybeKey === "string";
3538
+ const key = hasTarget ? maybeKey : targetOrKey;
3539
+ const pressOptions = hasTarget ? options : maybeKey || options;
3540
+ const {
3541
+ reactionDelay = 180,
3542
+ holdDelay = 45,
3543
+ focusDelay = 180,
3544
+ scrollIfNeeded = true,
3545
+ throwOnMissing = true,
3546
+ keyboardOptions = {}
3547
+ } = pressOptions || {};
3548
+ const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
3549
+ logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
3550
+ try {
3551
+ if (hasTarget) {
3552
+ await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
3553
+ }
3554
+ await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.45));
3555
+ await page.keyboard.press(key, {
3556
+ ...keyboardOptions,
3557
+ delay: this.jitterMs(holdDelay, 0.5)
3558
+ });
3559
+ logger6.success("humanPress");
3560
+ return true;
3771
3561
  } catch (error) {
3772
- logger5.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
3773
- if (!allowMouseFallback) throw error;
3562
+ logger6.fail("humanPress", error);
3563
+ throw error;
3774
3564
  }
3775
- }
3776
- await withTimeout(
3777
- () => page.mouse.click(point.x, point.y),
3778
- mouseFallbackTimeoutMs,
3779
- "mouse.click fallback"
3780
- );
3781
- return { method: "mouse" };
3782
- };
3783
- var MobileHumanize = {
3784
- jitterMs,
3785
- async initializeCursor(page) {
3786
- if (initializedPages.has(page)) return;
3787
- initializedPages.add(page);
3788
- logger5.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
3789
3565
  },
3790
- async humanMove(page, target) {
3791
- logger5.debug(`humanMove: mobile no-op target=${typeof target === "string" ? target : "element/coords"}`);
3792
- if (typeof target === "string" || target && typeof target.boundingBox === "function") {
3793
- const element = await resolveElement(page, target, { throwOnMissing: false });
3794
- if (!element) {
3795
- return false;
3566
+ /**
3567
+ * 人类化清空输入框 - 模拟人类删除文本的行为
3568
+ * @param {import('playwright').Page} page
3569
+ * @param {string} selector - 输入框选择器
3570
+ */
3571
+ async humanClear(page, selector) {
3572
+ logger6.start("humanClear", `selector=${selector}`);
3573
+ try {
3574
+ const locator = page.locator(selector);
3575
+ await locator.click();
3576
+ await (0, import_delay2.default)(this.jitterMs(200, 0.4));
3577
+ const currentValue = await locator.inputValue();
3578
+ if (!currentValue || currentValue.length === 0) {
3579
+ logger6.success("humanClear", "already empty");
3580
+ return;
3796
3581
  }
3582
+ await page.keyboard.press("Meta+A");
3583
+ await (0, import_delay2.default)(this.jitterMs(100, 0.4));
3584
+ await page.keyboard.press("Backspace");
3585
+ logger6.success("humanClear");
3586
+ } catch (error) {
3587
+ logger6.fail("humanClear", error);
3588
+ throw error;
3797
3589
  }
3798
- await waitJitter(80, 0.4);
3799
- return true;
3800
3590
  },
3801
- async humanScroll(page, target, options = {}) {
3802
- const {
3803
- maxSteps = 20,
3804
- minStep = 180,
3805
- maxStep = 520,
3806
- maxDurationMs = maxSteps * 280 + 1200,
3807
- throwOnMissing = false
3808
- } = options;
3809
- const targetDesc = describeTarget(target);
3810
- logger5.start("humanScroll", `target=${targetDesc}`);
3811
- const element = await resolveElement(page, target, { throwOnMissing });
3812
- if (!element) {
3813
- logger5.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
3814
- return { element: null, didScroll: false, restore: null };
3815
- }
3591
+ /**
3592
+ * 页面预热浏览 - 模拟人类进入页面后的探索行为
3593
+ * @param {import('playwright').Page} page
3594
+ * @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
3595
+ */
3596
+ async warmUpBrowsing(page, baseDuration = 3500) {
3597
+ const cursor = $GetCursor(page);
3598
+ const durationMs = this.jitterMs(baseDuration, 0.4);
3599
+ logger6.start("warmUpBrowsing", `duration=${durationMs}ms`);
3816
3600
  const startTime = Date.now();
3817
- let didScroll = false;
3818
- for (let i = 0; i < maxSteps; i += 1) {
3819
- const status = await checkElementVisibility(element);
3820
- if (status.code === "VISIBLE") {
3821
- if (status.isFixed) {
3822
- logger5.info("humanScroll | fixed/sticky \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3823
- } else {
3824
- logger5.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
3825
- }
3826
- logger5.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3827
- return { element, didScroll, restore: null };
3828
- }
3829
- if (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE") {
3830
- logger5.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
3831
- return { element, didScroll, restore: null };
3832
- }
3833
- const scrollRect = await getScrollableRect(element);
3834
- if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
3835
- logger5.warn(`humanScroll | fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (direction=${status.direction || "unknown"})`);
3836
- return { element, didScroll, restore: null, unscrollable: true };
3837
- }
3838
- if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
3839
- logger5.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
3840
- return { element, didScroll, restore: null, unscrollable: true };
3841
- }
3842
- if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
3843
- const moved = await scrollAwayFromObstruction(element, status);
3844
- if (moved.moved) {
3845
- logger5.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
3846
- await waitJitter(90, 0.3);
3847
- didScroll = true;
3848
- continue;
3849
- }
3850
- }
3851
- if (Date.now() - startTime > maxDurationMs) {
3852
- logger5.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
3853
- return { element, didScroll, restore: null };
3854
- }
3855
- const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
3856
- const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
3857
- logger5.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
3858
- const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
3859
- let deltaY = status.direction === "up" ? -distance : distance;
3860
- if (status.code === "OBSTRUCTED") {
3861
- if (status.obstruction?.isFixed && status.obstruction.top != null) {
3862
- const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
3863
- const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3864
- deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
3601
+ const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
3602
+ try {
3603
+ while (Date.now() - startTime < durationMs) {
3604
+ const action = Math.random();
3605
+ if (action < 0.4) {
3606
+ const x = 100 + Math.random() * (viewportSize.width - 200);
3607
+ const y = 100 + Math.random() * (viewportSize.height - 200);
3608
+ await cursor.actions.move({ x, y });
3609
+ await (0, import_delay2.default)(this.jitterMs(350, 0.4));
3610
+ } else if (action < 0.7) {
3611
+ const scrollY = (Math.random() - 0.5) * 200;
3612
+ await page.mouse.wheel(0, scrollY);
3613
+ await (0, import_delay2.default)(this.jitterMs(500, 0.4));
3865
3614
  } else {
3866
- const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3867
- deltaY = status.cy > halfY ? distance : -distance;
3615
+ await (0, import_delay2.default)(this.jitterMs(800, 0.5));
3868
3616
  }
3869
3617
  }
3870
- const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
3871
- const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3872
- const beforeState = scrollRect ? await element.evaluate((el) => {
3873
- const isScrollable = (node) => {
3874
- const style = window.getComputedStyle(node);
3875
- if (!style) return false;
3876
- const overflowY = style.overflowY;
3877
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3878
- return node.scrollHeight > node.clientHeight + 1;
3879
- };
3880
- let current = el;
3881
- while (current && current !== document.body) {
3882
- if (isScrollable(current)) {
3883
- return {
3884
- kind: "element",
3885
- top: current.scrollTop,
3886
- left: current.scrollLeft
3887
- };
3888
- }
3889
- current = current.parentElement;
3890
- }
3891
- return { kind: "window", top: window.scrollY, left: window.scrollX };
3892
- }) : null;
3893
- await dispatchTouchSwipe(page, deltaY, { rect: scrollRect });
3894
- if (scrollRect && beforeWindowState) {
3895
- const afterWindowState = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
3896
- if (Math.abs(afterWindowState.x - beforeWindowState.x) > 2 || Math.abs(afterWindowState.y - beforeWindowState.y) > 2) {
3897
- await page.evaluate((state2) => window.scrollTo(state2.x, state2.y), beforeWindowState);
3898
- logger5.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
3899
- }
3618
+ logger6.success("warmUpBrowsing");
3619
+ } catch (error) {
3620
+ logger6.fail("warmUpBrowsing", error);
3621
+ throw error;
3622
+ }
3623
+ },
3624
+ /**
3625
+ * 自然滚动 - 带惯性、减速效果和随机抖动
3626
+ * @param {import('playwright').Page} page
3627
+ * @param {'up' | 'down'} [direction='down'] - 滚动方向
3628
+ * @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
3629
+ * @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
3630
+ */
3631
+ async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
3632
+ const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
3633
+ const actualDistance = this.jitterMs(distance, 0.15);
3634
+ logger6.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
3635
+ const sign = direction === "down" ? 1 : -1;
3636
+ const stepDistance = actualDistance / steps;
3637
+ try {
3638
+ for (let i = 0; i < steps; i++) {
3639
+ const factor = 1 - i / steps * 0.5;
3640
+ const jitter = 0.9 + Math.random() * 0.2;
3641
+ const scrollAmount = stepDistance * factor * sign * jitter;
3642
+ await page.mouse.wheel(0, scrollAmount);
3643
+ const baseDelay = 60 + i * 25;
3644
+ await (0, import_delay2.default)(this.jitterMs(baseDelay, 0.3));
3900
3645
  }
3901
- let afterElementSnapshot = null;
3902
- const readAfterElementSnapshot = async () => {
3903
- if (!afterElementSnapshot) {
3904
- afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3905
- }
3906
- return afterElementSnapshot;
3907
- };
3908
- if (!scrollRect && beforeElementSnapshot) {
3909
- const afterSnapshot = await readAfterElementSnapshot();
3910
- if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
3911
- await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
3912
- logger5.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u9875\u9762\u6EDA\u52A8\u79FB\u52A8\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
3913
- return { element, didScroll, restore: null, unscrollable: true };
3914
- }
3646
+ logger6.success("naturalScroll");
3647
+ } catch (error) {
3648
+ logger6.fail("naturalScroll", error);
3649
+ throw error;
3650
+ }
3651
+ }
3652
+ };
3653
+
3654
+ // src/internals/humanize/shared.js
3655
+ var import_delay3 = __toESM(require("delay"), 1);
3656
+ var jitterMs = (base, jitterPercent = 0.3) => {
3657
+ const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
3658
+ return Math.max(10, Math.round(Number(base || 0) + jitter));
3659
+ };
3660
+ var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
3661
+ if (target == null) return null;
3662
+ let element = target;
3663
+ if (typeof target === "string") {
3664
+ element = await page.$(target);
3665
+ }
3666
+ if (!element) {
3667
+ if (throwOnMissing) {
3668
+ throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
3669
+ }
3670
+ return null;
3671
+ }
3672
+ return element;
3673
+ };
3674
+ var waitJitter = (base, jitterPercent = 0.3) => (0, import_delay3.default)(jitterMs(base, jitterPercent));
3675
+
3676
+ // src/internals/humanize/mobile.js
3677
+ var logger7 = createInternalLogger("Humanize.Mobile");
3678
+ var initializedPages = /* @__PURE__ */ new WeakSet();
3679
+ var DEFAULT_TAP_TIMEOUT_MS = 2500;
3680
+ var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3681
+ var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3682
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3683
+ var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3684
+ var describeTarget = (target) => {
3685
+ if (target == null) return "Current Position";
3686
+ return typeof target === "string" ? target : "ElementHandle";
3687
+ };
3688
+ var clipBoxToViewport = (box, viewport) => {
3689
+ if (!box || !viewport) return box;
3690
+ const left = clamp(box.x, 0, viewport.width);
3691
+ const top = clamp(box.y, 0, viewport.height);
3692
+ const right = clamp(box.x + box.width, 0, viewport.width);
3693
+ const bottom = clamp(box.y + box.height, 0, viewport.height);
3694
+ if (right - left > 1 && bottom - top > 1) {
3695
+ return {
3696
+ x: left,
3697
+ y: top,
3698
+ width: right - left,
3699
+ height: bottom - top
3700
+ };
3701
+ }
3702
+ return box;
3703
+ };
3704
+ var centerPointInBox = (box) => ({
3705
+ x: box.x + box.width / 2,
3706
+ y: box.y + box.height / 2
3707
+ });
3708
+ var withTimeout = async (operation, timeoutMs, label) => {
3709
+ const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3710
+ let timeoutId = null;
3711
+ try {
3712
+ return await Promise.race([
3713
+ Promise.resolve().then(operation),
3714
+ new Promise((_, reject) => {
3715
+ timeoutId = setTimeout(() => {
3716
+ reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
3717
+ }, safeTimeoutMs);
3718
+ })
3719
+ ]);
3720
+ } finally {
3721
+ if (timeoutId) clearTimeout(timeoutId);
3722
+ }
3723
+ };
3724
+ var checkElementVisibility = async (element) => {
3725
+ return element.evaluate((el) => {
3726
+ const targetStyle = window.getComputedStyle(el);
3727
+ if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3728
+ return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
3729
+ }
3730
+ const rect = el.getBoundingClientRect();
3731
+ if (!rect || rect.width <= 0 || rect.height <= 0) {
3732
+ return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6", direction: "down" };
3733
+ }
3734
+ const viewW = window.innerWidth;
3735
+ const viewH = window.innerHeight;
3736
+ const centerY = rect.top + rect.height / 2;
3737
+ let clipLeft = 0;
3738
+ let clipRight = viewW;
3739
+ let clipTop = 0;
3740
+ let clipBottom = viewH;
3741
+ let isFixed = false;
3742
+ let positioning = null;
3743
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
3744
+ const style = window.getComputedStyle(node);
3745
+ if (style && (style.position === "fixed" || style.position === "sticky")) {
3746
+ isFixed = true;
3747
+ positioning = style.position;
3748
+ break;
3915
3749
  }
3916
- if (scrollRect && beforeState) {
3917
- const afterState = await element.evaluate((el) => {
3918
- const isScrollable = (node) => {
3919
- const style = window.getComputedStyle(node);
3920
- if (!style) return false;
3921
- const overflowY = style.overflowY;
3922
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3923
- return node.scrollHeight > node.clientHeight + 1;
3924
- };
3925
- let current = el;
3926
- while (current && current !== document.body) {
3927
- if (isScrollable(current)) {
3928
- return {
3929
- kind: "element",
3930
- top: current.scrollTop,
3931
- left: current.scrollLeft
3932
- };
3933
- }
3934
- current = current.parentElement;
3935
- }
3936
- return { kind: "window", top: window.scrollY, left: window.scrollX };
3937
- });
3938
- const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
3939
- const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
3940
- const expectedDelta = Number(deltaY || 0);
3941
- const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
3942
- if (!moved) {
3943
- const fallback = await scrollScrollableAncestor(element, deltaY);
3944
- logger5.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u65E0\u6548\uFF0C\u76F4\u63A5\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
3945
- } else if (beforeState.kind === afterState.kind && Math.abs(expectedDelta) > 24 && Math.sign(topDelta || expectedDelta) === Math.sign(expectedDelta) && Math.abs(topDelta) < Math.min(Math.abs(expectedDelta) * 0.45, 96)) {
3946
- const residualDelta = expectedDelta - topDelta;
3947
- if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
3948
- const fallback = await scrollScrollableAncestor(element, residualDelta);
3949
- logger5.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u8DDD\u79BB\u4E0D\u8DB3\uFF0C\u8865\u507F\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
3950
- }
3951
- }
3750
+ }
3751
+ for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
3752
+ const style = window.getComputedStyle(node);
3753
+ if (!style) continue;
3754
+ const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
3755
+ const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
3756
+ if (!clipsX && !clipsY) continue;
3757
+ const nodeRect = node.getBoundingClientRect();
3758
+ if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
3759
+ if (clipsX) {
3760
+ clipLeft = Math.max(clipLeft, nodeRect.left);
3761
+ clipRight = Math.min(clipRight, nodeRect.right);
3952
3762
  }
3953
- if (scrollRect && beforeElementSnapshot) {
3954
- const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3955
- if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
3956
- await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
3957
- logger5.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u6EDA\u52A8\u5BB9\u5668\u79FB\u52A8\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
3958
- return { element, didScroll, restore: null, unscrollable: true };
3959
- }
3763
+ if (clipsY) {
3764
+ clipTop = Math.max(clipTop, nodeRect.top);
3765
+ clipBottom = Math.min(clipBottom, nodeRect.bottom);
3960
3766
  }
3961
- didScroll = true;
3962
3767
  }
3963
- try {
3964
- await element.scrollIntoViewIfNeeded?.();
3965
- await waitJitter(80, 0.3);
3966
- const finalStatus = await checkElementVisibility(element);
3967
- if (finalStatus.code === "VISIBLE") {
3968
- logger5.info("humanScroll | \u539F\u751F scrollIntoViewIfNeeded \u515C\u5E95\u6210\u529F");
3969
- logger5.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3970
- return { element, didScroll: true, restore: null };
3971
- }
3972
- } catch (fallbackError) {
3973
- logger5.debug(`humanScroll | native fallback failed: ${fallbackError?.message || fallbackError}`);
3768
+ const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
3769
+ const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
3770
+ const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
3771
+ const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
3772
+ const visibleWidth = visibleRight - visibleLeft;
3773
+ const visibleHeight = visibleBottom - visibleTop;
3774
+ const cx = visibleLeft + visibleWidth / 2;
3775
+ const cy = visibleTop + visibleHeight / 2;
3776
+ if (visibleWidth <= 1 || visibleHeight <= 1) {
3777
+ return {
3778
+ code: "OUT_OF_VIEWPORT",
3779
+ reason: "\u4E0D\u5728\u89C6\u53E3\u5185",
3780
+ direction: centerY < clipTop ? "up" : "down",
3781
+ cy: centerY,
3782
+ viewH,
3783
+ isFixed,
3784
+ positioning
3785
+ };
3974
3786
  }
3975
- logger5.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
3976
- return { element, didScroll, restore: null };
3977
- },
3978
- async humanClick(page, target, options = {}) {
3979
- const {
3980
- reactionDelay = 220,
3981
- throwOnMissing = true,
3982
- scrollIfNeeded = true,
3983
- tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
3984
- mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
3985
- activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
3986
- fallbackDomClick = true,
3987
- fallbackDomClickOnTapError = true
3988
- } = options;
3989
- const targetDesc = describeTarget(target);
3990
- logger5.start("humanClick", `target=${targetDesc}`);
3991
- try {
3992
- if (target == null) {
3993
- const viewport = resolveViewport(page);
3994
- await waitJitter(reactionDelay, 0.45);
3995
- await tapPoint(page, {
3996
- x: viewport.width * (0.45 + Math.random() * 0.1),
3997
- y: viewport.height * (0.48 + Math.random() * 0.12)
3998
- }, {
3999
- timeoutMs: tapTimeoutMs,
4000
- mouseFallbackTimeoutMs
4001
- });
4002
- logger5.success("humanClick", "Tapped current position");
3787
+ const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3788
+ const commonAncestor = (a, b) => {
3789
+ for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3790
+ if (current.contains(b)) return current;
3791
+ }
3792
+ return null;
3793
+ };
3794
+ const sameTapTarget = (pointElement) => {
3795
+ if (!pointElement) return false;
3796
+ if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
4003
3797
  return true;
4004
3798
  }
4005
- const element = await resolveElement(page, target, { throwOnMissing });
4006
- if (!element) {
4007
- logger5.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
3799
+ const common = commonAncestor(el, pointElement);
3800
+ if (!common) return false;
3801
+ const commonRect = common.getBoundingClientRect?.();
3802
+ if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3803
+ const commonArea = commonRect.width * commonRect.height;
3804
+ const targetArea = Math.max(1, rect.width * rect.height);
3805
+ const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3806
+ if (commonArea > maxSharedRegionArea) return false;
3807
+ if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
4008
3808
  return false;
4009
3809
  }
4010
- const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
4011
- const status = await checkElementVisibility(element).catch(() => null);
4012
- if (status && status.code !== "VISIBLE") {
4013
- if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
4014
- let fallback = await withTimeout(
4015
- () => activateElementFallback(element, null, {
4016
- editableOnly: true
4017
- }),
4018
- activateFallbackTimeoutMs,
4019
- "focus fallback"
4020
- ).catch(() => null);
4021
- if (!fallback?.activated) {
4022
- fallback = await withTimeout(
4023
- () => activateElementFallback(element, null, {
4024
- editableOnly: false
4025
- }),
4026
- activateFallbackTimeoutMs,
4027
- "activation fallback"
4028
- ).catch(() => null);
4029
- }
4030
- if (fallback?.activated) {
4031
- logger5.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4032
- return true;
4033
- }
4034
- }
4035
- const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
4036
- if (throwOnMissing) throw new Error(message);
4037
- logger5.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
4038
- return false;
3810
+ return common.contains(el) && common.contains(pointElement);
3811
+ };
3812
+ const describeElement = (node) => {
3813
+ if (!node) return null;
3814
+ const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
3815
+ const rect2 = node.getBoundingClientRect?.();
3816
+ const style = window.getComputedStyle(node);
3817
+ return {
3818
+ tag: node.tagName,
3819
+ id: node.id || "",
3820
+ className,
3821
+ isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
3822
+ positioning: style?.position || "",
3823
+ top: rect2 ? rect2.top : null,
3824
+ bottom: rect2 ? rect2.bottom : null,
3825
+ left: rect2 ? rect2.left : null,
3826
+ right: rect2 ? rect2.right : null
3827
+ };
3828
+ };
3829
+ const samplePoints = [
3830
+ { x: cx, y: cy },
3831
+ { x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3832
+ { x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3833
+ { x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
3834
+ { x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
3835
+ ];
3836
+ let obstruction = null;
3837
+ for (const point of samplePoints) {
3838
+ const pointElement = document.elementFromPoint(point.x, point.y);
3839
+ if (sameTapTarget(pointElement)) {
3840
+ return { code: "VISIBLE", isFixed, positioning };
4039
3841
  }
4040
- const box = await element.boundingBox();
4041
- if (!box) {
4042
- if (throwOnMissing) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4043
- logger5.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
4044
- return false;
3842
+ obstruction = obstruction || describeElement(pointElement);
3843
+ }
3844
+ if (obstruction) {
3845
+ return {
3846
+ code: "OBSTRUCTED",
3847
+ reason: "\u88AB\u906E\u6321",
3848
+ direction: cy > viewH / 2 ? "down" : "up",
3849
+ obstruction,
3850
+ cy,
3851
+ viewH,
3852
+ isFixed,
3853
+ positioning
3854
+ };
3855
+ }
3856
+ return { code: "VISIBLE", isFixed, positioning };
3857
+ });
3858
+ };
3859
+ var activateElementFallback = async (element, point = null, options = {}) => {
3860
+ return element.evaluate((el, { innerOptions }) => {
3861
+ const isEditable = (node) => {
3862
+ if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3863
+ if (node.isContentEditable) return true;
3864
+ if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3865
+ if (node instanceof HTMLInputElement) {
3866
+ return !node.disabled && !node.readOnly && typeof node.select === "function";
3867
+ }
3868
+ return false;
3869
+ };
3870
+ const findEditable = (node) => {
3871
+ for (let current = node; current && current !== document.body; current = current.parentElement) {
3872
+ if (isEditable(current)) return current;
4045
3873
  }
4046
- await waitJitter(reactionDelay, 0.45);
4047
- const visibleBox = clipBoxToViewport(box, resolveViewport(page));
4048
- const tapTarget = centerPointInBox(visibleBox);
4049
- try {
4050
- await tapPoint(page, tapTarget, {
4051
- timeoutMs: tapTimeoutMs,
4052
- mouseFallbackTimeoutMs
4053
- });
4054
- } catch (tapError) {
4055
- if (!fallbackDomClickOnTapError) throw tapError;
4056
- const fallback = await withTimeout(
4057
- () => activateElementFallback(element, tapTarget, {
4058
- editableOnly: false
4059
- }),
4060
- activateFallbackTimeoutMs,
4061
- "activation fallback"
4062
- ).catch(() => null);
4063
- if (!fallback?.activated) throw tapError;
4064
- logger5.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
3874
+ return null;
3875
+ };
3876
+ const editable = findEditable(el);
3877
+ if (editable && typeof editable.focus === "function") {
3878
+ editable.focus({ preventScroll: true });
3879
+ if (innerOptions.editableOnly) {
3880
+ return { activated: true, method: "focus", tag: editable.tagName || "" };
4065
3881
  }
4066
- await waitJitter(120, 0.35);
4067
- logger5.success("humanClick");
4068
- return true;
4069
- } catch (error) {
4070
- logger5.fail("humanClick", error);
4071
- throw error;
4072
3882
  }
4073
- },
4074
- async randomSleep(baseMs, jitterPercent = 0.3) {
4075
- await waitJitter(baseMs, jitterPercent);
4076
- },
4077
- async simulateGaze(page, baseDurationMs = 2500) {
4078
- const durationMs = jitterMs(baseDurationMs, 0.4);
4079
- const startTime = Date.now();
4080
- while (Date.now() - startTime < durationMs) {
4081
- if (Math.random() < 0.28) {
4082
- const distance = 70 + Math.random() * 120;
4083
- await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
4084
- durationMs: 180,
4085
- steps: 4
4086
- });
4087
- } else {
4088
- await waitJitter(420, 0.55);
4089
- }
3883
+ if (innerOptions.editableOnly) {
3884
+ return { activated: false, method: "none", tag: el?.tagName || "" };
4090
3885
  }
4091
- },
4092
- async humanType(page, selector, text, options = {}) {
4093
- const {
4094
- baseDelay = 160,
4095
- pauseProbability = 0.08,
4096
- pauseBase = 700
4097
- } = options;
4098
- await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
4099
- await waitJitter(220, 0.45);
4100
- for (let i = 0; i < String(text).length; i += 1) {
4101
- const char = String(text)[i];
4102
- const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
4103
- await page.keyboard.type(char);
4104
- await waitJitter(charDelay, 0.15);
4105
- if (Math.random() < pauseProbability && i < String(text).length - 1) {
4106
- await waitJitter(pauseBase, 0.5);
4107
- }
3886
+ if (typeof el.focus === "function") {
3887
+ el.focus({ preventScroll: true });
4108
3888
  }
4109
- },
4110
- async humanPress(page, targetOrKey, maybeKey, options = {}) {
4111
- const hasTarget = typeof maybeKey === "string";
4112
- const key = hasTarget ? maybeKey : targetOrKey;
4113
- const pressOptions = hasTarget ? options : maybeKey || options;
4114
- const {
4115
- reactionDelay = 170,
4116
- holdDelay = 42,
4117
- focusDelay = 180,
4118
- scrollIfNeeded = true,
4119
- throwOnMissing = true,
4120
- keyboardOptions = {}
4121
- } = pressOptions || {};
4122
- const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
4123
- logger5.start("humanPress", `key=${key}, target=${targetDesc}`);
4124
- try {
4125
- if (hasTarget) {
4126
- await MobileHumanize.humanClick(page, targetOrKey, {
4127
- reactionDelay: focusDelay,
4128
- scrollIfNeeded,
4129
- throwOnMissing
4130
- });
4131
- }
4132
- await waitJitter(reactionDelay, 0.45);
4133
- await page.keyboard.press(key, {
4134
- ...keyboardOptions,
4135
- delay: jitterMs(holdDelay, 0.5)
4136
- });
4137
- logger5.success("humanPress");
4138
- return true;
4139
- } catch (error) {
4140
- logger5.fail("humanPress", error);
4141
- throw error;
3889
+ if (typeof el.click === "function") {
3890
+ el.click();
3891
+ return { activated: true, method: "dom-click", tag: el.tagName || "" };
4142
3892
  }
4143
- },
4144
- async humanClear(page, selector) {
4145
- const locator = page.locator(selector);
4146
- await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4147
- await waitJitter(160, 0.4);
4148
- const readValue = async () => {
4149
- try {
4150
- return await locator.inputValue({ timeout: 600 });
4151
- } catch {
4152
- return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4153
- }
3893
+ if (typeof el.dispatchEvent === "function") {
3894
+ el.dispatchEvent(new MouseEvent("click", {
3895
+ bubbles: true,
3896
+ cancelable: true,
3897
+ view: window
3898
+ }));
3899
+ return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
3900
+ }
3901
+ return {
3902
+ activated: Boolean(editable),
3903
+ method: editable ? "focus" : "none",
3904
+ tag: el?.tagName || ""
4154
3905
  };
4155
- const currentValue = await readValue();
4156
- if (!currentValue) return;
4157
- await page.keyboard.press("ControlOrMeta+A");
4158
- await waitJitter(90, 0.35);
4159
- await page.keyboard.press("Backspace");
4160
- await waitJitter(120, 0.35);
4161
- if (!await readValue()) return;
4162
- await locator.evaluate((el) => {
4163
- if ("value" in el) {
4164
- el.value = "";
4165
- el.dispatchEvent(new Event("input", { bubbles: true }));
4166
- el.dispatchEvent(new Event("change", { bubbles: true }));
4167
- return;
3906
+ }, {
3907
+ innerOptions: options || {}
3908
+ });
3909
+ };
3910
+ var getScrollableRect = async (element) => {
3911
+ return element.evaluate((el) => {
3912
+ const isScrollable = (node) => {
3913
+ const style = window.getComputedStyle(node);
3914
+ if (!style) return false;
3915
+ const overflowY = style.overflowY;
3916
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3917
+ return node.scrollHeight > node.clientHeight + 1;
3918
+ };
3919
+ let current = el;
3920
+ while (current && current !== document.body) {
3921
+ if (isScrollable(current)) {
3922
+ const rect = current.getBoundingClientRect();
3923
+ if (rect && rect.width > 0 && rect.height > 0) {
3924
+ return {
3925
+ x: rect.x,
3926
+ y: rect.y,
3927
+ width: rect.width,
3928
+ height: rect.height
3929
+ };
3930
+ }
4168
3931
  }
4169
- el.textContent = "";
4170
- el.dispatchEvent(new Event("input", { bubbles: true }));
4171
- });
4172
- },
4173
- async warmUpBrowsing(page, baseDuration = 3500) {
4174
- const durationMs = jitterMs(baseDuration, 0.4);
4175
- const startTime = Date.now();
4176
- while (Date.now() - startTime < durationMs) {
4177
- const action = Math.random();
4178
- if (action < 0.5) {
4179
- await dispatchTouchSwipe(page, 120 + Math.random() * 220, {
4180
- durationMs: 240,
4181
- steps: 5
4182
- });
4183
- } else if (action < 0.7) {
4184
- await dispatchTouchSwipe(page, -(80 + Math.random() * 160), {
4185
- durationMs: 220,
4186
- steps: 4
4187
- });
4188
- } else {
4189
- await waitJitter(560, 0.55);
3932
+ current = current.parentElement;
3933
+ }
3934
+ return null;
3935
+ });
3936
+ };
3937
+ var scrollScrollableAncestor = async (element, deltaY) => {
3938
+ return element.evaluate((el, amount) => {
3939
+ const isScrollable = (node) => {
3940
+ const style = window.getComputedStyle(node);
3941
+ if (!style) return false;
3942
+ const overflowY = style.overflowY;
3943
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3944
+ return node.scrollHeight > node.clientHeight + 1;
3945
+ };
3946
+ let current = el;
3947
+ while (current && current !== document.body) {
3948
+ if (isScrollable(current)) {
3949
+ const beforeTop2 = current.scrollTop;
3950
+ current.scrollTop = beforeTop2 + amount;
3951
+ return {
3952
+ scroller: true,
3953
+ moved: current.scrollTop !== beforeTop2,
3954
+ scrollTop: current.scrollTop
3955
+ };
4190
3956
  }
3957
+ current = current.parentElement;
4191
3958
  }
4192
- },
4193
- async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4194
- const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
4195
- const actualDistance = jitterMs(distance, 0.15);
4196
- const sign = direction === "down" ? 1 : -1;
4197
- for (let i = 0; i < steps; i += 1) {
4198
- const factor = 1 - i / steps * 0.45;
4199
- await dispatchTouchSwipe(page, actualDistance / steps * factor * sign, {
4200
- durationMs: 150 + i * 20,
4201
- steps: 4
4202
- });
3959
+ const beforeTop = window.scrollY;
3960
+ window.scrollBy(0, amount);
3961
+ return {
3962
+ scroller: null,
3963
+ moved: window.scrollY !== beforeTop,
3964
+ scrollTop: window.scrollY
3965
+ };
3966
+ }, deltaY);
3967
+ };
3968
+ var scrollAwayFromObstruction = async (element, status) => {
3969
+ return element.evaluate((el, innerStatus) => {
3970
+ const isScrollable = (node) => {
3971
+ const style = window.getComputedStyle(node);
3972
+ if (!style) return false;
3973
+ const overflowY = style.overflowY;
3974
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3975
+ return node.scrollHeight > node.clientHeight + 1;
3976
+ };
3977
+ let scroller = el;
3978
+ while (scroller && scroller !== document.body) {
3979
+ if (isScrollable(scroller)) break;
3980
+ scroller = scroller.parentElement;
3981
+ }
3982
+ if (!scroller || scroller === document.body) {
3983
+ return { moved: false, scrollTop: 0, deltaY: 0 };
3984
+ }
3985
+ const rect = el.getBoundingClientRect();
3986
+ const scrollerRect = scroller.getBoundingClientRect();
3987
+ const obstruction = innerStatus?.obstruction || {};
3988
+ const obstructionTop = Number(obstruction.top);
3989
+ const obstructionBottom = Number(obstruction.bottom);
3990
+ const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
3991
+ const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
3992
+ const padding = 18;
3993
+ let deltaY = 0;
3994
+ if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
3995
+ deltaY = rect.bottom - obstructionTop + padding;
3996
+ } else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
3997
+ deltaY = rect.top - obstructionBottom - padding;
3998
+ } else {
3999
+ const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
4000
+ deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
4203
4001
  }
4204
- }
4002
+ const beforeTop = scroller.scrollTop;
4003
+ scroller.scrollTop = beforeTop + deltaY;
4004
+ return {
4005
+ moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
4006
+ scrollTop: scroller.scrollTop,
4007
+ deltaY
4008
+ };
4009
+ }, status);
4205
4010
  };
4206
-
4207
- // src/internals/humanize/cloakbrowser.js
4208
- var FORCE_CLICK_OPTIONS = Object.freeze({ force: true });
4209
- var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4210
- var isPoint2 = (value) => value && typeof value === "object" && Number.isFinite(Number(value.x)) && Number.isFinite(Number(value.y));
4211
- var resolveTarget = (page, target) => typeof target === "string" ? page.locator(target).first() : target;
4212
- var resolveTargetPoint = async (page, target) => {
4213
- if (typeof target === "string") {
4214
- return await DeviceInput.selectorCenterPoint(page, target).catch(() => null);
4011
+ var getElementViewportSnapshot = async (element) => {
4012
+ return element.evaluate((el) => {
4013
+ const rect = el.getBoundingClientRect();
4014
+ return {
4015
+ top: rect.top,
4016
+ bottom: rect.bottom,
4017
+ left: rect.left,
4018
+ right: rect.right,
4019
+ width: rect.width,
4020
+ height: rect.height,
4021
+ scrollX: window.scrollX,
4022
+ scrollY: window.scrollY
4023
+ };
4024
+ });
4025
+ };
4026
+ var isTargetImmobileAfterScroll = (before, after) => {
4027
+ if (!before || !after) return false;
4028
+ const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
4029
+ const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
4030
+ const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
4031
+ const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
4032
+ const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
4033
+ const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
4034
+ if (!rectMoved && !pageMoved) return true;
4035
+ if (pageMoved && !rectMoved) return true;
4036
+ if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
4037
+ return true;
4215
4038
  }
4216
- const box = await target?.boundingBox?.().catch(() => null);
4217
- if (!box) {
4218
- return null;
4039
+ return false;
4040
+ };
4041
+ var restoreWindowFromSnapshot = async (page, before, after) => {
4042
+ if (!before || !after) return;
4043
+ if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
4044
+ return;
4219
4045
  }
4220
- return {
4221
- x: box.x + box.width / 2,
4222
- y: box.y + box.height / 2
4223
- };
4046
+ await page.evaluate(
4047
+ (state) => window.scrollTo(state.x, state.y),
4048
+ { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
4049
+ ).catch(() => {
4050
+ });
4224
4051
  };
4225
- var sleep = (baseMs, jitterPercent = 0.3) => (0, import_delay3.default)(jitterMs(baseMs, jitterPercent));
4226
- var buildKeyboardOptions = (options = {}) => ({
4227
- ...options.holdDelay != null ? { delay: Math.max(0, Number(options.holdDelay) || 0) } : {},
4228
- ...options.keyboardOptions || {}
4229
- });
4230
- var CloakBrowserDeviceHumanize = {
4231
- jitterMs(base, jitterPercent = 0.3) {
4232
- return jitterMs(base, jitterPercent);
4233
- },
4234
- async initializeCursor(page) {
4235
- return Boolean(page);
4236
- },
4237
- async humanMove(page, target) {
4238
- if (target == null) {
4239
- return false;
4240
- }
4241
- if (isPoint2(target)) {
4242
- await DeviceInput.move(page, target, void 0, { forceMouse: true });
4243
- return true;
4244
- }
4245
- const point = await resolveTargetPoint(page, target);
4246
- if (!point) {
4247
- return false;
4248
- }
4249
- await DeviceInput.move(page, point, void 0, { forceMouse: true });
4250
- return true;
4251
- },
4252
- async humanScroll(page, target) {
4253
- const element = resolveTarget(page, target);
4254
- if (!element) {
4255
- return { element: null, didScroll: false, restore: null };
4052
+ var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
4053
+ const viewport = resolveViewport(page);
4054
+ const rawRect = options.rect || null;
4055
+ const rect = rawRect ? {
4056
+ x: clamp(rawRect.x, 0, viewport.width),
4057
+ y: clamp(rawRect.y, 0, viewport.height),
4058
+ width: clamp(rawRect.width, 0, viewport.width),
4059
+ height: clamp(rawRect.height, 0, viewport.height)
4060
+ } : null;
4061
+ const area = rect && rect.width > 24 && rect.height > 48 ? {
4062
+ left: rect.x,
4063
+ right: Math.min(viewport.width, rect.x + rect.width),
4064
+ top: rect.y,
4065
+ bottom: Math.min(viewport.height, rect.y + rect.height)
4066
+ } : {
4067
+ left: 0,
4068
+ right: viewport.width,
4069
+ top: 0,
4070
+ bottom: viewport.height
4071
+ };
4072
+ const areaWidth = Math.max(1, area.right - area.left);
4073
+ const areaHeight = Math.max(1, area.bottom - area.top);
4074
+ const maxDistance = rect ? Math.max(60, areaHeight * 0.72) : Math.max(120, viewport.height * 0.72);
4075
+ const distance = clamp(Math.abs(deltaY), Math.min(80, maxDistance), maxDistance);
4076
+ const direction = deltaY >= 0 ? 1 : -1;
4077
+ const startX = clamp(
4078
+ area.left + areaWidth * (0.45 + Math.random() * 0.1),
4079
+ 24,
4080
+ viewport.width - 24
4081
+ );
4082
+ const startY = direction > 0 ? clamp(area.top + areaHeight * (0.72 + Math.random() * 0.1), area.top + 24, area.bottom - 16) : clamp(area.top + areaHeight * (0.28 + Math.random() * 0.1), area.top + 16, area.bottom - 24);
4083
+ const endY = clamp(startY - direction * distance, Math.max(16, area.top + 12), Math.min(viewport.height - 16, area.bottom - 12));
4084
+ const steps = Math.max(4, Math.round(Number(options.steps || 6)));
4085
+ const durationMs = jitterMs(options.durationMs || 320, 0.35);
4086
+ let client = null;
4087
+ const scrollBefore = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4088
+ try {
4089
+ if (page.context && typeof page.context().newCDPSession === "function") {
4090
+ client = await page.context().newCDPSession(page);
4256
4091
  }
4257
- await element.scrollIntoViewIfNeeded?.().catch(() => {
4092
+ if (!client) throw new Error("CDP session unavailable");
4093
+ await client.send("Input.synthesizeScrollGesture", {
4094
+ x: startX,
4095
+ y: startY,
4096
+ yDistance: -direction * distance,
4097
+ xOverscroll: (Math.random() - 0.5) * 4,
4098
+ yOverscroll: (Math.random() - 0.5) * 8,
4099
+ speed: 700 + Math.round(Math.random() * 350),
4100
+ gestureSourceType: "touch"
4258
4101
  });
4259
- return { element, didScroll: true, restore: null };
4260
- },
4261
- async humanClick(page, target) {
4262
- if (target == null) {
4263
- await DeviceInput.clickPoint(page, { x: 0, y: 0 }, void 0, { forceMouse: true });
4264
- return true;
4265
- }
4266
- if (isPoint2(target)) {
4267
- await DeviceInput.clickPoint(page, target, void 0, { forceMouse: true });
4102
+ await waitJitter(160, 0.35);
4103
+ const scrollAfterSynth = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4104
+ if (scrollBefore && scrollAfterSynth && (Math.abs(scrollAfterSynth.y - scrollBefore.y) > 2 || Math.abs(scrollAfterSynth.x - scrollBefore.x) > 2)) {
4268
4105
  return true;
4269
4106
  }
4270
- await DeviceInput.click(page, target, {
4271
- clickOptions: FORCE_CLICK_OPTIONS
4107
+ await client.send("Input.dispatchTouchEvent", {
4108
+ type: "touchStart",
4109
+ touchPoints: [{ x: startX, y: startY, id: 1 }]
4272
4110
  });
4273
- return true;
4274
- },
4275
- async randomSleep(baseMs, jitterPercent = 0.3) {
4276
- await sleep(baseMs, jitterPercent);
4277
- },
4278
- async simulateGaze(page, baseDurationMs = 2500) {
4279
- const durationMs = jitterMs(baseDurationMs, 0.4);
4280
- const startedAt = Date.now();
4281
- const viewport = page.viewportSize() || { width: 1365, height: 900 };
4282
- while (Date.now() - startedAt < durationMs) {
4283
- await DeviceInput.move(page, {
4284
- x: 100 + Math.random() * Math.max(120, viewport.width - 200),
4285
- y: 100 + Math.random() * Math.max(120, viewport.height - 200)
4286
- }, void 0, { forceMouse: true });
4287
- await sleep(300, 0.5);
4111
+ for (let i = 1; i <= steps; i += 1) {
4112
+ const progress = i / steps;
4113
+ const eased = 1 - Math.pow(1 - progress, 2);
4114
+ const x = startX + (Math.random() - 0.5) * 3;
4115
+ const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 5;
4116
+ await waitJitter(durationMs / steps, 0.25);
4117
+ await client.send("Input.dispatchTouchEvent", {
4118
+ type: "touchMove",
4119
+ touchPoints: [{ x, y, id: 1 }]
4120
+ });
4288
4121
  }
4289
- },
4290
- async humanType(page, target, text, options = {}) {
4291
- await DeviceInput.click(page, target, {
4292
- clickOptions: FORCE_CLICK_OPTIONS
4293
- });
4294
- await DeviceInput.keyboardType(page, text, {
4295
- ...options.baseDelay != null ? { delay: Math.max(0, Number(options.baseDelay) || 0) } : {}
4122
+ await client.send("Input.dispatchTouchEvent", {
4123
+ type: "touchEnd",
4124
+ touchPoints: []
4296
4125
  });
4126
+ await waitJitter(120, 0.35);
4127
+ const scrollAfterTouch = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4128
+ if (scrollBefore && scrollAfterTouch && Math.abs(scrollAfterTouch.y - scrollBefore.y) <= 2 && Math.abs(scrollAfterTouch.x - scrollBefore.x) <= 2) {
4129
+ await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
4130
+ await waitJitter(120, 0.35);
4131
+ }
4297
4132
  return true;
4298
- },
4299
- async humanPress(page, targetOrKey, maybeKey, options = {}) {
4300
- if (typeof maybeKey !== "string") {
4301
- await DeviceInput.press(page, targetOrKey, {
4302
- keyboardOptions: buildKeyboardOptions(maybeKey || options)
4303
- });
4133
+ } catch (error) {
4134
+ logger7.debug(`touch swipe fallback: ${error?.message || error}`);
4135
+ try {
4136
+ await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
4137
+ await waitJitter(120, 0.35);
4138
+ return true;
4139
+ } catch {
4140
+ await page.mouse.wheel(0, deltaY);
4141
+ await waitJitter(120, 0.35);
4304
4142
  return true;
4305
4143
  }
4306
- await DeviceInput.press(page, targetOrKey, maybeKey, {
4307
- clickOptions: FORCE_CLICK_OPTIONS,
4308
- keyboardOptions: buildKeyboardOptions(options)
4309
- });
4310
- return true;
4311
- },
4312
- async humanClear(page, target) {
4313
- await DeviceInput.fill(page, target, "", { force: true });
4314
- },
4315
- async warmUpBrowsing(page, baseDuration = 3500) {
4316
- await this.simulateGaze(page, Math.min(baseDuration, 900));
4317
- },
4318
- async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4319
- const steps = Math.max(1, Number(baseSteps) || 1);
4320
- const delta = (Number(distance) || 0) / steps * (direction === "down" ? 1 : -1);
4321
- for (let index = 0; index < steps; index += 1) {
4322
- await page.mouse.wheel(0, delta);
4323
- await sleep(60 + index * 20, 0.3);
4144
+ } finally {
4145
+ if (client && typeof client.detach === "function") {
4146
+ try {
4147
+ await client.detach();
4148
+ } catch {
4149
+ }
4324
4150
  }
4325
4151
  }
4326
4152
  };
4327
- var resolveCloakBrowserHumanizeDelegate = (page) => resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : CloakBrowserDeviceHumanize;
4328
- var CloakBrowserHumanizeContext = Object.freeze({
4329
- desktopDelegate: CloakBrowserDeviceHumanize,
4330
- resolveDelegate: resolveCloakBrowserHumanizeDelegate
4331
- });
4332
-
4333
- // src/internals/humanize/desktop.js
4334
- var import_delay4 = __toESM(require("delay"), 1);
4335
- var import_ghost_cursor_playwright = require("ghost-cursor-playwright");
4336
- var logger6 = createInternalLogger("Humanize");
4337
- var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
4338
- function $GetCursor(page) {
4339
- const cursor = $CursorWeakMap.get(page);
4340
- if (!cursor) {
4341
- throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
4153
+ var tapPoint = async (page, point, options = {}) => {
4154
+ const {
4155
+ timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
4156
+ mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
4157
+ allowMouseFallback = true
4158
+ } = options;
4159
+ if (page.touchscreen && typeof page.touchscreen.tap === "function") {
4160
+ try {
4161
+ await withTimeout(
4162
+ () => page.touchscreen.tap(point.x, point.y),
4163
+ timeoutMs,
4164
+ "touchscreen.tap"
4165
+ );
4166
+ return { method: "touchscreen" };
4167
+ } catch (error) {
4168
+ logger7.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
4169
+ if (!allowMouseFallback) throw error;
4170
+ }
4342
4171
  }
4343
- return cursor;
4344
- }
4345
- var Humanize = {
4346
- /**
4347
- * 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
4348
- * @param {number} base - 基础延迟 (ms)
4349
- * @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
4350
- * @returns {number} 抖动后的毫秒数
4351
- */
4352
- jitterMs(base, jitterPercent = 0.3) {
4353
- const jitter = base * jitterPercent * (Math.random() * 2 - 1);
4354
- return Math.max(10, Math.round(base + jitter));
4355
- },
4356
- /**
4357
- * 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
4358
- *
4359
- * @param {import('playwright').Page} page
4360
- * @returns {Promise<void>}
4361
- */
4172
+ await withTimeout(
4173
+ () => page.mouse.click(point.x, point.y),
4174
+ mouseFallbackTimeoutMs,
4175
+ "mouse.click fallback"
4176
+ );
4177
+ return { method: "mouse" };
4178
+ };
4179
+ var MobileHumanize = {
4180
+ jitterMs,
4362
4181
  async initializeCursor(page) {
4363
- if ($CursorWeakMap.has(page)) {
4364
- logger6.debug("initializeCursor: cursor already exists, skipping");
4365
- return;
4366
- }
4367
- logger6.start("initializeCursor", "creating cursor");
4368
- const cursor = await (0, import_ghost_cursor_playwright.createCursor)(page);
4369
- $CursorWeakMap.set(page, cursor);
4370
- logger6.success("initializeCursor", "cursor initialized");
4182
+ if (initializedPages.has(page)) return;
4183
+ initializedPages.add(page);
4184
+ logger7.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
4371
4185
  },
4372
- /**
4373
- * 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
4374
- *
4375
- * @param {import('playwright').Page} page
4376
- * @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
4377
- */
4378
4186
  async humanMove(page, target) {
4379
- const cursor = $GetCursor(page);
4380
- logger6.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
4381
- try {
4382
- if (typeof target === "string") {
4383
- const element = await page.$(target);
4384
- if (!element) {
4385
- logger6.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
4386
- return false;
4387
- }
4388
- const box = await element.boundingBox();
4389
- if (!box) {
4390
- logger6.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
4391
- return false;
4392
- }
4393
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
4394
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
4395
- await cursor.actions.move({ x, y });
4396
- } else if (target && typeof target.x === "number" && typeof target.y === "number") {
4397
- await cursor.actions.move(target);
4398
- } else if (target && typeof target.boundingBox === "function") {
4399
- const box = await target.boundingBox();
4400
- if (box) {
4401
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
4402
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
4403
- await cursor.actions.move({ x, y });
4404
- }
4187
+ logger7.debug(`humanMove: mobile no-op target=${typeof target === "string" ? target : "element/coords"}`);
4188
+ if (typeof target === "string" || target && typeof target.boundingBox === "function") {
4189
+ const element = await resolveElement(page, target, { throwOnMissing: false });
4190
+ if (!element) {
4191
+ return false;
4405
4192
  }
4406
- logger6.success("humanMove");
4407
- return true;
4408
- } catch (error) {
4409
- logger6.fail("humanMove", error);
4410
- throw error;
4411
4193
  }
4194
+ await waitJitter(80, 0.4);
4195
+ return true;
4412
4196
  },
4413
- /**
4414
- * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
4415
- * 返回 restore 方法,用于将滚动容器恢复到原位置
4416
- *
4417
- * @param {import('playwright').Page} page
4418
- * @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
4419
- * @param {Object} [options]
4420
- * @param {number} [options.maxSteps=20] - 最大滚动步数
4421
- * @param {number} [options.minStep=260] - 单次滚动最小步长
4422
- * @param {number} [options.maxStep=800] - 单次滚动最大步长
4423
- * @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
4424
- */
4425
4197
  async humanScroll(page, target, options = {}) {
4426
4198
  const {
4427
4199
  maxSteps = 20,
4428
- minStep = 260,
4429
- maxStep = 800,
4430
- maxDurationMs = maxSteps * 220 + 800
4200
+ minStep = 180,
4201
+ maxStep = 520,
4202
+ maxDurationMs = maxSteps * 280 + 1200,
4203
+ throwOnMissing = false
4431
4204
  } = options;
4432
- const targetDesc = typeof target === "string" ? target : "ElementHandle";
4433
- logger6.start("humanScroll", `target=${targetDesc}`);
4434
- let element;
4435
- if (typeof target === "string") {
4436
- element = await page.$(target);
4437
- if (!element) {
4438
- logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
4439
- return { element: null, didScroll: false };
4440
- }
4441
- } else {
4442
- element = target;
4205
+ const targetDesc = describeTarget(target);
4206
+ logger7.start("humanScroll", `target=${targetDesc}`);
4207
+ const element = await resolveElement(page, target, { throwOnMissing });
4208
+ if (!element) {
4209
+ logger7.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
4210
+ return { element: null, didScroll: false, restore: null };
4443
4211
  }
4444
- const cursor = $GetCursor(page);
4212
+ const startTime = Date.now();
4445
4213
  let didScroll = false;
4446
- const checkVisibility = async () => {
4447
- return await element.evaluate((el) => {
4448
- const rect = el.getBoundingClientRect();
4449
- if (!rect || rect.width === 0 || rect.height === 0) {
4450
- return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
4451
- }
4452
- const cx = rect.left + rect.width / 2;
4453
- const cy = rect.top + rect.height / 2;
4454
- const viewH = window.innerHeight;
4455
- const viewW = window.innerWidth;
4456
- let isFixed = false;
4457
- for (let node = el; node && node !== document.body; node = node.parentElement) {
4458
- const style = window.getComputedStyle(node);
4459
- if (style && style.position === "fixed") {
4460
- isFixed = true;
4461
- break;
4462
- }
4214
+ for (let i = 0; i < maxSteps; i += 1) {
4215
+ const status = await checkElementVisibility(element);
4216
+ if (status.code === "VISIBLE") {
4217
+ if (status.isFixed) {
4218
+ logger7.info("humanScroll | fixed/sticky \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4219
+ } else {
4220
+ logger7.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
4463
4221
  }
4464
- if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
4465
- const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
4466
- return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH, isFixed };
4222
+ logger7.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4223
+ return { element, didScroll, restore: null };
4224
+ }
4225
+ if (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE") {
4226
+ logger7.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
4227
+ return { element, didScroll, restore: null };
4228
+ }
4229
+ const scrollRect = await getScrollableRect(element);
4230
+ if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4231
+ logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (direction=${status.direction || "unknown"})`);
4232
+ return { element, didScroll, restore: null, unscrollable: true };
4233
+ }
4234
+ if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
4235
+ logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
4236
+ return { element, didScroll, restore: null, unscrollable: true };
4237
+ }
4238
+ if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
4239
+ const moved = await scrollAwayFromObstruction(element, status);
4240
+ if (moved.moved) {
4241
+ logger7.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
4242
+ await waitJitter(90, 0.3);
4243
+ didScroll = true;
4244
+ continue;
4467
4245
  }
4468
- const pointElement = document.elementFromPoint(cx, cy);
4469
- if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
4470
- return {
4471
- code: "OBSTRUCTED",
4472
- reason: "\u88AB\u906E\u6321",
4473
- obstruction: {
4474
- tag: pointElement.tagName,
4475
- id: pointElement.id,
4476
- className: pointElement.className
4477
- },
4478
- cy,
4479
- // Return Center Y for smart direction calculation
4480
- viewH,
4481
- isFixed
4482
- };
4246
+ }
4247
+ if (Date.now() - startTime > maxDurationMs) {
4248
+ logger7.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
4249
+ return { element, didScroll, restore: null };
4250
+ }
4251
+ const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
4252
+ const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
4253
+ logger7.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
4254
+ const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
4255
+ let deltaY = status.direction === "up" ? -distance : distance;
4256
+ if (status.code === "OBSTRUCTED") {
4257
+ if (status.obstruction?.isFixed && status.obstruction.top != null) {
4258
+ const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
4259
+ const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4260
+ deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
4261
+ } else {
4262
+ const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4263
+ deltaY = status.cy > halfY ? distance : -distance;
4483
4264
  }
4484
- return { code: "VISIBLE", isFixed };
4485
- });
4486
- };
4487
- const getScrollableRect2 = async () => {
4488
- return await element.evaluate((el) => {
4265
+ }
4266
+ const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
4267
+ const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4268
+ const beforeState = scrollRect ? await element.evaluate((el) => {
4489
4269
  const isScrollable = (node) => {
4490
4270
  const style = window.getComputedStyle(node);
4491
4271
  if (!style) return false;
@@ -4496,427 +4276,386 @@ var Humanize = {
4496
4276
  let current = el;
4497
4277
  while (current && current !== document.body) {
4498
4278
  if (isScrollable(current)) {
4499
- const rect = current.getBoundingClientRect();
4500
- if (rect && rect.width > 0 && rect.height > 0) {
4501
- return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
4502
- }
4279
+ return {
4280
+ kind: "element",
4281
+ top: current.scrollTop,
4282
+ left: current.scrollLeft
4283
+ };
4503
4284
  }
4504
4285
  current = current.parentElement;
4505
4286
  }
4506
- return null;
4507
- });
4508
- };
4509
- const startTime = Date.now();
4510
- try {
4511
- for (let i = 0; i < maxSteps; i++) {
4512
- if (Date.now() - startTime > maxDurationMs) {
4513
- logger6.warn(`humanScroll | \u8D85\u65F6\u4FDD\u62A4\u89E6\u53D1 (${maxDurationMs}ms)`);
4514
- return { element, didScroll };
4515
- }
4516
- const status = await checkVisibility();
4517
- if (status.code === "VISIBLE") {
4518
- if (status.isFixed) {
4519
- logger6.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4520
- } else {
4521
- logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
4522
- }
4523
- logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4524
- return { element, didScroll };
4525
- }
4526
- logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
4527
- if (status.code === "OBSTRUCTED" && status.obstruction) {
4528
- logger6.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
4287
+ return { kind: "window", top: window.scrollY, left: window.scrollX };
4288
+ }) : null;
4289
+ await dispatchTouchSwipe(page, deltaY, { rect: scrollRect });
4290
+ if (scrollRect && beforeWindowState) {
4291
+ const afterWindowState = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
4292
+ if (Math.abs(afterWindowState.x - beforeWindowState.x) > 2 || Math.abs(afterWindowState.y - beforeWindowState.y) > 2) {
4293
+ await page.evaluate((state) => window.scrollTo(state.x, state.y), beforeWindowState);
4294
+ logger7.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
4529
4295
  }
4530
- const scrollRect = await getScrollableRect2();
4531
- if (!scrollRect && status.isFixed) {
4532
- logger6.warn("humanScroll | fixed \u5BB9\u5668\u5185\u4E14\u65E0\u53EF\u6EDA\u52A8\u7956\u5148\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4533
- return { element, didScroll };
4296
+ }
4297
+ let afterElementSnapshot = null;
4298
+ const readAfterElementSnapshot = async () => {
4299
+ if (!afterElementSnapshot) {
4300
+ afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4534
4301
  }
4535
- const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
4536
- const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
4537
- let deltaY = 0;
4538
- if (status.code === "OUT_OF_VIEWPORT") {
4539
- if (status.direction === "down") {
4540
- deltaY = stepMin + Math.random() * (stepMax - stepMin);
4541
- } else if (status.direction === "up") {
4542
- deltaY = -(stepMin + Math.random() * (stepMax - stepMin));
4543
- } else {
4544
- deltaY = 100;
4545
- }
4546
- } else if (status.code === "OBSTRUCTED") {
4547
- const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4548
- const isBottomHalf = status.cy > halfY;
4549
- const direction = isBottomHalf ? 1 : -1;
4550
- deltaY = direction * (stepMin + Math.random() * 50);
4302
+ return afterElementSnapshot;
4303
+ };
4304
+ if (!scrollRect && beforeElementSnapshot) {
4305
+ const afterSnapshot = await readAfterElementSnapshot();
4306
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4307
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4308
+ logger7.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u9875\u9762\u6EDA\u52A8\u79FB\u52A8\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
4309
+ return { element, didScroll, restore: null, unscrollable: true };
4551
4310
  }
4552
- if (i === 0) {
4553
- const viewSize = page.viewportSize();
4554
- if (scrollRect) {
4555
- const safeX = scrollRect.x + scrollRect.width * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.width * 0.4);
4556
- const safeY = scrollRect.y + scrollRect.height * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.height * 0.4);
4557
- await cursor.actions.move({ x: safeX, y: safeY });
4558
- } else if (viewSize) {
4559
- const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 80;
4560
- const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 80;
4561
- await cursor.actions.move({ x: safeX, y: safeY });
4311
+ }
4312
+ if (scrollRect && beforeState) {
4313
+ const afterState = await element.evaluate((el) => {
4314
+ const isScrollable = (node) => {
4315
+ const style = window.getComputedStyle(node);
4316
+ if (!style) return false;
4317
+ const overflowY = style.overflowY;
4318
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
4319
+ return node.scrollHeight > node.clientHeight + 1;
4320
+ };
4321
+ let current = el;
4322
+ while (current && current !== document.body) {
4323
+ if (isScrollable(current)) {
4324
+ return {
4325
+ kind: "element",
4326
+ top: current.scrollTop,
4327
+ left: current.scrollLeft
4328
+ };
4329
+ }
4330
+ current = current.parentElement;
4331
+ }
4332
+ return { kind: "window", top: window.scrollY, left: window.scrollX };
4333
+ });
4334
+ const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
4335
+ const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
4336
+ const expectedDelta = Number(deltaY || 0);
4337
+ const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
4338
+ if (!moved) {
4339
+ const fallback = await scrollScrollableAncestor(element, deltaY);
4340
+ logger7.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u65E0\u6548\uFF0C\u76F4\u63A5\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
4341
+ } else if (beforeState.kind === afterState.kind && Math.abs(expectedDelta) > 24 && Math.sign(topDelta || expectedDelta) === Math.sign(expectedDelta) && Math.abs(topDelta) < Math.min(Math.abs(expectedDelta) * 0.45, 96)) {
4342
+ const residualDelta = expectedDelta - topDelta;
4343
+ if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
4344
+ const fallback = await scrollScrollableAncestor(element, residualDelta);
4345
+ logger7.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u8DDD\u79BB\u4E0D\u8DB3\uFF0C\u8865\u507F\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
4562
4346
  }
4563
4347
  }
4564
- await page.mouse.wheel(0, deltaY);
4565
- didScroll = true;
4566
- await (0, import_delay4.default)(this.jitterMs(20 + Math.random() * 40, 0.2));
4567
4348
  }
4568
- logger6.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
4569
- return { element, didScroll };
4570
- } catch (error) {
4571
- logger6.fail("humanScroll", error);
4572
- throw error;
4349
+ if (scrollRect && beforeElementSnapshot) {
4350
+ const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4351
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4352
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4353
+ logger7.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u6EDA\u52A8\u5BB9\u5668\u79FB\u52A8\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
4354
+ return { element, didScroll, restore: null, unscrollable: true };
4355
+ }
4356
+ }
4357
+ didScroll = true;
4358
+ }
4359
+ try {
4360
+ await element.scrollIntoViewIfNeeded?.();
4361
+ await waitJitter(80, 0.3);
4362
+ const finalStatus = await checkElementVisibility(element);
4363
+ if (finalStatus.code === "VISIBLE") {
4364
+ logger7.info("humanScroll | \u539F\u751F scrollIntoViewIfNeeded \u515C\u5E95\u6210\u529F");
4365
+ logger7.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4366
+ return { element, didScroll: true, restore: null };
4367
+ }
4368
+ } catch (fallbackError) {
4369
+ logger7.debug(`humanScroll | native fallback failed: ${fallbackError?.message || fallbackError}`);
4573
4370
  }
4371
+ logger7.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
4372
+ return { element, didScroll, restore: null };
4574
4373
  },
4575
- /**
4576
- * 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
4577
- *
4578
- * @param {import('playwright').Page} page
4579
- * @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
4580
- * @param {Object} [options]
4581
- * @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
4582
- * @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
4583
- * @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
4584
- */
4585
4374
  async humanClick(page, target, options = {}) {
4586
- const cursor = $GetCursor(page);
4587
- const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
4588
- const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
4589
- logger6.start("humanClick", `target=${targetDesc}`);
4590
- const restoreOnce = async () => {
4591
- if (restoreOnce.restored) return;
4592
- restoreOnce.restored = true;
4593
- if (typeof restoreOnce.do !== "function") return;
4594
- try {
4595
- await (0, import_delay4.default)(this.jitterMs(1e3));
4596
- await restoreOnce.do();
4597
- } catch (restoreError) {
4598
- logger6.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
4599
- }
4600
- };
4375
+ const {
4376
+ reactionDelay = 220,
4377
+ throwOnMissing = true,
4378
+ scrollIfNeeded = true,
4379
+ tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
4380
+ mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
4381
+ activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
4382
+ fallbackDomClick = true,
4383
+ fallbackDomClickOnTapError = true
4384
+ } = options;
4385
+ const targetDesc = describeTarget(target);
4386
+ logger7.start("humanClick", `target=${targetDesc}`);
4601
4387
  try {
4602
4388
  if (target == null) {
4603
- await (0, import_delay4.default)(this.jitterMs(reactionDelay, 0.4));
4604
- await cursor.actions.click();
4605
- logger6.success("humanClick", "Clicked current position");
4389
+ const viewport = resolveViewport(page);
4390
+ await waitJitter(reactionDelay, 0.45);
4391
+ await tapPoint(page, {
4392
+ x: viewport.width * (0.45 + Math.random() * 0.1),
4393
+ y: viewport.height * (0.48 + Math.random() * 0.12)
4394
+ }, {
4395
+ timeoutMs: tapTimeoutMs,
4396
+ mouseFallbackTimeoutMs
4397
+ });
4398
+ logger7.success("humanClick", "Tapped current position");
4606
4399
  return true;
4607
4400
  }
4608
- let element;
4609
- if (typeof target === "string") {
4610
- element = await page.$(target);
4611
- if (!element) {
4612
- if (throwOnMissing) {
4613
- throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
4401
+ const element = await resolveElement(page, target, { throwOnMissing });
4402
+ if (!element) {
4403
+ logger7.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
4404
+ return false;
4405
+ }
4406
+ const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
4407
+ const status = await checkElementVisibility(element).catch(() => null);
4408
+ if (status && status.code !== "VISIBLE") {
4409
+ if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
4410
+ let fallback = await withTimeout(
4411
+ () => activateElementFallback(element, null, {
4412
+ editableOnly: true
4413
+ }),
4414
+ activateFallbackTimeoutMs,
4415
+ "focus fallback"
4416
+ ).catch(() => null);
4417
+ if (!fallback?.activated) {
4418
+ fallback = await withTimeout(
4419
+ () => activateElementFallback(element, null, {
4420
+ editableOnly: false
4421
+ }),
4422
+ activateFallbackTimeoutMs,
4423
+ "activation fallback"
4424
+ ).catch(() => null);
4425
+ }
4426
+ if (fallback?.activated) {
4427
+ logger7.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4428
+ return true;
4614
4429
  }
4615
- logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
4616
- return false;
4617
4430
  }
4618
- } else {
4619
- element = target;
4620
- }
4621
- if (scrollIfNeeded) {
4622
- const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
4623
- restoreOnce.do = didScroll && restore ? restoreFn : null;
4431
+ const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
4432
+ if (throwOnMissing) throw new Error(message);
4433
+ logger7.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
4434
+ return false;
4624
4435
  }
4625
4436
  const box = await element.boundingBox();
4626
4437
  if (!box) {
4627
- await restoreOnce();
4628
- if (throwOnMissing) {
4629
- throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4630
- }
4631
- logger6.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
4438
+ if (throwOnMissing) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4439
+ logger7.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
4632
4440
  return false;
4633
4441
  }
4634
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
4635
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
4636
- await cursor.actions.move({ x, y });
4637
- await (0, import_delay4.default)(this.jitterMs(reactionDelay, 0.4));
4638
- await cursor.actions.click();
4639
- await restoreOnce();
4640
- logger6.success("humanClick");
4442
+ await waitJitter(reactionDelay, 0.45);
4443
+ const visibleBox = clipBoxToViewport(box, resolveViewport(page));
4444
+ const tapTarget = centerPointInBox(visibleBox);
4445
+ try {
4446
+ await tapPoint(page, tapTarget, {
4447
+ timeoutMs: tapTimeoutMs,
4448
+ mouseFallbackTimeoutMs
4449
+ });
4450
+ } catch (tapError) {
4451
+ if (!fallbackDomClickOnTapError) throw tapError;
4452
+ const fallback = await withTimeout(
4453
+ () => activateElementFallback(element, tapTarget, {
4454
+ editableOnly: false
4455
+ }),
4456
+ activateFallbackTimeoutMs,
4457
+ "activation fallback"
4458
+ ).catch(() => null);
4459
+ if (!fallback?.activated) throw tapError;
4460
+ logger7.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
4461
+ }
4462
+ await waitJitter(120, 0.35);
4463
+ logger7.success("humanClick");
4641
4464
  return true;
4642
4465
  } catch (error) {
4643
- await restoreOnce();
4644
- logger6.fail("humanClick", error);
4466
+ logger7.fail("humanClick", error);
4645
4467
  throw error;
4646
4468
  }
4647
4469
  },
4648
- /**
4649
- * 随机延迟一段毫秒数(带 ±30% 抖动)
4650
- * @param {number} baseMs - 基础延迟毫秒数
4651
- * @param {number} [jitterPercent=0.3] - 抖动百分比
4652
- */
4653
4470
  async randomSleep(baseMs, jitterPercent = 0.3) {
4654
- const ms = this.jitterMs(baseMs, jitterPercent);
4655
- logger6.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
4656
- await (0, import_delay4.default)(ms);
4657
- logger6.success("randomSleep");
4471
+ await waitJitter(baseMs, jitterPercent);
4658
4472
  },
4659
- /**
4660
- * 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
4661
- * @param {import('playwright').Page} page
4662
- * @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
4663
- */
4664
4473
  async simulateGaze(page, baseDurationMs = 2500) {
4665
- const cursor = $GetCursor(page);
4666
- const durationMs = this.jitterMs(baseDurationMs, 0.4);
4667
- logger6.start("simulateGaze", `duration=${durationMs}ms`);
4474
+ const durationMs = jitterMs(baseDurationMs, 0.4);
4668
4475
  const startTime = Date.now();
4669
- const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
4670
4476
  while (Date.now() - startTime < durationMs) {
4671
- const x = 100 + Math.random() * (viewportSize.width - 200);
4672
- const y = 100 + Math.random() * (viewportSize.height - 200);
4673
- await cursor.actions.move({ x, y });
4674
- await (0, import_delay4.default)(this.jitterMs(600, 0.5));
4477
+ if (Math.random() < 0.28) {
4478
+ const distance = 70 + Math.random() * 120;
4479
+ await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
4480
+ durationMs: 180,
4481
+ steps: 4
4482
+ });
4483
+ } else {
4484
+ await waitJitter(420, 0.55);
4485
+ }
4675
4486
  }
4676
- logger6.success("simulateGaze");
4677
4487
  },
4678
- /**
4679
- * 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
4680
- * @param {import('playwright').Page} page
4681
- * @param {string} selector - 输入框选择器
4682
- * @param {string} text - 要输入的文本
4683
- * @param {Object} [options]
4684
- * @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
4685
- * @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
4686
- * @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
4687
- */
4688
4488
  async humanType(page, selector, text, options = {}) {
4689
- logger6.start("humanType", `selector=${selector}, textLen=${text.length}`);
4690
4489
  const {
4691
- baseDelay = 180,
4490
+ baseDelay = 160,
4692
4491
  pauseProbability = 0.08,
4693
- pauseBase = 800
4492
+ pauseBase = 700
4694
4493
  } = options;
4695
- try {
4696
- const locator = page.locator(selector);
4697
- await Humanize.humanClick(page, locator);
4698
- await (0, import_delay4.default)(this.jitterMs(200, 0.4));
4699
- for (let i = 0; i < text.length; i++) {
4700
- const char = text[i];
4701
- let charDelay;
4702
- if (char === " ") {
4703
- charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
4704
- } else if (/[,.!?;:,。!?;:]/.test(char)) {
4705
- charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
4706
- } else {
4707
- charDelay = this.jitterMs(baseDelay, 0.4);
4708
- }
4709
- await page.keyboard.type(char);
4710
- await (0, import_delay4.default)(charDelay);
4711
- if (Math.random() < pauseProbability && i < text.length - 1) {
4712
- const pauseTime = this.jitterMs(pauseBase, 0.5);
4713
- logger6.debug(`\u505C\u987F ${pauseTime}ms...`);
4714
- await (0, import_delay4.default)(pauseTime);
4715
- }
4494
+ await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
4495
+ await waitJitter(220, 0.45);
4496
+ for (let i = 0; i < String(text).length; i += 1) {
4497
+ const char = String(text)[i];
4498
+ const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
4499
+ await page.keyboard.type(char);
4500
+ await waitJitter(charDelay, 0.15);
4501
+ if (Math.random() < pauseProbability && i < String(text).length - 1) {
4502
+ await waitJitter(pauseBase, 0.5);
4716
4503
  }
4717
- logger6.success("humanType");
4718
- } catch (error) {
4719
- logger6.fail("humanType", error);
4720
- throw error;
4721
4504
  }
4722
4505
  },
4723
- /**
4724
- * 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
4725
- * @param {import('playwright').Page} page
4726
- * @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
4727
- * @param {string|Object} [maybeKey] - 按键或选项
4728
- * @param {Object} [options]
4729
- */
4730
4506
  async humanPress(page, targetOrKey, maybeKey, options = {}) {
4731
4507
  const hasTarget = typeof maybeKey === "string";
4732
4508
  const key = hasTarget ? maybeKey : targetOrKey;
4733
4509
  const pressOptions = hasTarget ? options : maybeKey || options;
4734
4510
  const {
4735
- reactionDelay = 180,
4736
- holdDelay = 45,
4511
+ reactionDelay = 170,
4512
+ holdDelay = 42,
4737
4513
  focusDelay = 180,
4738
4514
  scrollIfNeeded = true,
4739
4515
  throwOnMissing = true,
4740
4516
  keyboardOptions = {}
4741
4517
  } = pressOptions || {};
4742
- const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
4743
- logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
4518
+ const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
4519
+ logger7.start("humanPress", `key=${key}, target=${targetDesc}`);
4744
4520
  try {
4745
4521
  if (hasTarget) {
4746
- await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
4522
+ await MobileHumanize.humanClick(page, targetOrKey, {
4523
+ reactionDelay: focusDelay,
4524
+ scrollIfNeeded,
4525
+ throwOnMissing
4526
+ });
4747
4527
  }
4748
- await (0, import_delay4.default)(this.jitterMs(reactionDelay, 0.45));
4528
+ await waitJitter(reactionDelay, 0.45);
4749
4529
  await page.keyboard.press(key, {
4750
4530
  ...keyboardOptions,
4751
- delay: this.jitterMs(holdDelay, 0.5)
4531
+ delay: jitterMs(holdDelay, 0.5)
4752
4532
  });
4753
- logger6.success("humanPress");
4533
+ logger7.success("humanPress");
4754
4534
  return true;
4755
4535
  } catch (error) {
4756
- logger6.fail("humanPress", error);
4536
+ logger7.fail("humanPress", error);
4757
4537
  throw error;
4758
4538
  }
4759
4539
  },
4760
- /**
4761
- * 人类化清空输入框 - 模拟人类删除文本的行为
4762
- * @param {import('playwright').Page} page
4763
- * @param {string} selector - 输入框选择器
4764
- */
4765
4540
  async humanClear(page, selector) {
4766
- logger6.start("humanClear", `selector=${selector}`);
4767
- try {
4768
- const locator = page.locator(selector);
4769
- await locator.click();
4770
- await (0, import_delay4.default)(this.jitterMs(200, 0.4));
4771
- const currentValue = await locator.inputValue();
4772
- if (!currentValue || currentValue.length === 0) {
4773
- logger6.success("humanClear", "already empty");
4541
+ const locator = page.locator(selector);
4542
+ await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4543
+ await waitJitter(160, 0.4);
4544
+ const readValue = async () => {
4545
+ try {
4546
+ return await locator.inputValue({ timeout: 600 });
4547
+ } catch {
4548
+ return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4549
+ }
4550
+ };
4551
+ const currentValue = await readValue();
4552
+ if (!currentValue) return;
4553
+ await page.keyboard.press("ControlOrMeta+A");
4554
+ await waitJitter(90, 0.35);
4555
+ await page.keyboard.press("Backspace");
4556
+ await waitJitter(120, 0.35);
4557
+ if (!await readValue()) return;
4558
+ await locator.evaluate((el) => {
4559
+ if ("value" in el) {
4560
+ el.value = "";
4561
+ el.dispatchEvent(new Event("input", { bubbles: true }));
4562
+ el.dispatchEvent(new Event("change", { bubbles: true }));
4774
4563
  return;
4775
4564
  }
4776
- await page.keyboard.press("Meta+A");
4777
- await (0, import_delay4.default)(this.jitterMs(100, 0.4));
4778
- await page.keyboard.press("Backspace");
4779
- logger6.success("humanClear");
4780
- } catch (error) {
4781
- logger6.fail("humanClear", error);
4782
- throw error;
4783
- }
4565
+ el.textContent = "";
4566
+ el.dispatchEvent(new Event("input", { bubbles: true }));
4567
+ });
4784
4568
  },
4785
- /**
4786
- * 页面预热浏览 - 模拟人类进入页面后的探索行为
4787
- * @param {import('playwright').Page} page
4788
- * @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
4789
- */
4790
4569
  async warmUpBrowsing(page, baseDuration = 3500) {
4791
- const cursor = $GetCursor(page);
4792
- const durationMs = this.jitterMs(baseDuration, 0.4);
4793
- logger6.start("warmUpBrowsing", `duration=${durationMs}ms`);
4570
+ const durationMs = jitterMs(baseDuration, 0.4);
4794
4571
  const startTime = Date.now();
4795
- const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
4796
- try {
4797
- while (Date.now() - startTime < durationMs) {
4798
- const action = Math.random();
4799
- if (action < 0.4) {
4800
- const x = 100 + Math.random() * (viewportSize.width - 200);
4801
- const y = 100 + Math.random() * (viewportSize.height - 200);
4802
- await cursor.actions.move({ x, y });
4803
- await (0, import_delay4.default)(this.jitterMs(350, 0.4));
4804
- } else if (action < 0.7) {
4805
- const scrollY = (Math.random() - 0.5) * 200;
4806
- await page.mouse.wheel(0, scrollY);
4807
- await (0, import_delay4.default)(this.jitterMs(500, 0.4));
4808
- } else {
4809
- await (0, import_delay4.default)(this.jitterMs(800, 0.5));
4810
- }
4572
+ while (Date.now() - startTime < durationMs) {
4573
+ const action = Math.random();
4574
+ if (action < 0.5) {
4575
+ await dispatchTouchSwipe(page, 120 + Math.random() * 220, {
4576
+ durationMs: 240,
4577
+ steps: 5
4578
+ });
4579
+ } else if (action < 0.7) {
4580
+ await dispatchTouchSwipe(page, -(80 + Math.random() * 160), {
4581
+ durationMs: 220,
4582
+ steps: 4
4583
+ });
4584
+ } else {
4585
+ await waitJitter(560, 0.55);
4811
4586
  }
4812
- logger6.success("warmUpBrowsing");
4813
- } catch (error) {
4814
- logger6.fail("warmUpBrowsing", error);
4815
- throw error;
4816
4587
  }
4817
4588
  },
4818
- /**
4819
- * 自然滚动 - 带惯性、减速效果和随机抖动
4820
- * @param {import('playwright').Page} page
4821
- * @param {'up' | 'down'} [direction='down'] - 滚动方向
4822
- * @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
4823
- * @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
4824
- */
4825
4589
  async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4826
4590
  const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
4827
- const actualDistance = this.jitterMs(distance, 0.15);
4828
- logger6.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
4591
+ const actualDistance = jitterMs(distance, 0.15);
4829
4592
  const sign = direction === "down" ? 1 : -1;
4830
- const stepDistance = actualDistance / steps;
4831
- try {
4832
- for (let i = 0; i < steps; i++) {
4833
- const factor = 1 - i / steps * 0.5;
4834
- const jitter = 0.9 + Math.random() * 0.2;
4835
- const scrollAmount = stepDistance * factor * sign * jitter;
4836
- await page.mouse.wheel(0, scrollAmount);
4837
- const baseDelay = 60 + i * 25;
4838
- await (0, import_delay4.default)(this.jitterMs(baseDelay, 0.3));
4839
- }
4840
- logger6.success("naturalScroll");
4841
- } catch (error) {
4842
- logger6.fail("naturalScroll", error);
4843
- throw error;
4593
+ for (let i = 0; i < steps; i += 1) {
4594
+ const factor = 1 - i / steps * 0.45;
4595
+ await dispatchTouchSwipe(page, actualDistance / steps * factor * sign, {
4596
+ durationMs: 150 + i * 20,
4597
+ steps: 4
4598
+ });
4844
4599
  }
4845
4600
  }
4846
4601
  };
4847
4602
 
4848
- // src/internals/humanize/default.js
4849
- var resolveDeviceFromPage3 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4850
- var resolveDefaultHumanizeDelegate = (page) => resolveDeviceFromPage3(page) === Device.Mobile ? MobileHumanize : Humanize;
4851
- var DefaultHumanizeContext = Object.freeze({
4852
- desktopDelegate: Humanize,
4853
- resolveDelegate: resolveDefaultHumanizeDelegate
4854
- });
4855
-
4856
- // src/internals/humanize/index.js
4857
- var HUMANIZE_DELEGATED_METHODS = Object.freeze({
4858
- initializeCursor: { enumerable: true },
4859
- humanMove: { enumerable: true },
4860
- humanScroll: { enumerable: true },
4861
- humanClick: { enumerable: true },
4862
- simulateGaze: { enumerable: true },
4863
- humanType: { enumerable: true },
4864
- humanPress: { enumerable: true },
4865
- humanClear: { enumerable: true },
4866
- warmUpBrowsing: { enumerable: true },
4867
- naturalScroll: { enumerable: true }
4868
- });
4869
- var isPageLike2 = (value) => value && typeof value === "object" && typeof value.evaluate === "function";
4870
- var createSharedHumanizeMethods = ({ desktopDelegate, resolveDelegate }) => ({
4603
+ // src/humanize.js
4604
+ var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4605
+ var resolveDelegate = (page) => {
4606
+ return resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : Humanize;
4607
+ };
4608
+ var callDelegate = (method, page, args) => {
4609
+ const delegate = resolveDelegate(page);
4610
+ return delegate[method](page, ...args);
4611
+ };
4612
+ var Humanize2 = {
4871
4613
  jitterMs(base, jitterPercent = 0.3) {
4872
- return desktopDelegate.jitterMs(base, jitterPercent);
4614
+ return Humanize.jitterMs(base, jitterPercent);
4615
+ },
4616
+ initializeCursor(page) {
4617
+ return callDelegate("initializeCursor", page, []);
4618
+ },
4619
+ humanMove(page, target) {
4620
+ return callDelegate("humanMove", page, [target]);
4621
+ },
4622
+ humanScroll(page, target, options = {}) {
4623
+ return callDelegate("humanScroll", page, [target, options]);
4624
+ },
4625
+ humanClick(page, target, options = {}) {
4626
+ return callDelegate("humanClick", page, [target, options]);
4873
4627
  },
4874
4628
  randomSleep(pageOrBaseMs, maybeBaseMs, maybeJitterPercent) {
4875
- if (isPageLike2(pageOrBaseMs)) {
4876
- return resolveDelegate(pageOrBaseMs).randomSleep(maybeBaseMs, maybeJitterPercent);
4629
+ if (pageOrBaseMs && typeof pageOrBaseMs === "object" && typeof pageOrBaseMs.evaluate === "function") {
4630
+ const delegate = resolveDelegate(pageOrBaseMs);
4631
+ return delegate.randomSleep(maybeBaseMs, maybeJitterPercent);
4632
+ }
4633
+ return Humanize.randomSleep(pageOrBaseMs, maybeBaseMs);
4634
+ },
4635
+ simulateGaze(page, baseDurationMs = 2500) {
4636
+ return callDelegate("simulateGaze", page, [baseDurationMs]);
4637
+ },
4638
+ humanType(page, selector, text, options = {}) {
4639
+ return callDelegate("humanType", page, [selector, text, options]);
4640
+ },
4641
+ humanPress(page, targetOrKey, maybeKey, options = {}) {
4642
+ if (typeof maybeKey === "string") {
4643
+ return callDelegate("humanPress", page, [targetOrKey, maybeKey, options]);
4877
4644
  }
4878
- return desktopDelegate.randomSleep(pageOrBaseMs, maybeBaseMs);
4645
+ return callDelegate("humanPress", page, [targetOrKey, maybeKey || options]);
4646
+ },
4647
+ humanClear(page, selector) {
4648
+ return callDelegate("humanClear", page, [selector]);
4649
+ },
4650
+ warmUpBrowsing(page, baseDuration = 3500) {
4651
+ return callDelegate("warmUpBrowsing", page, [baseDuration]);
4652
+ },
4653
+ naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4654
+ return callDelegate("naturalScroll", page, [direction, distance, baseSteps]);
4879
4655
  }
4880
- });
4881
- var withHumanizeDelegates = (target, resolveDelegate) => withDelegatedProperties(target, {
4882
- namespace: "Humanize",
4883
- methods: HUMANIZE_DELEGATED_METHODS,
4884
- resolveDelegate: (_method, args) => ({
4885
- delegate: resolveDelegate(args[0]),
4886
- label: "resolved humanize delegate"
4887
- })
4888
- });
4889
- var createHumanizeExport = (context = {}) => {
4890
- return Object.assign(
4891
- createSharedHumanizeMethods(context),
4892
- withHumanizeDelegates({}, context.resolveDelegate)
4893
- );
4894
4656
  };
4895
- var DefaultHumanize = createHumanizeExport(DefaultHumanizeContext);
4896
- var CloakBrowserHumanize = createHumanizeExport(CloakBrowserHumanizeContext);
4897
-
4898
- // src/humanize.js
4899
- var humanizeStrategies = {
4900
- [Mode.Default]: DefaultHumanize,
4901
- [Mode.CloakBrowser]: CloakBrowserHumanize
4902
- };
4903
- var humanizeFacadeMethods = Object.freeze({
4904
- jitterMs: { enumerable: true },
4905
- initializeCursor: { enumerable: true },
4906
- humanMove: { enumerable: true },
4907
- humanScroll: { enumerable: true },
4908
- humanClick: { enumerable: true },
4909
- randomSleep: { enumerable: true },
4910
- simulateGaze: { enumerable: true },
4911
- humanType: { enumerable: true },
4912
- humanPress: { enumerable: true },
4913
- humanClear: { enumerable: true },
4914
- warmUpBrowsing: { enumerable: true },
4915
- naturalScroll: { enumerable: true }
4916
- });
4917
- var Humanize2 = createDelegatedFacade("Humanize", humanizeStrategies, humanizeFacadeMethods);
4918
4657
 
4919
- // src/internals/launch/default.js
4658
+ // src/launch.js
4920
4659
  var import_node_child_process = require("node:child_process");
4921
4660
  var import_fingerprint_generator = require("fingerprint-generator");
4922
4661
  var import_fingerprint_injector = require("fingerprint-injector");
@@ -5001,8 +4740,8 @@ var ByPass = {
5001
4740
  resolveRouteByProxy
5002
4741
  };
5003
4742
 
5004
- // src/internals/launch/default.js
5005
- var logger7 = createInternalLogger("Launch");
4743
+ // src/launch.js
4744
+ var logger8 = createInternalLogger("Launch");
5006
4745
  var REQUEST_HOOK_FLAG = Symbol("playwright-toolkit-request-hook");
5007
4746
  var injectedContexts = /* @__PURE__ */ new WeakSet();
5008
4747
  var browserMajorVersionCache = /* @__PURE__ */ new Map();
@@ -5054,7 +4793,7 @@ var detectBrowserMajorVersion = (launcher) => {
5054
4793
  });
5055
4794
  detectedVersion = parseChromeMajorVersion(rawVersion);
5056
4795
  } catch (error) {
5057
- logger7.warn(`\u8BFB\u53D6\u6D4F\u89C8\u5668\u7248\u672C\u5931\u8D25: ${error?.message || error}`);
4796
+ logger8.warn(`\u8BFB\u53D6\u6D4F\u89C8\u5668\u7248\u672C\u5931\u8D25: ${error?.message || error}`);
5058
4797
  }
5059
4798
  browserMajorVersionCache.set(executablePath, detectedVersion);
5060
4799
  return detectedVersion;
@@ -5068,7 +4807,7 @@ var resolveCoreDevice = (core = {}) => {
5068
4807
  };
5069
4808
  var buildFingerprintGenerator = ({ locale, browserMajorVersion, device }) => {
5070
4809
  return new import_fingerprint_generator.FingerprintGenerator(
5071
- DefaultAntiCheat.getFingerprintGeneratorOptions({
4810
+ AntiCheat.getFingerprintGeneratorOptions({
5072
4811
  locale,
5073
4812
  browserMajorVersion,
5074
4813
  device
@@ -5091,7 +4830,7 @@ var generateFingerprintForCore = ({ locale, browserMajorVersion, device }) => {
5091
4830
  if (requestedBrowserMajorVersion <= 0) {
5092
4831
  throw error;
5093
4832
  }
5094
- logger7.warn(
4833
+ logger8.warn(
5095
4834
  `\u5F53\u524D\u6D4F\u89C8\u5668\u5927\u7248\u672C ${requestedBrowserMajorVersion} \u65E0\u53EF\u7528 strict \u6307\u7EB9\u6837\u672C\uFF0C\u9000\u56DE\u4E0D\u7ED1\u5B9A\u5927\u7248\u672C\u751F\u6210: ${error?.message || error}`
5096
4835
  );
5097
4836
  }
@@ -5112,7 +4851,7 @@ var buildReplayableBrowserProfile = (runtimeState, launcher) => {
5112
4851
  }
5113
4852
  let nextState = RuntimeEnv.rememberState(runtimeState);
5114
4853
  let browserProfileCore = RuntimeEnv.getBrowserProfileCore(nextState);
5115
- const timezoneId = String(browserProfileCore?.timezone_id || "").trim() || DefaultAntiCheat.getBaseConfig().timezoneId;
4854
+ const timezoneId = String(browserProfileCore?.timezone_id || "").trim() || AntiCheat.getBaseConfig().timezoneId;
5116
4855
  const locale = DEFAULT_LOCALE;
5117
4856
  const currentBrowserMajorVersion = detectBrowserMajorVersion(launcher);
5118
4857
  const storedBrowserMajorVersion = Number(browserProfileCore?.browser_major_version || 0);
@@ -5136,7 +4875,7 @@ var buildReplayableBrowserProfile = (runtimeState, launcher) => {
5136
4875
  schema_version: DEFAULT_BROWSER_PROFILE_SCHEMA_VERSION
5137
4876
  };
5138
4877
  nextState = RuntimeEnv.setBrowserProfileCore(nextState, browserProfileCore);
5139
- logger7.info(
4878
+ logger8.info(
5140
4879
  `\u5DF2\u751F\u6210\u6D4F\u89C8\u5668\u6307\u7EB9\u771F\u6E90 | env=${String(nextState.envId || "-")} | device=${device} | version=${browserProfileCore.browser_major_version || "-"} | fingerprintVersion=${fingerprintBrowserMajorVersion || "-"} | exactVersion=${generated.exactBrowserMajorVersion ? "true" : "false"} | timezone=${timezoneId}`
5141
4880
  );
5142
4881
  return { runtimeState: nextState, browserProfileCore };
@@ -5191,7 +4930,7 @@ var applyFingerprintPageOptions = (pageOptions = {}, { fingerprintWithHeaders =
5191
4930
  pageOptions.timezoneId = timezoneId;
5192
4931
  }
5193
4932
  };
5194
- var buildReplayBrowserPoolOptions = (browserProfileCore) => {
4933
+ var buildReplayBrowserPoolOptions = (browserProfileCore, modifyPageOptions = null) => {
5195
4934
  const fingerprintWithHeaders = browserProfileCore?.fingerprint;
5196
4935
  const fingerprint = fingerprintWithHeaders?.fingerprint;
5197
4936
  if (!fingerprintWithHeaders || !fingerprint) {
@@ -5200,13 +4939,16 @@ var buildReplayBrowserPoolOptions = (browserProfileCore) => {
5200
4939
  return {
5201
4940
  useFingerprints: false,
5202
4941
  prePageCreateHooks: [
5203
- (_pageId, _browserController, pageOptions = {}) => {
4942
+ async (pageId, browserController, pageOptions = {}) => {
5204
4943
  if (!pageOptions || typeof pageOptions !== "object") return;
5205
4944
  applyFingerprintPageOptions(pageOptions, {
5206
4945
  fingerprintWithHeaders,
5207
4946
  locale: browserProfileCore.locale,
5208
4947
  timezoneId: browserProfileCore.timezone_id
5209
4948
  });
4949
+ if (modifyPageOptions) {
4950
+ await modifyPageOptions(pageOptions, { pageId, browserController });
4951
+ }
5210
4952
  }
5211
4953
  ],
5212
4954
  postPageCreateHooks: [
@@ -5221,7 +4963,7 @@ var buildReplayBrowserPoolOptions = (browserProfileCore) => {
5221
4963
  ]
5222
4964
  };
5223
4965
  };
5224
- var DefaultLaunch = {
4966
+ var Launch = {
5225
4967
  getPlaywrightCrawlerOptions(options = {}) {
5226
4968
  const normalizedOptions = Array.isArray(options) ? { customArgs: options } : options || {};
5227
4969
  const {
@@ -5232,11 +4974,13 @@ var DefaultLaunch = {
5232
4974
  debugMode = false,
5233
4975
  isRunningOnApify = false,
5234
4976
  launcher = null,
4977
+ hooks = {},
5235
4978
  preNavigationHooks = [],
5236
4979
  postNavigationHooks = [],
5237
4980
  runtimeState = null
5238
4981
  } = normalizedOptions;
5239
4982
  const device = resolveRuntimeDevice(runtimeState);
4983
+ const modifyPageOptions = typeof hooks?.modifyPageOptions === "function" ? hooks.modifyPageOptions : null;
5240
4984
  const { byPassDomains, enableProxy, proxyUrl } = resolveProxyLaunchOptions(proxyConfiguration);
5241
4985
  const byPassRules = ByPass.buildByPassDomainRules(byPassDomains);
5242
4986
  const proxyMeter = enableProxy && proxyUrl ? ProxyMeterRuntime.startProxyMeter({ proxyUrl, debugMode }) : null;
@@ -5245,11 +4989,11 @@ var DefaultLaunch = {
5245
4989
  launchProxy.bypass = byPassDomains.join(",");
5246
4990
  }
5247
4991
  const replayContext = buildReplayableBrowserProfile(runtimeState, launcher);
5248
- const replayBrowserPoolOptions = buildReplayBrowserPoolOptions(replayContext.browserProfileCore);
4992
+ const replayBrowserPoolOptions = buildReplayBrowserPoolOptions(replayContext.browserProfileCore, modifyPageOptions);
5249
4993
  const launchLocale = String(replayContext.browserProfileCore?.locale || DEFAULT_LOCALE).trim() || DEFAULT_LOCALE;
5250
4994
  const launchOptions = {
5251
4995
  args: [
5252
- ...DefaultAntiCheat.getLaunchArgs({ locale: launchLocale }),
4996
+ ...AntiCheat.getLaunchArgs({ locale: launchLocale }),
5253
4997
  ...customArgs
5254
4998
  ],
5255
4999
  ignoreDefaultArgs: ["--enable-automation"]
@@ -5265,18 +5009,18 @@ var DefaultLaunch = {
5265
5009
  upstreamLabel = `${parsedProxyUrl.protocol}//${parsedProxyUrl.host}`;
5266
5010
  } catch {
5267
5011
  }
5268
- logger7.info(
5012
+ logger8.info(
5269
5013
  `[\u4EE3\u7406\u5DF2\u542F\u7528] \u672C\u5730=${launchProxy.server} \u4E0A\u6E38=${upstreamLabel || "-"} \u76F4\u8FDE\u57DF\u540D=${(byPassDomains || []).join(",")}`
5270
5014
  );
5271
- logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
5015
+ logger8.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
5272
5016
  } else if (enableByPassLogger && enableProxy && !launchProxy) {
5273
- logger7.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=true \u4F46 proxy_url \u4E3A\u7A7A");
5274
- logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
5017
+ logger8.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=true \u4F46 proxy_url \u4E3A\u7A7A");
5018
+ logger8.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
5275
5019
  } else if (enableByPassLogger && !enableProxy && proxyUrl) {
5276
- logger7.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=false \u4E14 proxy_url \u5DF2\u914D\u7F6E");
5277
- logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
5020
+ logger8.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=false \u4E14 proxy_url \u5DF2\u914D\u7F6E");
5021
+ logger8.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
5278
5022
  } else if (enableByPassLogger) {
5279
- logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
5023
+ logger8.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
5280
5024
  }
5281
5025
  const onPageCreated = (page) => {
5282
5026
  const recommendedGotoOptions = {
@@ -5298,7 +5042,7 @@ var DefaultLaunch = {
5298
5042
  }
5299
5043
  if (!enableByPassLogger || byPassDomains.length === 0) return;
5300
5044
  if (!matched || !matched.rule) return;
5301
- logger7.info(`[\u76F4\u8FDE\u547D\u4E2D] \u89C4\u5219=${matched.rule.pattern} \u57DF\u540D=${matched.hostname} \u8D44\u6E90\u7C7B\u578B=${resourceType} \u65B9\u6CD5=${req.method()} \u5730\u5740=${requestUrl}`);
5045
+ logger8.info(`[\u76F4\u8FDE\u547D\u4E2D] \u89C4\u5219=${matched.rule.pattern} \u57DF\u540D=${matched.hostname} \u8D44\u6E90\u7C7B\u578B=${resourceType} \u65B9\u6CD5=${req.method()} \u5730\u5740=${requestUrl}`);
5302
5046
  };
5303
5047
  page.on("request", requestHandler);
5304
5048
  return recommendedGotoOptions;
@@ -5317,20 +5061,23 @@ var DefaultLaunch = {
5317
5061
  browserPoolOptions: replayBrowserPoolOptions || {
5318
5062
  useFingerprints: true,
5319
5063
  fingerprintOptions: {
5320
- fingerprintGeneratorOptions: DefaultAntiCheat.getFingerprintGeneratorOptions({
5064
+ fingerprintGeneratorOptions: AntiCheat.getFingerprintGeneratorOptions({
5321
5065
  locale: launchLocale,
5322
5066
  device
5323
5067
  })
5324
5068
  },
5325
5069
  prePageCreateHooks: [
5326
- (_pageId, browserController, pageOptions = {}) => {
5070
+ async (pageId, browserController, pageOptions = {}) => {
5327
5071
  const fingerprintWithHeaders = browserController?.launchContext?.fingerprint;
5328
- const timezoneId = DefaultAntiCheat.getBaseConfig().timezoneId;
5072
+ const timezoneId = AntiCheat.getBaseConfig().timezoneId;
5329
5073
  applyFingerprintPageOptions(pageOptions, {
5330
5074
  fingerprintWithHeaders,
5331
5075
  locale: launchLocale,
5332
5076
  timezoneId
5333
5077
  });
5078
+ if (modifyPageOptions) {
5079
+ await modifyPageOptions(pageOptions, { pageId, browserController });
5080
+ }
5334
5081
  }
5335
5082
  ]
5336
5083
  },
@@ -5352,263 +5099,6 @@ var DefaultLaunch = {
5352
5099
  }
5353
5100
  };
5354
5101
 
5355
- // src/internals/launch/cloakbrowser.js
5356
- var import_node_child_process2 = require("node:child_process");
5357
- var import_node_util = require("node:util");
5358
- var logger8 = createInternalLogger("CloakBrowser");
5359
- var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
5360
- var DEFAULT_CLOAK_CRAWLER_BASE_OPTIONS = Object.freeze({
5361
- maxConcurrency: 1,
5362
- maxRequestRetries: 0,
5363
- requestHandlerTimeoutSecs: 240,
5364
- navigationTimeoutSecs: 120
5365
- });
5366
- var DEFAULT_CLOAK_HUMANIZE_OPTIONS = Object.freeze({
5367
- humanize: true
5368
- });
5369
- var DEFAULT_CLOAK_GOTO_OPTIONS = Object.freeze({
5370
- waitUntil: "commit"
5371
- });
5372
- var cachedCloakBrowserModulePromise = null;
5373
- var hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);
5374
- var loadCloakBrowserModule = async () => {
5375
- if (!cachedCloakBrowserModulePromise) {
5376
- cachedCloakBrowserModulePromise = import("cloakbrowser").catch((error) => {
5377
- cachedCloakBrowserModulePromise = null;
5378
- throw new Error("cloakbrowser \u6A21\u5757\u52A0\u8F7D\u5931\u8D25\uFF0C\u8BF7\u786E\u8BA4\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u5DF2\u5B89\u88C5 cloakbrowser\u3002", {
5379
- cause: error
5380
- });
5381
- });
5382
- }
5383
- return cachedCloakBrowserModulePromise;
5384
- };
5385
- var buildCloakLaunchOptions = async (options = {}) => {
5386
- const { buildLaunchOptions } = await loadCloakBrowserModule();
5387
- return await buildLaunchOptions(normalizeObject(options));
5388
- };
5389
- var normalizeObject = (value) => {
5390
- if (!value || typeof value !== "object" || Array.isArray(value)) {
5391
- return {};
5392
- }
5393
- return value;
5394
- };
5395
- var normalizeStringArray = (value) => {
5396
- if (!Array.isArray(value)) {
5397
- return [];
5398
- }
5399
- return value.map((item) => String(item || "").trim()).filter(Boolean);
5400
- };
5401
- var resolveCloakBrowserProxy = (proxyConfiguration = {}) => {
5402
- const config = normalizeObject(proxyConfiguration);
5403
- const proxyUrl = String(config.proxy_url || "").trim();
5404
- const enableProxy = typeof config.enable_proxy === "boolean" ? config.enable_proxy : proxyUrl !== "";
5405
- if (!enableProxy || !proxyUrl) {
5406
- return null;
5407
- }
5408
- const byPassDomains = ByPass.normalizeByPassDomains(config.by_pass_domains);
5409
- if (byPassDomains.length === 0) {
5410
- return proxyUrl;
5411
- }
5412
- const parsedProxyUrl = new URL(proxyUrl.includes("://") ? proxyUrl : `http://${proxyUrl}`);
5413
- return {
5414
- server: `${parsedProxyUrl.protocol}//${parsedProxyUrl.host}`,
5415
- username: decodeURIComponent(parsedProxyUrl.username || ""),
5416
- password: decodeURIComponent(parsedProxyUrl.password || ""),
5417
- bypass: byPassDomains.join(",")
5418
- };
5419
- };
5420
- var extractFingerprintArg = (launchOptions = {}) => {
5421
- const args = Array.isArray(launchOptions?.args) ? launchOptions.args : [];
5422
- return args.find((value) => String(value || "").startsWith("--fingerprint=")) || "";
5423
- };
5424
- var createStableGotoHook = (recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS) => {
5425
- const normalizedRecommendedGotoOptions = normalizeObject(recommendedGotoOptions);
5426
- const fallbackGotoOptions = Object.keys(normalizedRecommendedGotoOptions).length > 0 ? normalizedRecommendedGotoOptions : DEFAULT_CLOAK_GOTO_OPTIONS;
5427
- return async (_crawlingContext, gotoOptions = {}) => {
5428
- for (const [key, value] of Object.entries(fallbackGotoOptions)) {
5429
- if (gotoOptions[key] == null) {
5430
- gotoOptions[key] = value;
5431
- }
5432
- }
5433
- };
5434
- };
5435
- var attachCloakBrowserHumanizeHook = ({
5436
- browserPoolOptions = {},
5437
- activeBrowsers,
5438
- patchedBrowsers,
5439
- humanizeOptions = DEFAULT_CLOAK_HUMANIZE_OPTIONS
5440
- } = {}) => {
5441
- const normalizedBrowserPoolOptions = normalizeObject(browserPoolOptions);
5442
- const shouldHumanize = humanizeOptions !== false;
5443
- const normalizedHumanizeOptions = shouldHumanize ? {
5444
- ...DEFAULT_CLOAK_HUMANIZE_OPTIONS,
5445
- ...normalizeObject(humanizeOptions)
5446
- } : null;
5447
- return {
5448
- ...normalizedBrowserPoolOptions,
5449
- useFingerprints: false,
5450
- postLaunchHooks: [
5451
- ...Array.isArray(normalizedBrowserPoolOptions.postLaunchHooks) ? normalizedBrowserPoolOptions.postLaunchHooks : [],
5452
- async (_pageId, browserController) => {
5453
- const browser = browserController?.browser;
5454
- if (!browser || typeof browser.contexts !== "function") {
5455
- return;
5456
- }
5457
- activeBrowsers.add(browser);
5458
- if (typeof browser.once === "function") {
5459
- browser.once("disconnected", () => {
5460
- activeBrowsers.delete(browser);
5461
- });
5462
- }
5463
- if (!shouldHumanize || patchedBrowsers.has(browser)) {
5464
- return;
5465
- }
5466
- const { humanizeBrowser } = await loadCloakBrowserModule();
5467
- await humanizeBrowser(browser, normalizedHumanizeOptions);
5468
- patchedBrowsers.add(browser);
5469
- }
5470
- ]
5471
- };
5472
- };
5473
- var closeTrackedBrowsers = async (activeBrowsers) => {
5474
- const browsers = Array.from(activeBrowsers || []);
5475
- activeBrowsers?.clear?.();
5476
- await Promise.allSettled(
5477
- browsers.map(async (browser) => {
5478
- if (!browser || typeof browser.isConnected !== "function" || !browser.isConnected()) {
5479
- return;
5480
- }
5481
- await browser.close().catch(() => {
5482
- });
5483
- })
5484
- );
5485
- };
5486
- var forceTerminateBrowsersByFingerprintArg = async (fingerprintArg) => {
5487
- if (!fingerprintArg) {
5488
- return;
5489
- }
5490
- await execFileAsync("pkill", ["-f", "--", fingerprintArg]).catch((error) => {
5491
- if (error?.code === 1 || error?.code === "ENOENT") {
5492
- return;
5493
- }
5494
- logger8.info(`\u5F3A\u5236\u5173\u95ED CloakBrowser \u8FDB\u7A0B\u5931\u8D25\uFF08\u5FFD\u7565\uFF09: ${error?.message || String(error)}`);
5495
- });
5496
- };
5497
- var CloakBrowserLaunch = {
5498
- resolveProxyConfiguration(proxyConfiguration = {}) {
5499
- return resolveCloakBrowserProxy(proxyConfiguration);
5500
- },
5501
- extractFingerprintArg(launchOptions = {}) {
5502
- return extractFingerprintArg(launchOptions);
5503
- },
5504
- createStableGotoHook(recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS) {
5505
- return createStableGotoHook(recommendedGotoOptions);
5506
- },
5507
- async getPlaywrightCrawlerOptions(options = {}) {
5508
- const runtime2 = await CloakBrowserLaunch.createPlaywrightCrawlerRuntime(options);
5509
- return Object.defineProperties(runtime2.crawlerOptions, {
5510
- cleanup: {
5511
- enumerable: false,
5512
- value: runtime2.cleanup
5513
- },
5514
- closeActiveBrowsers: {
5515
- enumerable: false,
5516
- value: runtime2.closeActiveBrowsers
5517
- },
5518
- forceTerminateActiveProcesses: {
5519
- enumerable: false,
5520
- value: runtime2.forceTerminateActiveProcesses
5521
- }
5522
- });
5523
- },
5524
- async buildLaunchOptions(options = {}) {
5525
- return await buildCloakLaunchOptions(options);
5526
- },
5527
- async createPlaywrightCrawlerRuntime(options = {}) {
5528
- const normalizedOptions = normalizeObject(options);
5529
- const {
5530
- proxyConfiguration = {},
5531
- runInHeadfulMode = false,
5532
- isRunningOnApify = false,
5533
- launcher = null,
5534
- cloakOptions = {},
5535
- humanizeOptions = DEFAULT_CLOAK_HUMANIZE_OPTIONS,
5536
- crawlerBaseOptions = {},
5537
- browserPoolOptions = {},
5538
- launchContext = {},
5539
- preNavigationHooks = [],
5540
- postNavigationHooks = [],
5541
- recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS
5542
- } = normalizedOptions;
5543
- const normalizedCloakOptions = normalizeObject(cloakOptions);
5544
- const activeBrowsers = /* @__PURE__ */ new Set();
5545
- const patchedBrowsers = /* @__PURE__ */ new WeakSet();
5546
- const defaultArgs = isRunningOnApify ? ["--no-sandbox", "--disable-setuid-sandbox"] : [];
5547
- const extraArgs = normalizeStringArray(normalizedCloakOptions.args);
5548
- const proxy = hasOwn(normalizedCloakOptions, "proxy") ? normalizedCloakOptions.proxy : resolveCloakBrowserProxy(proxyConfiguration);
5549
- const headless = hasOwn(normalizedCloakOptions, "headless") ? normalizedCloakOptions.headless : !runInHeadfulMode || isRunningOnApify;
5550
- const mergedCloakOptions = {
5551
- ...normalizedCloakOptions,
5552
- headless,
5553
- proxy,
5554
- args: [...defaultArgs, ...extraArgs]
5555
- };
5556
- const launchOptions = await buildCloakLaunchOptions(mergedCloakOptions);
5557
- const fingerprintArg = extractFingerprintArg(launchOptions);
5558
- const internalPreNavigationHook = createStableGotoHook(recommendedGotoOptions);
5559
- const normalizedPreNavigationHooks = Array.isArray(preNavigationHooks) ? preNavigationHooks : [];
5560
- const normalizedPostNavigationHooks = Array.isArray(postNavigationHooks) ? postNavigationHooks : [];
5561
- const crawlerOptions = {
5562
- ...DEFAULT_CLOAK_CRAWLER_BASE_OPTIONS,
5563
- ...normalizeObject(crawlerBaseOptions),
5564
- headless,
5565
- launchContext: {
5566
- useIncognitoPages: true,
5567
- ...normalizeObject(launchContext),
5568
- ...launcher ? { launcher } : {},
5569
- launchOptions
5570
- },
5571
- browserPoolOptions: attachCloakBrowserHumanizeHook({
5572
- browserPoolOptions,
5573
- activeBrowsers,
5574
- patchedBrowsers,
5575
- humanizeOptions
5576
- }),
5577
- preNavigationHooks: [internalPreNavigationHook, ...normalizedPreNavigationHooks],
5578
- ...normalizedPostNavigationHooks.length > 0 ? { postNavigationHooks: normalizedPostNavigationHooks } : {}
5579
- };
5580
- const closeActiveBrowsers = async () => {
5581
- await closeTrackedBrowsers(activeBrowsers);
5582
- };
5583
- const forceTerminateActiveProcesses = async () => {
5584
- await forceTerminateBrowsersByFingerprintArg(fingerprintArg);
5585
- };
5586
- const cleanup = async () => {
5587
- await closeActiveBrowsers();
5588
- await forceTerminateActiveProcesses();
5589
- };
5590
- return {
5591
- headless,
5592
- launchOptions,
5593
- fingerprintArg,
5594
- crawlerOptions,
5595
- closeActiveBrowsers,
5596
- forceTerminateActiveProcesses,
5597
- cleanup
5598
- };
5599
- }
5600
- };
5601
-
5602
- // src/launch.js
5603
- var launchStrategies = {
5604
- [Mode.Default]: DefaultLaunch,
5605
- [Mode.CloakBrowser]: CloakBrowserLaunch
5606
- };
5607
- var launchFacadeMethods = Object.freeze({
5608
- getPlaywrightCrawlerOptions: { enumerable: true }
5609
- });
5610
- var Launch = createDelegatedFacade("Launch", launchStrategies, launchFacadeMethods);
5611
-
5612
5102
  // src/live-view.js
5613
5103
  var import_express = __toESM(require("express"), 1);
5614
5104
  var import_apify = require("apify");
@@ -6696,7 +6186,7 @@ var Mutation = {
6696
6186
  const overallTimeout = options.timeout ?? 180 * 1e3;
6697
6187
  const onMutation = options.onMutation;
6698
6188
  const pollInterval = 500;
6699
- const sleep2 = (ms) => new Promise((resolve) => {
6189
+ const sleep = (ms) => new Promise((resolve) => {
6700
6190
  setTimeout(resolve, ms);
6701
6191
  });
6702
6192
  const truncate = (value, max = 800) => {
@@ -6857,8 +6347,8 @@ var Mutation = {
6857
6347
  throw e;
6858
6348
  }
6859
6349
  }
6860
- let state2 = await buildState();
6861
- if (!state2?.hasMatched) {
6350
+ let state = await buildState();
6351
+ if (!state?.hasMatched) {
6862
6352
  logger12.warning("waitForStableAcrossRoots \u672A\u627E\u5230\u53EF\u76D1\u63A7\u7684\u5143\u7D20");
6863
6353
  return { mutationCount: 0, stableTime: 0, wasPaused: false };
6864
6354
  }
@@ -6866,7 +6356,7 @@ var Mutation = {
6866
6356
  let stableSince = 0;
6867
6357
  let isPaused = false;
6868
6358
  let wasPaused = false;
6869
- let lastSnapshotKey = state2.snapshotKey;
6359
+ let lastSnapshotKey = state.snapshotKey;
6870
6360
  const applyPauseSignal = (signal) => {
6871
6361
  const nextPaused = signal === "__PAUSE__";
6872
6362
  if (nextPaused) {
@@ -6880,15 +6370,15 @@ var Mutation = {
6880
6370
  };
6881
6371
  const initialSignal = await invokeMutationCallback({
6882
6372
  mutationCount: 0,
6883
- html: state2.html || "",
6884
- text: state2.text || "",
6885
- mutationNodes: state2.mutationNodes || []
6373
+ html: state.html || "",
6374
+ text: state.text || "",
6375
+ mutationNodes: state.mutationNodes || []
6886
6376
  });
6887
6377
  applyPauseSignal(initialSignal);
6888
6378
  const deadline = Date.now() + overallTimeout;
6889
- let lastState = state2;
6379
+ let lastState = state;
6890
6380
  while (Date.now() < deadline) {
6891
- await sleep2(pollInterval);
6381
+ await sleep(pollInterval);
6892
6382
  lastState = await buildState();
6893
6383
  if (!lastState?.hasMatched) {
6894
6384
  continue;
@@ -7976,7 +7466,7 @@ var Logger = {
7976
7466
  };
7977
7467
 
7978
7468
  // src/share.js
7979
- var import_delay5 = __toESM(require("delay"), 1);
7469
+ var import_delay4 = __toESM(require("delay"), 1);
7980
7470
 
7981
7471
  // src/internals/watermarkify.js
7982
7472
  var DEFAULT_TIMEZONE_OFFSET = 8;
@@ -8472,10 +7962,7 @@ var buildWatermarkifyRenderHtml = ({ imageSrc, overlaySvg, width, height, imageH
8472
7962
  </html>
8473
7963
  `;
8474
7964
  };
8475
- var normalizeWatermarkifyRenderMode = (value) => {
8476
- return String(value || "default").trim().toLowerCase() === "cloakbrowser" ? "cloakbrowser" : "default";
8477
- };
8478
- var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageInfo = {}, options = {}) => {
7965
+ var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageInfo = {}) => {
8479
7966
  if (!page || typeof page.context !== "function") {
8480
7967
  logger13.warning("watermarkify \u6D4F\u89C8\u5668\u5408\u6210\u8DF3\u8FC7: \u7F3A\u5C11\u53EF\u7528 page");
8481
7968
  return buffer;
@@ -8499,35 +7986,15 @@ var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageI
8499
7986
  height: viewportHeight
8500
7987
  }).catch(() => {
8501
7988
  });
8502
- const renderMode = normalizeWatermarkifyRenderMode(options.mode);
8503
- if (renderMode === "cloakbrowser") {
8504
- const renderHtml = buildWatermarkifyRenderHtml({
8505
- imageSrc: `data:${imageInfo.mimeType || "image/png"};base64,${buffer.toString("base64")}`,
8506
- overlaySvg,
8507
- width: safeWidth,
8508
- height: safeHeight,
8509
- imageHeight: safeImageHeight
8510
- });
8511
- await renderPage.goto("about:blank", {
8512
- waitUntil: "commit"
8513
- }).catch(() => {
8514
- });
8515
- await renderPage.evaluate((html) => {
8516
- document.open();
8517
- document.write(html);
8518
- document.close();
8519
- }, renderHtml);
8520
- } else {
8521
- await renderPage.setContent(buildWatermarkifyRenderHtml({
8522
- imageSrc: `data:${imageInfo.mimeType || "image/png"};base64,${buffer.toString("base64")}`,
8523
- overlaySvg,
8524
- width: safeWidth,
8525
- height: safeHeight,
8526
- imageHeight: safeImageHeight
8527
- }), {
8528
- waitUntil: "load"
8529
- });
8530
- }
7989
+ await renderPage.setContent(buildWatermarkifyRenderHtml({
7990
+ imageSrc: `data:${imageInfo.mimeType || "image/png"};base64,${buffer.toString("base64")}`,
7991
+ overlaySvg,
7992
+ width: safeWidth,
7993
+ height: safeHeight,
7994
+ imageHeight: safeImageHeight
7995
+ }), {
7996
+ waitUntil: "load"
7997
+ });
8531
7998
  await renderPage.waitForFunction(() => {
8532
7999
  const image = document.getElementById("pk-base-image");
8533
8000
  return image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0 && image.naturalHeight > 0;
@@ -9462,7 +8929,7 @@ var buildWatermarkifySvg = (meta, imageWidth, imageHeight) => {
9462
8929
  </svg>
9463
8930
  `;
9464
8931
  };
9465
- var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {}) => {
8932
+ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null) => {
9466
8933
  const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
9467
8934
  const hasStrip = meta?.strip?.enabled !== false && Array.isArray(meta?.stripSegments) && meta.stripSegments.length > 0;
9468
8935
  if (!Buffer.isBuffer(buffer) || !meta || !hasWatermark && !hasStrip) {
@@ -9483,7 +8950,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
9483
8950
  if (!overlaySvg) {
9484
8951
  return buffer;
9485
8952
  }
9486
- return await composeScreenshotBufferWithBrowser(page, buffer, overlaySvg, outputImageInfo, options);
8953
+ return await composeScreenshotBufferWithBrowser(page, buffer, overlaySvg, outputImageInfo);
9487
8954
  };
9488
8955
 
9489
8956
  // src/internals/compression.js
@@ -10047,7 +9514,7 @@ var Share = {
10047
9514
  );
10048
9515
  nextProgressLogTs = now + 5e3;
10049
9516
  }
10050
- await (0, import_delay5.default)(Math.max(0, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining)));
9517
+ await (0, import_delay4.default)(Math.max(0, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining)));
10051
9518
  }
10052
9519
  if (!timeoutDisabled && share.mode === "response" && stats.responseMatched === 0) {
10053
9520
  logger15.warning(
@@ -10081,7 +9548,6 @@ var Share = {
10081
9548
  * @param {number} [options.maxBytes] 默认 5MiB,返回 base64 超过后会压缩
10082
9549
  * @param {'jpeg'|'jpg'} [options.type] 压缩输出格式,默认 jpeg
10083
9550
  * @param {boolean|Object} [options.compression] 传 false 可关闭压缩
10084
- * @param {'default'|'cloakbrowser'} [options.mode] 截图水印合成模式,默认 default
10085
9551
  * @returns {Promise<string>} base64 image
10086
9552
  */
10087
9553
  async captureScreen(page, options = {}) {
@@ -10102,9 +9568,7 @@ var Share = {
10102
9568
  ...screenshotWatermarkify,
10103
9569
  capturedAt
10104
9570
  });
10105
- outputBuffer = await watermarkifyScreenshotBuffer(rawBuffer, watermarkifyMeta, page, {
10106
- mode: options.mode === "cloakbrowser" ? "cloakbrowser" : "default"
10107
- });
9571
+ outputBuffer = await watermarkifyScreenshotBuffer(rawBuffer, watermarkifyMeta, page);
10108
9572
  }
10109
9573
  return await compressImageBufferToBase64(outputBuffer, compression);
10110
9574
  }
@@ -10112,9 +9576,8 @@ var Share = {
10112
9576
 
10113
9577
  // entrys/node.js
10114
9578
  Logger.setLogger(import_crawlee.log);
10115
- var usePlaywrightToolKit = (mode = "default") => {
10116
- setToolkitMode(mode);
10117
- const toolkit = {
9579
+ var usePlaywrightToolKit = () => {
9580
+ return {
10118
9581
  ApifyKit,
10119
9582
  AntiCheat,
10120
9583
  DeviceInput,
@@ -10134,7 +9597,6 @@ var usePlaywrightToolKit = (mode = "default") => {
10134
9597
  ByPass,
10135
9598
  $Internals: { LOG_TEMPLATES, stripAnsi }
10136
9599
  };
10137
- return toolkit;
10138
9600
  };
10139
9601
  // Annotate the CommonJS export names for ESM import in node:
10140
9602
  0 && (module.exports = {