@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.js CHANGED
@@ -13,11 +13,9 @@ __export(constants_exports, {
13
13
  ActorInfo: () => ActorInfo,
14
14
  Code: () => Code,
15
15
  Device: () => Device,
16
- Mode: () => Mode,
17
16
  PresetOfLiveViewKey: () => PresetOfLiveViewKey,
18
17
  Status: () => Status,
19
- normalizeDevice: () => normalizeDevice,
20
- normalizeMode: () => normalizeMode
18
+ normalizeDevice: () => normalizeDevice
21
19
  });
22
20
  var Code = {
23
21
  Success: 0,
@@ -37,10 +35,6 @@ var Device = Object.freeze({
37
35
  Desktop: "desktop",
38
36
  Mobile: "mobile"
39
37
  });
40
- var Mode = Object.freeze({
41
- Default: "default",
42
- CloakBrowser: "cloakbrowser"
43
- });
44
38
  var normalizeDevice = (value, fallback = Device.Desktop) => {
45
39
  const normalizedFallback = String(fallback || "").trim().toLowerCase() === Device.Mobile ? Device.Mobile : Device.Desktop;
46
40
  const raw = String(value || "").trim().toLowerCase();
@@ -48,13 +42,6 @@ var normalizeDevice = (value, fallback = Device.Desktop) => {
48
42
  if (raw === Device.Desktop) return Device.Desktop;
49
43
  return normalizedFallback;
50
44
  };
51
- var normalizeMode = (value, fallback = Mode.Default) => {
52
- const normalizedFallback = String(fallback || "").trim().toLowerCase() === Mode.CloakBrowser ? Mode.CloakBrowser : Mode.Default;
53
- const raw = String(value || "").trim().toLowerCase();
54
- if (raw === Mode.CloakBrowser) return Mode.CloakBrowser;
55
- if (raw === Mode.Default) return Mode.Default;
56
- return normalizedFallback;
57
- };
58
45
  var createActorInfo = (info) => {
59
46
  const normalizeDomain = (value) => {
60
47
  if (!value) return "";
@@ -122,6 +109,7 @@ var createActorInfo = (info) => {
122
109
  const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path4 }) => {
123
110
  const safeProtocol = String(protocol2).trim();
124
111
  const safeDomain = normalizeDomain(domain2);
112
+ if (!safeDomain) return "";
125
113
  const safePath = normalizePath(path4);
126
114
  return `${safeProtocol}://${safeDomain}${safePath}`;
127
115
  };
@@ -329,6 +317,18 @@ var ActorInfo = {
329
317
  prefix: "",
330
318
  xurl: []
331
319
  }
320
+ }),
321
+ // 通用网页抓取 Actor:入口 URL 来自 query,因此这里只声明统一的 Actor 元信息。
322
+ webpage: createActorInfo({
323
+ key: "webpage",
324
+ name: "\u901A\u7528\u7F51\u9875",
325
+ domain: "",
326
+ path: "/",
327
+ share: {
328
+ mode: "dom",
329
+ prefix: "",
330
+ xurl: []
331
+ }
332
332
  })
333
333
  };
334
334
 
@@ -753,7 +753,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
753
753
  if (safeTargetHeight <= viewportHeight + 1) {
754
754
  return 0;
755
755
  }
756
- const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
756
+ const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
757
757
  const isVisible = (el, style, rect) => {
758
758
  if (!el || !style || !rect) return false;
759
759
  if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") {
@@ -793,7 +793,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
793
793
  return true;
794
794
  });
795
795
  topLevelCandidates.forEach(({ el, position, edge }) => {
796
- if (!hasOwn2(el.dataset, "pkAffixedAdjusted")) {
796
+ if (!hasOwn(el.dataset, "pkAffixedAdjusted")) {
797
797
  el.dataset.pkAffixedAdjusted = "1";
798
798
  el.dataset.pkOrigPosition = el.style.getPropertyValue("position") || "";
799
799
  el.dataset.pkOrigPositionPriority = el.style.getPropertyPriority("position") || "";
@@ -824,7 +824,7 @@ var adjustAffixedElementsForExpandedScreenshot = async (page, options = {}) => {
824
824
  };
825
825
  var restoreAffixedElementsForExpandedScreenshot = async (page) => {
826
826
  await page.evaluate((className) => {
827
- const hasOwn2 = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
827
+ const hasOwn = (source, key) => Object.prototype.hasOwnProperty.call(source, key);
828
828
  const expansionKeys = [
829
829
  "pkOrigOverflow",
830
830
  "pkOrigHeight",
@@ -832,28 +832,28 @@ var restoreAffixedElementsForExpandedScreenshot = async (page) => {
832
832
  "pkOrigMaxHeight"
833
833
  ];
834
834
  document.querySelectorAll('[data-pk-affixed-adjusted="1"]').forEach((el) => {
835
- if (hasOwn2(el.dataset, "pkOrigPosition")) {
835
+ if (hasOwn(el.dataset, "pkOrigPosition")) {
836
836
  el.style.setProperty("position", el.dataset.pkOrigPosition || "", el.dataset.pkOrigPositionPriority || "");
837
837
  delete el.dataset.pkOrigPosition;
838
838
  delete el.dataset.pkOrigPositionPriority;
839
839
  }
840
- if (hasOwn2(el.dataset, "pkOrigTop")) {
840
+ if (hasOwn(el.dataset, "pkOrigTop")) {
841
841
  el.style.setProperty("top", el.dataset.pkOrigTop || "", el.dataset.pkOrigTopPriority || "");
842
842
  delete el.dataset.pkOrigTop;
843
843
  delete el.dataset.pkOrigTopPriority;
844
844
  }
845
- if (hasOwn2(el.dataset, "pkOrigBottom")) {
845
+ if (hasOwn(el.dataset, "pkOrigBottom")) {
846
846
  el.style.setProperty("bottom", el.dataset.pkOrigBottom || "", el.dataset.pkOrigBottomPriority || "");
847
847
  delete el.dataset.pkOrigBottom;
848
848
  delete el.dataset.pkOrigBottomPriority;
849
849
  }
850
- if (hasOwn2(el.dataset, "pkOrigTranslate")) {
850
+ if (hasOwn(el.dataset, "pkOrigTranslate")) {
851
851
  el.style.setProperty("translate", el.dataset.pkOrigTranslate || "", el.dataset.pkOrigTranslatePriority || "");
852
852
  delete el.dataset.pkOrigTranslate;
853
853
  delete el.dataset.pkOrigTranslatePriority;
854
854
  }
855
855
  delete el.dataset.pkAffixedAdjusted;
856
- const stillExpanded = expansionKeys.some((key) => hasOwn2(el.dataset, key));
856
+ const stillExpanded = expansionKeys.some((key) => hasOwn(el.dataset, key));
857
857
  if (!stillExpanded) {
858
858
  el.classList.remove(className);
859
859
  }
@@ -896,7 +896,7 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
896
896
  viewportResized
897
897
  };
898
898
  };
899
- var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
899
+ var restoreExpandedFullPageScreenshot = async (page, state = {}) => {
900
900
  await page.evaluate((className) => {
901
901
  const targets = new Set([
902
902
  ...document.querySelectorAll(`.${className}`),
@@ -920,8 +920,8 @@ var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
920
920
  el.classList.remove(className);
921
921
  });
922
922
  }, EXPANDED_SCROLLABLE_CLASS);
923
- if (state2?.originalViewport && state2?.viewportResized) {
924
- await page.setViewportSize(state2.originalViewport);
923
+ if (state?.originalViewport && state?.viewportResized) {
924
+ await page.setViewportSize(state.originalViewport);
925
925
  }
926
926
  };
927
927
  var capturePageScreenshot = async (page, options = {}) => {
@@ -978,21 +978,21 @@ var capturePageScreenshot = async (page, options = {}) => {
978
978
  }
979
979
  };
980
980
  var captureExpandedFullPageScreenshot = async (page, options = {}) => {
981
- const state2 = await prepareExpandedFullPageScreenshot(page, options);
981
+ const state = await prepareExpandedFullPageScreenshot(page, options);
982
982
  try {
983
983
  return await capturePageScreenshot(page, {
984
984
  fullPage: true,
985
985
  type: options.type || "png",
986
986
  quality: options.quality,
987
987
  timeout: options.timeout,
988
- maxClipHeight: state2.targetHeight
988
+ maxClipHeight: state.targetHeight
989
989
  });
990
990
  } finally {
991
991
  await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
992
992
  logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
993
993
  });
994
994
  if (options.restore) {
995
- await restoreExpandedFullPageScreenshot(page, state2);
995
+ await restoreExpandedFullPageScreenshot(page, state);
996
996
  }
997
997
  }
998
998
  };
@@ -1059,7 +1059,7 @@ import { serializeError as serializeError2 } from "serialize-error";
1059
1059
 
1060
1060
  // src/internals/proxy-meter-runtime.js
1061
1061
  import { spawn, spawnSync } from "child_process";
1062
- import { existsSync, mkdirSync, readFileSync, rmSync } from "fs";
1062
+ import { createWriteStream, existsSync, mkdirSync, readFileSync, rmSync } from "fs";
1063
1063
  import { tmpdir } from "os";
1064
1064
  import path from "path";
1065
1065
  import { fileURLToPath } from "url";
@@ -1168,6 +1168,19 @@ var ensureLogPath = () => {
1168
1168
  const label = runId ? `proxy-meter-${runId}-${suffix}.json` : `proxy-meter-${process.pid}-${suffix}.json`;
1169
1169
  return path.join(baseDir, label);
1170
1170
  };
1171
+ var ensureStdioLogPath = (logPath) => `${logPath}.stdio.log`;
1172
+ var writeChildOutput = (stream, output, prefix) => {
1173
+ if (!stream || !output) return;
1174
+ output.on("data", (chunk) => {
1175
+ const text = chunk?.toString?.() || String(chunk || "");
1176
+ if (!text) return;
1177
+ for (const line of text.split(/\r?\n/)) {
1178
+ if (!line) continue;
1179
+ stream.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${prefix} ${line}
1180
+ `);
1181
+ }
1182
+ });
1183
+ };
1171
1184
  var readSnapshot = (logPath) => {
1172
1185
  if (!logPath || !existsSync(logPath)) return null;
1173
1186
  try {
@@ -1335,6 +1348,7 @@ var startProxyMeter = (options = {}) => {
1335
1348
  observedDomainResourceTypes = /* @__PURE__ */ new Map();
1336
1349
  const port = pickFreePort();
1337
1350
  const logPath = ensureLogPath();
1351
+ const stdioLogPath = ensureStdioLogPath(logPath);
1338
1352
  const scriptPath = resolveScriptPath();
1339
1353
  const debugMode = Boolean(options.debugMode);
1340
1354
  const debugMaxEvents = Math.max(10, toSafeInt(options.debugMaxEvents) || DEFAULT_DEBUG_MAX_EVENTS);
@@ -1347,19 +1361,28 @@ var startProxyMeter = (options = {}) => {
1347
1361
  PROXY_METER_DEBUG: debugMode ? "1" : "0",
1348
1362
  PROXY_METER_DEBUG_MAX_EVENTS: String(debugMaxEvents)
1349
1363
  };
1364
+ const stdioLog = createWriteStream(stdioLogPath, { flags: "a" });
1365
+ stdioLog.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [proxy-meter-runtime] start script=${scriptPath} port=${port} snapshot=${logPath}
1366
+ `);
1350
1367
  const child = spawn(process.execPath, [scriptPath], {
1351
1368
  env,
1352
- stdio: ["ignore", "ignore", "ignore"]
1369
+ stdio: ["ignore", "pipe", "pipe"]
1353
1370
  });
1354
- child.once("exit", (code) => {
1371
+ writeChildOutput(stdioLog, child.stdout, "stdout");
1372
+ writeChildOutput(stdioLog, child.stderr, "stderr");
1373
+ child.once("exit", (code, signal) => {
1374
+ stdioLog.write(`[${(/* @__PURE__ */ new Date()).toISOString()}] [proxy-meter-runtime] exit code=${code ?? ""} signal=${signal ?? ""}
1375
+ `);
1376
+ stdioLog.end();
1355
1377
  if (code && code !== 0) {
1356
- logger2.warn(`[proxy-meter] exited with code ${code}`);
1378
+ logger2.warn(`[proxy-meter] exited with code ${code}; stdio=${stdioLogPath}`);
1357
1379
  }
1358
1380
  });
1359
1381
  runtime = {
1360
1382
  proc: child,
1361
1383
  port,
1362
1384
  logPath,
1385
+ stdioLogPath,
1363
1386
  startedAt: Date.now()
1364
1387
  };
1365
1388
  registerCleanup();
@@ -1887,8 +1910,8 @@ var normalizeBrowserProfile = (value) => {
1887
1910
  payload: buildBrowserProfilePayload(core, observed)
1888
1911
  };
1889
1912
  };
1890
- var rememberRuntimeState = (state2) => {
1891
- rememberedRuntimeState = deepClone(state2);
1913
+ var rememberRuntimeState = (state) => {
1914
+ rememberedRuntimeState = deepClone(state);
1892
1915
  return rememberedRuntimeState;
1893
1916
  };
1894
1917
  var normalizeRuntimeState = (source = {}, actor = "") => {
@@ -1947,7 +1970,7 @@ var RuntimeEnv = {
1947
1970
  } else {
1948
1971
  delete normalizedRuntime.browser_profile;
1949
1972
  }
1950
- const state2 = {
1973
+ const state = {
1951
1974
  actor: resolvedActor,
1952
1975
  device,
1953
1976
  runtime: normalizedRuntime,
@@ -1961,73 +1984,73 @@ var RuntimeEnv = {
1961
1984
  browserProfileCore: browserProfile.core,
1962
1985
  browserProfileObserved: browserProfile.observed
1963
1986
  };
1964
- rememberRuntimeState(state2);
1965
- return state2;
1987
+ rememberRuntimeState(state);
1988
+ return state;
1966
1989
  },
1967
1990
  // buildEnvPatch 只构造允许回写到后端 env 的字段集合。
1968
1991
  buildEnvPatch(source = {}, actor = "") {
1969
- const state2 = normalizeRuntimeState(source, actor);
1970
- const browserProfile = buildBrowserProfilePayload(state2.browserProfileCore, state2.browserProfileObserved);
1992
+ const state = normalizeRuntimeState(source, actor);
1993
+ const browserProfile = buildBrowserProfilePayload(state.browserProfileCore, state.browserProfileObserved);
1971
1994
  const envPatch = {
1972
- ...Array.isArray(state2.cookies) && state2.cookies.length > 0 ? { cookies: state2.cookies } : {},
1973
- ...Object.keys(state2.localStorage || {}).length > 0 ? { local_storage: state2.localStorage } : {},
1974
- ...Object.keys(state2.sessionStorage || {}).length > 0 ? { session_storage: state2.sessionStorage } : {},
1995
+ ...Array.isArray(state.cookies) && state.cookies.length > 0 ? { cookies: state.cookies } : {},
1996
+ ...Object.keys(state.localStorage || {}).length > 0 ? { local_storage: state.localStorage } : {},
1997
+ ...Object.keys(state.sessionStorage || {}).length > 0 ? { session_storage: state.sessionStorage } : {},
1975
1998
  ...Object.keys(browserProfile).length > 0 ? { browser_profile: browserProfile } : {}
1976
1999
  };
1977
2000
  return Object.keys(envPatch).length > 0 ? envPatch : null;
1978
2001
  },
1979
2002
  // hasLoginState 只判断 runtime 是否存在有效载荷,不再区分具体字段来源。
1980
2003
  hasLoginState(source = {}, actor = "") {
1981
- const state2 = normalizeRuntimeState(source, actor);
1982
- return isPlainObject(state2.runtime) && Object.keys(state2.runtime || {}).length > 0;
2004
+ const state = normalizeRuntimeState(source, actor);
2005
+ return isPlainObject(state.runtime) && Object.keys(state.runtime || {}).length > 0;
1983
2006
  },
1984
2007
  rememberState(source = {}) {
1985
- const state2 = normalizeRuntimeState(source);
1986
- rememberRuntimeState(state2);
2008
+ const state = normalizeRuntimeState(source);
2009
+ rememberRuntimeState(state);
1987
2010
  return RuntimeEnv.peekRememberedState();
1988
2011
  },
1989
2012
  peekRememberedState() {
1990
2013
  return rememberedRuntimeState ? deepClone(rememberedRuntimeState) : null;
1991
2014
  },
1992
2015
  getBrowserProfileCore(source = {}, actor = "") {
1993
- const state2 = normalizeRuntimeState(source, actor);
1994
- return deepClone(state2.browserProfileCore || {});
2016
+ const state = normalizeRuntimeState(source, actor);
2017
+ return deepClone(state.browserProfileCore || {});
1995
2018
  },
1996
2019
  setBrowserProfileCore(source = {}, core = {}, actor = "") {
1997
- const state2 = normalizeRuntimeState(source, actor);
2020
+ const state = normalizeRuntimeState(source, actor);
1998
2021
  const normalizedCore = normalizeBrowserProfileCore({
1999
2022
  ...core,
2000
- device: normalizeKnownDevice(core?.device) || state2.device
2023
+ device: normalizeKnownDevice(core?.device) || state.device
2001
2024
  });
2002
- state2.browserProfileCore = normalizedCore;
2003
- state2.browserProfile = buildBrowserProfilePayload(normalizedCore, state2.browserProfileObserved);
2004
- if (Object.keys(state2.browserProfile).length > 0) {
2005
- state2.runtime.browser_profile = state2.browserProfile;
2025
+ state.browserProfileCore = normalizedCore;
2026
+ state.browserProfile = buildBrowserProfilePayload(normalizedCore, state.browserProfileObserved);
2027
+ if (Object.keys(state.browserProfile).length > 0) {
2028
+ state.runtime.browser_profile = state.browserProfile;
2006
2029
  } else {
2007
- delete state2.runtime.browser_profile;
2030
+ delete state.runtime.browser_profile;
2008
2031
  }
2009
- rememberRuntimeState(state2);
2010
- return state2;
2032
+ rememberRuntimeState(state);
2033
+ return state;
2011
2034
  },
2012
2035
  // applyToPage 只负责把登录态相关字段注入页面:
2013
2036
  // cookies / localStorage / sessionStorage。
2014
2037
  // 指纹、时区、UA、viewport 的回放发生在 launch.js 启动阶段,不在这里做。
2015
2038
  async applyToPage(page, source = {}, options = {}) {
2016
2039
  if (!page) return;
2017
- let state2 = normalizeRuntimeState(source, options?.actor || "");
2040
+ let state = normalizeRuntimeState(source, options?.actor || "");
2018
2041
  if (typeof options?.preapply === "function") {
2019
- state2 = await options.preapply(state2) || state2;
2020
- rememberRuntimeState(state2);
2042
+ state = await options.preapply(state) || state;
2043
+ rememberRuntimeState(state);
2021
2044
  }
2022
2045
  Object.defineProperty(page, PageRuntimeStateKey, {
2023
2046
  configurable: true,
2024
2047
  enumerable: false,
2025
2048
  writable: true,
2026
- value: state2
2049
+ value: state
2027
2050
  });
2028
- const localStorage = state2.localStorage || {};
2029
- const sessionStorage = state2.sessionStorage || {};
2030
- const cookies = (state2.cookies || []).map((cookie) => {
2051
+ const localStorage = state.localStorage || {};
2052
+ const sessionStorage = state.sessionStorage || {};
2053
+ const cookies = (state.cookies || []).map((cookie) => {
2031
2054
  const normalized = { ...cookie };
2032
2055
  if (!normalized.path) {
2033
2056
  normalized.path = "/";
@@ -2065,8 +2088,8 @@ var RuntimeEnv = {
2065
2088
  },
2066
2089
  // captureEnvPatch 在任务结束时采集最新环境快照,用于 pushSuccess / pushFailed 自动回写。
2067
2090
  async captureEnvPatch(page, source = {}, options = {}) {
2068
- const state2 = normalizeRuntimeState(source, options?.actor || "");
2069
- const baseline = RuntimeEnv.buildEnvPatch(state2) || {};
2091
+ const state = normalizeRuntimeState(source, options?.actor || "");
2092
+ const baseline = RuntimeEnv.buildEnvPatch(state) || {};
2070
2093
  if (!page || typeof page.evaluate !== "function" || typeof page.context !== "function") {
2071
2094
  return Object.keys(baseline).length > 0 ? baseline : null;
2072
2095
  }
@@ -2085,7 +2108,7 @@ var RuntimeEnv = {
2085
2108
  cookies
2086
2109
  },
2087
2110
  {
2088
- browserProfileCore: state2.browserProfileCore
2111
+ browserProfileCore: state.browserProfileCore
2089
2112
  }
2090
2113
  );
2091
2114
  return RuntimeEnv.mergeEnvPatches(baseline, capturedPatch);
@@ -2099,11 +2122,11 @@ var RuntimeEnv = {
2099
2122
  var logger3 = createInternalLogger("ApifyKit");
2100
2123
  var resolveRuntimeContext = (input) => {
2101
2124
  const rememberedState = RuntimeEnv.peekRememberedState();
2102
- const state2 = rememberedState || RuntimeEnv.parseInput(input || {});
2103
- const envPatch = RuntimeEnv.buildEnvPatch(state2) || null;
2125
+ const state = rememberedState || RuntimeEnv.parseInput(input || {});
2126
+ const envPatch = RuntimeEnv.buildEnvPatch(state) || null;
2104
2127
  return {
2105
- actor: state2.actor,
2106
- runtime: state2.runtime,
2128
+ actor: state.actor,
2129
+ runtime: state.runtime,
2107
2130
  envPatch
2108
2131
  };
2109
2132
  };
@@ -2462,99 +2485,8 @@ var Utils = {
2462
2485
  }
2463
2486
  };
2464
2487
 
2465
- // src/internals/context.js
2466
- var state = {
2467
- mode: Mode.Default
2468
- };
2469
- var normalizeStrategies = (strategies) => strategies && typeof strategies === "object" ? strategies : {};
2470
- var ToolkitContext = {
2471
- get mode() {
2472
- return state.mode;
2473
- },
2474
- setMode(mode = Mode.Default) {
2475
- state.mode = normalizeMode(mode, Mode.Default);
2476
- return state.mode;
2477
- }
2478
- };
2479
- var getToolkitMode = () => state.mode;
2480
- var setToolkitMode = (mode = Mode.Default) => ToolkitContext.setMode(mode);
2481
- var resolveModeStrategy = (strategies = {}, mode = getToolkitMode(), fallbackMode = Mode.Default) => {
2482
- const normalizedStrategies = normalizeStrategies(strategies);
2483
- const normalizedMode = normalizeMode(mode, fallbackMode);
2484
- const delegate = normalizedStrategies[normalizedMode] ?? normalizedStrategies[fallbackMode] ?? Object.values(normalizedStrategies).find(Boolean) ?? null;
2485
- return {
2486
- mode: normalizedMode,
2487
- delegate
2488
- };
2489
- };
2490
-
2491
- // src/internals/delegate.js
2492
- var normalizeArrayMethodDefinition = (definition) => typeof definition === "string" ? { name: definition, enumerable: true } : {
2493
- name: String(definition?.name || "").trim(),
2494
- enumerable: definition?.enumerable !== false
2495
- };
2496
- var normalizeMethodDefinitions = (methods = {}) => {
2497
- if (Array.isArray(methods)) {
2498
- return methods.map(normalizeArrayMethodDefinition).filter((method) => method.name);
2499
- }
2500
- if (!methods || typeof methods !== "object") {
2501
- return [];
2502
- }
2503
- return Object.entries(methods).map(([name, definition]) => ({
2504
- name: String(name || "").trim(),
2505
- enumerable: definition?.enumerable !== false
2506
- })).filter((method) => method.name);
2507
- };
2508
- var normalizeDelegateResolution = (resolution) => {
2509
- if (resolution && typeof resolution === "object" && ("delegate" in resolution || "label" in resolution)) {
2510
- return {
2511
- delegate: resolution.delegate ?? null,
2512
- label: String(resolution.label || "current delegate").trim() || "current delegate"
2513
- };
2514
- }
2515
- return {
2516
- delegate: resolution,
2517
- label: "current delegate"
2518
- };
2519
- };
2520
- var createMethodDescriptor = (namespace, methodName, enumerable, resolveDelegate) => ({
2521
- enumerable,
2522
- value: (...args) => {
2523
- const { delegate, label } = normalizeDelegateResolution(resolveDelegate(methodName, args));
2524
- if (typeof delegate?.[methodName] !== "function") {
2525
- throw new Error(`${namespace}.${methodName} is not available in ${label}`);
2526
- }
2527
- return delegate[methodName](...args);
2528
- }
2529
- });
2530
- var withDelegatedProperties = (target = {}, {
2531
- namespace = "Delegate",
2532
- methods = {},
2533
- resolveDelegate = () => null
2534
- } = {}) => {
2535
- const descriptors = Object.fromEntries(
2536
- normalizeMethodDefinitions(methods).map((method) => [
2537
- method.name,
2538
- createMethodDescriptor(namespace, method.name, method.enumerable, resolveDelegate)
2539
- ])
2540
- );
2541
- return Object.defineProperties(target, descriptors);
2542
- };
2543
- var createDelegatedFacade = (namespace, strategies = {}, methods = {}) => {
2544
- return withDelegatedProperties({}, {
2545
- namespace,
2546
- methods,
2547
- resolveDelegate: () => {
2548
- const { mode, delegate } = resolveModeStrategy(strategies);
2549
- return {
2550
- delegate,
2551
- label: `${mode} mode`
2552
- };
2553
- }
2554
- });
2555
- };
2556
-
2557
- // src/internals/anti-cheat/default.js
2488
+ // src/anti-cheat.js
2489
+ var logger5 = createInternalLogger("AntiCheat");
2558
2490
  var BASE_CONFIG = Object.freeze({
2559
2491
  locale: "zh-CN",
2560
2492
  acceptLanguage: "zh-CN,zh;q=0.9",
@@ -2589,7 +2521,7 @@ function buildFingerprintOptions({ locale = BASE_CONFIG.locale, browserMajorVers
2589
2521
  }
2590
2522
  return options;
2591
2523
  }
2592
- var DefaultAntiCheat = {
2524
+ var AntiCheat = {
2593
2525
  /**
2594
2526
  * 获取统一的基础配置
2595
2527
  */
@@ -2626,54 +2558,6 @@ var DefaultAntiCheat = {
2626
2558
  }
2627
2559
  };
2628
2560
 
2629
- // src/internals/anti-cheat/cloakbrowser.js
2630
- var CLOAK_BROWSER_BASE_CONFIG = Object.freeze({
2631
- locale: "",
2632
- acceptLanguage: "",
2633
- timezoneId: "",
2634
- timezoneOffset: null,
2635
- geolocation: null
2636
- });
2637
- var normalizeHeaders = (headers) => headers && typeof headers === "object" ? headers : {};
2638
- var CloakBrowserAntiCheat = {
2639
- /**
2640
- * CloakBrowser 自身会负责浏览器指纹,toolkit 在该模式下尽量不再注入额外反检测配置。
2641
- */
2642
- getBaseConfig() {
2643
- return { ...CLOAK_BROWSER_BASE_CONFIG };
2644
- },
2645
- getFingerprintGeneratorOptions() {
2646
- return {};
2647
- },
2648
- getLaunchArgs() {
2649
- return [];
2650
- },
2651
- getTlsFingerprintOptions() {
2652
- return {};
2653
- },
2654
- applyLocaleHeaders(headers, acceptLanguage = "") {
2655
- const normalizedHeaders = normalizeHeaders(headers);
2656
- if (acceptLanguage && !normalizedHeaders["accept-language"]) {
2657
- normalizedHeaders["accept-language"] = acceptLanguage;
2658
- }
2659
- return normalizedHeaders;
2660
- }
2661
- };
2662
-
2663
- // src/anti-cheat.js
2664
- var antiCheatStrategies = {
2665
- [Mode.Default]: DefaultAntiCheat,
2666
- [Mode.CloakBrowser]: CloakBrowserAntiCheat
2667
- };
2668
- var antiCheatFacadeMethods = Object.freeze({
2669
- getBaseConfig: { enumerable: true },
2670
- getFingerprintGeneratorOptions: { enumerable: true },
2671
- getLaunchArgs: { enumerable: true },
2672
- getTlsFingerprintOptions: { enumerable: true },
2673
- applyLocaleHeaders: { enumerable: true }
2674
- });
2675
- var AntiCheat = createDelegatedFacade("AntiCheat", antiCheatStrategies, antiCheatFacadeMethods);
2676
-
2677
2561
  // src/device-input.js
2678
2562
  var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
2679
2563
  var assertPage = (page, method) => {
@@ -3073,12 +2957,12 @@ var resolveDescriptor = (descriptor, device) => {
3073
2957
  }
3074
2958
  return resolved;
3075
2959
  };
3076
- var attachRuntimeState = (page, state2) => {
2960
+ var attachRuntimeState = (page, state) => {
3077
2961
  Object.defineProperty(page, PageRuntimeStateKey, {
3078
2962
  configurable: true,
3079
2963
  enumerable: false,
3080
2964
  writable: true,
3081
- value: state2
2965
+ value: state
3082
2966
  });
3083
2967
  };
3084
2968
  var restoreRuntimeState = (page, snapshot) => {
@@ -3224,1240 +3108,1136 @@ var DeviceView = {
3224
3108
  }
3225
3109
  };
3226
3110
 
3227
- // src/internals/humanize/cloakbrowser.js
3228
- import delay3 from "delay";
3229
-
3230
- // src/internals/humanize/shared.js
3111
+ // src/internals/humanize/desktop.js
3231
3112
  import delay2 from "delay";
3232
- var jitterMs = (base, jitterPercent = 0.3) => {
3233
- const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
3234
- return Math.max(10, Math.round(Number(base || 0) + jitter));
3235
- };
3236
- var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
3237
- if (target == null) return null;
3238
- let element = target;
3239
- if (typeof target === "string") {
3240
- element = await page.$(target);
3241
- }
3242
- if (!element) {
3243
- if (throwOnMissing) {
3244
- throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
3245
- }
3246
- return null;
3247
- }
3248
- return element;
3249
- };
3250
- var waitJitter = (base, jitterPercent = 0.3) => delay2(jitterMs(base, jitterPercent));
3251
-
3252
- // src/internals/humanize/mobile.js
3253
- var logger5 = createInternalLogger("Humanize.Mobile");
3254
- var initializedPages = /* @__PURE__ */ new WeakSet();
3255
- var DEFAULT_TAP_TIMEOUT_MS = 2500;
3256
- var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3257
- var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3258
- var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3259
- var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3260
- var describeTarget = (target) => {
3261
- if (target == null) return "Current Position";
3262
- return typeof target === "string" ? target : "ElementHandle";
3263
- };
3264
- var clipBoxToViewport = (box, viewport) => {
3265
- if (!box || !viewport) return box;
3266
- const left = clamp(box.x, 0, viewport.width);
3267
- const top = clamp(box.y, 0, viewport.height);
3268
- const right = clamp(box.x + box.width, 0, viewport.width);
3269
- const bottom = clamp(box.y + box.height, 0, viewport.height);
3270
- if (right - left > 1 && bottom - top > 1) {
3271
- return {
3272
- x: left,
3273
- y: top,
3274
- width: right - left,
3275
- height: bottom - top
3276
- };
3277
- }
3278
- return box;
3279
- };
3280
- var centerPointInBox = (box) => ({
3281
- x: box.x + box.width / 2,
3282
- y: box.y + box.height / 2
3283
- });
3284
- var withTimeout = async (operation, timeoutMs, label) => {
3285
- const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3286
- let timeoutId = null;
3287
- try {
3288
- return await Promise.race([
3289
- Promise.resolve().then(operation),
3290
- new Promise((_, reject) => {
3291
- timeoutId = setTimeout(() => {
3292
- reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
3293
- }, safeTimeoutMs);
3294
- })
3295
- ]);
3296
- } finally {
3297
- if (timeoutId) clearTimeout(timeoutId);
3113
+ import { createCursor } from "ghost-cursor-playwright";
3114
+ var logger6 = createInternalLogger("Humanize");
3115
+ var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
3116
+ function $GetCursor(page) {
3117
+ const cursor = $CursorWeakMap.get(page);
3118
+ if (!cursor) {
3119
+ throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
3298
3120
  }
3299
- };
3300
- var checkElementVisibility = async (element) => {
3301
- return element.evaluate((el) => {
3302
- const targetStyle = window.getComputedStyle(el);
3303
- if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3304
- return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
3305
- }
3306
- const rect = el.getBoundingClientRect();
3307
- if (!rect || rect.width <= 0 || rect.height <= 0) {
3308
- return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6", direction: "down" };
3309
- }
3310
- const viewW = window.innerWidth;
3311
- const viewH = window.innerHeight;
3312
- const centerY = rect.top + rect.height / 2;
3313
- let clipLeft = 0;
3314
- let clipRight = viewW;
3315
- let clipTop = 0;
3316
- let clipBottom = viewH;
3317
- let isFixed = false;
3318
- let positioning = null;
3319
- for (let node = el; node && node !== document.body; node = node.parentElement) {
3320
- const style = window.getComputedStyle(node);
3321
- if (style && (style.position === "fixed" || style.position === "sticky")) {
3322
- isFixed = true;
3323
- positioning = style.position;
3324
- break;
3325
- }
3121
+ return cursor;
3122
+ }
3123
+ var Humanize = {
3124
+ /**
3125
+ * 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
3126
+ * @param {number} base - 基础延迟 (ms)
3127
+ * @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
3128
+ * @returns {number} 抖动后的毫秒数
3129
+ */
3130
+ jitterMs(base, jitterPercent = 0.3) {
3131
+ const jitter = base * jitterPercent * (Math.random() * 2 - 1);
3132
+ return Math.max(10, Math.round(base + jitter));
3133
+ },
3134
+ /**
3135
+ * 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
3136
+ *
3137
+ * @param {import('playwright').Page} page
3138
+ * @returns {Promise<void>}
3139
+ */
3140
+ async initializeCursor(page) {
3141
+ if ($CursorWeakMap.has(page)) {
3142
+ logger6.debug("initializeCursor: cursor already exists, skipping");
3143
+ return;
3326
3144
  }
3327
- for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
3328
- const style = window.getComputedStyle(node);
3329
- if (!style) continue;
3330
- const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
3331
- const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
3332
- if (!clipsX && !clipsY) continue;
3333
- const nodeRect = node.getBoundingClientRect();
3334
- if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
3335
- if (clipsX) {
3336
- clipLeft = Math.max(clipLeft, nodeRect.left);
3337
- clipRight = Math.min(clipRight, nodeRect.right);
3338
- }
3339
- if (clipsY) {
3340
- clipTop = Math.max(clipTop, nodeRect.top);
3341
- clipBottom = Math.min(clipBottom, nodeRect.bottom);
3145
+ logger6.start("initializeCursor", "creating cursor");
3146
+ const cursor = await createCursor(page);
3147
+ $CursorWeakMap.set(page, cursor);
3148
+ logger6.success("initializeCursor", "cursor initialized");
3149
+ },
3150
+ /**
3151
+ * 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
3152
+ *
3153
+ * @param {import('playwright').Page} page
3154
+ * @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
3155
+ */
3156
+ async humanMove(page, target) {
3157
+ const cursor = $GetCursor(page);
3158
+ logger6.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
3159
+ try {
3160
+ if (typeof target === "string") {
3161
+ const element = await page.$(target);
3162
+ if (!element) {
3163
+ logger6.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
3164
+ return false;
3165
+ }
3166
+ const box = await element.boundingBox();
3167
+ if (!box) {
3168
+ logger6.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
3169
+ return false;
3170
+ }
3171
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
3172
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
3173
+ await cursor.actions.move({ x, y });
3174
+ } else if (target && typeof target.x === "number" && typeof target.y === "number") {
3175
+ await cursor.actions.move(target);
3176
+ } else if (target && typeof target.boundingBox === "function") {
3177
+ const box = await target.boundingBox();
3178
+ if (box) {
3179
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
3180
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
3181
+ await cursor.actions.move({ x, y });
3182
+ }
3342
3183
  }
3184
+ logger6.success("humanMove");
3185
+ return true;
3186
+ } catch (error) {
3187
+ logger6.fail("humanMove", error);
3188
+ throw error;
3343
3189
  }
3344
- const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
3345
- const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
3346
- const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
3347
- const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
3348
- const visibleWidth = visibleRight - visibleLeft;
3349
- const visibleHeight = visibleBottom - visibleTop;
3350
- const cx = visibleLeft + visibleWidth / 2;
3351
- const cy = visibleTop + visibleHeight / 2;
3352
- if (visibleWidth <= 1 || visibleHeight <= 1) {
3353
- return {
3354
- code: "OUT_OF_VIEWPORT",
3355
- reason: "\u4E0D\u5728\u89C6\u53E3\u5185",
3356
- direction: centerY < clipTop ? "up" : "down",
3357
- cy: centerY,
3358
- viewH,
3359
- isFixed,
3360
- positioning
3361
- };
3362
- }
3363
- const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3364
- const commonAncestor = (a, b) => {
3365
- for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3366
- if (current.contains(b)) return current;
3367
- }
3368
- return null;
3369
- };
3370
- const sameTapTarget = (pointElement) => {
3371
- if (!pointElement) return false;
3372
- if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3373
- return true;
3374
- }
3375
- const common = commonAncestor(el, pointElement);
3376
- if (!common) return false;
3377
- const commonRect = common.getBoundingClientRect?.();
3378
- if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3379
- const commonArea = commonRect.width * commonRect.height;
3380
- const targetArea = Math.max(1, rect.width * rect.height);
3381
- const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3382
- if (commonArea > maxSharedRegionArea) return false;
3383
- if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3384
- return false;
3385
- }
3386
- return common.contains(el) && common.contains(pointElement);
3387
- };
3388
- const describeElement = (node) => {
3389
- if (!node) return null;
3390
- const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
3391
- const rect2 = node.getBoundingClientRect?.();
3392
- const style = window.getComputedStyle(node);
3393
- return {
3394
- tag: node.tagName,
3395
- id: node.id || "",
3396
- className,
3397
- isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
3398
- positioning: style?.position || "",
3399
- top: rect2 ? rect2.top : null,
3400
- bottom: rect2 ? rect2.bottom : null,
3401
- left: rect2 ? rect2.left : null,
3402
- right: rect2 ? rect2.right : null
3403
- };
3404
- };
3405
- const samplePoints = [
3406
- { x: cx, y: cy },
3407
- { x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3408
- { x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3409
- { x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
3410
- { x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
3411
- ];
3412
- let obstruction = null;
3413
- for (const point of samplePoints) {
3414
- const pointElement = document.elementFromPoint(point.x, point.y);
3415
- if (sameTapTarget(pointElement)) {
3416
- return { code: "VISIBLE", isFixed, positioning };
3417
- }
3418
- obstruction = obstruction || describeElement(pointElement);
3419
- }
3420
- if (obstruction) {
3421
- return {
3422
- code: "OBSTRUCTED",
3423
- reason: "\u88AB\u906E\u6321",
3424
- direction: cy > viewH / 2 ? "down" : "up",
3425
- obstruction,
3426
- cy,
3427
- viewH,
3428
- isFixed,
3429
- positioning
3430
- };
3431
- }
3432
- return { code: "VISIBLE", isFixed, positioning };
3433
- });
3434
- };
3435
- var activateElementFallback = async (element, point = null, options = {}) => {
3436
- return element.evaluate((el, { innerOptions }) => {
3437
- const isEditable = (node) => {
3438
- if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3439
- if (node.isContentEditable) return true;
3440
- if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3441
- if (node instanceof HTMLInputElement) {
3442
- return !node.disabled && !node.readOnly && typeof node.select === "function";
3443
- }
3444
- return false;
3445
- };
3446
- const findEditable = (node) => {
3447
- for (let current = node; current && current !== document.body; current = current.parentElement) {
3448
- if (isEditable(current)) return current;
3449
- }
3450
- return null;
3451
- };
3452
- const editable = findEditable(el);
3453
- if (editable && typeof editable.focus === "function") {
3454
- editable.focus({ preventScroll: true });
3455
- if (innerOptions.editableOnly) {
3456
- return { activated: true, method: "focus", tag: editable.tagName || "" };
3190
+ },
3191
+ /**
3192
+ * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
3193
+ * 返回 restore 方法,用于将滚动容器恢复到原位置
3194
+ *
3195
+ * @param {import('playwright').Page} page
3196
+ * @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
3197
+ * @param {Object} [options]
3198
+ * @param {number} [options.maxSteps=20] - 最大滚动步数
3199
+ * @param {number} [options.minStep=260] - 单次滚动最小步长
3200
+ * @param {number} [options.maxStep=800] - 单次滚动最大步长
3201
+ * @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
3202
+ */
3203
+ async humanScroll(page, target, options = {}) {
3204
+ const {
3205
+ maxSteps = 20,
3206
+ minStep = 260,
3207
+ maxStep = 800,
3208
+ maxDurationMs = maxSteps * 220 + 800
3209
+ } = options;
3210
+ const targetDesc = typeof target === "string" ? target : "ElementHandle";
3211
+ logger6.start("humanScroll", `target=${targetDesc}`);
3212
+ let element;
3213
+ if (typeof target === "string") {
3214
+ element = await page.$(target);
3215
+ if (!element) {
3216
+ logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
3217
+ return { element: null, didScroll: false };
3457
3218
  }
3219
+ } else {
3220
+ element = target;
3458
3221
  }
3459
- if (innerOptions.editableOnly) {
3460
- return { activated: false, method: "none", tag: el?.tagName || "" };
3461
- }
3462
- if (typeof el.focus === "function") {
3463
- el.focus({ preventScroll: true });
3464
- }
3465
- if (typeof el.click === "function") {
3466
- el.click();
3467
- return { activated: true, method: "dom-click", tag: el.tagName || "" };
3468
- }
3469
- if (typeof el.dispatchEvent === "function") {
3470
- el.dispatchEvent(new MouseEvent("click", {
3471
- bubbles: true,
3472
- cancelable: true,
3473
- view: window
3474
- }));
3475
- return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
3476
- }
3477
- return {
3478
- activated: Boolean(editable),
3479
- method: editable ? "focus" : "none",
3480
- tag: el?.tagName || ""
3481
- };
3482
- }, {
3483
- innerOptions: options || {}
3484
- });
3485
- };
3486
- var getScrollableRect = async (element) => {
3487
- return element.evaluate((el) => {
3488
- const isScrollable = (node) => {
3489
- const style = window.getComputedStyle(node);
3490
- if (!style) return false;
3491
- const overflowY = style.overflowY;
3492
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3493
- return node.scrollHeight > node.clientHeight + 1;
3494
- };
3495
- let current = el;
3496
- while (current && current !== document.body) {
3497
- if (isScrollable(current)) {
3498
- const rect = current.getBoundingClientRect();
3499
- if (rect && rect.width > 0 && rect.height > 0) {
3222
+ const cursor = $GetCursor(page);
3223
+ let didScroll = false;
3224
+ const checkVisibility = async () => {
3225
+ return await element.evaluate((el) => {
3226
+ const rect = el.getBoundingClientRect();
3227
+ if (!rect || rect.width === 0 || rect.height === 0) {
3228
+ return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
3229
+ }
3230
+ const cx = rect.left + rect.width / 2;
3231
+ const cy = rect.top + rect.height / 2;
3232
+ const viewH = window.innerHeight;
3233
+ const viewW = window.innerWidth;
3234
+ let isFixed = false;
3235
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
3236
+ const style = window.getComputedStyle(node);
3237
+ if (style && style.position === "fixed") {
3238
+ isFixed = true;
3239
+ break;
3240
+ }
3241
+ }
3242
+ if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
3243
+ const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
3244
+ return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH, isFixed };
3245
+ }
3246
+ const pointElement = document.elementFromPoint(cx, cy);
3247
+ if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
3500
3248
  return {
3501
- x: rect.x,
3502
- y: rect.y,
3503
- width: rect.width,
3504
- height: rect.height
3249
+ code: "OBSTRUCTED",
3250
+ reason: "\u88AB\u906E\u6321",
3251
+ obstruction: {
3252
+ tag: pointElement.tagName,
3253
+ id: pointElement.id,
3254
+ className: pointElement.className
3255
+ },
3256
+ cy,
3257
+ // Return Center Y for smart direction calculation
3258
+ viewH,
3259
+ isFixed
3505
3260
  };
3506
3261
  }
3507
- }
3508
- current = current.parentElement;
3509
- }
3510
- return null;
3511
- });
3512
- };
3513
- var scrollScrollableAncestor = async (element, deltaY) => {
3514
- return element.evaluate((el, amount) => {
3515
- const isScrollable = (node) => {
3516
- const style = window.getComputedStyle(node);
3517
- if (!style) return false;
3518
- const overflowY = style.overflowY;
3519
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3520
- return node.scrollHeight > node.clientHeight + 1;
3262
+ return { code: "VISIBLE", isFixed };
3263
+ });
3521
3264
  };
3522
- let current = el;
3523
- while (current && current !== document.body) {
3524
- if (isScrollable(current)) {
3525
- const beforeTop2 = current.scrollTop;
3526
- current.scrollTop = beforeTop2 + amount;
3527
- return {
3528
- scroller: true,
3529
- moved: current.scrollTop !== beforeTop2,
3530
- scrollTop: current.scrollTop
3265
+ const getScrollableRect2 = async () => {
3266
+ return await element.evaluate((el) => {
3267
+ const isScrollable = (node) => {
3268
+ const style = window.getComputedStyle(node);
3269
+ if (!style) return false;
3270
+ const overflowY = style.overflowY;
3271
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3272
+ return node.scrollHeight > node.clientHeight + 1;
3531
3273
  };
3532
- }
3533
- current = current.parentElement;
3534
- }
3535
- const beforeTop = window.scrollY;
3536
- window.scrollBy(0, amount);
3537
- return {
3538
- scroller: null,
3539
- moved: window.scrollY !== beforeTop,
3540
- scrollTop: window.scrollY
3541
- };
3542
- }, deltaY);
3543
- };
3544
- var scrollAwayFromObstruction = async (element, status) => {
3545
- return element.evaluate((el, innerStatus) => {
3546
- const isScrollable = (node) => {
3547
- const style = window.getComputedStyle(node);
3548
- if (!style) return false;
3549
- const overflowY = style.overflowY;
3550
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3551
- return node.scrollHeight > node.clientHeight + 1;
3552
- };
3553
- let scroller = el;
3554
- while (scroller && scroller !== document.body) {
3555
- if (isScrollable(scroller)) break;
3556
- scroller = scroller.parentElement;
3557
- }
3558
- if (!scroller || scroller === document.body) {
3559
- return { moved: false, scrollTop: 0, deltaY: 0 };
3560
- }
3561
- const rect = el.getBoundingClientRect();
3562
- const scrollerRect = scroller.getBoundingClientRect();
3563
- const obstruction = innerStatus?.obstruction || {};
3564
- const obstructionTop = Number(obstruction.top);
3565
- const obstructionBottom = Number(obstruction.bottom);
3566
- const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
3567
- const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
3568
- const padding = 18;
3569
- let deltaY = 0;
3570
- if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
3571
- deltaY = rect.bottom - obstructionTop + padding;
3572
- } else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
3573
- deltaY = rect.top - obstructionBottom - padding;
3574
- } else {
3575
- const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
3576
- deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
3577
- }
3578
- const beforeTop = scroller.scrollTop;
3579
- scroller.scrollTop = beforeTop + deltaY;
3580
- return {
3581
- moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
3582
- scrollTop: scroller.scrollTop,
3583
- deltaY
3584
- };
3585
- }, status);
3586
- };
3587
- var getElementViewportSnapshot = async (element) => {
3588
- return element.evaluate((el) => {
3589
- const rect = el.getBoundingClientRect();
3590
- return {
3591
- top: rect.top,
3592
- bottom: rect.bottom,
3593
- left: rect.left,
3594
- right: rect.right,
3595
- width: rect.width,
3596
- height: rect.height,
3597
- scrollX: window.scrollX,
3598
- scrollY: window.scrollY
3274
+ let current = el;
3275
+ while (current && current !== document.body) {
3276
+ if (isScrollable(current)) {
3277
+ const rect = current.getBoundingClientRect();
3278
+ if (rect && rect.width > 0 && rect.height > 0) {
3279
+ return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
3280
+ }
3281
+ }
3282
+ current = current.parentElement;
3283
+ }
3284
+ return null;
3285
+ });
3599
3286
  };
3600
- });
3601
- };
3602
- var isTargetImmobileAfterScroll = (before, after) => {
3603
- if (!before || !after) return false;
3604
- const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
3605
- const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
3606
- const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
3607
- const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
3608
- const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
3609
- const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
3610
- if (!rectMoved && !pageMoved) return true;
3611
- if (pageMoved && !rectMoved) return true;
3612
- if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
3613
- return true;
3614
- }
3615
- return false;
3616
- };
3617
- var restoreWindowFromSnapshot = async (page, before, after) => {
3618
- if (!before || !after) return;
3619
- if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
3620
- return;
3621
- }
3622
- await page.evaluate(
3623
- (state2) => window.scrollTo(state2.x, state2.y),
3624
- { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
3625
- ).catch(() => {
3626
- });
3627
- };
3628
- var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
3629
- const viewport = resolveViewport(page);
3630
- const rawRect = options.rect || null;
3631
- const rect = rawRect ? {
3632
- x: clamp(rawRect.x, 0, viewport.width),
3633
- y: clamp(rawRect.y, 0, viewport.height),
3634
- width: clamp(rawRect.width, 0, viewport.width),
3635
- height: clamp(rawRect.height, 0, viewport.height)
3636
- } : null;
3637
- const area = rect && rect.width > 24 && rect.height > 48 ? {
3638
- left: rect.x,
3639
- right: Math.min(viewport.width, rect.x + rect.width),
3640
- top: rect.y,
3641
- bottom: Math.min(viewport.height, rect.y + rect.height)
3642
- } : {
3643
- left: 0,
3644
- right: viewport.width,
3645
- top: 0,
3646
- bottom: viewport.height
3647
- };
3648
- const areaWidth = Math.max(1, area.right - area.left);
3649
- const areaHeight = Math.max(1, area.bottom - area.top);
3650
- const maxDistance = rect ? Math.max(60, areaHeight * 0.72) : Math.max(120, viewport.height * 0.72);
3651
- const distance = clamp(Math.abs(deltaY), Math.min(80, maxDistance), maxDistance);
3652
- const direction = deltaY >= 0 ? 1 : -1;
3653
- const startX = clamp(
3654
- area.left + areaWidth * (0.45 + Math.random() * 0.1),
3655
- 24,
3656
- viewport.width - 24
3657
- );
3658
- 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);
3659
- const endY = clamp(startY - direction * distance, Math.max(16, area.top + 12), Math.min(viewport.height - 16, area.bottom - 12));
3660
- const steps = Math.max(4, Math.round(Number(options.steps || 6)));
3661
- const durationMs = jitterMs(options.durationMs || 320, 0.35);
3662
- let client = null;
3663
- const scrollBefore = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3664
- try {
3665
- if (page.context && typeof page.context().newCDPSession === "function") {
3666
- client = await page.context().newCDPSession(page);
3287
+ const startTime = Date.now();
3288
+ try {
3289
+ for (let i = 0; i < maxSteps; i++) {
3290
+ if (Date.now() - startTime > maxDurationMs) {
3291
+ logger6.warn(`humanScroll | \u8D85\u65F6\u4FDD\u62A4\u89E6\u53D1 (${maxDurationMs}ms)`);
3292
+ return { element, didScroll };
3293
+ }
3294
+ const status = await checkVisibility();
3295
+ if (status.code === "VISIBLE") {
3296
+ if (status.isFixed) {
3297
+ logger6.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3298
+ } else {
3299
+ logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
3300
+ }
3301
+ logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3302
+ return { element, didScroll };
3303
+ }
3304
+ logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
3305
+ if (status.code === "OBSTRUCTED" && status.obstruction) {
3306
+ logger6.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
3307
+ }
3308
+ const scrollRect = await getScrollableRect2();
3309
+ if (!scrollRect && status.isFixed) {
3310
+ logger6.warn("humanScroll | fixed \u5BB9\u5668\u5185\u4E14\u65E0\u53EF\u6EDA\u52A8\u7956\u5148\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3311
+ return { element, didScroll };
3312
+ }
3313
+ const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
3314
+ const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
3315
+ let deltaY = 0;
3316
+ if (status.code === "OUT_OF_VIEWPORT") {
3317
+ if (status.direction === "down") {
3318
+ deltaY = stepMin + Math.random() * (stepMax - stepMin);
3319
+ } else if (status.direction === "up") {
3320
+ deltaY = -(stepMin + Math.random() * (stepMax - stepMin));
3321
+ } else {
3322
+ deltaY = 100;
3323
+ }
3324
+ } else if (status.code === "OBSTRUCTED") {
3325
+ const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3326
+ const isBottomHalf = status.cy > halfY;
3327
+ const direction = isBottomHalf ? 1 : -1;
3328
+ deltaY = direction * (stepMin + Math.random() * 50);
3329
+ }
3330
+ if (i === 0) {
3331
+ const viewSize = page.viewportSize();
3332
+ if (scrollRect) {
3333
+ const safeX = scrollRect.x + scrollRect.width * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.width * 0.4);
3334
+ const safeY = scrollRect.y + scrollRect.height * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.height * 0.4);
3335
+ await cursor.actions.move({ x: safeX, y: safeY });
3336
+ } else if (viewSize) {
3337
+ const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 80;
3338
+ const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 80;
3339
+ await cursor.actions.move({ x: safeX, y: safeY });
3340
+ }
3341
+ }
3342
+ await page.mouse.wheel(0, deltaY);
3343
+ didScroll = true;
3344
+ await delay2(this.jitterMs(20 + Math.random() * 40, 0.2));
3345
+ }
3346
+ logger6.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
3347
+ return { element, didScroll };
3348
+ } catch (error) {
3349
+ logger6.fail("humanScroll", error);
3350
+ throw error;
3667
3351
  }
3668
- if (!client) throw new Error("CDP session unavailable");
3669
- await client.send("Input.synthesizeScrollGesture", {
3670
- x: startX,
3671
- y: startY,
3672
- yDistance: -direction * distance,
3673
- xOverscroll: (Math.random() - 0.5) * 4,
3674
- yOverscroll: (Math.random() - 0.5) * 8,
3675
- speed: 700 + Math.round(Math.random() * 350),
3676
- gestureSourceType: "touch"
3677
- });
3678
- await waitJitter(160, 0.35);
3679
- const scrollAfterSynth = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3680
- if (scrollBefore && scrollAfterSynth && (Math.abs(scrollAfterSynth.y - scrollBefore.y) > 2 || Math.abs(scrollAfterSynth.x - scrollBefore.x) > 2)) {
3352
+ },
3353
+ /**
3354
+ * 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
3355
+ *
3356
+ * @param {import('playwright').Page} page
3357
+ * @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
3358
+ * @param {Object} [options]
3359
+ * @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
3360
+ * @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
3361
+ * @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
3362
+ */
3363
+ async humanClick(page, target, options = {}) {
3364
+ const cursor = $GetCursor(page);
3365
+ const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
3366
+ const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
3367
+ logger6.start("humanClick", `target=${targetDesc}`);
3368
+ const restoreOnce = async () => {
3369
+ if (restoreOnce.restored) return;
3370
+ restoreOnce.restored = true;
3371
+ if (typeof restoreOnce.do !== "function") return;
3372
+ try {
3373
+ await delay2(this.jitterMs(1e3));
3374
+ await restoreOnce.do();
3375
+ } catch (restoreError) {
3376
+ logger6.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
3377
+ }
3378
+ };
3379
+ try {
3380
+ if (target == null) {
3381
+ await delay2(this.jitterMs(reactionDelay, 0.4));
3382
+ await cursor.actions.click();
3383
+ logger6.success("humanClick", "Clicked current position");
3384
+ return true;
3385
+ }
3386
+ let element;
3387
+ if (typeof target === "string") {
3388
+ element = await page.$(target);
3389
+ if (!element) {
3390
+ if (throwOnMissing) {
3391
+ throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
3392
+ }
3393
+ logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
3394
+ return false;
3395
+ }
3396
+ } else {
3397
+ element = target;
3398
+ }
3399
+ if (scrollIfNeeded) {
3400
+ const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
3401
+ restoreOnce.do = didScroll && restore ? restoreFn : null;
3402
+ }
3403
+ const box = await element.boundingBox();
3404
+ if (!box) {
3405
+ await restoreOnce();
3406
+ if (throwOnMissing) {
3407
+ throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
3408
+ }
3409
+ logger6.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
3410
+ return false;
3411
+ }
3412
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
3413
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
3414
+ await cursor.actions.move({ x, y });
3415
+ await delay2(this.jitterMs(reactionDelay, 0.4));
3416
+ await cursor.actions.click();
3417
+ await restoreOnce();
3418
+ logger6.success("humanClick");
3681
3419
  return true;
3420
+ } catch (error) {
3421
+ await restoreOnce();
3422
+ logger6.fail("humanClick", error);
3423
+ throw error;
3682
3424
  }
3683
- await client.send("Input.dispatchTouchEvent", {
3684
- type: "touchStart",
3685
- touchPoints: [{ x: startX, y: startY, id: 1 }]
3686
- });
3687
- for (let i = 1; i <= steps; i += 1) {
3688
- const progress = i / steps;
3689
- const eased = 1 - Math.pow(1 - progress, 2);
3690
- const x = startX + (Math.random() - 0.5) * 3;
3691
- const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 5;
3692
- await waitJitter(durationMs / steps, 0.25);
3693
- await client.send("Input.dispatchTouchEvent", {
3694
- type: "touchMove",
3695
- touchPoints: [{ x, y, id: 1 }]
3696
- });
3697
- }
3698
- await client.send("Input.dispatchTouchEvent", {
3699
- type: "touchEnd",
3700
- touchPoints: []
3701
- });
3702
- await waitJitter(120, 0.35);
3703
- const scrollAfterTouch = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3704
- if (scrollBefore && scrollAfterTouch && Math.abs(scrollAfterTouch.y - scrollBefore.y) <= 2 && Math.abs(scrollAfterTouch.x - scrollBefore.x) <= 2) {
3705
- await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
3706
- await waitJitter(120, 0.35);
3425
+ },
3426
+ /**
3427
+ * 随机延迟一段毫秒数(带 ±30% 抖动)
3428
+ * @param {number} baseMs - 基础延迟毫秒数
3429
+ * @param {number} [jitterPercent=0.3] - 抖动百分比
3430
+ */
3431
+ async randomSleep(baseMs, jitterPercent = 0.3) {
3432
+ const ms = this.jitterMs(baseMs, jitterPercent);
3433
+ logger6.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
3434
+ await delay2(ms);
3435
+ logger6.success("randomSleep");
3436
+ },
3437
+ /**
3438
+ * 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
3439
+ * @param {import('playwright').Page} page
3440
+ * @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
3441
+ */
3442
+ async simulateGaze(page, baseDurationMs = 2500) {
3443
+ const cursor = $GetCursor(page);
3444
+ const durationMs = this.jitterMs(baseDurationMs, 0.4);
3445
+ logger6.start("simulateGaze", `duration=${durationMs}ms`);
3446
+ const startTime = Date.now();
3447
+ const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
3448
+ while (Date.now() - startTime < durationMs) {
3449
+ const x = 100 + Math.random() * (viewportSize.width - 200);
3450
+ const y = 100 + Math.random() * (viewportSize.height - 200);
3451
+ await cursor.actions.move({ x, y });
3452
+ await delay2(this.jitterMs(600, 0.5));
3707
3453
  }
3708
- return true;
3709
- } catch (error) {
3710
- logger5.debug(`touch swipe fallback: ${error?.message || error}`);
3454
+ logger6.success("simulateGaze");
3455
+ },
3456
+ /**
3457
+ * 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
3458
+ * @param {import('playwright').Page} page
3459
+ * @param {string} selector - 输入框选择器
3460
+ * @param {string} text - 要输入的文本
3461
+ * @param {Object} [options]
3462
+ * @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
3463
+ * @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
3464
+ * @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
3465
+ */
3466
+ async humanType(page, selector, text, options = {}) {
3467
+ logger6.start("humanType", `selector=${selector}, textLen=${text.length}`);
3468
+ const {
3469
+ baseDelay = 180,
3470
+ pauseProbability = 0.08,
3471
+ pauseBase = 800
3472
+ } = options;
3711
3473
  try {
3712
- await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
3713
- await waitJitter(120, 0.35);
3714
- return true;
3715
- } catch {
3716
- await page.mouse.wheel(0, deltaY);
3717
- await waitJitter(120, 0.35);
3718
- return true;
3719
- }
3720
- } finally {
3721
- if (client && typeof client.detach === "function") {
3722
- try {
3723
- await client.detach();
3724
- } catch {
3474
+ const locator = page.locator(selector);
3475
+ await Humanize.humanClick(page, locator);
3476
+ await delay2(this.jitterMs(200, 0.4));
3477
+ for (let i = 0; i < text.length; i++) {
3478
+ const char = text[i];
3479
+ let charDelay;
3480
+ if (char === " ") {
3481
+ charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
3482
+ } else if (/[,.!?;:,。!?;:]/.test(char)) {
3483
+ charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
3484
+ } else {
3485
+ charDelay = this.jitterMs(baseDelay, 0.4);
3486
+ }
3487
+ await page.keyboard.type(char);
3488
+ await delay2(charDelay);
3489
+ if (Math.random() < pauseProbability && i < text.length - 1) {
3490
+ const pauseTime = this.jitterMs(pauseBase, 0.5);
3491
+ logger6.debug(`\u505C\u987F ${pauseTime}ms...`);
3492
+ await delay2(pauseTime);
3493
+ }
3725
3494
  }
3495
+ logger6.success("humanType");
3496
+ } catch (error) {
3497
+ logger6.fail("humanType", error);
3498
+ throw error;
3726
3499
  }
3727
- }
3728
- };
3729
- var tapPoint = async (page, point, options = {}) => {
3730
- const {
3731
- timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
3732
- mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
3733
- allowMouseFallback = true
3734
- } = options;
3735
- if (page.touchscreen && typeof page.touchscreen.tap === "function") {
3736
- try {
3737
- await withTimeout(
3738
- () => page.touchscreen.tap(point.x, point.y),
3739
- timeoutMs,
3740
- "touchscreen.tap"
3741
- );
3742
- return { method: "touchscreen" };
3500
+ },
3501
+ /**
3502
+ * 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
3503
+ * @param {import('playwright').Page} page
3504
+ * @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
3505
+ * @param {string|Object} [maybeKey] - 按键或选项
3506
+ * @param {Object} [options]
3507
+ */
3508
+ async humanPress(page, targetOrKey, maybeKey, options = {}) {
3509
+ const hasTarget = typeof maybeKey === "string";
3510
+ const key = hasTarget ? maybeKey : targetOrKey;
3511
+ const pressOptions = hasTarget ? options : maybeKey || options;
3512
+ const {
3513
+ reactionDelay = 180,
3514
+ holdDelay = 45,
3515
+ focusDelay = 180,
3516
+ scrollIfNeeded = true,
3517
+ throwOnMissing = true,
3518
+ keyboardOptions = {}
3519
+ } = pressOptions || {};
3520
+ const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
3521
+ logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
3522
+ try {
3523
+ if (hasTarget) {
3524
+ await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
3525
+ }
3526
+ await delay2(this.jitterMs(reactionDelay, 0.45));
3527
+ await page.keyboard.press(key, {
3528
+ ...keyboardOptions,
3529
+ delay: this.jitterMs(holdDelay, 0.5)
3530
+ });
3531
+ logger6.success("humanPress");
3532
+ return true;
3743
3533
  } catch (error) {
3744
- logger5.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
3745
- if (!allowMouseFallback) throw error;
3534
+ logger6.fail("humanPress", error);
3535
+ throw error;
3746
3536
  }
3747
- }
3748
- await withTimeout(
3749
- () => page.mouse.click(point.x, point.y),
3750
- mouseFallbackTimeoutMs,
3751
- "mouse.click fallback"
3752
- );
3753
- return { method: "mouse" };
3754
- };
3755
- var MobileHumanize = {
3756
- jitterMs,
3757
- async initializeCursor(page) {
3758
- if (initializedPages.has(page)) return;
3759
- initializedPages.add(page);
3760
- logger5.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
3761
3537
  },
3762
- async humanMove(page, target) {
3763
- logger5.debug(`humanMove: mobile no-op target=${typeof target === "string" ? target : "element/coords"}`);
3764
- if (typeof target === "string" || target && typeof target.boundingBox === "function") {
3765
- const element = await resolveElement(page, target, { throwOnMissing: false });
3766
- if (!element) {
3767
- return false;
3538
+ /**
3539
+ * 人类化清空输入框 - 模拟人类删除文本的行为
3540
+ * @param {import('playwright').Page} page
3541
+ * @param {string} selector - 输入框选择器
3542
+ */
3543
+ async humanClear(page, selector) {
3544
+ logger6.start("humanClear", `selector=${selector}`);
3545
+ try {
3546
+ const locator = page.locator(selector);
3547
+ await locator.click();
3548
+ await delay2(this.jitterMs(200, 0.4));
3549
+ const currentValue = await locator.inputValue();
3550
+ if (!currentValue || currentValue.length === 0) {
3551
+ logger6.success("humanClear", "already empty");
3552
+ return;
3768
3553
  }
3554
+ await page.keyboard.press("Meta+A");
3555
+ await delay2(this.jitterMs(100, 0.4));
3556
+ await page.keyboard.press("Backspace");
3557
+ logger6.success("humanClear");
3558
+ } catch (error) {
3559
+ logger6.fail("humanClear", error);
3560
+ throw error;
3769
3561
  }
3770
- await waitJitter(80, 0.4);
3771
- return true;
3772
3562
  },
3773
- async humanScroll(page, target, options = {}) {
3774
- const {
3775
- maxSteps = 20,
3776
- minStep = 180,
3777
- maxStep = 520,
3778
- maxDurationMs = maxSteps * 280 + 1200,
3779
- throwOnMissing = false
3780
- } = options;
3781
- const targetDesc = describeTarget(target);
3782
- logger5.start("humanScroll", `target=${targetDesc}`);
3783
- const element = await resolveElement(page, target, { throwOnMissing });
3784
- if (!element) {
3785
- logger5.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
3786
- return { element: null, didScroll: false, restore: null };
3787
- }
3563
+ /**
3564
+ * 页面预热浏览 - 模拟人类进入页面后的探索行为
3565
+ * @param {import('playwright').Page} page
3566
+ * @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
3567
+ */
3568
+ async warmUpBrowsing(page, baseDuration = 3500) {
3569
+ const cursor = $GetCursor(page);
3570
+ const durationMs = this.jitterMs(baseDuration, 0.4);
3571
+ logger6.start("warmUpBrowsing", `duration=${durationMs}ms`);
3788
3572
  const startTime = Date.now();
3789
- let didScroll = false;
3790
- for (let i = 0; i < maxSteps; i += 1) {
3791
- const status = await checkElementVisibility(element);
3792
- if (status.code === "VISIBLE") {
3793
- if (status.isFixed) {
3794
- logger5.info("humanScroll | fixed/sticky \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3795
- } else {
3796
- logger5.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
3797
- }
3798
- logger5.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3799
- return { element, didScroll, restore: null };
3800
- }
3801
- if (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE") {
3802
- logger5.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
3803
- return { element, didScroll, restore: null };
3804
- }
3805
- const scrollRect = await getScrollableRect(element);
3806
- if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
3807
- 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"})`);
3808
- return { element, didScroll, restore: null, unscrollable: true };
3809
- }
3810
- if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
3811
- logger5.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
3812
- return { element, didScroll, restore: null, unscrollable: true };
3813
- }
3814
- if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
3815
- const moved = await scrollAwayFromObstruction(element, status);
3816
- if (moved.moved) {
3817
- logger5.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
3818
- await waitJitter(90, 0.3);
3819
- didScroll = true;
3820
- continue;
3821
- }
3822
- }
3823
- if (Date.now() - startTime > maxDurationMs) {
3824
- logger5.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
3825
- return { element, didScroll, restore: null };
3826
- }
3827
- const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
3828
- const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
3829
- logger5.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
3830
- const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
3831
- let deltaY = status.direction === "up" ? -distance : distance;
3832
- if (status.code === "OBSTRUCTED") {
3833
- if (status.obstruction?.isFixed && status.obstruction.top != null) {
3834
- const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
3835
- const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3836
- deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
3573
+ const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
3574
+ try {
3575
+ while (Date.now() - startTime < durationMs) {
3576
+ const action = Math.random();
3577
+ if (action < 0.4) {
3578
+ const x = 100 + Math.random() * (viewportSize.width - 200);
3579
+ const y = 100 + Math.random() * (viewportSize.height - 200);
3580
+ await cursor.actions.move({ x, y });
3581
+ await delay2(this.jitterMs(350, 0.4));
3582
+ } else if (action < 0.7) {
3583
+ const scrollY = (Math.random() - 0.5) * 200;
3584
+ await page.mouse.wheel(0, scrollY);
3585
+ await delay2(this.jitterMs(500, 0.4));
3837
3586
  } else {
3838
- const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3839
- deltaY = status.cy > halfY ? distance : -distance;
3587
+ await delay2(this.jitterMs(800, 0.5));
3840
3588
  }
3841
3589
  }
3842
- const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
3843
- const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3844
- const beforeState = scrollRect ? await element.evaluate((el) => {
3845
- const isScrollable = (node) => {
3846
- const style = window.getComputedStyle(node);
3847
- if (!style) return false;
3848
- const overflowY = style.overflowY;
3849
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3850
- return node.scrollHeight > node.clientHeight + 1;
3851
- };
3852
- let current = el;
3853
- while (current && current !== document.body) {
3854
- if (isScrollable(current)) {
3855
- return {
3856
- kind: "element",
3857
- top: current.scrollTop,
3858
- left: current.scrollLeft
3859
- };
3860
- }
3861
- current = current.parentElement;
3862
- }
3863
- return { kind: "window", top: window.scrollY, left: window.scrollX };
3864
- }) : null;
3865
- await dispatchTouchSwipe(page, deltaY, { rect: scrollRect });
3866
- if (scrollRect && beforeWindowState) {
3867
- const afterWindowState = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
3868
- if (Math.abs(afterWindowState.x - beforeWindowState.x) > 2 || Math.abs(afterWindowState.y - beforeWindowState.y) > 2) {
3869
- await page.evaluate((state2) => window.scrollTo(state2.x, state2.y), beforeWindowState);
3870
- logger5.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
3871
- }
3590
+ logger6.success("warmUpBrowsing");
3591
+ } catch (error) {
3592
+ logger6.fail("warmUpBrowsing", error);
3593
+ throw error;
3594
+ }
3595
+ },
3596
+ /**
3597
+ * 自然滚动 - 带惯性、减速效果和随机抖动
3598
+ * @param {import('playwright').Page} page
3599
+ * @param {'up' | 'down'} [direction='down'] - 滚动方向
3600
+ * @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
3601
+ * @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
3602
+ */
3603
+ async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
3604
+ const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
3605
+ const actualDistance = this.jitterMs(distance, 0.15);
3606
+ logger6.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
3607
+ const sign = direction === "down" ? 1 : -1;
3608
+ const stepDistance = actualDistance / steps;
3609
+ try {
3610
+ for (let i = 0; i < steps; i++) {
3611
+ const factor = 1 - i / steps * 0.5;
3612
+ const jitter = 0.9 + Math.random() * 0.2;
3613
+ const scrollAmount = stepDistance * factor * sign * jitter;
3614
+ await page.mouse.wheel(0, scrollAmount);
3615
+ const baseDelay = 60 + i * 25;
3616
+ await delay2(this.jitterMs(baseDelay, 0.3));
3872
3617
  }
3873
- let afterElementSnapshot = null;
3874
- const readAfterElementSnapshot = async () => {
3875
- if (!afterElementSnapshot) {
3876
- afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3877
- }
3878
- return afterElementSnapshot;
3879
- };
3880
- if (!scrollRect && beforeElementSnapshot) {
3881
- const afterSnapshot = await readAfterElementSnapshot();
3882
- if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
3883
- await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
3884
- 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"})`);
3885
- return { element, didScroll, restore: null, unscrollable: true };
3886
- }
3618
+ logger6.success("naturalScroll");
3619
+ } catch (error) {
3620
+ logger6.fail("naturalScroll", error);
3621
+ throw error;
3622
+ }
3623
+ }
3624
+ };
3625
+
3626
+ // src/internals/humanize/shared.js
3627
+ import delay3 from "delay";
3628
+ var jitterMs = (base, jitterPercent = 0.3) => {
3629
+ const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
3630
+ return Math.max(10, Math.round(Number(base || 0) + jitter));
3631
+ };
3632
+ var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
3633
+ if (target == null) return null;
3634
+ let element = target;
3635
+ if (typeof target === "string") {
3636
+ element = await page.$(target);
3637
+ }
3638
+ if (!element) {
3639
+ if (throwOnMissing) {
3640
+ throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
3641
+ }
3642
+ return null;
3643
+ }
3644
+ return element;
3645
+ };
3646
+ var waitJitter = (base, jitterPercent = 0.3) => delay3(jitterMs(base, jitterPercent));
3647
+
3648
+ // src/internals/humanize/mobile.js
3649
+ var logger7 = createInternalLogger("Humanize.Mobile");
3650
+ var initializedPages = /* @__PURE__ */ new WeakSet();
3651
+ var DEFAULT_TAP_TIMEOUT_MS = 2500;
3652
+ var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3653
+ var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3654
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3655
+ var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3656
+ var describeTarget = (target) => {
3657
+ if (target == null) return "Current Position";
3658
+ return typeof target === "string" ? target : "ElementHandle";
3659
+ };
3660
+ var clipBoxToViewport = (box, viewport) => {
3661
+ if (!box || !viewport) return box;
3662
+ const left = clamp(box.x, 0, viewport.width);
3663
+ const top = clamp(box.y, 0, viewport.height);
3664
+ const right = clamp(box.x + box.width, 0, viewport.width);
3665
+ const bottom = clamp(box.y + box.height, 0, viewport.height);
3666
+ if (right - left > 1 && bottom - top > 1) {
3667
+ return {
3668
+ x: left,
3669
+ y: top,
3670
+ width: right - left,
3671
+ height: bottom - top
3672
+ };
3673
+ }
3674
+ return box;
3675
+ };
3676
+ var centerPointInBox = (box) => ({
3677
+ x: box.x + box.width / 2,
3678
+ y: box.y + box.height / 2
3679
+ });
3680
+ var withTimeout = async (operation, timeoutMs, label) => {
3681
+ const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3682
+ let timeoutId = null;
3683
+ try {
3684
+ return await Promise.race([
3685
+ Promise.resolve().then(operation),
3686
+ new Promise((_, reject) => {
3687
+ timeoutId = setTimeout(() => {
3688
+ reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
3689
+ }, safeTimeoutMs);
3690
+ })
3691
+ ]);
3692
+ } finally {
3693
+ if (timeoutId) clearTimeout(timeoutId);
3694
+ }
3695
+ };
3696
+ var checkElementVisibility = async (element) => {
3697
+ return element.evaluate((el) => {
3698
+ const targetStyle = window.getComputedStyle(el);
3699
+ if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3700
+ return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
3701
+ }
3702
+ const rect = el.getBoundingClientRect();
3703
+ if (!rect || rect.width <= 0 || rect.height <= 0) {
3704
+ return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6", direction: "down" };
3705
+ }
3706
+ const viewW = window.innerWidth;
3707
+ const viewH = window.innerHeight;
3708
+ const centerY = rect.top + rect.height / 2;
3709
+ let clipLeft = 0;
3710
+ let clipRight = viewW;
3711
+ let clipTop = 0;
3712
+ let clipBottom = viewH;
3713
+ let isFixed = false;
3714
+ let positioning = null;
3715
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
3716
+ const style = window.getComputedStyle(node);
3717
+ if (style && (style.position === "fixed" || style.position === "sticky")) {
3718
+ isFixed = true;
3719
+ positioning = style.position;
3720
+ break;
3887
3721
  }
3888
- if (scrollRect && beforeState) {
3889
- const afterState = await element.evaluate((el) => {
3890
- const isScrollable = (node) => {
3891
- const style = window.getComputedStyle(node);
3892
- if (!style) return false;
3893
- const overflowY = style.overflowY;
3894
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3895
- return node.scrollHeight > node.clientHeight + 1;
3896
- };
3897
- let current = el;
3898
- while (current && current !== document.body) {
3899
- if (isScrollable(current)) {
3900
- return {
3901
- kind: "element",
3902
- top: current.scrollTop,
3903
- left: current.scrollLeft
3904
- };
3905
- }
3906
- current = current.parentElement;
3907
- }
3908
- return { kind: "window", top: window.scrollY, left: window.scrollX };
3909
- });
3910
- const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
3911
- const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
3912
- const expectedDelta = Number(deltaY || 0);
3913
- const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
3914
- if (!moved) {
3915
- const fallback = await scrollScrollableAncestor(element, deltaY);
3916
- 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)}`);
3917
- } 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)) {
3918
- const residualDelta = expectedDelta - topDelta;
3919
- if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
3920
- const fallback = await scrollScrollableAncestor(element, residualDelta);
3921
- 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)}`);
3922
- }
3923
- }
3722
+ }
3723
+ for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
3724
+ const style = window.getComputedStyle(node);
3725
+ if (!style) continue;
3726
+ const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
3727
+ const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
3728
+ if (!clipsX && !clipsY) continue;
3729
+ const nodeRect = node.getBoundingClientRect();
3730
+ if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
3731
+ if (clipsX) {
3732
+ clipLeft = Math.max(clipLeft, nodeRect.left);
3733
+ clipRight = Math.min(clipRight, nodeRect.right);
3924
3734
  }
3925
- if (scrollRect && beforeElementSnapshot) {
3926
- const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3927
- if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
3928
- await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
3929
- 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"})`);
3930
- return { element, didScroll, restore: null, unscrollable: true };
3931
- }
3735
+ if (clipsY) {
3736
+ clipTop = Math.max(clipTop, nodeRect.top);
3737
+ clipBottom = Math.min(clipBottom, nodeRect.bottom);
3932
3738
  }
3933
- didScroll = true;
3934
3739
  }
3935
- try {
3936
- await element.scrollIntoViewIfNeeded?.();
3937
- await waitJitter(80, 0.3);
3938
- const finalStatus = await checkElementVisibility(element);
3939
- if (finalStatus.code === "VISIBLE") {
3940
- logger5.info("humanScroll | \u539F\u751F scrollIntoViewIfNeeded \u515C\u5E95\u6210\u529F");
3941
- logger5.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3942
- return { element, didScroll: true, restore: null };
3943
- }
3944
- } catch (fallbackError) {
3945
- logger5.debug(`humanScroll | native fallback failed: ${fallbackError?.message || fallbackError}`);
3740
+ const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
3741
+ const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
3742
+ const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
3743
+ const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
3744
+ const visibleWidth = visibleRight - visibleLeft;
3745
+ const visibleHeight = visibleBottom - visibleTop;
3746
+ const cx = visibleLeft + visibleWidth / 2;
3747
+ const cy = visibleTop + visibleHeight / 2;
3748
+ if (visibleWidth <= 1 || visibleHeight <= 1) {
3749
+ return {
3750
+ code: "OUT_OF_VIEWPORT",
3751
+ reason: "\u4E0D\u5728\u89C6\u53E3\u5185",
3752
+ direction: centerY < clipTop ? "up" : "down",
3753
+ cy: centerY,
3754
+ viewH,
3755
+ isFixed,
3756
+ positioning
3757
+ };
3946
3758
  }
3947
- logger5.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
3948
- return { element, didScroll, restore: null };
3949
- },
3950
- async humanClick(page, target, options = {}) {
3951
- const {
3952
- reactionDelay = 220,
3953
- throwOnMissing = true,
3954
- scrollIfNeeded = true,
3955
- tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
3956
- mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
3957
- activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
3958
- fallbackDomClick = true,
3959
- fallbackDomClickOnTapError = true
3960
- } = options;
3961
- const targetDesc = describeTarget(target);
3962
- logger5.start("humanClick", `target=${targetDesc}`);
3963
- try {
3964
- if (target == null) {
3965
- const viewport = resolveViewport(page);
3966
- await waitJitter(reactionDelay, 0.45);
3967
- await tapPoint(page, {
3968
- x: viewport.width * (0.45 + Math.random() * 0.1),
3969
- y: viewport.height * (0.48 + Math.random() * 0.12)
3970
- }, {
3971
- timeoutMs: tapTimeoutMs,
3972
- mouseFallbackTimeoutMs
3973
- });
3974
- logger5.success("humanClick", "Tapped current position");
3759
+ const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3760
+ const commonAncestor = (a, b) => {
3761
+ for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3762
+ if (current.contains(b)) return current;
3763
+ }
3764
+ return null;
3765
+ };
3766
+ const sameTapTarget = (pointElement) => {
3767
+ if (!pointElement) return false;
3768
+ if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3975
3769
  return true;
3976
3770
  }
3977
- const element = await resolveElement(page, target, { throwOnMissing });
3978
- if (!element) {
3979
- logger5.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
3771
+ const common = commonAncestor(el, pointElement);
3772
+ if (!common) return false;
3773
+ const commonRect = common.getBoundingClientRect?.();
3774
+ if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3775
+ const commonArea = commonRect.width * commonRect.height;
3776
+ const targetArea = Math.max(1, rect.width * rect.height);
3777
+ const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3778
+ if (commonArea > maxSharedRegionArea) return false;
3779
+ if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3980
3780
  return false;
3981
3781
  }
3982
- const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
3983
- const status = await checkElementVisibility(element).catch(() => null);
3984
- if (status && status.code !== "VISIBLE") {
3985
- if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
3986
- let fallback = await withTimeout(
3987
- () => activateElementFallback(element, null, {
3988
- editableOnly: true
3989
- }),
3990
- activateFallbackTimeoutMs,
3991
- "focus fallback"
3992
- ).catch(() => null);
3993
- if (!fallback?.activated) {
3994
- fallback = await withTimeout(
3995
- () => activateElementFallback(element, null, {
3996
- editableOnly: false
3997
- }),
3998
- activateFallbackTimeoutMs,
3999
- "activation fallback"
4000
- ).catch(() => null);
4001
- }
4002
- if (fallback?.activated) {
4003
- logger5.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4004
- return true;
4005
- }
4006
- }
4007
- const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
4008
- if (throwOnMissing) throw new Error(message);
4009
- logger5.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
4010
- return false;
3782
+ return common.contains(el) && common.contains(pointElement);
3783
+ };
3784
+ const describeElement = (node) => {
3785
+ if (!node) return null;
3786
+ const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
3787
+ const rect2 = node.getBoundingClientRect?.();
3788
+ const style = window.getComputedStyle(node);
3789
+ return {
3790
+ tag: node.tagName,
3791
+ id: node.id || "",
3792
+ className,
3793
+ isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
3794
+ positioning: style?.position || "",
3795
+ top: rect2 ? rect2.top : null,
3796
+ bottom: rect2 ? rect2.bottom : null,
3797
+ left: rect2 ? rect2.left : null,
3798
+ right: rect2 ? rect2.right : null
3799
+ };
3800
+ };
3801
+ const samplePoints = [
3802
+ { x: cx, y: cy },
3803
+ { x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3804
+ { x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3805
+ { x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
3806
+ { x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
3807
+ ];
3808
+ let obstruction = null;
3809
+ for (const point of samplePoints) {
3810
+ const pointElement = document.elementFromPoint(point.x, point.y);
3811
+ if (sameTapTarget(pointElement)) {
3812
+ return { code: "VISIBLE", isFixed, positioning };
4011
3813
  }
4012
- const box = await element.boundingBox();
4013
- if (!box) {
4014
- if (throwOnMissing) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4015
- logger5.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
4016
- return false;
3814
+ obstruction = obstruction || describeElement(pointElement);
3815
+ }
3816
+ if (obstruction) {
3817
+ return {
3818
+ code: "OBSTRUCTED",
3819
+ reason: "\u88AB\u906E\u6321",
3820
+ direction: cy > viewH / 2 ? "down" : "up",
3821
+ obstruction,
3822
+ cy,
3823
+ viewH,
3824
+ isFixed,
3825
+ positioning
3826
+ };
3827
+ }
3828
+ return { code: "VISIBLE", isFixed, positioning };
3829
+ });
3830
+ };
3831
+ var activateElementFallback = async (element, point = null, options = {}) => {
3832
+ return element.evaluate((el, { innerOptions }) => {
3833
+ const isEditable = (node) => {
3834
+ if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3835
+ if (node.isContentEditable) return true;
3836
+ if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3837
+ if (node instanceof HTMLInputElement) {
3838
+ return !node.disabled && !node.readOnly && typeof node.select === "function";
3839
+ }
3840
+ return false;
3841
+ };
3842
+ const findEditable = (node) => {
3843
+ for (let current = node; current && current !== document.body; current = current.parentElement) {
3844
+ if (isEditable(current)) return current;
4017
3845
  }
4018
- await waitJitter(reactionDelay, 0.45);
4019
- const visibleBox = clipBoxToViewport(box, resolveViewport(page));
4020
- const tapTarget = centerPointInBox(visibleBox);
4021
- try {
4022
- await tapPoint(page, tapTarget, {
4023
- timeoutMs: tapTimeoutMs,
4024
- mouseFallbackTimeoutMs
4025
- });
4026
- } catch (tapError) {
4027
- if (!fallbackDomClickOnTapError) throw tapError;
4028
- const fallback = await withTimeout(
4029
- () => activateElementFallback(element, tapTarget, {
4030
- editableOnly: false
4031
- }),
4032
- activateFallbackTimeoutMs,
4033
- "activation fallback"
4034
- ).catch(() => null);
4035
- if (!fallback?.activated) throw tapError;
4036
- logger5.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
3846
+ return null;
3847
+ };
3848
+ const editable = findEditable(el);
3849
+ if (editable && typeof editable.focus === "function") {
3850
+ editable.focus({ preventScroll: true });
3851
+ if (innerOptions.editableOnly) {
3852
+ return { activated: true, method: "focus", tag: editable.tagName || "" };
4037
3853
  }
4038
- await waitJitter(120, 0.35);
4039
- logger5.success("humanClick");
4040
- return true;
4041
- } catch (error) {
4042
- logger5.fail("humanClick", error);
4043
- throw error;
4044
3854
  }
4045
- },
4046
- async randomSleep(baseMs, jitterPercent = 0.3) {
4047
- await waitJitter(baseMs, jitterPercent);
4048
- },
4049
- async simulateGaze(page, baseDurationMs = 2500) {
4050
- const durationMs = jitterMs(baseDurationMs, 0.4);
4051
- const startTime = Date.now();
4052
- while (Date.now() - startTime < durationMs) {
4053
- if (Math.random() < 0.28) {
4054
- const distance = 70 + Math.random() * 120;
4055
- await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
4056
- durationMs: 180,
4057
- steps: 4
4058
- });
4059
- } else {
4060
- await waitJitter(420, 0.55);
4061
- }
3855
+ if (innerOptions.editableOnly) {
3856
+ return { activated: false, method: "none", tag: el?.tagName || "" };
4062
3857
  }
4063
- },
4064
- async humanType(page, selector, text, options = {}) {
4065
- const {
4066
- baseDelay = 160,
4067
- pauseProbability = 0.08,
4068
- pauseBase = 700
4069
- } = options;
4070
- await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
4071
- await waitJitter(220, 0.45);
4072
- for (let i = 0; i < String(text).length; i += 1) {
4073
- const char = String(text)[i];
4074
- const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
4075
- await page.keyboard.type(char);
4076
- await waitJitter(charDelay, 0.15);
4077
- if (Math.random() < pauseProbability && i < String(text).length - 1) {
4078
- await waitJitter(pauseBase, 0.5);
4079
- }
3858
+ if (typeof el.focus === "function") {
3859
+ el.focus({ preventScroll: true });
4080
3860
  }
4081
- },
4082
- async humanPress(page, targetOrKey, maybeKey, options = {}) {
4083
- const hasTarget = typeof maybeKey === "string";
4084
- const key = hasTarget ? maybeKey : targetOrKey;
4085
- const pressOptions = hasTarget ? options : maybeKey || options;
4086
- const {
4087
- reactionDelay = 170,
4088
- holdDelay = 42,
4089
- focusDelay = 180,
4090
- scrollIfNeeded = true,
4091
- throwOnMissing = true,
4092
- keyboardOptions = {}
4093
- } = pressOptions || {};
4094
- const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
4095
- logger5.start("humanPress", `key=${key}, target=${targetDesc}`);
4096
- try {
4097
- if (hasTarget) {
4098
- await MobileHumanize.humanClick(page, targetOrKey, {
4099
- reactionDelay: focusDelay,
4100
- scrollIfNeeded,
4101
- throwOnMissing
4102
- });
4103
- }
4104
- await waitJitter(reactionDelay, 0.45);
4105
- await page.keyboard.press(key, {
4106
- ...keyboardOptions,
4107
- delay: jitterMs(holdDelay, 0.5)
4108
- });
4109
- logger5.success("humanPress");
4110
- return true;
4111
- } catch (error) {
4112
- logger5.fail("humanPress", error);
4113
- throw error;
3861
+ if (typeof el.click === "function") {
3862
+ el.click();
3863
+ return { activated: true, method: "dom-click", tag: el.tagName || "" };
4114
3864
  }
4115
- },
4116
- async humanClear(page, selector) {
4117
- const locator = page.locator(selector);
4118
- await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4119
- await waitJitter(160, 0.4);
4120
- const readValue = async () => {
4121
- try {
4122
- return await locator.inputValue({ timeout: 600 });
4123
- } catch {
4124
- return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4125
- }
3865
+ if (typeof el.dispatchEvent === "function") {
3866
+ el.dispatchEvent(new MouseEvent("click", {
3867
+ bubbles: true,
3868
+ cancelable: true,
3869
+ view: window
3870
+ }));
3871
+ return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
3872
+ }
3873
+ return {
3874
+ activated: Boolean(editable),
3875
+ method: editable ? "focus" : "none",
3876
+ tag: el?.tagName || ""
4126
3877
  };
4127
- const currentValue = await readValue();
4128
- if (!currentValue) return;
4129
- await page.keyboard.press("ControlOrMeta+A");
4130
- await waitJitter(90, 0.35);
4131
- await page.keyboard.press("Backspace");
4132
- await waitJitter(120, 0.35);
4133
- if (!await readValue()) return;
4134
- await locator.evaluate((el) => {
4135
- if ("value" in el) {
4136
- el.value = "";
4137
- el.dispatchEvent(new Event("input", { bubbles: true }));
4138
- el.dispatchEvent(new Event("change", { bubbles: true }));
4139
- return;
3878
+ }, {
3879
+ innerOptions: options || {}
3880
+ });
3881
+ };
3882
+ var getScrollableRect = async (element) => {
3883
+ return element.evaluate((el) => {
3884
+ const isScrollable = (node) => {
3885
+ const style = window.getComputedStyle(node);
3886
+ if (!style) return false;
3887
+ const overflowY = style.overflowY;
3888
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3889
+ return node.scrollHeight > node.clientHeight + 1;
3890
+ };
3891
+ let current = el;
3892
+ while (current && current !== document.body) {
3893
+ if (isScrollable(current)) {
3894
+ const rect = current.getBoundingClientRect();
3895
+ if (rect && rect.width > 0 && rect.height > 0) {
3896
+ return {
3897
+ x: rect.x,
3898
+ y: rect.y,
3899
+ width: rect.width,
3900
+ height: rect.height
3901
+ };
3902
+ }
4140
3903
  }
4141
- el.textContent = "";
4142
- el.dispatchEvent(new Event("input", { bubbles: true }));
4143
- });
4144
- },
4145
- async warmUpBrowsing(page, baseDuration = 3500) {
4146
- const durationMs = jitterMs(baseDuration, 0.4);
4147
- const startTime = Date.now();
4148
- while (Date.now() - startTime < durationMs) {
4149
- const action = Math.random();
4150
- if (action < 0.5) {
4151
- await dispatchTouchSwipe(page, 120 + Math.random() * 220, {
4152
- durationMs: 240,
4153
- steps: 5
4154
- });
4155
- } else if (action < 0.7) {
4156
- await dispatchTouchSwipe(page, -(80 + Math.random() * 160), {
4157
- durationMs: 220,
4158
- steps: 4
4159
- });
4160
- } else {
4161
- await waitJitter(560, 0.55);
3904
+ current = current.parentElement;
3905
+ }
3906
+ return null;
3907
+ });
3908
+ };
3909
+ var scrollScrollableAncestor = async (element, deltaY) => {
3910
+ return element.evaluate((el, amount) => {
3911
+ const isScrollable = (node) => {
3912
+ const style = window.getComputedStyle(node);
3913
+ if (!style) return false;
3914
+ const overflowY = style.overflowY;
3915
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3916
+ return node.scrollHeight > node.clientHeight + 1;
3917
+ };
3918
+ let current = el;
3919
+ while (current && current !== document.body) {
3920
+ if (isScrollable(current)) {
3921
+ const beforeTop2 = current.scrollTop;
3922
+ current.scrollTop = beforeTop2 + amount;
3923
+ return {
3924
+ scroller: true,
3925
+ moved: current.scrollTop !== beforeTop2,
3926
+ scrollTop: current.scrollTop
3927
+ };
4162
3928
  }
3929
+ current = current.parentElement;
4163
3930
  }
4164
- },
4165
- async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4166
- const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
4167
- const actualDistance = jitterMs(distance, 0.15);
4168
- const sign = direction === "down" ? 1 : -1;
4169
- for (let i = 0; i < steps; i += 1) {
4170
- const factor = 1 - i / steps * 0.45;
4171
- await dispatchTouchSwipe(page, actualDistance / steps * factor * sign, {
4172
- durationMs: 150 + i * 20,
4173
- steps: 4
4174
- });
3931
+ const beforeTop = window.scrollY;
3932
+ window.scrollBy(0, amount);
3933
+ return {
3934
+ scroller: null,
3935
+ moved: window.scrollY !== beforeTop,
3936
+ scrollTop: window.scrollY
3937
+ };
3938
+ }, deltaY);
3939
+ };
3940
+ var scrollAwayFromObstruction = async (element, status) => {
3941
+ return element.evaluate((el, innerStatus) => {
3942
+ const isScrollable = (node) => {
3943
+ const style = window.getComputedStyle(node);
3944
+ if (!style) return false;
3945
+ const overflowY = style.overflowY;
3946
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3947
+ return node.scrollHeight > node.clientHeight + 1;
3948
+ };
3949
+ let scroller = el;
3950
+ while (scroller && scroller !== document.body) {
3951
+ if (isScrollable(scroller)) break;
3952
+ scroller = scroller.parentElement;
3953
+ }
3954
+ if (!scroller || scroller === document.body) {
3955
+ return { moved: false, scrollTop: 0, deltaY: 0 };
3956
+ }
3957
+ const rect = el.getBoundingClientRect();
3958
+ const scrollerRect = scroller.getBoundingClientRect();
3959
+ const obstruction = innerStatus?.obstruction || {};
3960
+ const obstructionTop = Number(obstruction.top);
3961
+ const obstructionBottom = Number(obstruction.bottom);
3962
+ const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
3963
+ const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
3964
+ const padding = 18;
3965
+ let deltaY = 0;
3966
+ if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
3967
+ deltaY = rect.bottom - obstructionTop + padding;
3968
+ } else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
3969
+ deltaY = rect.top - obstructionBottom - padding;
3970
+ } else {
3971
+ const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
3972
+ deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
4175
3973
  }
4176
- }
3974
+ const beforeTop = scroller.scrollTop;
3975
+ scroller.scrollTop = beforeTop + deltaY;
3976
+ return {
3977
+ moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
3978
+ scrollTop: scroller.scrollTop,
3979
+ deltaY
3980
+ };
3981
+ }, status);
4177
3982
  };
4178
-
4179
- // src/internals/humanize/cloakbrowser.js
4180
- var FORCE_CLICK_OPTIONS = Object.freeze({ force: true });
4181
- var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4182
- var isPoint2 = (value) => value && typeof value === "object" && Number.isFinite(Number(value.x)) && Number.isFinite(Number(value.y));
4183
- var resolveTarget = (page, target) => typeof target === "string" ? page.locator(target).first() : target;
4184
- var resolveTargetPoint = async (page, target) => {
4185
- if (typeof target === "string") {
4186
- return await DeviceInput.selectorCenterPoint(page, target).catch(() => null);
3983
+ var getElementViewportSnapshot = async (element) => {
3984
+ return element.evaluate((el) => {
3985
+ const rect = el.getBoundingClientRect();
3986
+ return {
3987
+ top: rect.top,
3988
+ bottom: rect.bottom,
3989
+ left: rect.left,
3990
+ right: rect.right,
3991
+ width: rect.width,
3992
+ height: rect.height,
3993
+ scrollX: window.scrollX,
3994
+ scrollY: window.scrollY
3995
+ };
3996
+ });
3997
+ };
3998
+ var isTargetImmobileAfterScroll = (before, after) => {
3999
+ if (!before || !after) return false;
4000
+ const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
4001
+ const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
4002
+ const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
4003
+ const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
4004
+ const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
4005
+ const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
4006
+ if (!rectMoved && !pageMoved) return true;
4007
+ if (pageMoved && !rectMoved) return true;
4008
+ if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
4009
+ return true;
4187
4010
  }
4188
- const box = await target?.boundingBox?.().catch(() => null);
4189
- if (!box) {
4190
- return null;
4011
+ return false;
4012
+ };
4013
+ var restoreWindowFromSnapshot = async (page, before, after) => {
4014
+ if (!before || !after) return;
4015
+ if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
4016
+ return;
4191
4017
  }
4192
- return {
4193
- x: box.x + box.width / 2,
4194
- y: box.y + box.height / 2
4195
- };
4018
+ await page.evaluate(
4019
+ (state) => window.scrollTo(state.x, state.y),
4020
+ { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
4021
+ ).catch(() => {
4022
+ });
4196
4023
  };
4197
- var sleep = (baseMs, jitterPercent = 0.3) => delay3(jitterMs(baseMs, jitterPercent));
4198
- var buildKeyboardOptions = (options = {}) => ({
4199
- ...options.holdDelay != null ? { delay: Math.max(0, Number(options.holdDelay) || 0) } : {},
4200
- ...options.keyboardOptions || {}
4201
- });
4202
- var CloakBrowserDeviceHumanize = {
4203
- jitterMs(base, jitterPercent = 0.3) {
4204
- return jitterMs(base, jitterPercent);
4205
- },
4206
- async initializeCursor(page) {
4207
- return Boolean(page);
4208
- },
4209
- async humanMove(page, target) {
4210
- if (target == null) {
4211
- return false;
4212
- }
4213
- if (isPoint2(target)) {
4214
- await DeviceInput.move(page, target, void 0, { forceMouse: true });
4215
- return true;
4216
- }
4217
- const point = await resolveTargetPoint(page, target);
4218
- if (!point) {
4219
- return false;
4220
- }
4221
- await DeviceInput.move(page, point, void 0, { forceMouse: true });
4222
- return true;
4223
- },
4224
- async humanScroll(page, target) {
4225
- const element = resolveTarget(page, target);
4226
- if (!element) {
4227
- return { element: null, didScroll: false, restore: null };
4024
+ var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
4025
+ const viewport = resolveViewport(page);
4026
+ const rawRect = options.rect || null;
4027
+ const rect = rawRect ? {
4028
+ x: clamp(rawRect.x, 0, viewport.width),
4029
+ y: clamp(rawRect.y, 0, viewport.height),
4030
+ width: clamp(rawRect.width, 0, viewport.width),
4031
+ height: clamp(rawRect.height, 0, viewport.height)
4032
+ } : null;
4033
+ const area = rect && rect.width > 24 && rect.height > 48 ? {
4034
+ left: rect.x,
4035
+ right: Math.min(viewport.width, rect.x + rect.width),
4036
+ top: rect.y,
4037
+ bottom: Math.min(viewport.height, rect.y + rect.height)
4038
+ } : {
4039
+ left: 0,
4040
+ right: viewport.width,
4041
+ top: 0,
4042
+ bottom: viewport.height
4043
+ };
4044
+ const areaWidth = Math.max(1, area.right - area.left);
4045
+ const areaHeight = Math.max(1, area.bottom - area.top);
4046
+ const maxDistance = rect ? Math.max(60, areaHeight * 0.72) : Math.max(120, viewport.height * 0.72);
4047
+ const distance = clamp(Math.abs(deltaY), Math.min(80, maxDistance), maxDistance);
4048
+ const direction = deltaY >= 0 ? 1 : -1;
4049
+ const startX = clamp(
4050
+ area.left + areaWidth * (0.45 + Math.random() * 0.1),
4051
+ 24,
4052
+ viewport.width - 24
4053
+ );
4054
+ 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);
4055
+ const endY = clamp(startY - direction * distance, Math.max(16, area.top + 12), Math.min(viewport.height - 16, area.bottom - 12));
4056
+ const steps = Math.max(4, Math.round(Number(options.steps || 6)));
4057
+ const durationMs = jitterMs(options.durationMs || 320, 0.35);
4058
+ let client = null;
4059
+ const scrollBefore = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4060
+ try {
4061
+ if (page.context && typeof page.context().newCDPSession === "function") {
4062
+ client = await page.context().newCDPSession(page);
4228
4063
  }
4229
- await element.scrollIntoViewIfNeeded?.().catch(() => {
4064
+ if (!client) throw new Error("CDP session unavailable");
4065
+ await client.send("Input.synthesizeScrollGesture", {
4066
+ x: startX,
4067
+ y: startY,
4068
+ yDistance: -direction * distance,
4069
+ xOverscroll: (Math.random() - 0.5) * 4,
4070
+ yOverscroll: (Math.random() - 0.5) * 8,
4071
+ speed: 700 + Math.round(Math.random() * 350),
4072
+ gestureSourceType: "touch"
4230
4073
  });
4231
- return { element, didScroll: true, restore: null };
4232
- },
4233
- async humanClick(page, target) {
4234
- if (target == null) {
4235
- await DeviceInput.clickPoint(page, { x: 0, y: 0 }, void 0, { forceMouse: true });
4236
- return true;
4237
- }
4238
- if (isPoint2(target)) {
4239
- await DeviceInput.clickPoint(page, target, void 0, { forceMouse: true });
4074
+ await waitJitter(160, 0.35);
4075
+ const scrollAfterSynth = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4076
+ if (scrollBefore && scrollAfterSynth && (Math.abs(scrollAfterSynth.y - scrollBefore.y) > 2 || Math.abs(scrollAfterSynth.x - scrollBefore.x) > 2)) {
4240
4077
  return true;
4241
4078
  }
4242
- await DeviceInput.click(page, target, {
4243
- clickOptions: FORCE_CLICK_OPTIONS
4079
+ await client.send("Input.dispatchTouchEvent", {
4080
+ type: "touchStart",
4081
+ touchPoints: [{ x: startX, y: startY, id: 1 }]
4244
4082
  });
4245
- return true;
4246
- },
4247
- async randomSleep(baseMs, jitterPercent = 0.3) {
4248
- await sleep(baseMs, jitterPercent);
4249
- },
4250
- async simulateGaze(page, baseDurationMs = 2500) {
4251
- const durationMs = jitterMs(baseDurationMs, 0.4);
4252
- const startedAt = Date.now();
4253
- const viewport = page.viewportSize() || { width: 1365, height: 900 };
4254
- while (Date.now() - startedAt < durationMs) {
4255
- await DeviceInput.move(page, {
4256
- x: 100 + Math.random() * Math.max(120, viewport.width - 200),
4257
- y: 100 + Math.random() * Math.max(120, viewport.height - 200)
4258
- }, void 0, { forceMouse: true });
4259
- await sleep(300, 0.5);
4083
+ for (let i = 1; i <= steps; i += 1) {
4084
+ const progress = i / steps;
4085
+ const eased = 1 - Math.pow(1 - progress, 2);
4086
+ const x = startX + (Math.random() - 0.5) * 3;
4087
+ const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 5;
4088
+ await waitJitter(durationMs / steps, 0.25);
4089
+ await client.send("Input.dispatchTouchEvent", {
4090
+ type: "touchMove",
4091
+ touchPoints: [{ x, y, id: 1 }]
4092
+ });
4260
4093
  }
4261
- },
4262
- async humanType(page, target, text, options = {}) {
4263
- await DeviceInput.click(page, target, {
4264
- clickOptions: FORCE_CLICK_OPTIONS
4265
- });
4266
- await DeviceInput.keyboardType(page, text, {
4267
- ...options.baseDelay != null ? { delay: Math.max(0, Number(options.baseDelay) || 0) } : {}
4094
+ await client.send("Input.dispatchTouchEvent", {
4095
+ type: "touchEnd",
4096
+ touchPoints: []
4268
4097
  });
4098
+ await waitJitter(120, 0.35);
4099
+ const scrollAfterTouch = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4100
+ if (scrollBefore && scrollAfterTouch && Math.abs(scrollAfterTouch.y - scrollBefore.y) <= 2 && Math.abs(scrollAfterTouch.x - scrollBefore.x) <= 2) {
4101
+ await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
4102
+ await waitJitter(120, 0.35);
4103
+ }
4269
4104
  return true;
4270
- },
4271
- async humanPress(page, targetOrKey, maybeKey, options = {}) {
4272
- if (typeof maybeKey !== "string") {
4273
- await DeviceInput.press(page, targetOrKey, {
4274
- keyboardOptions: buildKeyboardOptions(maybeKey || options)
4275
- });
4105
+ } catch (error) {
4106
+ logger7.debug(`touch swipe fallback: ${error?.message || error}`);
4107
+ try {
4108
+ await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
4109
+ await waitJitter(120, 0.35);
4110
+ return true;
4111
+ } catch {
4112
+ await page.mouse.wheel(0, deltaY);
4113
+ await waitJitter(120, 0.35);
4276
4114
  return true;
4277
4115
  }
4278
- await DeviceInput.press(page, targetOrKey, maybeKey, {
4279
- clickOptions: FORCE_CLICK_OPTIONS,
4280
- keyboardOptions: buildKeyboardOptions(options)
4281
- });
4282
- return true;
4283
- },
4284
- async humanClear(page, target) {
4285
- await DeviceInput.fill(page, target, "", { force: true });
4286
- },
4287
- async warmUpBrowsing(page, baseDuration = 3500) {
4288
- await this.simulateGaze(page, Math.min(baseDuration, 900));
4289
- },
4290
- async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4291
- const steps = Math.max(1, Number(baseSteps) || 1);
4292
- const delta = (Number(distance) || 0) / steps * (direction === "down" ? 1 : -1);
4293
- for (let index = 0; index < steps; index += 1) {
4294
- await page.mouse.wheel(0, delta);
4295
- await sleep(60 + index * 20, 0.3);
4116
+ } finally {
4117
+ if (client && typeof client.detach === "function") {
4118
+ try {
4119
+ await client.detach();
4120
+ } catch {
4121
+ }
4296
4122
  }
4297
4123
  }
4298
4124
  };
4299
- var resolveCloakBrowserHumanizeDelegate = (page) => resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : CloakBrowserDeviceHumanize;
4300
- var CloakBrowserHumanizeContext = Object.freeze({
4301
- desktopDelegate: CloakBrowserDeviceHumanize,
4302
- resolveDelegate: resolveCloakBrowserHumanizeDelegate
4303
- });
4304
-
4305
- // src/internals/humanize/desktop.js
4306
- import delay4 from "delay";
4307
- import { createCursor } from "ghost-cursor-playwright";
4308
- var logger6 = createInternalLogger("Humanize");
4309
- var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
4310
- function $GetCursor(page) {
4311
- const cursor = $CursorWeakMap.get(page);
4312
- if (!cursor) {
4313
- throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
4125
+ var tapPoint = async (page, point, options = {}) => {
4126
+ const {
4127
+ timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
4128
+ mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
4129
+ allowMouseFallback = true
4130
+ } = options;
4131
+ if (page.touchscreen && typeof page.touchscreen.tap === "function") {
4132
+ try {
4133
+ await withTimeout(
4134
+ () => page.touchscreen.tap(point.x, point.y),
4135
+ timeoutMs,
4136
+ "touchscreen.tap"
4137
+ );
4138
+ return { method: "touchscreen" };
4139
+ } catch (error) {
4140
+ logger7.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
4141
+ if (!allowMouseFallback) throw error;
4142
+ }
4314
4143
  }
4315
- return cursor;
4316
- }
4317
- var Humanize = {
4318
- /**
4319
- * 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
4320
- * @param {number} base - 基础延迟 (ms)
4321
- * @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
4322
- * @returns {number} 抖动后的毫秒数
4323
- */
4324
- jitterMs(base, jitterPercent = 0.3) {
4325
- const jitter = base * jitterPercent * (Math.random() * 2 - 1);
4326
- return Math.max(10, Math.round(base + jitter));
4327
- },
4328
- /**
4329
- * 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
4330
- *
4331
- * @param {import('playwright').Page} page
4332
- * @returns {Promise<void>}
4333
- */
4144
+ await withTimeout(
4145
+ () => page.mouse.click(point.x, point.y),
4146
+ mouseFallbackTimeoutMs,
4147
+ "mouse.click fallback"
4148
+ );
4149
+ return { method: "mouse" };
4150
+ };
4151
+ var MobileHumanize = {
4152
+ jitterMs,
4334
4153
  async initializeCursor(page) {
4335
- if ($CursorWeakMap.has(page)) {
4336
- logger6.debug("initializeCursor: cursor already exists, skipping");
4337
- return;
4338
- }
4339
- logger6.start("initializeCursor", "creating cursor");
4340
- const cursor = await createCursor(page);
4341
- $CursorWeakMap.set(page, cursor);
4342
- logger6.success("initializeCursor", "cursor initialized");
4154
+ if (initializedPages.has(page)) return;
4155
+ initializedPages.add(page);
4156
+ logger7.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
4343
4157
  },
4344
- /**
4345
- * 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
4346
- *
4347
- * @param {import('playwright').Page} page
4348
- * @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
4349
- */
4350
4158
  async humanMove(page, target) {
4351
- const cursor = $GetCursor(page);
4352
- logger6.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
4353
- try {
4354
- if (typeof target === "string") {
4355
- const element = await page.$(target);
4356
- if (!element) {
4357
- logger6.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
4358
- return false;
4359
- }
4360
- const box = await element.boundingBox();
4361
- if (!box) {
4362
- logger6.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
4363
- return false;
4364
- }
4365
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
4366
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
4367
- await cursor.actions.move({ x, y });
4368
- } else if (target && typeof target.x === "number" && typeof target.y === "number") {
4369
- await cursor.actions.move(target);
4370
- } else if (target && typeof target.boundingBox === "function") {
4371
- const box = await target.boundingBox();
4372
- if (box) {
4373
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
4374
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
4375
- await cursor.actions.move({ x, y });
4376
- }
4159
+ logger7.debug(`humanMove: mobile no-op target=${typeof target === "string" ? target : "element/coords"}`);
4160
+ if (typeof target === "string" || target && typeof target.boundingBox === "function") {
4161
+ const element = await resolveElement(page, target, { throwOnMissing: false });
4162
+ if (!element) {
4163
+ return false;
4377
4164
  }
4378
- logger6.success("humanMove");
4379
- return true;
4380
- } catch (error) {
4381
- logger6.fail("humanMove", error);
4382
- throw error;
4383
4165
  }
4166
+ await waitJitter(80, 0.4);
4167
+ return true;
4384
4168
  },
4385
- /**
4386
- * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
4387
- * 返回 restore 方法,用于将滚动容器恢复到原位置
4388
- *
4389
- * @param {import('playwright').Page} page
4390
- * @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
4391
- * @param {Object} [options]
4392
- * @param {number} [options.maxSteps=20] - 最大滚动步数
4393
- * @param {number} [options.minStep=260] - 单次滚动最小步长
4394
- * @param {number} [options.maxStep=800] - 单次滚动最大步长
4395
- * @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
4396
- */
4397
4169
  async humanScroll(page, target, options = {}) {
4398
4170
  const {
4399
4171
  maxSteps = 20,
4400
- minStep = 260,
4401
- maxStep = 800,
4402
- maxDurationMs = maxSteps * 220 + 800
4172
+ minStep = 180,
4173
+ maxStep = 520,
4174
+ maxDurationMs = maxSteps * 280 + 1200,
4175
+ throwOnMissing = false
4403
4176
  } = options;
4404
- const targetDesc = typeof target === "string" ? target : "ElementHandle";
4405
- logger6.start("humanScroll", `target=${targetDesc}`);
4406
- let element;
4407
- if (typeof target === "string") {
4408
- element = await page.$(target);
4409
- if (!element) {
4410
- logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
4411
- return { element: null, didScroll: false };
4412
- }
4413
- } else {
4414
- element = target;
4177
+ const targetDesc = describeTarget(target);
4178
+ logger7.start("humanScroll", `target=${targetDesc}`);
4179
+ const element = await resolveElement(page, target, { throwOnMissing });
4180
+ if (!element) {
4181
+ logger7.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
4182
+ return { element: null, didScroll: false, restore: null };
4415
4183
  }
4416
- const cursor = $GetCursor(page);
4184
+ const startTime = Date.now();
4417
4185
  let didScroll = false;
4418
- const checkVisibility = async () => {
4419
- return await element.evaluate((el) => {
4420
- const rect = el.getBoundingClientRect();
4421
- if (!rect || rect.width === 0 || rect.height === 0) {
4422
- return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
4423
- }
4424
- const cx = rect.left + rect.width / 2;
4425
- const cy = rect.top + rect.height / 2;
4426
- const viewH = window.innerHeight;
4427
- const viewW = window.innerWidth;
4428
- let isFixed = false;
4429
- for (let node = el; node && node !== document.body; node = node.parentElement) {
4430
- const style = window.getComputedStyle(node);
4431
- if (style && style.position === "fixed") {
4432
- isFixed = true;
4433
- break;
4434
- }
4186
+ for (let i = 0; i < maxSteps; i += 1) {
4187
+ const status = await checkElementVisibility(element);
4188
+ if (status.code === "VISIBLE") {
4189
+ if (status.isFixed) {
4190
+ logger7.info("humanScroll | fixed/sticky \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4191
+ } else {
4192
+ logger7.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
4435
4193
  }
4436
- if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
4437
- const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
4438
- return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH, isFixed };
4194
+ logger7.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4195
+ return { element, didScroll, restore: null };
4196
+ }
4197
+ if (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE") {
4198
+ logger7.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
4199
+ return { element, didScroll, restore: null };
4200
+ }
4201
+ const scrollRect = await getScrollableRect(element);
4202
+ if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4203
+ 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"})`);
4204
+ return { element, didScroll, restore: null, unscrollable: true };
4205
+ }
4206
+ if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
4207
+ logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
4208
+ return { element, didScroll, restore: null, unscrollable: true };
4209
+ }
4210
+ if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
4211
+ const moved = await scrollAwayFromObstruction(element, status);
4212
+ if (moved.moved) {
4213
+ logger7.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
4214
+ await waitJitter(90, 0.3);
4215
+ didScroll = true;
4216
+ continue;
4439
4217
  }
4440
- const pointElement = document.elementFromPoint(cx, cy);
4441
- if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
4442
- return {
4443
- code: "OBSTRUCTED",
4444
- reason: "\u88AB\u906E\u6321",
4445
- obstruction: {
4446
- tag: pointElement.tagName,
4447
- id: pointElement.id,
4448
- className: pointElement.className
4449
- },
4450
- cy,
4451
- // Return Center Y for smart direction calculation
4452
- viewH,
4453
- isFixed
4454
- };
4218
+ }
4219
+ if (Date.now() - startTime > maxDurationMs) {
4220
+ logger7.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
4221
+ return { element, didScroll, restore: null };
4222
+ }
4223
+ const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
4224
+ const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
4225
+ logger7.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
4226
+ const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
4227
+ let deltaY = status.direction === "up" ? -distance : distance;
4228
+ if (status.code === "OBSTRUCTED") {
4229
+ if (status.obstruction?.isFixed && status.obstruction.top != null) {
4230
+ const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
4231
+ const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4232
+ deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
4233
+ } else {
4234
+ const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4235
+ deltaY = status.cy > halfY ? distance : -distance;
4455
4236
  }
4456
- return { code: "VISIBLE", isFixed };
4457
- });
4458
- };
4459
- const getScrollableRect2 = async () => {
4460
- return await element.evaluate((el) => {
4237
+ }
4238
+ const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
4239
+ const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4240
+ const beforeState = scrollRect ? await element.evaluate((el) => {
4461
4241
  const isScrollable = (node) => {
4462
4242
  const style = window.getComputedStyle(node);
4463
4243
  if (!style) return false;
@@ -4468,427 +4248,386 @@ var Humanize = {
4468
4248
  let current = el;
4469
4249
  while (current && current !== document.body) {
4470
4250
  if (isScrollable(current)) {
4471
- const rect = current.getBoundingClientRect();
4472
- if (rect && rect.width > 0 && rect.height > 0) {
4473
- return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
4474
- }
4251
+ return {
4252
+ kind: "element",
4253
+ top: current.scrollTop,
4254
+ left: current.scrollLeft
4255
+ };
4475
4256
  }
4476
4257
  current = current.parentElement;
4477
4258
  }
4478
- return null;
4479
- });
4480
- };
4481
- const startTime = Date.now();
4482
- try {
4483
- for (let i = 0; i < maxSteps; i++) {
4484
- if (Date.now() - startTime > maxDurationMs) {
4485
- logger6.warn(`humanScroll | \u8D85\u65F6\u4FDD\u62A4\u89E6\u53D1 (${maxDurationMs}ms)`);
4486
- return { element, didScroll };
4487
- }
4488
- const status = await checkVisibility();
4489
- if (status.code === "VISIBLE") {
4490
- if (status.isFixed) {
4491
- logger6.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4492
- } else {
4493
- logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
4494
- }
4495
- logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4496
- return { element, didScroll };
4497
- }
4498
- logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
4499
- if (status.code === "OBSTRUCTED" && status.obstruction) {
4500
- logger6.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
4259
+ return { kind: "window", top: window.scrollY, left: window.scrollX };
4260
+ }) : null;
4261
+ await dispatchTouchSwipe(page, deltaY, { rect: scrollRect });
4262
+ if (scrollRect && beforeWindowState) {
4263
+ const afterWindowState = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
4264
+ if (Math.abs(afterWindowState.x - beforeWindowState.x) > 2 || Math.abs(afterWindowState.y - beforeWindowState.y) > 2) {
4265
+ await page.evaluate((state) => window.scrollTo(state.x, state.y), beforeWindowState);
4266
+ logger7.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
4501
4267
  }
4502
- const scrollRect = await getScrollableRect2();
4503
- if (!scrollRect && status.isFixed) {
4504
- logger6.warn("humanScroll | fixed \u5BB9\u5668\u5185\u4E14\u65E0\u53EF\u6EDA\u52A8\u7956\u5148\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4505
- return { element, didScroll };
4268
+ }
4269
+ let afterElementSnapshot = null;
4270
+ const readAfterElementSnapshot = async () => {
4271
+ if (!afterElementSnapshot) {
4272
+ afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4506
4273
  }
4507
- const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
4508
- const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
4509
- let deltaY = 0;
4510
- if (status.code === "OUT_OF_VIEWPORT") {
4511
- if (status.direction === "down") {
4512
- deltaY = stepMin + Math.random() * (stepMax - stepMin);
4513
- } else if (status.direction === "up") {
4514
- deltaY = -(stepMin + Math.random() * (stepMax - stepMin));
4515
- } else {
4516
- deltaY = 100;
4517
- }
4518
- } else if (status.code === "OBSTRUCTED") {
4519
- const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4520
- const isBottomHalf = status.cy > halfY;
4521
- const direction = isBottomHalf ? 1 : -1;
4522
- deltaY = direction * (stepMin + Math.random() * 50);
4274
+ return afterElementSnapshot;
4275
+ };
4276
+ if (!scrollRect && beforeElementSnapshot) {
4277
+ const afterSnapshot = await readAfterElementSnapshot();
4278
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4279
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4280
+ 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"})`);
4281
+ return { element, didScroll, restore: null, unscrollable: true };
4523
4282
  }
4524
- if (i === 0) {
4525
- const viewSize = page.viewportSize();
4526
- if (scrollRect) {
4527
- const safeX = scrollRect.x + scrollRect.width * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.width * 0.4);
4528
- const safeY = scrollRect.y + scrollRect.height * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.height * 0.4);
4529
- await cursor.actions.move({ x: safeX, y: safeY });
4530
- } else if (viewSize) {
4531
- const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 80;
4532
- const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 80;
4533
- await cursor.actions.move({ x: safeX, y: safeY });
4283
+ }
4284
+ if (scrollRect && beforeState) {
4285
+ const afterState = await element.evaluate((el) => {
4286
+ const isScrollable = (node) => {
4287
+ const style = window.getComputedStyle(node);
4288
+ if (!style) return false;
4289
+ const overflowY = style.overflowY;
4290
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
4291
+ return node.scrollHeight > node.clientHeight + 1;
4292
+ };
4293
+ let current = el;
4294
+ while (current && current !== document.body) {
4295
+ if (isScrollable(current)) {
4296
+ return {
4297
+ kind: "element",
4298
+ top: current.scrollTop,
4299
+ left: current.scrollLeft
4300
+ };
4301
+ }
4302
+ current = current.parentElement;
4303
+ }
4304
+ return { kind: "window", top: window.scrollY, left: window.scrollX };
4305
+ });
4306
+ const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
4307
+ const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
4308
+ const expectedDelta = Number(deltaY || 0);
4309
+ const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
4310
+ if (!moved) {
4311
+ const fallback = await scrollScrollableAncestor(element, deltaY);
4312
+ 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)}`);
4313
+ } 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)) {
4314
+ const residualDelta = expectedDelta - topDelta;
4315
+ if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
4316
+ const fallback = await scrollScrollableAncestor(element, residualDelta);
4317
+ 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)}`);
4534
4318
  }
4535
4319
  }
4536
- await page.mouse.wheel(0, deltaY);
4537
- didScroll = true;
4538
- await delay4(this.jitterMs(20 + Math.random() * 40, 0.2));
4539
4320
  }
4540
- logger6.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
4541
- return { element, didScroll };
4542
- } catch (error) {
4543
- logger6.fail("humanScroll", error);
4544
- throw error;
4321
+ if (scrollRect && beforeElementSnapshot) {
4322
+ const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4323
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4324
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4325
+ 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"})`);
4326
+ return { element, didScroll, restore: null, unscrollable: true };
4327
+ }
4328
+ }
4329
+ didScroll = true;
4330
+ }
4331
+ try {
4332
+ await element.scrollIntoViewIfNeeded?.();
4333
+ await waitJitter(80, 0.3);
4334
+ const finalStatus = await checkElementVisibility(element);
4335
+ if (finalStatus.code === "VISIBLE") {
4336
+ logger7.info("humanScroll | \u539F\u751F scrollIntoViewIfNeeded \u515C\u5E95\u6210\u529F");
4337
+ logger7.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4338
+ return { element, didScroll: true, restore: null };
4339
+ }
4340
+ } catch (fallbackError) {
4341
+ logger7.debug(`humanScroll | native fallback failed: ${fallbackError?.message || fallbackError}`);
4545
4342
  }
4343
+ logger7.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
4344
+ return { element, didScroll, restore: null };
4546
4345
  },
4547
- /**
4548
- * 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
4549
- *
4550
- * @param {import('playwright').Page} page
4551
- * @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
4552
- * @param {Object} [options]
4553
- * @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
4554
- * @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
4555
- * @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
4556
- */
4557
4346
  async humanClick(page, target, options = {}) {
4558
- const cursor = $GetCursor(page);
4559
- const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
4560
- const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
4561
- logger6.start("humanClick", `target=${targetDesc}`);
4562
- const restoreOnce = async () => {
4563
- if (restoreOnce.restored) return;
4564
- restoreOnce.restored = true;
4565
- if (typeof restoreOnce.do !== "function") return;
4566
- try {
4567
- await delay4(this.jitterMs(1e3));
4568
- await restoreOnce.do();
4569
- } catch (restoreError) {
4570
- logger6.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
4571
- }
4572
- };
4347
+ const {
4348
+ reactionDelay = 220,
4349
+ throwOnMissing = true,
4350
+ scrollIfNeeded = true,
4351
+ tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
4352
+ mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
4353
+ activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
4354
+ fallbackDomClick = true,
4355
+ fallbackDomClickOnTapError = true
4356
+ } = options;
4357
+ const targetDesc = describeTarget(target);
4358
+ logger7.start("humanClick", `target=${targetDesc}`);
4573
4359
  try {
4574
4360
  if (target == null) {
4575
- await delay4(this.jitterMs(reactionDelay, 0.4));
4576
- await cursor.actions.click();
4577
- logger6.success("humanClick", "Clicked current position");
4361
+ const viewport = resolveViewport(page);
4362
+ await waitJitter(reactionDelay, 0.45);
4363
+ await tapPoint(page, {
4364
+ x: viewport.width * (0.45 + Math.random() * 0.1),
4365
+ y: viewport.height * (0.48 + Math.random() * 0.12)
4366
+ }, {
4367
+ timeoutMs: tapTimeoutMs,
4368
+ mouseFallbackTimeoutMs
4369
+ });
4370
+ logger7.success("humanClick", "Tapped current position");
4578
4371
  return true;
4579
4372
  }
4580
- let element;
4581
- if (typeof target === "string") {
4582
- element = await page.$(target);
4583
- if (!element) {
4584
- if (throwOnMissing) {
4585
- throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
4373
+ const element = await resolveElement(page, target, { throwOnMissing });
4374
+ if (!element) {
4375
+ logger7.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
4376
+ return false;
4377
+ }
4378
+ const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
4379
+ const status = await checkElementVisibility(element).catch(() => null);
4380
+ if (status && status.code !== "VISIBLE") {
4381
+ if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
4382
+ let fallback = await withTimeout(
4383
+ () => activateElementFallback(element, null, {
4384
+ editableOnly: true
4385
+ }),
4386
+ activateFallbackTimeoutMs,
4387
+ "focus fallback"
4388
+ ).catch(() => null);
4389
+ if (!fallback?.activated) {
4390
+ fallback = await withTimeout(
4391
+ () => activateElementFallback(element, null, {
4392
+ editableOnly: false
4393
+ }),
4394
+ activateFallbackTimeoutMs,
4395
+ "activation fallback"
4396
+ ).catch(() => null);
4397
+ }
4398
+ if (fallback?.activated) {
4399
+ logger7.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4400
+ return true;
4586
4401
  }
4587
- logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
4588
- return false;
4589
4402
  }
4590
- } else {
4591
- element = target;
4592
- }
4593
- if (scrollIfNeeded) {
4594
- const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
4595
- restoreOnce.do = didScroll && restore ? restoreFn : null;
4403
+ const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
4404
+ if (throwOnMissing) throw new Error(message);
4405
+ logger7.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
4406
+ return false;
4596
4407
  }
4597
4408
  const box = await element.boundingBox();
4598
4409
  if (!box) {
4599
- await restoreOnce();
4600
- if (throwOnMissing) {
4601
- throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4602
- }
4603
- logger6.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
4410
+ if (throwOnMissing) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4411
+ logger7.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
4604
4412
  return false;
4605
4413
  }
4606
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
4607
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
4608
- await cursor.actions.move({ x, y });
4609
- await delay4(this.jitterMs(reactionDelay, 0.4));
4610
- await cursor.actions.click();
4611
- await restoreOnce();
4612
- logger6.success("humanClick");
4414
+ await waitJitter(reactionDelay, 0.45);
4415
+ const visibleBox = clipBoxToViewport(box, resolveViewport(page));
4416
+ const tapTarget = centerPointInBox(visibleBox);
4417
+ try {
4418
+ await tapPoint(page, tapTarget, {
4419
+ timeoutMs: tapTimeoutMs,
4420
+ mouseFallbackTimeoutMs
4421
+ });
4422
+ } catch (tapError) {
4423
+ if (!fallbackDomClickOnTapError) throw tapError;
4424
+ const fallback = await withTimeout(
4425
+ () => activateElementFallback(element, tapTarget, {
4426
+ editableOnly: false
4427
+ }),
4428
+ activateFallbackTimeoutMs,
4429
+ "activation fallback"
4430
+ ).catch(() => null);
4431
+ if (!fallback?.activated) throw tapError;
4432
+ logger7.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
4433
+ }
4434
+ await waitJitter(120, 0.35);
4435
+ logger7.success("humanClick");
4613
4436
  return true;
4614
4437
  } catch (error) {
4615
- await restoreOnce();
4616
- logger6.fail("humanClick", error);
4438
+ logger7.fail("humanClick", error);
4617
4439
  throw error;
4618
4440
  }
4619
4441
  },
4620
- /**
4621
- * 随机延迟一段毫秒数(带 ±30% 抖动)
4622
- * @param {number} baseMs - 基础延迟毫秒数
4623
- * @param {number} [jitterPercent=0.3] - 抖动百分比
4624
- */
4625
4442
  async randomSleep(baseMs, jitterPercent = 0.3) {
4626
- const ms = this.jitterMs(baseMs, jitterPercent);
4627
- logger6.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
4628
- await delay4(ms);
4629
- logger6.success("randomSleep");
4443
+ await waitJitter(baseMs, jitterPercent);
4630
4444
  },
4631
- /**
4632
- * 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
4633
- * @param {import('playwright').Page} page
4634
- * @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
4635
- */
4636
4445
  async simulateGaze(page, baseDurationMs = 2500) {
4637
- const cursor = $GetCursor(page);
4638
- const durationMs = this.jitterMs(baseDurationMs, 0.4);
4639
- logger6.start("simulateGaze", `duration=${durationMs}ms`);
4446
+ const durationMs = jitterMs(baseDurationMs, 0.4);
4640
4447
  const startTime = Date.now();
4641
- const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
4642
4448
  while (Date.now() - startTime < durationMs) {
4643
- const x = 100 + Math.random() * (viewportSize.width - 200);
4644
- const y = 100 + Math.random() * (viewportSize.height - 200);
4645
- await cursor.actions.move({ x, y });
4646
- await delay4(this.jitterMs(600, 0.5));
4449
+ if (Math.random() < 0.28) {
4450
+ const distance = 70 + Math.random() * 120;
4451
+ await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
4452
+ durationMs: 180,
4453
+ steps: 4
4454
+ });
4455
+ } else {
4456
+ await waitJitter(420, 0.55);
4457
+ }
4647
4458
  }
4648
- logger6.success("simulateGaze");
4649
4459
  },
4650
- /**
4651
- * 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
4652
- * @param {import('playwright').Page} page
4653
- * @param {string} selector - 输入框选择器
4654
- * @param {string} text - 要输入的文本
4655
- * @param {Object} [options]
4656
- * @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
4657
- * @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
4658
- * @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
4659
- */
4660
4460
  async humanType(page, selector, text, options = {}) {
4661
- logger6.start("humanType", `selector=${selector}, textLen=${text.length}`);
4662
4461
  const {
4663
- baseDelay = 180,
4462
+ baseDelay = 160,
4664
4463
  pauseProbability = 0.08,
4665
- pauseBase = 800
4464
+ pauseBase = 700
4666
4465
  } = options;
4667
- try {
4668
- const locator = page.locator(selector);
4669
- await Humanize.humanClick(page, locator);
4670
- await delay4(this.jitterMs(200, 0.4));
4671
- for (let i = 0; i < text.length; i++) {
4672
- const char = text[i];
4673
- let charDelay;
4674
- if (char === " ") {
4675
- charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
4676
- } else if (/[,.!?;:,。!?;:]/.test(char)) {
4677
- charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
4678
- } else {
4679
- charDelay = this.jitterMs(baseDelay, 0.4);
4680
- }
4681
- await page.keyboard.type(char);
4682
- await delay4(charDelay);
4683
- if (Math.random() < pauseProbability && i < text.length - 1) {
4684
- const pauseTime = this.jitterMs(pauseBase, 0.5);
4685
- logger6.debug(`\u505C\u987F ${pauseTime}ms...`);
4686
- await delay4(pauseTime);
4687
- }
4466
+ await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
4467
+ await waitJitter(220, 0.45);
4468
+ for (let i = 0; i < String(text).length; i += 1) {
4469
+ const char = String(text)[i];
4470
+ const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
4471
+ await page.keyboard.type(char);
4472
+ await waitJitter(charDelay, 0.15);
4473
+ if (Math.random() < pauseProbability && i < String(text).length - 1) {
4474
+ await waitJitter(pauseBase, 0.5);
4688
4475
  }
4689
- logger6.success("humanType");
4690
- } catch (error) {
4691
- logger6.fail("humanType", error);
4692
- throw error;
4693
4476
  }
4694
4477
  },
4695
- /**
4696
- * 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
4697
- * @param {import('playwright').Page} page
4698
- * @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
4699
- * @param {string|Object} [maybeKey] - 按键或选项
4700
- * @param {Object} [options]
4701
- */
4702
4478
  async humanPress(page, targetOrKey, maybeKey, options = {}) {
4703
4479
  const hasTarget = typeof maybeKey === "string";
4704
4480
  const key = hasTarget ? maybeKey : targetOrKey;
4705
4481
  const pressOptions = hasTarget ? options : maybeKey || options;
4706
4482
  const {
4707
- reactionDelay = 180,
4708
- holdDelay = 45,
4483
+ reactionDelay = 170,
4484
+ holdDelay = 42,
4709
4485
  focusDelay = 180,
4710
4486
  scrollIfNeeded = true,
4711
4487
  throwOnMissing = true,
4712
4488
  keyboardOptions = {}
4713
4489
  } = pressOptions || {};
4714
- const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
4715
- logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
4490
+ const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
4491
+ logger7.start("humanPress", `key=${key}, target=${targetDesc}`);
4716
4492
  try {
4717
4493
  if (hasTarget) {
4718
- await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
4494
+ await MobileHumanize.humanClick(page, targetOrKey, {
4495
+ reactionDelay: focusDelay,
4496
+ scrollIfNeeded,
4497
+ throwOnMissing
4498
+ });
4719
4499
  }
4720
- await delay4(this.jitterMs(reactionDelay, 0.45));
4500
+ await waitJitter(reactionDelay, 0.45);
4721
4501
  await page.keyboard.press(key, {
4722
4502
  ...keyboardOptions,
4723
- delay: this.jitterMs(holdDelay, 0.5)
4503
+ delay: jitterMs(holdDelay, 0.5)
4724
4504
  });
4725
- logger6.success("humanPress");
4505
+ logger7.success("humanPress");
4726
4506
  return true;
4727
4507
  } catch (error) {
4728
- logger6.fail("humanPress", error);
4508
+ logger7.fail("humanPress", error);
4729
4509
  throw error;
4730
4510
  }
4731
4511
  },
4732
- /**
4733
- * 人类化清空输入框 - 模拟人类删除文本的行为
4734
- * @param {import('playwright').Page} page
4735
- * @param {string} selector - 输入框选择器
4736
- */
4737
4512
  async humanClear(page, selector) {
4738
- logger6.start("humanClear", `selector=${selector}`);
4739
- try {
4740
- const locator = page.locator(selector);
4741
- await locator.click();
4742
- await delay4(this.jitterMs(200, 0.4));
4743
- const currentValue = await locator.inputValue();
4744
- if (!currentValue || currentValue.length === 0) {
4745
- logger6.success("humanClear", "already empty");
4513
+ const locator = page.locator(selector);
4514
+ await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4515
+ await waitJitter(160, 0.4);
4516
+ const readValue = async () => {
4517
+ try {
4518
+ return await locator.inputValue({ timeout: 600 });
4519
+ } catch {
4520
+ return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4521
+ }
4522
+ };
4523
+ const currentValue = await readValue();
4524
+ if (!currentValue) return;
4525
+ await page.keyboard.press("ControlOrMeta+A");
4526
+ await waitJitter(90, 0.35);
4527
+ await page.keyboard.press("Backspace");
4528
+ await waitJitter(120, 0.35);
4529
+ if (!await readValue()) return;
4530
+ await locator.evaluate((el) => {
4531
+ if ("value" in el) {
4532
+ el.value = "";
4533
+ el.dispatchEvent(new Event("input", { bubbles: true }));
4534
+ el.dispatchEvent(new Event("change", { bubbles: true }));
4746
4535
  return;
4747
4536
  }
4748
- await page.keyboard.press("Meta+A");
4749
- await delay4(this.jitterMs(100, 0.4));
4750
- await page.keyboard.press("Backspace");
4751
- logger6.success("humanClear");
4752
- } catch (error) {
4753
- logger6.fail("humanClear", error);
4754
- throw error;
4755
- }
4537
+ el.textContent = "";
4538
+ el.dispatchEvent(new Event("input", { bubbles: true }));
4539
+ });
4756
4540
  },
4757
- /**
4758
- * 页面预热浏览 - 模拟人类进入页面后的探索行为
4759
- * @param {import('playwright').Page} page
4760
- * @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
4761
- */
4762
4541
  async warmUpBrowsing(page, baseDuration = 3500) {
4763
- const cursor = $GetCursor(page);
4764
- const durationMs = this.jitterMs(baseDuration, 0.4);
4765
- logger6.start("warmUpBrowsing", `duration=${durationMs}ms`);
4542
+ const durationMs = jitterMs(baseDuration, 0.4);
4766
4543
  const startTime = Date.now();
4767
- const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
4768
- try {
4769
- while (Date.now() - startTime < durationMs) {
4770
- const action = Math.random();
4771
- if (action < 0.4) {
4772
- const x = 100 + Math.random() * (viewportSize.width - 200);
4773
- const y = 100 + Math.random() * (viewportSize.height - 200);
4774
- await cursor.actions.move({ x, y });
4775
- await delay4(this.jitterMs(350, 0.4));
4776
- } else if (action < 0.7) {
4777
- const scrollY = (Math.random() - 0.5) * 200;
4778
- await page.mouse.wheel(0, scrollY);
4779
- await delay4(this.jitterMs(500, 0.4));
4780
- } else {
4781
- await delay4(this.jitterMs(800, 0.5));
4782
- }
4544
+ while (Date.now() - startTime < durationMs) {
4545
+ const action = Math.random();
4546
+ if (action < 0.5) {
4547
+ await dispatchTouchSwipe(page, 120 + Math.random() * 220, {
4548
+ durationMs: 240,
4549
+ steps: 5
4550
+ });
4551
+ } else if (action < 0.7) {
4552
+ await dispatchTouchSwipe(page, -(80 + Math.random() * 160), {
4553
+ durationMs: 220,
4554
+ steps: 4
4555
+ });
4556
+ } else {
4557
+ await waitJitter(560, 0.55);
4783
4558
  }
4784
- logger6.success("warmUpBrowsing");
4785
- } catch (error) {
4786
- logger6.fail("warmUpBrowsing", error);
4787
- throw error;
4788
4559
  }
4789
4560
  },
4790
- /**
4791
- * 自然滚动 - 带惯性、减速效果和随机抖动
4792
- * @param {import('playwright').Page} page
4793
- * @param {'up' | 'down'} [direction='down'] - 滚动方向
4794
- * @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
4795
- * @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
4796
- */
4797
4561
  async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4798
4562
  const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
4799
- const actualDistance = this.jitterMs(distance, 0.15);
4800
- logger6.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
4563
+ const actualDistance = jitterMs(distance, 0.15);
4801
4564
  const sign = direction === "down" ? 1 : -1;
4802
- const stepDistance = actualDistance / steps;
4803
- try {
4804
- for (let i = 0; i < steps; i++) {
4805
- const factor = 1 - i / steps * 0.5;
4806
- const jitter = 0.9 + Math.random() * 0.2;
4807
- const scrollAmount = stepDistance * factor * sign * jitter;
4808
- await page.mouse.wheel(0, scrollAmount);
4809
- const baseDelay = 60 + i * 25;
4810
- await delay4(this.jitterMs(baseDelay, 0.3));
4811
- }
4812
- logger6.success("naturalScroll");
4813
- } catch (error) {
4814
- logger6.fail("naturalScroll", error);
4815
- throw error;
4565
+ for (let i = 0; i < steps; i += 1) {
4566
+ const factor = 1 - i / steps * 0.45;
4567
+ await dispatchTouchSwipe(page, actualDistance / steps * factor * sign, {
4568
+ durationMs: 150 + i * 20,
4569
+ steps: 4
4570
+ });
4816
4571
  }
4817
4572
  }
4818
4573
  };
4819
4574
 
4820
- // src/internals/humanize/default.js
4821
- var resolveDeviceFromPage3 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4822
- var resolveDefaultHumanizeDelegate = (page) => resolveDeviceFromPage3(page) === Device.Mobile ? MobileHumanize : Humanize;
4823
- var DefaultHumanizeContext = Object.freeze({
4824
- desktopDelegate: Humanize,
4825
- resolveDelegate: resolveDefaultHumanizeDelegate
4826
- });
4827
-
4828
- // src/internals/humanize/index.js
4829
- var HUMANIZE_DELEGATED_METHODS = Object.freeze({
4830
- initializeCursor: { enumerable: true },
4831
- humanMove: { enumerable: true },
4832
- humanScroll: { enumerable: true },
4833
- humanClick: { enumerable: true },
4834
- simulateGaze: { enumerable: true },
4835
- humanType: { enumerable: true },
4836
- humanPress: { enumerable: true },
4837
- humanClear: { enumerable: true },
4838
- warmUpBrowsing: { enumerable: true },
4839
- naturalScroll: { enumerable: true }
4840
- });
4841
- var isPageLike2 = (value) => value && typeof value === "object" && typeof value.evaluate === "function";
4842
- var createSharedHumanizeMethods = ({ desktopDelegate, resolveDelegate }) => ({
4575
+ // src/humanize.js
4576
+ var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4577
+ var resolveDelegate = (page) => {
4578
+ return resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : Humanize;
4579
+ };
4580
+ var callDelegate = (method, page, args) => {
4581
+ const delegate = resolveDelegate(page);
4582
+ return delegate[method](page, ...args);
4583
+ };
4584
+ var Humanize2 = {
4843
4585
  jitterMs(base, jitterPercent = 0.3) {
4844
- return desktopDelegate.jitterMs(base, jitterPercent);
4586
+ return Humanize.jitterMs(base, jitterPercent);
4587
+ },
4588
+ initializeCursor(page) {
4589
+ return callDelegate("initializeCursor", page, []);
4590
+ },
4591
+ humanMove(page, target) {
4592
+ return callDelegate("humanMove", page, [target]);
4593
+ },
4594
+ humanScroll(page, target, options = {}) {
4595
+ return callDelegate("humanScroll", page, [target, options]);
4596
+ },
4597
+ humanClick(page, target, options = {}) {
4598
+ return callDelegate("humanClick", page, [target, options]);
4845
4599
  },
4846
4600
  randomSleep(pageOrBaseMs, maybeBaseMs, maybeJitterPercent) {
4847
- if (isPageLike2(pageOrBaseMs)) {
4848
- return resolveDelegate(pageOrBaseMs).randomSleep(maybeBaseMs, maybeJitterPercent);
4601
+ if (pageOrBaseMs && typeof pageOrBaseMs === "object" && typeof pageOrBaseMs.evaluate === "function") {
4602
+ const delegate = resolveDelegate(pageOrBaseMs);
4603
+ return delegate.randomSleep(maybeBaseMs, maybeJitterPercent);
4604
+ }
4605
+ return Humanize.randomSleep(pageOrBaseMs, maybeBaseMs);
4606
+ },
4607
+ simulateGaze(page, baseDurationMs = 2500) {
4608
+ return callDelegate("simulateGaze", page, [baseDurationMs]);
4609
+ },
4610
+ humanType(page, selector, text, options = {}) {
4611
+ return callDelegate("humanType", page, [selector, text, options]);
4612
+ },
4613
+ humanPress(page, targetOrKey, maybeKey, options = {}) {
4614
+ if (typeof maybeKey === "string") {
4615
+ return callDelegate("humanPress", page, [targetOrKey, maybeKey, options]);
4849
4616
  }
4850
- return desktopDelegate.randomSleep(pageOrBaseMs, maybeBaseMs);
4617
+ return callDelegate("humanPress", page, [targetOrKey, maybeKey || options]);
4618
+ },
4619
+ humanClear(page, selector) {
4620
+ return callDelegate("humanClear", page, [selector]);
4621
+ },
4622
+ warmUpBrowsing(page, baseDuration = 3500) {
4623
+ return callDelegate("warmUpBrowsing", page, [baseDuration]);
4624
+ },
4625
+ naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4626
+ return callDelegate("naturalScroll", page, [direction, distance, baseSteps]);
4851
4627
  }
4852
- });
4853
- var withHumanizeDelegates = (target, resolveDelegate) => withDelegatedProperties(target, {
4854
- namespace: "Humanize",
4855
- methods: HUMANIZE_DELEGATED_METHODS,
4856
- resolveDelegate: (_method, args) => ({
4857
- delegate: resolveDelegate(args[0]),
4858
- label: "resolved humanize delegate"
4859
- })
4860
- });
4861
- var createHumanizeExport = (context = {}) => {
4862
- return Object.assign(
4863
- createSharedHumanizeMethods(context),
4864
- withHumanizeDelegates({}, context.resolveDelegate)
4865
- );
4866
4628
  };
4867
- var DefaultHumanize = createHumanizeExport(DefaultHumanizeContext);
4868
- var CloakBrowserHumanize = createHumanizeExport(CloakBrowserHumanizeContext);
4869
-
4870
- // src/humanize.js
4871
- var humanizeStrategies = {
4872
- [Mode.Default]: DefaultHumanize,
4873
- [Mode.CloakBrowser]: CloakBrowserHumanize
4874
- };
4875
- var humanizeFacadeMethods = Object.freeze({
4876
- jitterMs: { enumerable: true },
4877
- initializeCursor: { enumerable: true },
4878
- humanMove: { enumerable: true },
4879
- humanScroll: { enumerable: true },
4880
- humanClick: { enumerable: true },
4881
- randomSleep: { enumerable: true },
4882
- simulateGaze: { enumerable: true },
4883
- humanType: { enumerable: true },
4884
- humanPress: { enumerable: true },
4885
- humanClear: { enumerable: true },
4886
- warmUpBrowsing: { enumerable: true },
4887
- naturalScroll: { enumerable: true }
4888
- });
4889
- var Humanize2 = createDelegatedFacade("Humanize", humanizeStrategies, humanizeFacadeMethods);
4890
4629
 
4891
- // src/internals/launch/default.js
4630
+ // src/launch.js
4892
4631
  import { execFileSync } from "node:child_process";
4893
4632
  import { FingerprintGenerator } from "fingerprint-generator";
4894
4633
  import { FingerprintInjector } from "fingerprint-injector";
@@ -4973,8 +4712,8 @@ var ByPass = {
4973
4712
  resolveRouteByProxy
4974
4713
  };
4975
4714
 
4976
- // src/internals/launch/default.js
4977
- var logger7 = createInternalLogger("Launch");
4715
+ // src/launch.js
4716
+ var logger8 = createInternalLogger("Launch");
4978
4717
  var REQUEST_HOOK_FLAG = Symbol("playwright-toolkit-request-hook");
4979
4718
  var injectedContexts = /* @__PURE__ */ new WeakSet();
4980
4719
  var browserMajorVersionCache = /* @__PURE__ */ new Map();
@@ -5026,7 +4765,7 @@ var detectBrowserMajorVersion = (launcher) => {
5026
4765
  });
5027
4766
  detectedVersion = parseChromeMajorVersion(rawVersion);
5028
4767
  } catch (error) {
5029
- logger7.warn(`\u8BFB\u53D6\u6D4F\u89C8\u5668\u7248\u672C\u5931\u8D25: ${error?.message || error}`);
4768
+ logger8.warn(`\u8BFB\u53D6\u6D4F\u89C8\u5668\u7248\u672C\u5931\u8D25: ${error?.message || error}`);
5030
4769
  }
5031
4770
  browserMajorVersionCache.set(executablePath, detectedVersion);
5032
4771
  return detectedVersion;
@@ -5040,7 +4779,7 @@ var resolveCoreDevice = (core = {}) => {
5040
4779
  };
5041
4780
  var buildFingerprintGenerator = ({ locale, browserMajorVersion, device }) => {
5042
4781
  return new FingerprintGenerator(
5043
- DefaultAntiCheat.getFingerprintGeneratorOptions({
4782
+ AntiCheat.getFingerprintGeneratorOptions({
5044
4783
  locale,
5045
4784
  browserMajorVersion,
5046
4785
  device
@@ -5063,7 +4802,7 @@ var generateFingerprintForCore = ({ locale, browserMajorVersion, device }) => {
5063
4802
  if (requestedBrowserMajorVersion <= 0) {
5064
4803
  throw error;
5065
4804
  }
5066
- logger7.warn(
4805
+ logger8.warn(
5067
4806
  `\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}`
5068
4807
  );
5069
4808
  }
@@ -5084,7 +4823,7 @@ var buildReplayableBrowserProfile = (runtimeState, launcher) => {
5084
4823
  }
5085
4824
  let nextState = RuntimeEnv.rememberState(runtimeState);
5086
4825
  let browserProfileCore = RuntimeEnv.getBrowserProfileCore(nextState);
5087
- const timezoneId = String(browserProfileCore?.timezone_id || "").trim() || DefaultAntiCheat.getBaseConfig().timezoneId;
4826
+ const timezoneId = String(browserProfileCore?.timezone_id || "").trim() || AntiCheat.getBaseConfig().timezoneId;
5088
4827
  const locale = DEFAULT_LOCALE;
5089
4828
  const currentBrowserMajorVersion = detectBrowserMajorVersion(launcher);
5090
4829
  const storedBrowserMajorVersion = Number(browserProfileCore?.browser_major_version || 0);
@@ -5108,7 +4847,7 @@ var buildReplayableBrowserProfile = (runtimeState, launcher) => {
5108
4847
  schema_version: DEFAULT_BROWSER_PROFILE_SCHEMA_VERSION
5109
4848
  };
5110
4849
  nextState = RuntimeEnv.setBrowserProfileCore(nextState, browserProfileCore);
5111
- logger7.info(
4850
+ logger8.info(
5112
4851
  `\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}`
5113
4852
  );
5114
4853
  return { runtimeState: nextState, browserProfileCore };
@@ -5163,7 +4902,7 @@ var applyFingerprintPageOptions = (pageOptions = {}, { fingerprintWithHeaders =
5163
4902
  pageOptions.timezoneId = timezoneId;
5164
4903
  }
5165
4904
  };
5166
- var buildReplayBrowserPoolOptions = (browserProfileCore) => {
4905
+ var buildReplayBrowserPoolOptions = (browserProfileCore, modifyPageOptions = null) => {
5167
4906
  const fingerprintWithHeaders = browserProfileCore?.fingerprint;
5168
4907
  const fingerprint = fingerprintWithHeaders?.fingerprint;
5169
4908
  if (!fingerprintWithHeaders || !fingerprint) {
@@ -5172,13 +4911,16 @@ var buildReplayBrowserPoolOptions = (browserProfileCore) => {
5172
4911
  return {
5173
4912
  useFingerprints: false,
5174
4913
  prePageCreateHooks: [
5175
- (_pageId, _browserController, pageOptions = {}) => {
4914
+ async (pageId, browserController, pageOptions = {}) => {
5176
4915
  if (!pageOptions || typeof pageOptions !== "object") return;
5177
4916
  applyFingerprintPageOptions(pageOptions, {
5178
4917
  fingerprintWithHeaders,
5179
4918
  locale: browserProfileCore.locale,
5180
4919
  timezoneId: browserProfileCore.timezone_id
5181
4920
  });
4921
+ if (modifyPageOptions) {
4922
+ await modifyPageOptions(pageOptions, { pageId, browserController });
4923
+ }
5182
4924
  }
5183
4925
  ],
5184
4926
  postPageCreateHooks: [
@@ -5193,7 +4935,7 @@ var buildReplayBrowserPoolOptions = (browserProfileCore) => {
5193
4935
  ]
5194
4936
  };
5195
4937
  };
5196
- var DefaultLaunch = {
4938
+ var Launch = {
5197
4939
  getPlaywrightCrawlerOptions(options = {}) {
5198
4940
  const normalizedOptions = Array.isArray(options) ? { customArgs: options } : options || {};
5199
4941
  const {
@@ -5204,11 +4946,13 @@ var DefaultLaunch = {
5204
4946
  debugMode = false,
5205
4947
  isRunningOnApify = false,
5206
4948
  launcher = null,
4949
+ hooks = {},
5207
4950
  preNavigationHooks = [],
5208
4951
  postNavigationHooks = [],
5209
4952
  runtimeState = null
5210
4953
  } = normalizedOptions;
5211
4954
  const device = resolveRuntimeDevice(runtimeState);
4955
+ const modifyPageOptions = typeof hooks?.modifyPageOptions === "function" ? hooks.modifyPageOptions : null;
5212
4956
  const { byPassDomains, enableProxy, proxyUrl } = resolveProxyLaunchOptions(proxyConfiguration);
5213
4957
  const byPassRules = ByPass.buildByPassDomainRules(byPassDomains);
5214
4958
  const proxyMeter = enableProxy && proxyUrl ? ProxyMeterRuntime.startProxyMeter({ proxyUrl, debugMode }) : null;
@@ -5217,11 +4961,11 @@ var DefaultLaunch = {
5217
4961
  launchProxy.bypass = byPassDomains.join(",");
5218
4962
  }
5219
4963
  const replayContext = buildReplayableBrowserProfile(runtimeState, launcher);
5220
- const replayBrowserPoolOptions = buildReplayBrowserPoolOptions(replayContext.browserProfileCore);
4964
+ const replayBrowserPoolOptions = buildReplayBrowserPoolOptions(replayContext.browserProfileCore, modifyPageOptions);
5221
4965
  const launchLocale = String(replayContext.browserProfileCore?.locale || DEFAULT_LOCALE).trim() || DEFAULT_LOCALE;
5222
4966
  const launchOptions = {
5223
4967
  args: [
5224
- ...DefaultAntiCheat.getLaunchArgs({ locale: launchLocale }),
4968
+ ...AntiCheat.getLaunchArgs({ locale: launchLocale }),
5225
4969
  ...customArgs
5226
4970
  ],
5227
4971
  ignoreDefaultArgs: ["--enable-automation"]
@@ -5237,18 +4981,18 @@ var DefaultLaunch = {
5237
4981
  upstreamLabel = `${parsedProxyUrl.protocol}//${parsedProxyUrl.host}`;
5238
4982
  } catch {
5239
4983
  }
5240
- logger7.info(
4984
+ logger8.info(
5241
4985
  `[\u4EE3\u7406\u5DF2\u542F\u7528] \u672C\u5730=${launchProxy.server} \u4E0A\u6E38=${upstreamLabel || "-"} \u76F4\u8FDE\u57DF\u540D=${(byPassDomains || []).join(",")}`
5242
4986
  );
5243
- 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`);
4987
+ 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`);
5244
4988
  } else if (enableByPassLogger && enableProxy && !launchProxy) {
5245
- logger7.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=true \u4F46 proxy_url \u4E3A\u7A7A");
5246
- 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`);
4989
+ logger8.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=true \u4F46 proxy_url \u4E3A\u7A7A");
4990
+ 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`);
5247
4991
  } else if (enableByPassLogger && !enableProxy && proxyUrl) {
5248
- logger7.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=false \u4E14 proxy_url \u5DF2\u914D\u7F6E");
5249
- 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`);
4992
+ logger8.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=false \u4E14 proxy_url \u5DF2\u914D\u7F6E");
4993
+ 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`);
5250
4994
  } else if (enableByPassLogger) {
5251
- 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`);
4995
+ 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`);
5252
4996
  }
5253
4997
  const onPageCreated = (page) => {
5254
4998
  const recommendedGotoOptions = {
@@ -5270,7 +5014,7 @@ var DefaultLaunch = {
5270
5014
  }
5271
5015
  if (!enableByPassLogger || byPassDomains.length === 0) return;
5272
5016
  if (!matched || !matched.rule) return;
5273
- 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}`);
5017
+ 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}`);
5274
5018
  };
5275
5019
  page.on("request", requestHandler);
5276
5020
  return recommendedGotoOptions;
@@ -5289,20 +5033,23 @@ var DefaultLaunch = {
5289
5033
  browserPoolOptions: replayBrowserPoolOptions || {
5290
5034
  useFingerprints: true,
5291
5035
  fingerprintOptions: {
5292
- fingerprintGeneratorOptions: DefaultAntiCheat.getFingerprintGeneratorOptions({
5036
+ fingerprintGeneratorOptions: AntiCheat.getFingerprintGeneratorOptions({
5293
5037
  locale: launchLocale,
5294
5038
  device
5295
5039
  })
5296
5040
  },
5297
5041
  prePageCreateHooks: [
5298
- (_pageId, browserController, pageOptions = {}) => {
5042
+ async (pageId, browserController, pageOptions = {}) => {
5299
5043
  const fingerprintWithHeaders = browserController?.launchContext?.fingerprint;
5300
- const timezoneId = DefaultAntiCheat.getBaseConfig().timezoneId;
5044
+ const timezoneId = AntiCheat.getBaseConfig().timezoneId;
5301
5045
  applyFingerprintPageOptions(pageOptions, {
5302
5046
  fingerprintWithHeaders,
5303
5047
  locale: launchLocale,
5304
5048
  timezoneId
5305
5049
  });
5050
+ if (modifyPageOptions) {
5051
+ await modifyPageOptions(pageOptions, { pageId, browserController });
5052
+ }
5306
5053
  }
5307
5054
  ]
5308
5055
  },
@@ -5324,263 +5071,6 @@ var DefaultLaunch = {
5324
5071
  }
5325
5072
  };
5326
5073
 
5327
- // src/internals/launch/cloakbrowser.js
5328
- import { execFile } from "node:child_process";
5329
- import { promisify } from "node:util";
5330
- var logger8 = createInternalLogger("CloakBrowser");
5331
- var execFileAsync = promisify(execFile);
5332
- var DEFAULT_CLOAK_CRAWLER_BASE_OPTIONS = Object.freeze({
5333
- maxConcurrency: 1,
5334
- maxRequestRetries: 0,
5335
- requestHandlerTimeoutSecs: 240,
5336
- navigationTimeoutSecs: 120
5337
- });
5338
- var DEFAULT_CLOAK_HUMANIZE_OPTIONS = Object.freeze({
5339
- humanize: true
5340
- });
5341
- var DEFAULT_CLOAK_GOTO_OPTIONS = Object.freeze({
5342
- waitUntil: "commit"
5343
- });
5344
- var cachedCloakBrowserModulePromise = null;
5345
- var hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);
5346
- var loadCloakBrowserModule = async () => {
5347
- if (!cachedCloakBrowserModulePromise) {
5348
- cachedCloakBrowserModulePromise = import("cloakbrowser").catch((error) => {
5349
- cachedCloakBrowserModulePromise = null;
5350
- 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", {
5351
- cause: error
5352
- });
5353
- });
5354
- }
5355
- return cachedCloakBrowserModulePromise;
5356
- };
5357
- var buildCloakLaunchOptions = async (options = {}) => {
5358
- const { buildLaunchOptions } = await loadCloakBrowserModule();
5359
- return await buildLaunchOptions(normalizeObject(options));
5360
- };
5361
- var normalizeObject = (value) => {
5362
- if (!value || typeof value !== "object" || Array.isArray(value)) {
5363
- return {};
5364
- }
5365
- return value;
5366
- };
5367
- var normalizeStringArray = (value) => {
5368
- if (!Array.isArray(value)) {
5369
- return [];
5370
- }
5371
- return value.map((item) => String(item || "").trim()).filter(Boolean);
5372
- };
5373
- var resolveCloakBrowserProxy = (proxyConfiguration = {}) => {
5374
- const config = normalizeObject(proxyConfiguration);
5375
- const proxyUrl = String(config.proxy_url || "").trim();
5376
- const enableProxy = typeof config.enable_proxy === "boolean" ? config.enable_proxy : proxyUrl !== "";
5377
- if (!enableProxy || !proxyUrl) {
5378
- return null;
5379
- }
5380
- const byPassDomains = ByPass.normalizeByPassDomains(config.by_pass_domains);
5381
- if (byPassDomains.length === 0) {
5382
- return proxyUrl;
5383
- }
5384
- const parsedProxyUrl = new URL(proxyUrl.includes("://") ? proxyUrl : `http://${proxyUrl}`);
5385
- return {
5386
- server: `${parsedProxyUrl.protocol}//${parsedProxyUrl.host}`,
5387
- username: decodeURIComponent(parsedProxyUrl.username || ""),
5388
- password: decodeURIComponent(parsedProxyUrl.password || ""),
5389
- bypass: byPassDomains.join(",")
5390
- };
5391
- };
5392
- var extractFingerprintArg = (launchOptions = {}) => {
5393
- const args = Array.isArray(launchOptions?.args) ? launchOptions.args : [];
5394
- return args.find((value) => String(value || "").startsWith("--fingerprint=")) || "";
5395
- };
5396
- var createStableGotoHook = (recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS) => {
5397
- const normalizedRecommendedGotoOptions = normalizeObject(recommendedGotoOptions);
5398
- const fallbackGotoOptions = Object.keys(normalizedRecommendedGotoOptions).length > 0 ? normalizedRecommendedGotoOptions : DEFAULT_CLOAK_GOTO_OPTIONS;
5399
- return async (_crawlingContext, gotoOptions = {}) => {
5400
- for (const [key, value] of Object.entries(fallbackGotoOptions)) {
5401
- if (gotoOptions[key] == null) {
5402
- gotoOptions[key] = value;
5403
- }
5404
- }
5405
- };
5406
- };
5407
- var attachCloakBrowserHumanizeHook = ({
5408
- browserPoolOptions = {},
5409
- activeBrowsers,
5410
- patchedBrowsers,
5411
- humanizeOptions = DEFAULT_CLOAK_HUMANIZE_OPTIONS
5412
- } = {}) => {
5413
- const normalizedBrowserPoolOptions = normalizeObject(browserPoolOptions);
5414
- const shouldHumanize = humanizeOptions !== false;
5415
- const normalizedHumanizeOptions = shouldHumanize ? {
5416
- ...DEFAULT_CLOAK_HUMANIZE_OPTIONS,
5417
- ...normalizeObject(humanizeOptions)
5418
- } : null;
5419
- return {
5420
- ...normalizedBrowserPoolOptions,
5421
- useFingerprints: false,
5422
- postLaunchHooks: [
5423
- ...Array.isArray(normalizedBrowserPoolOptions.postLaunchHooks) ? normalizedBrowserPoolOptions.postLaunchHooks : [],
5424
- async (_pageId, browserController) => {
5425
- const browser = browserController?.browser;
5426
- if (!browser || typeof browser.contexts !== "function") {
5427
- return;
5428
- }
5429
- activeBrowsers.add(browser);
5430
- if (typeof browser.once === "function") {
5431
- browser.once("disconnected", () => {
5432
- activeBrowsers.delete(browser);
5433
- });
5434
- }
5435
- if (!shouldHumanize || patchedBrowsers.has(browser)) {
5436
- return;
5437
- }
5438
- const { humanizeBrowser } = await loadCloakBrowserModule();
5439
- await humanizeBrowser(browser, normalizedHumanizeOptions);
5440
- patchedBrowsers.add(browser);
5441
- }
5442
- ]
5443
- };
5444
- };
5445
- var closeTrackedBrowsers = async (activeBrowsers) => {
5446
- const browsers = Array.from(activeBrowsers || []);
5447
- activeBrowsers?.clear?.();
5448
- await Promise.allSettled(
5449
- browsers.map(async (browser) => {
5450
- if (!browser || typeof browser.isConnected !== "function" || !browser.isConnected()) {
5451
- return;
5452
- }
5453
- await browser.close().catch(() => {
5454
- });
5455
- })
5456
- );
5457
- };
5458
- var forceTerminateBrowsersByFingerprintArg = async (fingerprintArg) => {
5459
- if (!fingerprintArg) {
5460
- return;
5461
- }
5462
- await execFileAsync("pkill", ["-f", "--", fingerprintArg]).catch((error) => {
5463
- if (error?.code === 1 || error?.code === "ENOENT") {
5464
- return;
5465
- }
5466
- logger8.info(`\u5F3A\u5236\u5173\u95ED CloakBrowser \u8FDB\u7A0B\u5931\u8D25\uFF08\u5FFD\u7565\uFF09: ${error?.message || String(error)}`);
5467
- });
5468
- };
5469
- var CloakBrowserLaunch = {
5470
- resolveProxyConfiguration(proxyConfiguration = {}) {
5471
- return resolveCloakBrowserProxy(proxyConfiguration);
5472
- },
5473
- extractFingerprintArg(launchOptions = {}) {
5474
- return extractFingerprintArg(launchOptions);
5475
- },
5476
- createStableGotoHook(recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS) {
5477
- return createStableGotoHook(recommendedGotoOptions);
5478
- },
5479
- async getPlaywrightCrawlerOptions(options = {}) {
5480
- const runtime2 = await CloakBrowserLaunch.createPlaywrightCrawlerRuntime(options);
5481
- return Object.defineProperties(runtime2.crawlerOptions, {
5482
- cleanup: {
5483
- enumerable: false,
5484
- value: runtime2.cleanup
5485
- },
5486
- closeActiveBrowsers: {
5487
- enumerable: false,
5488
- value: runtime2.closeActiveBrowsers
5489
- },
5490
- forceTerminateActiveProcesses: {
5491
- enumerable: false,
5492
- value: runtime2.forceTerminateActiveProcesses
5493
- }
5494
- });
5495
- },
5496
- async buildLaunchOptions(options = {}) {
5497
- return await buildCloakLaunchOptions(options);
5498
- },
5499
- async createPlaywrightCrawlerRuntime(options = {}) {
5500
- const normalizedOptions = normalizeObject(options);
5501
- const {
5502
- proxyConfiguration = {},
5503
- runInHeadfulMode = false,
5504
- isRunningOnApify = false,
5505
- launcher = null,
5506
- cloakOptions = {},
5507
- humanizeOptions = DEFAULT_CLOAK_HUMANIZE_OPTIONS,
5508
- crawlerBaseOptions = {},
5509
- browserPoolOptions = {},
5510
- launchContext = {},
5511
- preNavigationHooks = [],
5512
- postNavigationHooks = [],
5513
- recommendedGotoOptions = DEFAULT_CLOAK_GOTO_OPTIONS
5514
- } = normalizedOptions;
5515
- const normalizedCloakOptions = normalizeObject(cloakOptions);
5516
- const activeBrowsers = /* @__PURE__ */ new Set();
5517
- const patchedBrowsers = /* @__PURE__ */ new WeakSet();
5518
- const defaultArgs = isRunningOnApify ? ["--no-sandbox", "--disable-setuid-sandbox"] : [];
5519
- const extraArgs = normalizeStringArray(normalizedCloakOptions.args);
5520
- const proxy = hasOwn(normalizedCloakOptions, "proxy") ? normalizedCloakOptions.proxy : resolveCloakBrowserProxy(proxyConfiguration);
5521
- const headless = hasOwn(normalizedCloakOptions, "headless") ? normalizedCloakOptions.headless : !runInHeadfulMode || isRunningOnApify;
5522
- const mergedCloakOptions = {
5523
- ...normalizedCloakOptions,
5524
- headless,
5525
- proxy,
5526
- args: [...defaultArgs, ...extraArgs]
5527
- };
5528
- const launchOptions = await buildCloakLaunchOptions(mergedCloakOptions);
5529
- const fingerprintArg = extractFingerprintArg(launchOptions);
5530
- const internalPreNavigationHook = createStableGotoHook(recommendedGotoOptions);
5531
- const normalizedPreNavigationHooks = Array.isArray(preNavigationHooks) ? preNavigationHooks : [];
5532
- const normalizedPostNavigationHooks = Array.isArray(postNavigationHooks) ? postNavigationHooks : [];
5533
- const crawlerOptions = {
5534
- ...DEFAULT_CLOAK_CRAWLER_BASE_OPTIONS,
5535
- ...normalizeObject(crawlerBaseOptions),
5536
- headless,
5537
- launchContext: {
5538
- useIncognitoPages: true,
5539
- ...normalizeObject(launchContext),
5540
- ...launcher ? { launcher } : {},
5541
- launchOptions
5542
- },
5543
- browserPoolOptions: attachCloakBrowserHumanizeHook({
5544
- browserPoolOptions,
5545
- activeBrowsers,
5546
- patchedBrowsers,
5547
- humanizeOptions
5548
- }),
5549
- preNavigationHooks: [internalPreNavigationHook, ...normalizedPreNavigationHooks],
5550
- ...normalizedPostNavigationHooks.length > 0 ? { postNavigationHooks: normalizedPostNavigationHooks } : {}
5551
- };
5552
- const closeActiveBrowsers = async () => {
5553
- await closeTrackedBrowsers(activeBrowsers);
5554
- };
5555
- const forceTerminateActiveProcesses = async () => {
5556
- await forceTerminateBrowsersByFingerprintArg(fingerprintArg);
5557
- };
5558
- const cleanup = async () => {
5559
- await closeActiveBrowsers();
5560
- await forceTerminateActiveProcesses();
5561
- };
5562
- return {
5563
- headless,
5564
- launchOptions,
5565
- fingerprintArg,
5566
- crawlerOptions,
5567
- closeActiveBrowsers,
5568
- forceTerminateActiveProcesses,
5569
- cleanup
5570
- };
5571
- }
5572
- };
5573
-
5574
- // src/launch.js
5575
- var launchStrategies = {
5576
- [Mode.Default]: DefaultLaunch,
5577
- [Mode.CloakBrowser]: CloakBrowserLaunch
5578
- };
5579
- var launchFacadeMethods = Object.freeze({
5580
- getPlaywrightCrawlerOptions: { enumerable: true }
5581
- });
5582
- var Launch = createDelegatedFacade("Launch", launchStrategies, launchFacadeMethods);
5583
-
5584
5074
  // src/live-view.js
5585
5075
  import express from "express";
5586
5076
  import { Actor } from "apify";
@@ -6668,7 +6158,7 @@ var Mutation = {
6668
6158
  const overallTimeout = options.timeout ?? 180 * 1e3;
6669
6159
  const onMutation = options.onMutation;
6670
6160
  const pollInterval = 500;
6671
- const sleep2 = (ms) => new Promise((resolve) => {
6161
+ const sleep = (ms) => new Promise((resolve) => {
6672
6162
  setTimeout(resolve, ms);
6673
6163
  });
6674
6164
  const truncate = (value, max = 800) => {
@@ -6829,8 +6319,8 @@ var Mutation = {
6829
6319
  throw e;
6830
6320
  }
6831
6321
  }
6832
- let state2 = await buildState();
6833
- if (!state2?.hasMatched) {
6322
+ let state = await buildState();
6323
+ if (!state?.hasMatched) {
6834
6324
  logger12.warning("waitForStableAcrossRoots \u672A\u627E\u5230\u53EF\u76D1\u63A7\u7684\u5143\u7D20");
6835
6325
  return { mutationCount: 0, stableTime: 0, wasPaused: false };
6836
6326
  }
@@ -6838,7 +6328,7 @@ var Mutation = {
6838
6328
  let stableSince = 0;
6839
6329
  let isPaused = false;
6840
6330
  let wasPaused = false;
6841
- let lastSnapshotKey = state2.snapshotKey;
6331
+ let lastSnapshotKey = state.snapshotKey;
6842
6332
  const applyPauseSignal = (signal) => {
6843
6333
  const nextPaused = signal === "__PAUSE__";
6844
6334
  if (nextPaused) {
@@ -6852,15 +6342,15 @@ var Mutation = {
6852
6342
  };
6853
6343
  const initialSignal = await invokeMutationCallback({
6854
6344
  mutationCount: 0,
6855
- html: state2.html || "",
6856
- text: state2.text || "",
6857
- mutationNodes: state2.mutationNodes || []
6345
+ html: state.html || "",
6346
+ text: state.text || "",
6347
+ mutationNodes: state.mutationNodes || []
6858
6348
  });
6859
6349
  applyPauseSignal(initialSignal);
6860
6350
  const deadline = Date.now() + overallTimeout;
6861
- let lastState = state2;
6351
+ let lastState = state;
6862
6352
  while (Date.now() < deadline) {
6863
- await sleep2(pollInterval);
6353
+ await sleep(pollInterval);
6864
6354
  lastState = await buildState();
6865
6355
  if (!lastState?.hasMatched) {
6866
6356
  continue;
@@ -7948,7 +7438,7 @@ var Logger = {
7948
7438
  };
7949
7439
 
7950
7440
  // src/share.js
7951
- import delay5 from "delay";
7441
+ import delay4 from "delay";
7952
7442
 
7953
7443
  // src/internals/watermarkify.js
7954
7444
  var DEFAULT_TIMEZONE_OFFSET = 8;
@@ -8444,10 +7934,7 @@ var buildWatermarkifyRenderHtml = ({ imageSrc, overlaySvg, width, height, imageH
8444
7934
  </html>
8445
7935
  `;
8446
7936
  };
8447
- var normalizeWatermarkifyRenderMode = (value) => {
8448
- return String(value || "default").trim().toLowerCase() === "cloakbrowser" ? "cloakbrowser" : "default";
8449
- };
8450
- var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageInfo = {}, options = {}) => {
7937
+ var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageInfo = {}) => {
8451
7938
  if (!page || typeof page.context !== "function") {
8452
7939
  logger13.warning("watermarkify \u6D4F\u89C8\u5668\u5408\u6210\u8DF3\u8FC7: \u7F3A\u5C11\u53EF\u7528 page");
8453
7940
  return buffer;
@@ -8471,35 +7958,15 @@ var composeScreenshotBufferWithBrowser = async (page, buffer, overlaySvg, imageI
8471
7958
  height: viewportHeight
8472
7959
  }).catch(() => {
8473
7960
  });
8474
- const renderMode = normalizeWatermarkifyRenderMode(options.mode);
8475
- if (renderMode === "cloakbrowser") {
8476
- const renderHtml = buildWatermarkifyRenderHtml({
8477
- imageSrc: `data:${imageInfo.mimeType || "image/png"};base64,${buffer.toString("base64")}`,
8478
- overlaySvg,
8479
- width: safeWidth,
8480
- height: safeHeight,
8481
- imageHeight: safeImageHeight
8482
- });
8483
- await renderPage.goto("about:blank", {
8484
- waitUntil: "commit"
8485
- }).catch(() => {
8486
- });
8487
- await renderPage.evaluate((html) => {
8488
- document.open();
8489
- document.write(html);
8490
- document.close();
8491
- }, renderHtml);
8492
- } else {
8493
- await renderPage.setContent(buildWatermarkifyRenderHtml({
8494
- imageSrc: `data:${imageInfo.mimeType || "image/png"};base64,${buffer.toString("base64")}`,
8495
- overlaySvg,
8496
- width: safeWidth,
8497
- height: safeHeight,
8498
- imageHeight: safeImageHeight
8499
- }), {
8500
- waitUntil: "load"
8501
- });
8502
- }
7961
+ await renderPage.setContent(buildWatermarkifyRenderHtml({
7962
+ imageSrc: `data:${imageInfo.mimeType || "image/png"};base64,${buffer.toString("base64")}`,
7963
+ overlaySvg,
7964
+ width: safeWidth,
7965
+ height: safeHeight,
7966
+ imageHeight: safeImageHeight
7967
+ }), {
7968
+ waitUntil: "load"
7969
+ });
8503
7970
  await renderPage.waitForFunction(() => {
8504
7971
  const image = document.getElementById("pk-base-image");
8505
7972
  return image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0 && image.naturalHeight > 0;
@@ -9434,7 +8901,7 @@ var buildWatermarkifySvg = (meta, imageWidth, imageHeight) => {
9434
8901
  </svg>
9435
8902
  `;
9436
8903
  };
9437
- var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {}) => {
8904
+ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null) => {
9438
8905
  const hasWatermark = meta?.watermark?.enabled !== false && normalizeText(meta?.watermarkText);
9439
8906
  const hasStrip = meta?.strip?.enabled !== false && Array.isArray(meta?.stripSegments) && meta.stripSegments.length > 0;
9440
8907
  if (!Buffer.isBuffer(buffer) || !meta || !hasWatermark && !hasStrip) {
@@ -9455,7 +8922,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
9455
8922
  if (!overlaySvg) {
9456
8923
  return buffer;
9457
8924
  }
9458
- return await composeScreenshotBufferWithBrowser(page, buffer, overlaySvg, outputImageInfo, options);
8925
+ return await composeScreenshotBufferWithBrowser(page, buffer, overlaySvg, outputImageInfo);
9459
8926
  };
9460
8927
 
9461
8928
  // src/internals/compression.js
@@ -10019,7 +9486,7 @@ var Share = {
10019
9486
  );
10020
9487
  nextProgressLogTs = now + 5e3;
10021
9488
  }
10022
- await delay5(Math.max(0, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining)));
9489
+ await delay4(Math.max(0, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining)));
10023
9490
  }
10024
9491
  if (!timeoutDisabled && share.mode === "response" && stats.responseMatched === 0) {
10025
9492
  logger15.warning(
@@ -10053,7 +9520,6 @@ var Share = {
10053
9520
  * @param {number} [options.maxBytes] 默认 5MiB,返回 base64 超过后会压缩
10054
9521
  * @param {'jpeg'|'jpg'} [options.type] 压缩输出格式,默认 jpeg
10055
9522
  * @param {boolean|Object} [options.compression] 传 false 可关闭压缩
10056
- * @param {'default'|'cloakbrowser'} [options.mode] 截图水印合成模式,默认 default
10057
9523
  * @returns {Promise<string>} base64 image
10058
9524
  */
10059
9525
  async captureScreen(page, options = {}) {
@@ -10074,9 +9540,7 @@ var Share = {
10074
9540
  ...screenshotWatermarkify,
10075
9541
  capturedAt
10076
9542
  });
10077
- outputBuffer = await watermarkifyScreenshotBuffer(rawBuffer, watermarkifyMeta, page, {
10078
- mode: options.mode === "cloakbrowser" ? "cloakbrowser" : "default"
10079
- });
9543
+ outputBuffer = await watermarkifyScreenshotBuffer(rawBuffer, watermarkifyMeta, page);
10080
9544
  }
10081
9545
  return await compressImageBufferToBase64(outputBuffer, compression);
10082
9546
  }
@@ -10084,9 +9548,8 @@ var Share = {
10084
9548
 
10085
9549
  // entrys/node.js
10086
9550
  Logger.setLogger(crawleeLog);
10087
- var usePlaywrightToolKit = (mode = "default") => {
10088
- setToolkitMode(mode);
10089
- const toolkit = {
9551
+ var usePlaywrightToolKit = () => {
9552
+ return {
10090
9553
  ApifyKit,
10091
9554
  AntiCheat,
10092
9555
  DeviceInput,
@@ -10106,7 +9569,6 @@ var usePlaywrightToolKit = (mode = "default") => {
10106
9569
  ByPass,
10107
9570
  $Internals: { LOG_TEMPLATES, stripAnsi }
10108
9571
  };
10109
- return toolkit;
10110
9572
  };
10111
9573
  export {
10112
9574
  usePlaywrightToolKit