@skrillex1224/playwright-toolkit 2.1.285 → 2.1.286

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2517,28 +2517,69 @@ var resolveModeStrategy = (strategies = {}, mode = getToolkitMode(), fallbackMod
2517
2517
  };
2518
2518
 
2519
2519
  // src/internals/delegate.js
2520
- var normalizeMethodDefinition = (definition) => typeof definition === "string" ? { name: definition, enumerable: true } : {
2520
+ var normalizeArrayMethodDefinition = (definition) => typeof definition === "string" ? { name: definition, enumerable: true } : {
2521
2521
  name: String(definition?.name || "").trim(),
2522
2522
  enumerable: definition?.enumerable !== false
2523
2523
  };
2524
- var createMethodDescriptor = (namespace, methodName, enumerable, strategies) => ({
2524
+ var normalizeMethodDefinitions = (methods = {}) => {
2525
+ if (Array.isArray(methods)) {
2526
+ return methods.map(normalizeArrayMethodDefinition).filter((method) => method.name);
2527
+ }
2528
+ if (!methods || typeof methods !== "object") {
2529
+ return [];
2530
+ }
2531
+ return Object.entries(methods).map(([name, definition]) => ({
2532
+ name: String(name || "").trim(),
2533
+ enumerable: definition?.enumerable !== false
2534
+ })).filter((method) => method.name);
2535
+ };
2536
+ var normalizeDelegateResolution = (resolution) => {
2537
+ if (resolution && typeof resolution === "object" && ("delegate" in resolution || "label" in resolution)) {
2538
+ return {
2539
+ delegate: resolution.delegate ?? null,
2540
+ label: String(resolution.label || "current delegate").trim() || "current delegate"
2541
+ };
2542
+ }
2543
+ return {
2544
+ delegate: resolution,
2545
+ label: "current delegate"
2546
+ };
2547
+ };
2548
+ var createMethodDescriptor = (namespace, methodName, enumerable, resolveDelegate) => ({
2525
2549
  enumerable,
2526
2550
  value: (...args) => {
2527
- const { mode, delegate } = resolveModeStrategy(strategies);
2551
+ const { delegate, label } = normalizeDelegateResolution(resolveDelegate(methodName, args));
2528
2552
  if (typeof delegate?.[methodName] !== "function") {
2529
- throw new Error(`${namespace}.${methodName} is not available in ${mode} mode`);
2553
+ throw new Error(`${namespace}.${methodName} is not available in ${label}`);
2530
2554
  }
2531
2555
  return delegate[methodName](...args);
2532
2556
  }
2533
2557
  });
2534
- var createDelegatedFacade = (namespace, strategies = {}, methods = []) => {
2558
+ var withDelegatedProperties = (target = {}, {
2559
+ namespace = "Delegate",
2560
+ methods = {},
2561
+ resolveDelegate = () => null
2562
+ } = {}) => {
2535
2563
  const descriptors = Object.fromEntries(
2536
- methods.map(normalizeMethodDefinition).filter((method) => method.name).map((method) => [
2564
+ normalizeMethodDefinitions(methods).map((method) => [
2537
2565
  method.name,
2538
- createMethodDescriptor(namespace, method.name, method.enumerable, strategies)
2566
+ createMethodDescriptor(namespace, method.name, method.enumerable, resolveDelegate)
2539
2567
  ])
2540
2568
  );
2541
- return Object.defineProperties({}, descriptors);
2569
+ return Object.defineProperties(target, descriptors);
2570
+ };
2571
+ var createDelegatedFacade = (namespace, strategies = {}, methods = {}) => {
2572
+ return withDelegatedProperties({}, {
2573
+ namespace,
2574
+ methods,
2575
+ resolveDelegate: () => {
2576
+ const { mode, delegate } = resolveModeStrategy(strategies);
2577
+ return {
2578
+ delegate,
2579
+ label: `${mode} mode`
2580
+ };
2581
+ }
2582
+ });
2542
2583
  };
2543
2584
 
2544
2585
  // src/internals/anti-cheat/default.js
@@ -2652,13 +2693,14 @@ var antiCheatStrategies = {
2652
2693
  [Mode.Default]: DefaultAntiCheat,
2653
2694
  [Mode.CloakBrowser]: CloakBrowserAntiCheat
2654
2695
  };
2655
- var AntiCheat = createDelegatedFacade("AntiCheat", antiCheatStrategies, [
2656
- "getBaseConfig",
2657
- "getFingerprintGeneratorOptions",
2658
- "getLaunchArgs",
2659
- "getTlsFingerprintOptions",
2660
- "applyLocaleHeaders"
2661
- ]);
2696
+ var antiCheatFacadeMethods = Object.freeze({
2697
+ getBaseConfig: { enumerable: true },
2698
+ getFingerprintGeneratorOptions: { enumerable: true },
2699
+ getLaunchArgs: { enumerable: true },
2700
+ getTlsFingerprintOptions: { enumerable: true },
2701
+ applyLocaleHeaders: { enumerable: true }
2702
+ });
2703
+ var AntiCheat = createDelegatedFacade("AntiCheat", antiCheatStrategies, antiCheatFacadeMethods);
2662
2704
 
2663
2705
  // src/device-input.js
2664
2706
  var resolveDeviceFromPage = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
@@ -3210,1136 +3252,1240 @@ var DeviceView = {
3210
3252
  }
3211
3253
  };
3212
3254
 
3213
- // src/internals/humanize/desktop.js
3255
+ // src/internals/humanize/cloakbrowser.js
3256
+ var import_delay3 = __toESM(require("delay"), 1);
3257
+
3258
+ // src/internals/humanize/shared.js
3214
3259
  var import_delay2 = __toESM(require("delay"), 1);
3215
- var import_ghost_cursor_playwright = require("ghost-cursor-playwright");
3216
- var logger5 = createInternalLogger("Humanize");
3217
- var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
3218
- function $GetCursor(page) {
3219
- const cursor = $CursorWeakMap.get(page);
3220
- if (!cursor) {
3221
- throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
3260
+ var jitterMs = (base, jitterPercent = 0.3) => {
3261
+ const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
3262
+ return Math.max(10, Math.round(Number(base || 0) + jitter));
3263
+ };
3264
+ var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
3265
+ if (target == null) return null;
3266
+ let element = target;
3267
+ if (typeof target === "string") {
3268
+ element = await page.$(target);
3222
3269
  }
3223
- return cursor;
3224
- }
3225
- var Humanize = {
3226
- /**
3227
- * 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
3228
- * @param {number} base - 基础延迟 (ms)
3229
- * @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
3230
- * @returns {number} 抖动后的毫秒数
3231
- */
3232
- jitterMs(base, jitterPercent = 0.3) {
3233
- const jitter = base * jitterPercent * (Math.random() * 2 - 1);
3234
- return Math.max(10, Math.round(base + jitter));
3235
- },
3236
- /**
3237
- * 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
3238
- *
3239
- * @param {import('playwright').Page} page
3240
- * @returns {Promise<void>}
3241
- */
3242
- async initializeCursor(page) {
3243
- if ($CursorWeakMap.has(page)) {
3244
- logger5.debug("initializeCursor: cursor already exists, skipping");
3245
- return;
3270
+ if (!element) {
3271
+ if (throwOnMissing) {
3272
+ throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
3246
3273
  }
3247
- logger5.start("initializeCursor", "creating cursor");
3248
- const cursor = await (0, import_ghost_cursor_playwright.createCursor)(page);
3249
- $CursorWeakMap.set(page, cursor);
3250
- logger5.success("initializeCursor", "cursor initialized");
3251
- },
3252
- /**
3253
- * 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
3254
- *
3255
- * @param {import('playwright').Page} page
3256
- * @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
3257
- */
3258
- async humanMove(page, target) {
3259
- const cursor = $GetCursor(page);
3260
- logger5.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
3261
- try {
3262
- if (typeof target === "string") {
3263
- const element = await page.$(target);
3264
- if (!element) {
3265
- logger5.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
3266
- return false;
3267
- }
3268
- const box = await element.boundingBox();
3269
- if (!box) {
3270
- logger5.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
3271
- return false;
3272
- }
3273
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
3274
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
3275
- await cursor.actions.move({ x, y });
3276
- } else if (target && typeof target.x === "number" && typeof target.y === "number") {
3277
- await cursor.actions.move(target);
3278
- } else if (target && typeof target.boundingBox === "function") {
3279
- const box = await target.boundingBox();
3280
- if (box) {
3281
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
3282
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
3283
- await cursor.actions.move({ x, y });
3284
- }
3274
+ return null;
3275
+ }
3276
+ return element;
3277
+ };
3278
+ var waitJitter = (base, jitterPercent = 0.3) => (0, import_delay2.default)(jitterMs(base, jitterPercent));
3279
+
3280
+ // src/internals/humanize/mobile.js
3281
+ var logger5 = createInternalLogger("Humanize.Mobile");
3282
+ var initializedPages = /* @__PURE__ */ new WeakSet();
3283
+ var DEFAULT_TAP_TIMEOUT_MS = 2500;
3284
+ var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3285
+ var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3286
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3287
+ var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3288
+ var describeTarget = (target) => {
3289
+ if (target == null) return "Current Position";
3290
+ return typeof target === "string" ? target : "ElementHandle";
3291
+ };
3292
+ var clipBoxToViewport = (box, viewport) => {
3293
+ if (!box || !viewport) return box;
3294
+ const left = clamp(box.x, 0, viewport.width);
3295
+ const top = clamp(box.y, 0, viewport.height);
3296
+ const right = clamp(box.x + box.width, 0, viewport.width);
3297
+ const bottom = clamp(box.y + box.height, 0, viewport.height);
3298
+ if (right - left > 1 && bottom - top > 1) {
3299
+ return {
3300
+ x: left,
3301
+ y: top,
3302
+ width: right - left,
3303
+ height: bottom - top
3304
+ };
3305
+ }
3306
+ return box;
3307
+ };
3308
+ var centerPointInBox = (box) => ({
3309
+ x: box.x + box.width / 2,
3310
+ y: box.y + box.height / 2
3311
+ });
3312
+ var withTimeout = async (operation, timeoutMs, label) => {
3313
+ const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3314
+ let timeoutId = null;
3315
+ try {
3316
+ return await Promise.race([
3317
+ Promise.resolve().then(operation),
3318
+ new Promise((_, reject) => {
3319
+ timeoutId = setTimeout(() => {
3320
+ reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
3321
+ }, safeTimeoutMs);
3322
+ })
3323
+ ]);
3324
+ } finally {
3325
+ if (timeoutId) clearTimeout(timeoutId);
3326
+ }
3327
+ };
3328
+ var checkElementVisibility = async (element) => {
3329
+ return element.evaluate((el) => {
3330
+ const targetStyle = window.getComputedStyle(el);
3331
+ if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3332
+ return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
3333
+ }
3334
+ const rect = el.getBoundingClientRect();
3335
+ if (!rect || rect.width <= 0 || rect.height <= 0) {
3336
+ return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6", direction: "down" };
3337
+ }
3338
+ const viewW = window.innerWidth;
3339
+ const viewH = window.innerHeight;
3340
+ const centerY = rect.top + rect.height / 2;
3341
+ let clipLeft = 0;
3342
+ let clipRight = viewW;
3343
+ let clipTop = 0;
3344
+ let clipBottom = viewH;
3345
+ let isFixed = false;
3346
+ let positioning = null;
3347
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
3348
+ const style = window.getComputedStyle(node);
3349
+ if (style && (style.position === "fixed" || style.position === "sticky")) {
3350
+ isFixed = true;
3351
+ positioning = style.position;
3352
+ break;
3285
3353
  }
3286
- logger5.success("humanMove");
3287
- return true;
3288
- } catch (error) {
3289
- logger5.fail("humanMove", error);
3290
- throw error;
3291
3354
  }
3292
- },
3293
- /**
3294
- * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
3295
- * 返回 restore 方法,用于将滚动容器恢复到原位置
3296
- *
3297
- * @param {import('playwright').Page} page
3298
- * @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
3299
- * @param {Object} [options]
3300
- * @param {number} [options.maxSteps=20] - 最大滚动步数
3301
- * @param {number} [options.minStep=260] - 单次滚动最小步长
3302
- * @param {number} [options.maxStep=800] - 单次滚动最大步长
3303
- * @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
3304
- */
3305
- async humanScroll(page, target, options = {}) {
3306
- const {
3307
- maxSteps = 20,
3308
- minStep = 260,
3309
- maxStep = 800,
3310
- maxDurationMs = maxSteps * 220 + 800
3311
- } = options;
3312
- const targetDesc = typeof target === "string" ? target : "ElementHandle";
3313
- logger5.start("humanScroll", `target=${targetDesc}`);
3314
- let element;
3315
- if (typeof target === "string") {
3316
- element = await page.$(target);
3317
- if (!element) {
3318
- logger5.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
3319
- return { element: null, didScroll: false };
3355
+ for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
3356
+ const style = window.getComputedStyle(node);
3357
+ if (!style) continue;
3358
+ const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
3359
+ const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
3360
+ if (!clipsX && !clipsY) continue;
3361
+ const nodeRect = node.getBoundingClientRect();
3362
+ if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
3363
+ if (clipsX) {
3364
+ clipLeft = Math.max(clipLeft, nodeRect.left);
3365
+ clipRight = Math.min(clipRight, nodeRect.right);
3366
+ }
3367
+ if (clipsY) {
3368
+ clipTop = Math.max(clipTop, nodeRect.top);
3369
+ clipBottom = Math.min(clipBottom, nodeRect.bottom);
3320
3370
  }
3321
- } else {
3322
- element = target;
3323
3371
  }
3324
- const cursor = $GetCursor(page);
3325
- let didScroll = false;
3326
- const checkVisibility = async () => {
3327
- return await element.evaluate((el) => {
3328
- const rect = el.getBoundingClientRect();
3329
- if (!rect || rect.width === 0 || rect.height === 0) {
3330
- return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
3331
- }
3332
- const cx = rect.left + rect.width / 2;
3333
- const cy = rect.top + rect.height / 2;
3334
- const viewH = window.innerHeight;
3335
- const viewW = window.innerWidth;
3336
- let isFixed = false;
3337
- for (let node = el; node && node !== document.body; node = node.parentElement) {
3338
- const style = window.getComputedStyle(node);
3339
- if (style && style.position === "fixed") {
3340
- isFixed = true;
3341
- break;
3342
- }
3343
- }
3344
- if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
3345
- const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
3346
- return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH, isFixed };
3347
- }
3348
- const pointElement = document.elementFromPoint(cx, cy);
3349
- if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
3350
- return {
3351
- code: "OBSTRUCTED",
3352
- reason: "\u88AB\u906E\u6321",
3353
- obstruction: {
3354
- tag: pointElement.tagName,
3355
- id: pointElement.id,
3356
- className: pointElement.className
3357
- },
3358
- cy,
3359
- // Return Center Y for smart direction calculation
3360
- viewH,
3361
- isFixed
3362
- };
3363
- }
3364
- return { code: "VISIBLE", isFixed };
3365
- });
3366
- };
3367
- const getScrollableRect2 = async () => {
3368
- return await element.evaluate((el) => {
3369
- const isScrollable = (node) => {
3370
- const style = window.getComputedStyle(node);
3371
- if (!style) return false;
3372
- const overflowY = style.overflowY;
3373
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3374
- return node.scrollHeight > node.clientHeight + 1;
3375
- };
3376
- let current = el;
3377
- while (current && current !== document.body) {
3378
- if (isScrollable(current)) {
3379
- const rect = current.getBoundingClientRect();
3380
- if (rect && rect.width > 0 && rect.height > 0) {
3381
- return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
3382
- }
3383
- }
3384
- current = current.parentElement;
3385
- }
3386
- return null;
3387
- });
3388
- };
3389
- const startTime = Date.now();
3390
- try {
3391
- for (let i = 0; i < maxSteps; i++) {
3392
- if (Date.now() - startTime > maxDurationMs) {
3393
- logger5.warn(`humanScroll | \u8D85\u65F6\u4FDD\u62A4\u89E6\u53D1 (${maxDurationMs}ms)`);
3394
- return { element, didScroll };
3395
- }
3396
- const status = await checkVisibility();
3397
- if (status.code === "VISIBLE") {
3398
- if (status.isFixed) {
3399
- logger5.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3400
- } else {
3401
- logger5.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
3402
- }
3403
- logger5.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3404
- return { element, didScroll };
3405
- }
3406
- logger5.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
3407
- if (status.code === "OBSTRUCTED" && status.obstruction) {
3408
- logger5.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
3409
- }
3410
- const scrollRect = await getScrollableRect2();
3411
- if (!scrollRect && status.isFixed) {
3412
- logger5.warn("humanScroll | fixed \u5BB9\u5668\u5185\u4E14\u65E0\u53EF\u6EDA\u52A8\u7956\u5148\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3413
- return { element, didScroll };
3414
- }
3415
- const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
3416
- const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
3417
- let deltaY = 0;
3418
- if (status.code === "OUT_OF_VIEWPORT") {
3419
- if (status.direction === "down") {
3420
- deltaY = stepMin + Math.random() * (stepMax - stepMin);
3421
- } else if (status.direction === "up") {
3422
- deltaY = -(stepMin + Math.random() * (stepMax - stepMin));
3423
- } else {
3424
- deltaY = 100;
3425
- }
3426
- } else if (status.code === "OBSTRUCTED") {
3427
- const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3428
- const isBottomHalf = status.cy > halfY;
3429
- const direction = isBottomHalf ? 1 : -1;
3430
- deltaY = direction * (stepMin + Math.random() * 50);
3431
- }
3432
- if (i === 0) {
3433
- const viewSize = page.viewportSize();
3434
- if (scrollRect) {
3435
- const safeX = scrollRect.x + scrollRect.width * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.width * 0.4);
3436
- const safeY = scrollRect.y + scrollRect.height * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.height * 0.4);
3437
- await cursor.actions.move({ x: safeX, y: safeY });
3438
- } else if (viewSize) {
3439
- const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 80;
3440
- const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 80;
3441
- await cursor.actions.move({ x: safeX, y: safeY });
3442
- }
3443
- }
3444
- await page.mouse.wheel(0, deltaY);
3445
- didScroll = true;
3446
- await (0, import_delay2.default)(this.jitterMs(20 + Math.random() * 40, 0.2));
3447
- }
3448
- logger5.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
3449
- return { element, didScroll };
3450
- } catch (error) {
3451
- logger5.fail("humanScroll", error);
3452
- throw error;
3372
+ const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
3373
+ const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
3374
+ const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
3375
+ const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
3376
+ const visibleWidth = visibleRight - visibleLeft;
3377
+ const visibleHeight = visibleBottom - visibleTop;
3378
+ const cx = visibleLeft + visibleWidth / 2;
3379
+ const cy = visibleTop + visibleHeight / 2;
3380
+ if (visibleWidth <= 1 || visibleHeight <= 1) {
3381
+ return {
3382
+ code: "OUT_OF_VIEWPORT",
3383
+ reason: "\u4E0D\u5728\u89C6\u53E3\u5185",
3384
+ direction: centerY < clipTop ? "up" : "down",
3385
+ cy: centerY,
3386
+ viewH,
3387
+ isFixed,
3388
+ positioning
3389
+ };
3453
3390
  }
3454
- },
3455
- /**
3456
- * 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
3457
- *
3458
- * @param {import('playwright').Page} page
3459
- * @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
3460
- * @param {Object} [options]
3461
- * @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
3462
- * @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
3463
- * @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
3464
- */
3465
- async humanClick(page, target, options = {}) {
3466
- const cursor = $GetCursor(page);
3467
- const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
3468
- const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
3469
- logger5.start("humanClick", `target=${targetDesc}`);
3470
- const restoreOnce = async () => {
3471
- if (restoreOnce.restored) return;
3472
- restoreOnce.restored = true;
3473
- if (typeof restoreOnce.do !== "function") return;
3474
- try {
3475
- await (0, import_delay2.default)(this.jitterMs(1e3));
3476
- await restoreOnce.do();
3477
- } catch (restoreError) {
3478
- logger5.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
3391
+ const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3392
+ const commonAncestor = (a, b) => {
3393
+ for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3394
+ if (current.contains(b)) return current;
3479
3395
  }
3396
+ return null;
3480
3397
  };
3481
- try {
3482
- if (target == null) {
3483
- await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
3484
- await cursor.actions.click();
3485
- logger5.success("humanClick", "Clicked current position");
3398
+ const sameTapTarget = (pointElement) => {
3399
+ if (!pointElement) return false;
3400
+ if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3486
3401
  return true;
3487
3402
  }
3488
- let element;
3489
- if (typeof target === "string") {
3490
- element = await page.$(target);
3491
- if (!element) {
3492
- if (throwOnMissing) {
3493
- throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
3494
- }
3495
- logger5.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
3496
- return false;
3497
- }
3498
- } else {
3499
- element = target;
3500
- }
3501
- if (scrollIfNeeded) {
3502
- const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
3503
- restoreOnce.do = didScroll && restore ? restoreFn : null;
3504
- }
3505
- const box = await element.boundingBox();
3506
- if (!box) {
3507
- await restoreOnce();
3508
- if (throwOnMissing) {
3509
- throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
3510
- }
3511
- logger5.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
3403
+ const common = commonAncestor(el, pointElement);
3404
+ if (!common) return false;
3405
+ const commonRect = common.getBoundingClientRect?.();
3406
+ if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3407
+ const commonArea = commonRect.width * commonRect.height;
3408
+ const targetArea = Math.max(1, rect.width * rect.height);
3409
+ const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3410
+ if (commonArea > maxSharedRegionArea) return false;
3411
+ if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
3512
3412
  return false;
3513
3413
  }
3514
- const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
3515
- const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
3516
- await cursor.actions.move({ x, y });
3517
- await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
3518
- await cursor.actions.click();
3519
- await restoreOnce();
3520
- logger5.success("humanClick");
3521
- return true;
3522
- } catch (error) {
3523
- await restoreOnce();
3524
- logger5.fail("humanClick", error);
3525
- throw error;
3414
+ return common.contains(el) && common.contains(pointElement);
3415
+ };
3416
+ const describeElement = (node) => {
3417
+ if (!node) return null;
3418
+ const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
3419
+ const rect2 = node.getBoundingClientRect?.();
3420
+ const style = window.getComputedStyle(node);
3421
+ return {
3422
+ tag: node.tagName,
3423
+ id: node.id || "",
3424
+ className,
3425
+ isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
3426
+ positioning: style?.position || "",
3427
+ top: rect2 ? rect2.top : null,
3428
+ bottom: rect2 ? rect2.bottom : null,
3429
+ left: rect2 ? rect2.left : null,
3430
+ right: rect2 ? rect2.right : null
3431
+ };
3432
+ };
3433
+ const samplePoints = [
3434
+ { x: cx, y: cy },
3435
+ { x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3436
+ { x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3437
+ { x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
3438
+ { x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
3439
+ ];
3440
+ let obstruction = null;
3441
+ for (const point of samplePoints) {
3442
+ const pointElement = document.elementFromPoint(point.x, point.y);
3443
+ if (sameTapTarget(pointElement)) {
3444
+ return { code: "VISIBLE", isFixed, positioning };
3445
+ }
3446
+ obstruction = obstruction || describeElement(pointElement);
3526
3447
  }
3527
- },
3528
- /**
3529
- * 随机延迟一段毫秒数(带 ±30% 抖动)
3530
- * @param {number} baseMs - 基础延迟毫秒数
3531
- * @param {number} [jitterPercent=0.3] - 抖动百分比
3532
- */
3533
- async randomSleep(baseMs, jitterPercent = 0.3) {
3534
- const ms = this.jitterMs(baseMs, jitterPercent);
3535
- logger5.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
3536
- await (0, import_delay2.default)(ms);
3537
- logger5.success("randomSleep");
3538
- },
3539
- /**
3540
- * 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
3541
- * @param {import('playwright').Page} page
3542
- * @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
3543
- */
3544
- async simulateGaze(page, baseDurationMs = 2500) {
3545
- const cursor = $GetCursor(page);
3546
- const durationMs = this.jitterMs(baseDurationMs, 0.4);
3547
- logger5.start("simulateGaze", `duration=${durationMs}ms`);
3548
- const startTime = Date.now();
3549
- const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
3550
- while (Date.now() - startTime < durationMs) {
3551
- const x = 100 + Math.random() * (viewportSize.width - 200);
3552
- const y = 100 + Math.random() * (viewportSize.height - 200);
3553
- await cursor.actions.move({ x, y });
3554
- await (0, import_delay2.default)(this.jitterMs(600, 0.5));
3448
+ if (obstruction) {
3449
+ return {
3450
+ code: "OBSTRUCTED",
3451
+ reason: "\u88AB\u906E\u6321",
3452
+ direction: cy > viewH / 2 ? "down" : "up",
3453
+ obstruction,
3454
+ cy,
3455
+ viewH,
3456
+ isFixed,
3457
+ positioning
3458
+ };
3555
3459
  }
3556
- logger5.success("simulateGaze");
3557
- },
3558
- /**
3559
- * 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
3560
- * @param {import('playwright').Page} page
3561
- * @param {string} selector - 输入框选择器
3562
- * @param {string} text - 要输入的文本
3563
- * @param {Object} [options]
3564
- * @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
3565
- * @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
3566
- * @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
3567
- */
3568
- async humanType(page, selector, text, options = {}) {
3569
- logger5.start("humanType", `selector=${selector}, textLen=${text.length}`);
3570
- const {
3571
- baseDelay = 180,
3572
- pauseProbability = 0.08,
3573
- pauseBase = 800
3574
- } = options;
3575
- try {
3576
- const locator = page.locator(selector);
3577
- await Humanize.humanClick(page, locator);
3578
- await (0, import_delay2.default)(this.jitterMs(200, 0.4));
3579
- for (let i = 0; i < text.length; i++) {
3580
- const char = text[i];
3581
- let charDelay;
3582
- if (char === " ") {
3583
- charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
3584
- } else if (/[,.!?;:,。!?;:]/.test(char)) {
3585
- charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
3586
- } else {
3587
- charDelay = this.jitterMs(baseDelay, 0.4);
3588
- }
3589
- await page.keyboard.type(char);
3590
- await (0, import_delay2.default)(charDelay);
3591
- if (Math.random() < pauseProbability && i < text.length - 1) {
3592
- const pauseTime = this.jitterMs(pauseBase, 0.5);
3593
- logger5.debug(`\u505C\u987F ${pauseTime}ms...`);
3594
- await (0, import_delay2.default)(pauseTime);
3460
+ return { code: "VISIBLE", isFixed, positioning };
3461
+ });
3462
+ };
3463
+ var activateElementFallback = async (element, point = null, options = {}) => {
3464
+ return element.evaluate((el, { innerOptions }) => {
3465
+ const isEditable = (node) => {
3466
+ if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3467
+ if (node.isContentEditable) return true;
3468
+ if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3469
+ if (node instanceof HTMLInputElement) {
3470
+ return !node.disabled && !node.readOnly && typeof node.select === "function";
3471
+ }
3472
+ return false;
3473
+ };
3474
+ const findEditable = (node) => {
3475
+ for (let current = node; current && current !== document.body; current = current.parentElement) {
3476
+ if (isEditable(current)) return current;
3477
+ }
3478
+ return null;
3479
+ };
3480
+ const editable = findEditable(el);
3481
+ if (editable && typeof editable.focus === "function") {
3482
+ editable.focus({ preventScroll: true });
3483
+ if (innerOptions.editableOnly) {
3484
+ return { activated: true, method: "focus", tag: editable.tagName || "" };
3485
+ }
3486
+ }
3487
+ if (innerOptions.editableOnly) {
3488
+ return { activated: false, method: "none", tag: el?.tagName || "" };
3489
+ }
3490
+ if (typeof el.focus === "function") {
3491
+ el.focus({ preventScroll: true });
3492
+ }
3493
+ if (typeof el.click === "function") {
3494
+ el.click();
3495
+ return { activated: true, method: "dom-click", tag: el.tagName || "" };
3496
+ }
3497
+ if (typeof el.dispatchEvent === "function") {
3498
+ el.dispatchEvent(new MouseEvent("click", {
3499
+ bubbles: true,
3500
+ cancelable: true,
3501
+ view: window
3502
+ }));
3503
+ return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
3504
+ }
3505
+ return {
3506
+ activated: Boolean(editable),
3507
+ method: editable ? "focus" : "none",
3508
+ tag: el?.tagName || ""
3509
+ };
3510
+ }, {
3511
+ innerOptions: options || {}
3512
+ });
3513
+ };
3514
+ var getScrollableRect = async (element) => {
3515
+ return element.evaluate((el) => {
3516
+ const isScrollable = (node) => {
3517
+ const style = window.getComputedStyle(node);
3518
+ if (!style) return false;
3519
+ const overflowY = style.overflowY;
3520
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3521
+ return node.scrollHeight > node.clientHeight + 1;
3522
+ };
3523
+ let current = el;
3524
+ while (current && current !== document.body) {
3525
+ if (isScrollable(current)) {
3526
+ const rect = current.getBoundingClientRect();
3527
+ if (rect && rect.width > 0 && rect.height > 0) {
3528
+ return {
3529
+ x: rect.x,
3530
+ y: rect.y,
3531
+ width: rect.width,
3532
+ height: rect.height
3533
+ };
3595
3534
  }
3596
3535
  }
3597
- logger5.success("humanType");
3598
- } catch (error) {
3599
- logger5.fail("humanType", error);
3600
- throw error;
3536
+ current = current.parentElement;
3601
3537
  }
3602
- },
3603
- /**
3604
- * 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
3605
- * @param {import('playwright').Page} page
3606
- * @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
3607
- * @param {string|Object} [maybeKey] - 按键或选项
3608
- * @param {Object} [options]
3609
- */
3610
- async humanPress(page, targetOrKey, maybeKey, options = {}) {
3611
- const hasTarget = typeof maybeKey === "string";
3612
- const key = hasTarget ? maybeKey : targetOrKey;
3613
- const pressOptions = hasTarget ? options : maybeKey || options;
3614
- const {
3615
- reactionDelay = 180,
3616
- holdDelay = 45,
3617
- focusDelay = 180,
3618
- scrollIfNeeded = true,
3619
- throwOnMissing = true,
3620
- keyboardOptions = {}
3621
- } = pressOptions || {};
3622
- const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
3623
- logger5.start("humanPress", `key=${key}, target=${targetDesc}`);
3624
- try {
3625
- if (hasTarget) {
3626
- await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
3538
+ return null;
3539
+ });
3540
+ };
3541
+ var scrollScrollableAncestor = async (element, deltaY) => {
3542
+ return element.evaluate((el, amount) => {
3543
+ const isScrollable = (node) => {
3544
+ const style = window.getComputedStyle(node);
3545
+ if (!style) return false;
3546
+ const overflowY = style.overflowY;
3547
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3548
+ return node.scrollHeight > node.clientHeight + 1;
3549
+ };
3550
+ let current = el;
3551
+ while (current && current !== document.body) {
3552
+ if (isScrollable(current)) {
3553
+ const beforeTop2 = current.scrollTop;
3554
+ current.scrollTop = beforeTop2 + amount;
3555
+ return {
3556
+ scroller: true,
3557
+ moved: current.scrollTop !== beforeTop2,
3558
+ scrollTop: current.scrollTop
3559
+ };
3627
3560
  }
3628
- await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.45));
3629
- await page.keyboard.press(key, {
3630
- ...keyboardOptions,
3631
- delay: this.jitterMs(holdDelay, 0.5)
3561
+ current = current.parentElement;
3562
+ }
3563
+ const beforeTop = window.scrollY;
3564
+ window.scrollBy(0, amount);
3565
+ return {
3566
+ scroller: null,
3567
+ moved: window.scrollY !== beforeTop,
3568
+ scrollTop: window.scrollY
3569
+ };
3570
+ }, deltaY);
3571
+ };
3572
+ var scrollAwayFromObstruction = async (element, status) => {
3573
+ return element.evaluate((el, innerStatus) => {
3574
+ const isScrollable = (node) => {
3575
+ const style = window.getComputedStyle(node);
3576
+ if (!style) return false;
3577
+ const overflowY = style.overflowY;
3578
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3579
+ return node.scrollHeight > node.clientHeight + 1;
3580
+ };
3581
+ let scroller = el;
3582
+ while (scroller && scroller !== document.body) {
3583
+ if (isScrollable(scroller)) break;
3584
+ scroller = scroller.parentElement;
3585
+ }
3586
+ if (!scroller || scroller === document.body) {
3587
+ return { moved: false, scrollTop: 0, deltaY: 0 };
3588
+ }
3589
+ const rect = el.getBoundingClientRect();
3590
+ const scrollerRect = scroller.getBoundingClientRect();
3591
+ const obstruction = innerStatus?.obstruction || {};
3592
+ const obstructionTop = Number(obstruction.top);
3593
+ const obstructionBottom = Number(obstruction.bottom);
3594
+ const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
3595
+ const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
3596
+ const padding = 18;
3597
+ let deltaY = 0;
3598
+ if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
3599
+ deltaY = rect.bottom - obstructionTop + padding;
3600
+ } else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
3601
+ deltaY = rect.top - obstructionBottom - padding;
3602
+ } else {
3603
+ const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
3604
+ deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
3605
+ }
3606
+ const beforeTop = scroller.scrollTop;
3607
+ scroller.scrollTop = beforeTop + deltaY;
3608
+ return {
3609
+ moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
3610
+ scrollTop: scroller.scrollTop,
3611
+ deltaY
3612
+ };
3613
+ }, status);
3614
+ };
3615
+ var getElementViewportSnapshot = async (element) => {
3616
+ return element.evaluate((el) => {
3617
+ const rect = el.getBoundingClientRect();
3618
+ return {
3619
+ top: rect.top,
3620
+ bottom: rect.bottom,
3621
+ left: rect.left,
3622
+ right: rect.right,
3623
+ width: rect.width,
3624
+ height: rect.height,
3625
+ scrollX: window.scrollX,
3626
+ scrollY: window.scrollY
3627
+ };
3628
+ });
3629
+ };
3630
+ var isTargetImmobileAfterScroll = (before, after) => {
3631
+ if (!before || !after) return false;
3632
+ const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
3633
+ const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
3634
+ const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
3635
+ const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
3636
+ const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
3637
+ const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
3638
+ if (!rectMoved && !pageMoved) return true;
3639
+ if (pageMoved && !rectMoved) return true;
3640
+ if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
3641
+ return true;
3642
+ }
3643
+ return false;
3644
+ };
3645
+ var restoreWindowFromSnapshot = async (page, before, after) => {
3646
+ if (!before || !after) return;
3647
+ if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
3648
+ return;
3649
+ }
3650
+ await page.evaluate(
3651
+ (state2) => window.scrollTo(state2.x, state2.y),
3652
+ { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
3653
+ ).catch(() => {
3654
+ });
3655
+ };
3656
+ var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
3657
+ const viewport = resolveViewport(page);
3658
+ const rawRect = options.rect || null;
3659
+ const rect = rawRect ? {
3660
+ x: clamp(rawRect.x, 0, viewport.width),
3661
+ y: clamp(rawRect.y, 0, viewport.height),
3662
+ width: clamp(rawRect.width, 0, viewport.width),
3663
+ height: clamp(rawRect.height, 0, viewport.height)
3664
+ } : null;
3665
+ const area = rect && rect.width > 24 && rect.height > 48 ? {
3666
+ left: rect.x,
3667
+ right: Math.min(viewport.width, rect.x + rect.width),
3668
+ top: rect.y,
3669
+ bottom: Math.min(viewport.height, rect.y + rect.height)
3670
+ } : {
3671
+ left: 0,
3672
+ right: viewport.width,
3673
+ top: 0,
3674
+ bottom: viewport.height
3675
+ };
3676
+ const areaWidth = Math.max(1, area.right - area.left);
3677
+ const areaHeight = Math.max(1, area.bottom - area.top);
3678
+ const maxDistance = rect ? Math.max(60, areaHeight * 0.72) : Math.max(120, viewport.height * 0.72);
3679
+ const distance = clamp(Math.abs(deltaY), Math.min(80, maxDistance), maxDistance);
3680
+ const direction = deltaY >= 0 ? 1 : -1;
3681
+ const startX = clamp(
3682
+ area.left + areaWidth * (0.45 + Math.random() * 0.1),
3683
+ 24,
3684
+ viewport.width - 24
3685
+ );
3686
+ const startY = direction > 0 ? clamp(area.top + areaHeight * (0.72 + Math.random() * 0.1), area.top + 24, area.bottom - 16) : clamp(area.top + areaHeight * (0.28 + Math.random() * 0.1), area.top + 16, area.bottom - 24);
3687
+ const endY = clamp(startY - direction * distance, Math.max(16, area.top + 12), Math.min(viewport.height - 16, area.bottom - 12));
3688
+ const steps = Math.max(4, Math.round(Number(options.steps || 6)));
3689
+ const durationMs = jitterMs(options.durationMs || 320, 0.35);
3690
+ let client = null;
3691
+ const scrollBefore = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3692
+ try {
3693
+ if (page.context && typeof page.context().newCDPSession === "function") {
3694
+ client = await page.context().newCDPSession(page);
3695
+ }
3696
+ if (!client) throw new Error("CDP session unavailable");
3697
+ await client.send("Input.synthesizeScrollGesture", {
3698
+ x: startX,
3699
+ y: startY,
3700
+ yDistance: -direction * distance,
3701
+ xOverscroll: (Math.random() - 0.5) * 4,
3702
+ yOverscroll: (Math.random() - 0.5) * 8,
3703
+ speed: 700 + Math.round(Math.random() * 350),
3704
+ gestureSourceType: "touch"
3705
+ });
3706
+ await waitJitter(160, 0.35);
3707
+ const scrollAfterSynth = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3708
+ if (scrollBefore && scrollAfterSynth && (Math.abs(scrollAfterSynth.y - scrollBefore.y) > 2 || Math.abs(scrollAfterSynth.x - scrollBefore.x) > 2)) {
3709
+ return true;
3710
+ }
3711
+ await client.send("Input.dispatchTouchEvent", {
3712
+ type: "touchStart",
3713
+ touchPoints: [{ x: startX, y: startY, id: 1 }]
3714
+ });
3715
+ for (let i = 1; i <= steps; i += 1) {
3716
+ const progress = i / steps;
3717
+ const eased = 1 - Math.pow(1 - progress, 2);
3718
+ const x = startX + (Math.random() - 0.5) * 3;
3719
+ const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 5;
3720
+ await waitJitter(durationMs / steps, 0.25);
3721
+ await client.send("Input.dispatchTouchEvent", {
3722
+ type: "touchMove",
3723
+ touchPoints: [{ x, y, id: 1 }]
3632
3724
  });
3633
- logger5.success("humanPress");
3634
- return true;
3635
- } catch (error) {
3636
- logger5.fail("humanPress", error);
3637
- throw error;
3638
3725
  }
3639
- },
3640
- /**
3641
- * 人类化清空输入框 - 模拟人类删除文本的行为
3642
- * @param {import('playwright').Page} page
3643
- * @param {string} selector - 输入框选择器
3644
- */
3645
- async humanClear(page, selector) {
3646
- logger5.start("humanClear", `selector=${selector}`);
3647
- try {
3648
- const locator = page.locator(selector);
3649
- await locator.click();
3650
- await (0, import_delay2.default)(this.jitterMs(200, 0.4));
3651
- const currentValue = await locator.inputValue();
3652
- if (!currentValue || currentValue.length === 0) {
3653
- logger5.success("humanClear", "already empty");
3654
- return;
3655
- }
3656
- await page.keyboard.press("Meta+A");
3657
- await (0, import_delay2.default)(this.jitterMs(100, 0.4));
3658
- await page.keyboard.press("Backspace");
3659
- logger5.success("humanClear");
3660
- } catch (error) {
3661
- logger5.fail("humanClear", error);
3662
- throw error;
3726
+ await client.send("Input.dispatchTouchEvent", {
3727
+ type: "touchEnd",
3728
+ touchPoints: []
3729
+ });
3730
+ await waitJitter(120, 0.35);
3731
+ const scrollAfterTouch = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
3732
+ if (scrollBefore && scrollAfterTouch && Math.abs(scrollAfterTouch.y - scrollBefore.y) <= 2 && Math.abs(scrollAfterTouch.x - scrollBefore.x) <= 2) {
3733
+ await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
3734
+ await waitJitter(120, 0.35);
3663
3735
  }
3664
- },
3665
- /**
3666
- * 页面预热浏览 - 模拟人类进入页面后的探索行为
3667
- * @param {import('playwright').Page} page
3668
- * @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
3669
- */
3670
- async warmUpBrowsing(page, baseDuration = 3500) {
3671
- const cursor = $GetCursor(page);
3672
- const durationMs = this.jitterMs(baseDuration, 0.4);
3673
- logger5.start("warmUpBrowsing", `duration=${durationMs}ms`);
3674
- const startTime = Date.now();
3675
- const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
3736
+ return true;
3737
+ } catch (error) {
3738
+ logger5.debug(`touch swipe fallback: ${error?.message || error}`);
3676
3739
  try {
3677
- while (Date.now() - startTime < durationMs) {
3678
- const action = Math.random();
3679
- if (action < 0.4) {
3680
- const x = 100 + Math.random() * (viewportSize.width - 200);
3681
- const y = 100 + Math.random() * (viewportSize.height - 200);
3682
- await cursor.actions.move({ x, y });
3683
- await (0, import_delay2.default)(this.jitterMs(350, 0.4));
3684
- } else if (action < 0.7) {
3685
- const scrollY = (Math.random() - 0.5) * 200;
3686
- await page.mouse.wheel(0, scrollY);
3687
- await (0, import_delay2.default)(this.jitterMs(500, 0.4));
3688
- } else {
3689
- await (0, import_delay2.default)(this.jitterMs(800, 0.5));
3690
- }
3691
- }
3692
- logger5.success("warmUpBrowsing");
3693
- } catch (error) {
3694
- logger5.fail("warmUpBrowsing", error);
3695
- throw error;
3740
+ await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
3741
+ await waitJitter(120, 0.35);
3742
+ return true;
3743
+ } catch {
3744
+ await page.mouse.wheel(0, deltaY);
3745
+ await waitJitter(120, 0.35);
3746
+ return true;
3696
3747
  }
3697
- },
3698
- /**
3699
- * 自然滚动 - 带惯性、减速效果和随机抖动
3700
- * @param {import('playwright').Page} page
3701
- * @param {'up' | 'down'} [direction='down'] - 滚动方向
3702
- * @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
3703
- * @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
3704
- */
3705
- async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
3706
- const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
3707
- const actualDistance = this.jitterMs(distance, 0.15);
3708
- logger5.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
3709
- const sign = direction === "down" ? 1 : -1;
3710
- const stepDistance = actualDistance / steps;
3711
- try {
3712
- for (let i = 0; i < steps; i++) {
3713
- const factor = 1 - i / steps * 0.5;
3714
- const jitter = 0.9 + Math.random() * 0.2;
3715
- const scrollAmount = stepDistance * factor * sign * jitter;
3716
- await page.mouse.wheel(0, scrollAmount);
3717
- const baseDelay = 60 + i * 25;
3718
- await (0, import_delay2.default)(this.jitterMs(baseDelay, 0.3));
3748
+ } finally {
3749
+ if (client && typeof client.detach === "function") {
3750
+ try {
3751
+ await client.detach();
3752
+ } catch {
3719
3753
  }
3720
- logger5.success("naturalScroll");
3721
- } catch (error) {
3722
- logger5.fail("naturalScroll", error);
3723
- throw error;
3724
3754
  }
3725
3755
  }
3726
3756
  };
3727
-
3728
- // src/internals/humanize/shared.js
3729
- var import_delay3 = __toESM(require("delay"), 1);
3730
- var jitterMs = (base, jitterPercent = 0.3) => {
3731
- const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
3732
- return Math.max(10, Math.round(Number(base || 0) + jitter));
3733
- };
3734
- var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
3735
- if (target == null) return null;
3736
- let element = target;
3737
- if (typeof target === "string") {
3738
- element = await page.$(target);
3739
- }
3740
- if (!element) {
3741
- if (throwOnMissing) {
3742
- throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${String(target)}`);
3757
+ var tapPoint = async (page, point, options = {}) => {
3758
+ const {
3759
+ timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
3760
+ mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
3761
+ allowMouseFallback = true
3762
+ } = options;
3763
+ if (page.touchscreen && typeof page.touchscreen.tap === "function") {
3764
+ try {
3765
+ await withTimeout(
3766
+ () => page.touchscreen.tap(point.x, point.y),
3767
+ timeoutMs,
3768
+ "touchscreen.tap"
3769
+ );
3770
+ return { method: "touchscreen" };
3771
+ } catch (error) {
3772
+ logger5.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
3773
+ if (!allowMouseFallback) throw error;
3743
3774
  }
3744
- return null;
3745
- }
3746
- return element;
3747
- };
3748
- var waitJitter = (base, jitterPercent = 0.3) => (0, import_delay3.default)(jitterMs(base, jitterPercent));
3749
-
3750
- // src/internals/humanize/mobile.js
3751
- var logger6 = createInternalLogger("Humanize.Mobile");
3752
- var initializedPages = /* @__PURE__ */ new WeakSet();
3753
- var DEFAULT_TAP_TIMEOUT_MS = 2500;
3754
- var DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS = 1200;
3755
- var DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS = 900;
3756
- var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
3757
- var resolveViewport = (page) => page?.viewportSize?.() || { width: 390, height: 844 };
3758
- var describeTarget = (target) => {
3759
- if (target == null) return "Current Position";
3760
- return typeof target === "string" ? target : "ElementHandle";
3761
- };
3762
- var clipBoxToViewport = (box, viewport) => {
3763
- if (!box || !viewport) return box;
3764
- const left = clamp(box.x, 0, viewport.width);
3765
- const top = clamp(box.y, 0, viewport.height);
3766
- const right = clamp(box.x + box.width, 0, viewport.width);
3767
- const bottom = clamp(box.y + box.height, 0, viewport.height);
3768
- if (right - left > 1 && bottom - top > 1) {
3769
- return {
3770
- x: left,
3771
- y: top,
3772
- width: right - left,
3773
- height: bottom - top
3774
- };
3775
- }
3776
- return box;
3777
- };
3778
- var centerPointInBox = (box) => ({
3779
- x: box.x + box.width / 2,
3780
- y: box.y + box.height / 2
3781
- });
3782
- var withTimeout = async (operation, timeoutMs, label) => {
3783
- const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
3784
- let timeoutId = null;
3785
- try {
3786
- return await Promise.race([
3787
- Promise.resolve().then(operation),
3788
- new Promise((_, reject) => {
3789
- timeoutId = setTimeout(() => {
3790
- reject(new Error(`${label} timeout after ${safeTimeoutMs}ms`));
3791
- }, safeTimeoutMs);
3792
- })
3793
- ]);
3794
- } finally {
3795
- if (timeoutId) clearTimeout(timeoutId);
3796
3775
  }
3776
+ await withTimeout(
3777
+ () => page.mouse.click(point.x, point.y),
3778
+ mouseFallbackTimeoutMs,
3779
+ "mouse.click fallback"
3780
+ );
3781
+ return { method: "mouse" };
3797
3782
  };
3798
- var checkElementVisibility = async (element) => {
3799
- return element.evaluate((el) => {
3800
- const targetStyle = window.getComputedStyle(el);
3801
- if (!targetStyle || targetStyle.display === "none" || targetStyle.visibility === "hidden" || targetStyle.visibility === "collapse") {
3802
- return { code: "NOT_INTERACTABLE", reason: "\u5143\u7D20\u4E0D\u53EF\u89C1", direction: "down" };
3783
+ var MobileHumanize = {
3784
+ jitterMs,
3785
+ async initializeCursor(page) {
3786
+ if (initializedPages.has(page)) return;
3787
+ initializedPages.add(page);
3788
+ logger5.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
3789
+ },
3790
+ async humanMove(page, target) {
3791
+ logger5.debug(`humanMove: mobile no-op target=${typeof target === "string" ? target : "element/coords"}`);
3792
+ if (typeof target === "string" || target && typeof target.boundingBox === "function") {
3793
+ const element = await resolveElement(page, target, { throwOnMissing: false });
3794
+ if (!element) {
3795
+ return false;
3796
+ }
3803
3797
  }
3804
- const rect = el.getBoundingClientRect();
3805
- if (!rect || rect.width <= 0 || rect.height <= 0) {
3806
- return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6", direction: "down" };
3798
+ await waitJitter(80, 0.4);
3799
+ return true;
3800
+ },
3801
+ async humanScroll(page, target, options = {}) {
3802
+ const {
3803
+ maxSteps = 20,
3804
+ minStep = 180,
3805
+ maxStep = 520,
3806
+ maxDurationMs = maxSteps * 280 + 1200,
3807
+ throwOnMissing = false
3808
+ } = options;
3809
+ const targetDesc = describeTarget(target);
3810
+ logger5.start("humanScroll", `target=${targetDesc}`);
3811
+ const element = await resolveElement(page, target, { throwOnMissing });
3812
+ if (!element) {
3813
+ logger5.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
3814
+ return { element: null, didScroll: false, restore: null };
3807
3815
  }
3808
- const viewW = window.innerWidth;
3809
- const viewH = window.innerHeight;
3810
- const centerY = rect.top + rect.height / 2;
3811
- let clipLeft = 0;
3812
- let clipRight = viewW;
3813
- let clipTop = 0;
3814
- let clipBottom = viewH;
3815
- let isFixed = false;
3816
- let positioning = null;
3817
- for (let node = el; node && node !== document.body; node = node.parentElement) {
3818
- const style = window.getComputedStyle(node);
3819
- if (style && (style.position === "fixed" || style.position === "sticky")) {
3820
- isFixed = true;
3821
- positioning = style.position;
3822
- break;
3816
+ const startTime = Date.now();
3817
+ let didScroll = false;
3818
+ for (let i = 0; i < maxSteps; i += 1) {
3819
+ const status = await checkElementVisibility(element);
3820
+ if (status.code === "VISIBLE") {
3821
+ if (status.isFixed) {
3822
+ logger5.info("humanScroll | fixed/sticky \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
3823
+ } else {
3824
+ logger5.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
3825
+ }
3826
+ logger5.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3827
+ return { element, didScroll, restore: null };
3828
+ }
3829
+ if (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE") {
3830
+ logger5.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
3831
+ return { element, didScroll, restore: null };
3832
+ }
3833
+ const scrollRect = await getScrollableRect(element);
3834
+ if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
3835
+ logger5.warn(`humanScroll | fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (direction=${status.direction || "unknown"})`);
3836
+ return { element, didScroll, restore: null, unscrollable: true };
3837
+ }
3838
+ if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
3839
+ logger5.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
3840
+ return { element, didScroll, restore: null, unscrollable: true };
3841
+ }
3842
+ if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
3843
+ const moved = await scrollAwayFromObstruction(element, status);
3844
+ if (moved.moved) {
3845
+ logger5.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
3846
+ await waitJitter(90, 0.3);
3847
+ didScroll = true;
3848
+ continue;
3849
+ }
3823
3850
  }
3824
- }
3825
- for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
3826
- const style = window.getComputedStyle(node);
3827
- if (!style) continue;
3828
- const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
3829
- const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
3830
- if (!clipsX && !clipsY) continue;
3831
- const nodeRect = node.getBoundingClientRect();
3832
- if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
3833
- if (clipsX) {
3834
- clipLeft = Math.max(clipLeft, nodeRect.left);
3835
- clipRight = Math.min(clipRight, nodeRect.right);
3851
+ if (Date.now() - startTime > maxDurationMs) {
3852
+ logger5.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
3853
+ return { element, didScroll, restore: null };
3836
3854
  }
3837
- if (clipsY) {
3838
- clipTop = Math.max(clipTop, nodeRect.top);
3839
- clipBottom = Math.min(clipBottom, nodeRect.bottom);
3855
+ const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
3856
+ const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
3857
+ logger5.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
3858
+ const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
3859
+ let deltaY = status.direction === "up" ? -distance : distance;
3860
+ if (status.code === "OBSTRUCTED") {
3861
+ if (status.obstruction?.isFixed && status.obstruction.top != null) {
3862
+ const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
3863
+ const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3864
+ deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
3865
+ } else {
3866
+ const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
3867
+ deltaY = status.cy > halfY ? distance : -distance;
3868
+ }
3840
3869
  }
3841
- }
3842
- const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
3843
- const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
3844
- const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
3845
- const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
3846
- const visibleWidth = visibleRight - visibleLeft;
3847
- const visibleHeight = visibleBottom - visibleTop;
3848
- const cx = visibleLeft + visibleWidth / 2;
3849
- const cy = visibleTop + visibleHeight / 2;
3850
- if (visibleWidth <= 1 || visibleHeight <= 1) {
3851
- return {
3852
- code: "OUT_OF_VIEWPORT",
3853
- reason: "\u4E0D\u5728\u89C6\u53E3\u5185",
3854
- direction: centerY < clipTop ? "up" : "down",
3855
- cy: centerY,
3856
- viewH,
3857
- isFixed,
3858
- positioning
3870
+ const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
3871
+ const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3872
+ const beforeState = scrollRect ? await element.evaluate((el) => {
3873
+ const isScrollable = (node) => {
3874
+ const style = window.getComputedStyle(node);
3875
+ if (!style) return false;
3876
+ const overflowY = style.overflowY;
3877
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3878
+ return node.scrollHeight > node.clientHeight + 1;
3879
+ };
3880
+ let current = el;
3881
+ while (current && current !== document.body) {
3882
+ if (isScrollable(current)) {
3883
+ return {
3884
+ kind: "element",
3885
+ top: current.scrollTop,
3886
+ left: current.scrollLeft
3887
+ };
3888
+ }
3889
+ current = current.parentElement;
3890
+ }
3891
+ return { kind: "window", top: window.scrollY, left: window.scrollX };
3892
+ }) : null;
3893
+ await dispatchTouchSwipe(page, deltaY, { rect: scrollRect });
3894
+ if (scrollRect && beforeWindowState) {
3895
+ const afterWindowState = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
3896
+ if (Math.abs(afterWindowState.x - beforeWindowState.x) > 2 || Math.abs(afterWindowState.y - beforeWindowState.y) > 2) {
3897
+ await page.evaluate((state2) => window.scrollTo(state2.x, state2.y), beforeWindowState);
3898
+ logger5.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
3899
+ }
3900
+ }
3901
+ let afterElementSnapshot = null;
3902
+ const readAfterElementSnapshot = async () => {
3903
+ if (!afterElementSnapshot) {
3904
+ afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3905
+ }
3906
+ return afterElementSnapshot;
3859
3907
  };
3908
+ if (!scrollRect && beforeElementSnapshot) {
3909
+ const afterSnapshot = await readAfterElementSnapshot();
3910
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
3911
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
3912
+ logger5.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u9875\u9762\u6EDA\u52A8\u79FB\u52A8\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
3913
+ return { element, didScroll, restore: null, unscrollable: true };
3914
+ }
3915
+ }
3916
+ if (scrollRect && beforeState) {
3917
+ const afterState = await element.evaluate((el) => {
3918
+ const isScrollable = (node) => {
3919
+ const style = window.getComputedStyle(node);
3920
+ if (!style) return false;
3921
+ const overflowY = style.overflowY;
3922
+ if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3923
+ return node.scrollHeight > node.clientHeight + 1;
3924
+ };
3925
+ let current = el;
3926
+ while (current && current !== document.body) {
3927
+ if (isScrollable(current)) {
3928
+ return {
3929
+ kind: "element",
3930
+ top: current.scrollTop,
3931
+ left: current.scrollLeft
3932
+ };
3933
+ }
3934
+ current = current.parentElement;
3935
+ }
3936
+ return { kind: "window", top: window.scrollY, left: window.scrollX };
3937
+ });
3938
+ const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
3939
+ const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
3940
+ const expectedDelta = Number(deltaY || 0);
3941
+ const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
3942
+ if (!moved) {
3943
+ const fallback = await scrollScrollableAncestor(element, deltaY);
3944
+ logger5.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u65E0\u6548\uFF0C\u76F4\u63A5\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
3945
+ } else if (beforeState.kind === afterState.kind && Math.abs(expectedDelta) > 24 && Math.sign(topDelta || expectedDelta) === Math.sign(expectedDelta) && Math.abs(topDelta) < Math.min(Math.abs(expectedDelta) * 0.45, 96)) {
3946
+ const residualDelta = expectedDelta - topDelta;
3947
+ if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
3948
+ const fallback = await scrollScrollableAncestor(element, residualDelta);
3949
+ logger5.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u8DDD\u79BB\u4E0D\u8DB3\uFF0C\u8865\u507F\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
3950
+ }
3951
+ }
3952
+ }
3953
+ if (scrollRect && beforeElementSnapshot) {
3954
+ const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
3955
+ if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
3956
+ await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
3957
+ logger5.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u6EDA\u52A8\u5BB9\u5668\u79FB\u52A8\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
3958
+ return { element, didScroll, restore: null, unscrollable: true };
3959
+ }
3960
+ }
3961
+ didScroll = true;
3860
3962
  }
3861
- const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
3862
- const commonAncestor = (a, b) => {
3863
- for (let current = a; current && !isRootNode(current); current = current.parentElement) {
3864
- if (current.contains(b)) return current;
3963
+ try {
3964
+ await element.scrollIntoViewIfNeeded?.();
3965
+ await waitJitter(80, 0.3);
3966
+ const finalStatus = await checkElementVisibility(element);
3967
+ if (finalStatus.code === "VISIBLE") {
3968
+ logger5.info("humanScroll | \u539F\u751F scrollIntoViewIfNeeded \u515C\u5E95\u6210\u529F");
3969
+ logger5.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
3970
+ return { element, didScroll: true, restore: null };
3865
3971
  }
3866
- return null;
3867
- };
3868
- const sameTapTarget = (pointElement) => {
3869
- if (!pointElement) return false;
3870
- if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
3972
+ } catch (fallbackError) {
3973
+ logger5.debug(`humanScroll | native fallback failed: ${fallbackError?.message || fallbackError}`);
3974
+ }
3975
+ logger5.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
3976
+ return { element, didScroll, restore: null };
3977
+ },
3978
+ async humanClick(page, target, options = {}) {
3979
+ const {
3980
+ reactionDelay = 220,
3981
+ throwOnMissing = true,
3982
+ scrollIfNeeded = true,
3983
+ tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
3984
+ mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
3985
+ activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
3986
+ fallbackDomClick = true,
3987
+ fallbackDomClickOnTapError = true
3988
+ } = options;
3989
+ const targetDesc = describeTarget(target);
3990
+ logger5.start("humanClick", `target=${targetDesc}`);
3991
+ try {
3992
+ if (target == null) {
3993
+ const viewport = resolveViewport(page);
3994
+ await waitJitter(reactionDelay, 0.45);
3995
+ await tapPoint(page, {
3996
+ x: viewport.width * (0.45 + Math.random() * 0.1),
3997
+ y: viewport.height * (0.48 + Math.random() * 0.12)
3998
+ }, {
3999
+ timeoutMs: tapTimeoutMs,
4000
+ mouseFallbackTimeoutMs
4001
+ });
4002
+ logger5.success("humanClick", "Tapped current position");
3871
4003
  return true;
3872
4004
  }
3873
- const common = commonAncestor(el, pointElement);
3874
- if (!common) return false;
3875
- const commonRect = common.getBoundingClientRect?.();
3876
- if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
3877
- const commonArea = commonRect.width * commonRect.height;
3878
- const targetArea = Math.max(1, rect.width * rect.height);
3879
- const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
3880
- if (commonArea > maxSharedRegionArea) return false;
3881
- if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
4005
+ const element = await resolveElement(page, target, { throwOnMissing });
4006
+ if (!element) {
4007
+ logger5.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
3882
4008
  return false;
3883
4009
  }
3884
- return common.contains(el) && common.contains(pointElement);
3885
- };
3886
- const describeElement = (node) => {
3887
- if (!node) return null;
3888
- const className = typeof node.className === "string" ? node.className : node.className && typeof node.className.baseVal === "string" ? node.className.baseVal : "";
3889
- const rect2 = node.getBoundingClientRect?.();
3890
- const style = window.getComputedStyle(node);
3891
- return {
3892
- tag: node.tagName,
3893
- id: node.id || "",
3894
- className,
3895
- isFixed: Boolean(style && (style.position === "fixed" || style.position === "sticky")),
3896
- positioning: style?.position || "",
3897
- top: rect2 ? rect2.top : null,
3898
- bottom: rect2 ? rect2.bottom : null,
3899
- left: rect2 ? rect2.left : null,
3900
- right: rect2 ? rect2.right : null
3901
- };
3902
- };
3903
- const samplePoints = [
3904
- { x: cx, y: cy },
3905
- { x: visibleLeft + Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3906
- { x: visibleRight - Math.min(8, Math.max(1, visibleWidth * 0.25)), y: cy },
3907
- { x: cx, y: visibleTop + Math.min(8, Math.max(1, visibleHeight * 0.25)) },
3908
- { x: cx, y: visibleBottom - Math.min(8, Math.max(1, visibleHeight * 0.25)) }
3909
- ];
3910
- let obstruction = null;
3911
- for (const point of samplePoints) {
3912
- const pointElement = document.elementFromPoint(point.x, point.y);
3913
- if (sameTapTarget(pointElement)) {
3914
- return { code: "VISIBLE", isFixed, positioning };
3915
- }
3916
- obstruction = obstruction || describeElement(pointElement);
3917
- }
3918
- if (obstruction) {
3919
- return {
3920
- code: "OBSTRUCTED",
3921
- reason: "\u88AB\u906E\u6321",
3922
- direction: cy > viewH / 2 ? "down" : "up",
3923
- obstruction,
3924
- cy,
3925
- viewH,
3926
- isFixed,
3927
- positioning
3928
- };
3929
- }
3930
- return { code: "VISIBLE", isFixed, positioning };
3931
- });
3932
- };
3933
- var activateElementFallback = async (element, point = null, options = {}) => {
3934
- return element.evaluate((el, { innerOptions }) => {
3935
- const isEditable = (node) => {
3936
- if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
3937
- if (node.isContentEditable) return true;
3938
- if (node instanceof HTMLTextAreaElement) return !node.disabled && !node.readOnly;
3939
- if (node instanceof HTMLInputElement) {
3940
- return !node.disabled && !node.readOnly && typeof node.select === "function";
4010
+ const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
4011
+ const status = await checkElementVisibility(element).catch(() => null);
4012
+ if (status && status.code !== "VISIBLE") {
4013
+ if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
4014
+ let fallback = await withTimeout(
4015
+ () => activateElementFallback(element, null, {
4016
+ editableOnly: true
4017
+ }),
4018
+ activateFallbackTimeoutMs,
4019
+ "focus fallback"
4020
+ ).catch(() => null);
4021
+ if (!fallback?.activated) {
4022
+ fallback = await withTimeout(
4023
+ () => activateElementFallback(element, null, {
4024
+ editableOnly: false
4025
+ }),
4026
+ activateFallbackTimeoutMs,
4027
+ "activation fallback"
4028
+ ).catch(() => null);
4029
+ }
4030
+ if (fallback?.activated) {
4031
+ logger5.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4032
+ return true;
4033
+ }
4034
+ }
4035
+ const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
4036
+ if (throwOnMissing) throw new Error(message);
4037
+ logger5.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
4038
+ return false;
3941
4039
  }
3942
- return false;
3943
- };
3944
- const findEditable = (node) => {
3945
- for (let current = node; current && current !== document.body; current = current.parentElement) {
3946
- if (isEditable(current)) return current;
4040
+ const box = await element.boundingBox();
4041
+ if (!box) {
4042
+ if (throwOnMissing) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4043
+ logger5.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
4044
+ return false;
3947
4045
  }
3948
- return null;
3949
- };
3950
- const editable = findEditable(el);
3951
- if (editable && typeof editable.focus === "function") {
3952
- editable.focus({ preventScroll: true });
3953
- if (innerOptions.editableOnly) {
3954
- return { activated: true, method: "focus", tag: editable.tagName || "" };
4046
+ await waitJitter(reactionDelay, 0.45);
4047
+ const visibleBox = clipBoxToViewport(box, resolveViewport(page));
4048
+ const tapTarget = centerPointInBox(visibleBox);
4049
+ try {
4050
+ await tapPoint(page, tapTarget, {
4051
+ timeoutMs: tapTimeoutMs,
4052
+ mouseFallbackTimeoutMs
4053
+ });
4054
+ } catch (tapError) {
4055
+ if (!fallbackDomClickOnTapError) throw tapError;
4056
+ const fallback = await withTimeout(
4057
+ () => activateElementFallback(element, tapTarget, {
4058
+ editableOnly: false
4059
+ }),
4060
+ activateFallbackTimeoutMs,
4061
+ "activation fallback"
4062
+ ).catch(() => null);
4063
+ if (!fallback?.activated) throw tapError;
4064
+ logger5.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
3955
4065
  }
4066
+ await waitJitter(120, 0.35);
4067
+ logger5.success("humanClick");
4068
+ return true;
4069
+ } catch (error) {
4070
+ logger5.fail("humanClick", error);
4071
+ throw error;
3956
4072
  }
3957
- if (innerOptions.editableOnly) {
3958
- return { activated: false, method: "none", tag: el?.tagName || "" };
3959
- }
3960
- if (typeof el.focus === "function") {
3961
- el.focus({ preventScroll: true });
3962
- }
3963
- if (typeof el.click === "function") {
3964
- el.click();
3965
- return { activated: true, method: "dom-click", tag: el.tagName || "" };
3966
- }
3967
- if (typeof el.dispatchEvent === "function") {
3968
- el.dispatchEvent(new MouseEvent("click", {
3969
- bubbles: true,
3970
- cancelable: true,
3971
- view: window
3972
- }));
3973
- return { activated: true, method: "dispatch-click", tag: el.tagName || "" };
4073
+ },
4074
+ async randomSleep(baseMs, jitterPercent = 0.3) {
4075
+ await waitJitter(baseMs, jitterPercent);
4076
+ },
4077
+ async simulateGaze(page, baseDurationMs = 2500) {
4078
+ const durationMs = jitterMs(baseDurationMs, 0.4);
4079
+ const startTime = Date.now();
4080
+ while (Date.now() - startTime < durationMs) {
4081
+ if (Math.random() < 0.28) {
4082
+ const distance = 70 + Math.random() * 120;
4083
+ await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
4084
+ durationMs: 180,
4085
+ steps: 4
4086
+ });
4087
+ } else {
4088
+ await waitJitter(420, 0.55);
4089
+ }
3974
4090
  }
3975
- return {
3976
- activated: Boolean(editable),
3977
- method: editable ? "focus" : "none",
3978
- tag: el?.tagName || ""
3979
- };
3980
- }, {
3981
- innerOptions: options || {}
3982
- });
3983
- };
3984
- var getScrollableRect = async (element) => {
3985
- return element.evaluate((el) => {
3986
- const isScrollable = (node) => {
3987
- const style = window.getComputedStyle(node);
3988
- if (!style) return false;
3989
- const overflowY = style.overflowY;
3990
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
3991
- return node.scrollHeight > node.clientHeight + 1;
3992
- };
3993
- let current = el;
3994
- while (current && current !== document.body) {
3995
- if (isScrollable(current)) {
3996
- const rect = current.getBoundingClientRect();
3997
- if (rect && rect.width > 0 && rect.height > 0) {
3998
- return {
3999
- x: rect.x,
4000
- y: rect.y,
4001
- width: rect.width,
4002
- height: rect.height
4003
- };
4004
- }
4091
+ },
4092
+ async humanType(page, selector, text, options = {}) {
4093
+ const {
4094
+ baseDelay = 160,
4095
+ pauseProbability = 0.08,
4096
+ pauseBase = 700
4097
+ } = options;
4098
+ await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
4099
+ await waitJitter(220, 0.45);
4100
+ for (let i = 0; i < String(text).length; i += 1) {
4101
+ const char = String(text)[i];
4102
+ const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
4103
+ await page.keyboard.type(char);
4104
+ await waitJitter(charDelay, 0.15);
4105
+ if (Math.random() < pauseProbability && i < String(text).length - 1) {
4106
+ await waitJitter(pauseBase, 0.5);
4005
4107
  }
4006
- current = current.parentElement;
4007
4108
  }
4008
- return null;
4009
- });
4010
- };
4011
- var scrollScrollableAncestor = async (element, deltaY) => {
4012
- return element.evaluate((el, amount) => {
4013
- const isScrollable = (node) => {
4014
- const style = window.getComputedStyle(node);
4015
- if (!style) return false;
4016
- const overflowY = style.overflowY;
4017
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
4018
- return node.scrollHeight > node.clientHeight + 1;
4019
- };
4020
- let current = el;
4021
- while (current && current !== document.body) {
4022
- if (isScrollable(current)) {
4023
- const beforeTop2 = current.scrollTop;
4024
- current.scrollTop = beforeTop2 + amount;
4025
- return {
4026
- scroller: true,
4027
- moved: current.scrollTop !== beforeTop2,
4028
- scrollTop: current.scrollTop
4029
- };
4109
+ },
4110
+ async humanPress(page, targetOrKey, maybeKey, options = {}) {
4111
+ const hasTarget = typeof maybeKey === "string";
4112
+ const key = hasTarget ? maybeKey : targetOrKey;
4113
+ const pressOptions = hasTarget ? options : maybeKey || options;
4114
+ const {
4115
+ reactionDelay = 170,
4116
+ holdDelay = 42,
4117
+ focusDelay = 180,
4118
+ scrollIfNeeded = true,
4119
+ throwOnMissing = true,
4120
+ keyboardOptions = {}
4121
+ } = pressOptions || {};
4122
+ const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
4123
+ logger5.start("humanPress", `key=${key}, target=${targetDesc}`);
4124
+ try {
4125
+ if (hasTarget) {
4126
+ await MobileHumanize.humanClick(page, targetOrKey, {
4127
+ reactionDelay: focusDelay,
4128
+ scrollIfNeeded,
4129
+ throwOnMissing
4130
+ });
4030
4131
  }
4031
- current = current.parentElement;
4132
+ await waitJitter(reactionDelay, 0.45);
4133
+ await page.keyboard.press(key, {
4134
+ ...keyboardOptions,
4135
+ delay: jitterMs(holdDelay, 0.5)
4136
+ });
4137
+ logger5.success("humanPress");
4138
+ return true;
4139
+ } catch (error) {
4140
+ logger5.fail("humanPress", error);
4141
+ throw error;
4032
4142
  }
4033
- const beforeTop = window.scrollY;
4034
- window.scrollBy(0, amount);
4035
- return {
4036
- scroller: null,
4037
- moved: window.scrollY !== beforeTop,
4038
- scrollTop: window.scrollY
4039
- };
4040
- }, deltaY);
4041
- };
4042
- var scrollAwayFromObstruction = async (element, status) => {
4043
- return element.evaluate((el, innerStatus) => {
4044
- const isScrollable = (node) => {
4045
- const style = window.getComputedStyle(node);
4046
- if (!style) return false;
4047
- const overflowY = style.overflowY;
4048
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
4049
- return node.scrollHeight > node.clientHeight + 1;
4143
+ },
4144
+ async humanClear(page, selector) {
4145
+ const locator = page.locator(selector);
4146
+ await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4147
+ await waitJitter(160, 0.4);
4148
+ const readValue = async () => {
4149
+ try {
4150
+ return await locator.inputValue({ timeout: 600 });
4151
+ } catch {
4152
+ return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4153
+ }
4050
4154
  };
4051
- let scroller = el;
4052
- while (scroller && scroller !== document.body) {
4053
- if (isScrollable(scroller)) break;
4054
- scroller = scroller.parentElement;
4055
- }
4056
- if (!scroller || scroller === document.body) {
4057
- return { moved: false, scrollTop: 0, deltaY: 0 };
4155
+ const currentValue = await readValue();
4156
+ if (!currentValue) return;
4157
+ await page.keyboard.press("ControlOrMeta+A");
4158
+ await waitJitter(90, 0.35);
4159
+ await page.keyboard.press("Backspace");
4160
+ await waitJitter(120, 0.35);
4161
+ if (!await readValue()) return;
4162
+ await locator.evaluate((el) => {
4163
+ if ("value" in el) {
4164
+ el.value = "";
4165
+ el.dispatchEvent(new Event("input", { bubbles: true }));
4166
+ el.dispatchEvent(new Event("change", { bubbles: true }));
4167
+ return;
4168
+ }
4169
+ el.textContent = "";
4170
+ el.dispatchEvent(new Event("input", { bubbles: true }));
4171
+ });
4172
+ },
4173
+ async warmUpBrowsing(page, baseDuration = 3500) {
4174
+ const durationMs = jitterMs(baseDuration, 0.4);
4175
+ const startTime = Date.now();
4176
+ while (Date.now() - startTime < durationMs) {
4177
+ const action = Math.random();
4178
+ if (action < 0.5) {
4179
+ await dispatchTouchSwipe(page, 120 + Math.random() * 220, {
4180
+ durationMs: 240,
4181
+ steps: 5
4182
+ });
4183
+ } else if (action < 0.7) {
4184
+ await dispatchTouchSwipe(page, -(80 + Math.random() * 160), {
4185
+ durationMs: 220,
4186
+ steps: 4
4187
+ });
4188
+ } else {
4189
+ await waitJitter(560, 0.55);
4190
+ }
4058
4191
  }
4059
- const rect = el.getBoundingClientRect();
4060
- const scrollerRect = scroller.getBoundingClientRect();
4061
- const obstruction = innerStatus?.obstruction || {};
4062
- const obstructionTop = Number(obstruction.top);
4063
- const obstructionBottom = Number(obstruction.bottom);
4064
- const obstructionMiddle = Number.isFinite(obstructionTop) && Number.isFinite(obstructionBottom) ? (obstructionTop + obstructionBottom) / 2 : scrollerRect.top + scrollerRect.height / 2;
4065
- const scrollerMiddle = scrollerRect.top + scrollerRect.height / 2;
4066
- const padding = 18;
4067
- let deltaY = 0;
4068
- if (Number.isFinite(obstructionTop) && rect.bottom > obstructionTop && obstructionTop >= scrollerRect.top && obstructionTop <= scrollerRect.bottom && obstructionMiddle >= scrollerMiddle) {
4069
- deltaY = rect.bottom - obstructionTop + padding;
4070
- } else if (Number.isFinite(obstructionBottom) && rect.top < obstructionBottom && obstructionBottom >= scrollerRect.top && obstructionBottom <= scrollerRect.bottom && obstructionMiddle < scrollerMiddle) {
4071
- deltaY = rect.top - obstructionBottom - padding;
4072
- } else {
4073
- const fallbackDistance = Math.max(48, Math.min(180, scrollerRect.height * 0.45));
4074
- deltaY = obstructionMiddle >= scrollerMiddle ? fallbackDistance : -fallbackDistance;
4192
+ },
4193
+ async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4194
+ const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
4195
+ const actualDistance = jitterMs(distance, 0.15);
4196
+ const sign = direction === "down" ? 1 : -1;
4197
+ for (let i = 0; i < steps; i += 1) {
4198
+ const factor = 1 - i / steps * 0.45;
4199
+ await dispatchTouchSwipe(page, actualDistance / steps * factor * sign, {
4200
+ durationMs: 150 + i * 20,
4201
+ steps: 4
4202
+ });
4075
4203
  }
4076
- const beforeTop = scroller.scrollTop;
4077
- scroller.scrollTop = beforeTop + deltaY;
4078
- return {
4079
- moved: Math.abs(scroller.scrollTop - beforeTop) > 2,
4080
- scrollTop: scroller.scrollTop,
4081
- deltaY
4082
- };
4083
- }, status);
4084
- };
4085
- var getElementViewportSnapshot = async (element) => {
4086
- return element.evaluate((el) => {
4087
- const rect = el.getBoundingClientRect();
4088
- return {
4089
- top: rect.top,
4090
- bottom: rect.bottom,
4091
- left: rect.left,
4092
- right: rect.right,
4093
- width: rect.width,
4094
- height: rect.height,
4095
- scrollX: window.scrollX,
4096
- scrollY: window.scrollY
4097
- };
4098
- });
4099
- };
4100
- var isTargetImmobileAfterScroll = (before, after) => {
4101
- if (!before || !after) return false;
4102
- const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
4103
- const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
4104
- const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
4105
- const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
4106
- const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
4107
- const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
4108
- if (!rectMoved && !pageMoved) return true;
4109
- if (pageMoved && !rectMoved) return true;
4110
- if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
4111
- return true;
4112
4204
  }
4113
- return false;
4114
4205
  };
4115
- var restoreWindowFromSnapshot = async (page, before, after) => {
4116
- if (!before || !after) return;
4117
- if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
4118
- return;
4206
+
4207
+ // src/internals/humanize/cloakbrowser.js
4208
+ var FORCE_CLICK_OPTIONS = Object.freeze({ force: true });
4209
+ var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4210
+ var isPoint2 = (value) => value && typeof value === "object" && Number.isFinite(Number(value.x)) && Number.isFinite(Number(value.y));
4211
+ var resolveTarget = (page, target) => typeof target === "string" ? page.locator(target).first() : target;
4212
+ var resolveTargetPoint = async (page, target) => {
4213
+ if (typeof target === "string") {
4214
+ return await DeviceInput.selectorCenterPoint(page, target).catch(() => null);
4119
4215
  }
4120
- await page.evaluate(
4121
- (state2) => window.scrollTo(state2.x, state2.y),
4122
- { x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
4123
- ).catch(() => {
4124
- });
4125
- };
4126
- var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
4127
- const viewport = resolveViewport(page);
4128
- const rawRect = options.rect || null;
4129
- const rect = rawRect ? {
4130
- x: clamp(rawRect.x, 0, viewport.width),
4131
- y: clamp(rawRect.y, 0, viewport.height),
4132
- width: clamp(rawRect.width, 0, viewport.width),
4133
- height: clamp(rawRect.height, 0, viewport.height)
4134
- } : null;
4135
- const area = rect && rect.width > 24 && rect.height > 48 ? {
4136
- left: rect.x,
4137
- right: Math.min(viewport.width, rect.x + rect.width),
4138
- top: rect.y,
4139
- bottom: Math.min(viewport.height, rect.y + rect.height)
4140
- } : {
4141
- left: 0,
4142
- right: viewport.width,
4143
- top: 0,
4144
- bottom: viewport.height
4216
+ const box = await target?.boundingBox?.().catch(() => null);
4217
+ if (!box) {
4218
+ return null;
4219
+ }
4220
+ return {
4221
+ x: box.x + box.width / 2,
4222
+ y: box.y + box.height / 2
4145
4223
  };
4146
- const areaWidth = Math.max(1, area.right - area.left);
4147
- const areaHeight = Math.max(1, area.bottom - area.top);
4148
- const maxDistance = rect ? Math.max(60, areaHeight * 0.72) : Math.max(120, viewport.height * 0.72);
4149
- const distance = clamp(Math.abs(deltaY), Math.min(80, maxDistance), maxDistance);
4150
- const direction = deltaY >= 0 ? 1 : -1;
4151
- const startX = clamp(
4152
- area.left + areaWidth * (0.45 + Math.random() * 0.1),
4153
- 24,
4154
- viewport.width - 24
4155
- );
4156
- 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);
4157
- const endY = clamp(startY - direction * distance, Math.max(16, area.top + 12), Math.min(viewport.height - 16, area.bottom - 12));
4158
- const steps = Math.max(4, Math.round(Number(options.steps || 6)));
4159
- const durationMs = jitterMs(options.durationMs || 320, 0.35);
4160
- let client = null;
4161
- const scrollBefore = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4162
- try {
4163
- if (page.context && typeof page.context().newCDPSession === "function") {
4164
- client = await page.context().newCDPSession(page);
4224
+ };
4225
+ var sleep = (baseMs, jitterPercent = 0.3) => (0, import_delay3.default)(jitterMs(baseMs, jitterPercent));
4226
+ var buildKeyboardOptions = (options = {}) => ({
4227
+ ...options.holdDelay != null ? { delay: Math.max(0, Number(options.holdDelay) || 0) } : {},
4228
+ ...options.keyboardOptions || {}
4229
+ });
4230
+ var CloakBrowserDeviceHumanize = {
4231
+ jitterMs(base, jitterPercent = 0.3) {
4232
+ return jitterMs(base, jitterPercent);
4233
+ },
4234
+ async initializeCursor(page) {
4235
+ return Boolean(page);
4236
+ },
4237
+ async humanMove(page, target) {
4238
+ if (target == null) {
4239
+ return false;
4165
4240
  }
4166
- if (!client) throw new Error("CDP session unavailable");
4167
- await client.send("Input.synthesizeScrollGesture", {
4168
- x: startX,
4169
- y: startY,
4170
- yDistance: -direction * distance,
4171
- xOverscroll: (Math.random() - 0.5) * 4,
4172
- yOverscroll: (Math.random() - 0.5) * 8,
4173
- speed: 700 + Math.round(Math.random() * 350),
4174
- gestureSourceType: "touch"
4175
- });
4176
- await waitJitter(160, 0.35);
4177
- const scrollAfterSynth = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4178
- if (scrollBefore && scrollAfterSynth && (Math.abs(scrollAfterSynth.y - scrollBefore.y) > 2 || Math.abs(scrollAfterSynth.x - scrollBefore.x) > 2)) {
4241
+ if (isPoint2(target)) {
4242
+ await DeviceInput.move(page, target, void 0, { forceMouse: true });
4179
4243
  return true;
4180
4244
  }
4181
- await client.send("Input.dispatchTouchEvent", {
4182
- type: "touchStart",
4183
- touchPoints: [{ x: startX, y: startY, id: 1 }]
4245
+ const point = await resolveTargetPoint(page, target);
4246
+ if (!point) {
4247
+ return false;
4248
+ }
4249
+ await DeviceInput.move(page, point, void 0, { forceMouse: true });
4250
+ return true;
4251
+ },
4252
+ async humanScroll(page, target) {
4253
+ const element = resolveTarget(page, target);
4254
+ if (!element) {
4255
+ return { element: null, didScroll: false, restore: null };
4256
+ }
4257
+ await element.scrollIntoViewIfNeeded?.().catch(() => {
4184
4258
  });
4185
- for (let i = 1; i <= steps; i += 1) {
4186
- const progress = i / steps;
4187
- const eased = 1 - Math.pow(1 - progress, 2);
4188
- const x = startX + (Math.random() - 0.5) * 3;
4189
- const y = startY + (endY - startY) * eased + (Math.random() - 0.5) * 5;
4190
- await waitJitter(durationMs / steps, 0.25);
4191
- await client.send("Input.dispatchTouchEvent", {
4192
- type: "touchMove",
4193
- touchPoints: [{ x, y, id: 1 }]
4194
- });
4259
+ return { element, didScroll: true, restore: null };
4260
+ },
4261
+ async humanClick(page, target) {
4262
+ if (target == null) {
4263
+ await DeviceInput.clickPoint(page, { x: 0, y: 0 }, void 0, { forceMouse: true });
4264
+ return true;
4195
4265
  }
4196
- await client.send("Input.dispatchTouchEvent", {
4197
- type: "touchEnd",
4198
- touchPoints: []
4266
+ if (isPoint2(target)) {
4267
+ await DeviceInput.clickPoint(page, target, void 0, { forceMouse: true });
4268
+ return true;
4269
+ }
4270
+ await DeviceInput.click(page, target, {
4271
+ clickOptions: FORCE_CLICK_OPTIONS
4199
4272
  });
4200
- await waitJitter(120, 0.35);
4201
- const scrollAfterTouch = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })).catch(() => null);
4202
- if (scrollBefore && scrollAfterTouch && Math.abs(scrollAfterTouch.y - scrollBefore.y) <= 2 && Math.abs(scrollAfterTouch.x - scrollBefore.x) <= 2) {
4203
- await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
4204
- await waitJitter(120, 0.35);
4273
+ return true;
4274
+ },
4275
+ async randomSleep(baseMs, jitterPercent = 0.3) {
4276
+ await sleep(baseMs, jitterPercent);
4277
+ },
4278
+ async simulateGaze(page, baseDurationMs = 2500) {
4279
+ const durationMs = jitterMs(baseDurationMs, 0.4);
4280
+ const startedAt = Date.now();
4281
+ const viewport = page.viewportSize() || { width: 1365, height: 900 };
4282
+ while (Date.now() - startedAt < durationMs) {
4283
+ await DeviceInput.move(page, {
4284
+ x: 100 + Math.random() * Math.max(120, viewport.width - 200),
4285
+ y: 100 + Math.random() * Math.max(120, viewport.height - 200)
4286
+ }, void 0, { forceMouse: true });
4287
+ await sleep(300, 0.5);
4205
4288
  }
4289
+ },
4290
+ async humanType(page, target, text, options = {}) {
4291
+ await DeviceInput.click(page, target, {
4292
+ clickOptions: FORCE_CLICK_OPTIONS
4293
+ });
4294
+ await DeviceInput.keyboardType(page, text, {
4295
+ ...options.baseDelay != null ? { delay: Math.max(0, Number(options.baseDelay) || 0) } : {}
4296
+ });
4206
4297
  return true;
4207
- } catch (error) {
4208
- logger6.debug(`touch swipe fallback: ${error?.message || error}`);
4209
- try {
4210
- await page.evaluate((amount) => window.scrollBy(0, amount), deltaY);
4211
- await waitJitter(120, 0.35);
4212
- return true;
4213
- } catch {
4214
- await page.mouse.wheel(0, deltaY);
4215
- await waitJitter(120, 0.35);
4298
+ },
4299
+ async humanPress(page, targetOrKey, maybeKey, options = {}) {
4300
+ if (typeof maybeKey !== "string") {
4301
+ await DeviceInput.press(page, targetOrKey, {
4302
+ keyboardOptions: buildKeyboardOptions(maybeKey || options)
4303
+ });
4216
4304
  return true;
4217
4305
  }
4218
- } finally {
4219
- if (client && typeof client.detach === "function") {
4220
- try {
4221
- await client.detach();
4222
- } catch {
4223
- }
4224
- }
4225
- }
4226
- };
4227
- var tapPoint = async (page, point, options = {}) => {
4228
- const {
4229
- timeoutMs = DEFAULT_TAP_TIMEOUT_MS,
4230
- mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
4231
- allowMouseFallback = true
4232
- } = options;
4233
- if (page.touchscreen && typeof page.touchscreen.tap === "function") {
4234
- try {
4235
- await withTimeout(
4236
- () => page.touchscreen.tap(point.x, point.y),
4237
- timeoutMs,
4238
- "touchscreen.tap"
4239
- );
4240
- return { method: "touchscreen" };
4241
- } catch (error) {
4242
- logger6.warn(`tapPoint | touchscreen.tap \u5931\u8D25\u6216\u8D85\u65F6\uFF0C\u5C1D\u8BD5\u9F20\u6807\u515C\u5E95: ${error?.message || error}`);
4243
- if (!allowMouseFallback) throw error;
4306
+ await DeviceInput.press(page, targetOrKey, maybeKey, {
4307
+ clickOptions: FORCE_CLICK_OPTIONS,
4308
+ keyboardOptions: buildKeyboardOptions(options)
4309
+ });
4310
+ return true;
4311
+ },
4312
+ async humanClear(page, target) {
4313
+ await DeviceInput.fill(page, target, "", { force: true });
4314
+ },
4315
+ async warmUpBrowsing(page, baseDuration = 3500) {
4316
+ await this.simulateGaze(page, Math.min(baseDuration, 900));
4317
+ },
4318
+ async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4319
+ const steps = Math.max(1, Number(baseSteps) || 1);
4320
+ const delta = (Number(distance) || 0) / steps * (direction === "down" ? 1 : -1);
4321
+ for (let index = 0; index < steps; index += 1) {
4322
+ await page.mouse.wheel(0, delta);
4323
+ await sleep(60 + index * 20, 0.3);
4244
4324
  }
4245
4325
  }
4246
- await withTimeout(
4247
- () => page.mouse.click(point.x, point.y),
4248
- mouseFallbackTimeoutMs,
4249
- "mouse.click fallback"
4250
- );
4251
- return { method: "mouse" };
4252
4326
  };
4253
- var MobileHumanize = {
4254
- jitterMs,
4327
+ var resolveCloakBrowserHumanizeDelegate = (page) => resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : CloakBrowserDeviceHumanize;
4328
+ var CloakBrowserHumanizeContext = Object.freeze({
4329
+ desktopDelegate: CloakBrowserDeviceHumanize,
4330
+ resolveDelegate: resolveCloakBrowserHumanizeDelegate
4331
+ });
4332
+
4333
+ // src/internals/humanize/desktop.js
4334
+ var import_delay4 = __toESM(require("delay"), 1);
4335
+ var import_ghost_cursor_playwright = require("ghost-cursor-playwright");
4336
+ var logger6 = createInternalLogger("Humanize");
4337
+ var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
4338
+ function $GetCursor(page) {
4339
+ const cursor = $CursorWeakMap.get(page);
4340
+ if (!cursor) {
4341
+ throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
4342
+ }
4343
+ return cursor;
4344
+ }
4345
+ var Humanize = {
4346
+ /**
4347
+ * 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
4348
+ * @param {number} base - 基础延迟 (ms)
4349
+ * @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
4350
+ * @returns {number} 抖动后的毫秒数
4351
+ */
4352
+ jitterMs(base, jitterPercent = 0.3) {
4353
+ const jitter = base * jitterPercent * (Math.random() * 2 - 1);
4354
+ return Math.max(10, Math.round(base + jitter));
4355
+ },
4356
+ /**
4357
+ * 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
4358
+ *
4359
+ * @param {import('playwright').Page} page
4360
+ * @returns {Promise<void>}
4361
+ */
4255
4362
  async initializeCursor(page) {
4256
- if (initializedPages.has(page)) return;
4257
- initializedPages.add(page);
4258
- logger6.debug("initializeCursor: mobile mode uses touch gestures, cursor init skipped");
4363
+ if ($CursorWeakMap.has(page)) {
4364
+ logger6.debug("initializeCursor: cursor already exists, skipping");
4365
+ return;
4366
+ }
4367
+ logger6.start("initializeCursor", "creating cursor");
4368
+ const cursor = await (0, import_ghost_cursor_playwright.createCursor)(page);
4369
+ $CursorWeakMap.set(page, cursor);
4370
+ logger6.success("initializeCursor", "cursor initialized");
4259
4371
  },
4372
+ /**
4373
+ * 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
4374
+ *
4375
+ * @param {import('playwright').Page} page
4376
+ * @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
4377
+ */
4260
4378
  async humanMove(page, target) {
4261
- logger6.debug(`humanMove: mobile no-op target=${typeof target === "string" ? target : "element/coords"}`);
4262
- if (typeof target === "string" || target && typeof target.boundingBox === "function") {
4263
- const element = await resolveElement(page, target, { throwOnMissing: false });
4264
- if (!element) {
4265
- return false;
4379
+ const cursor = $GetCursor(page);
4380
+ logger6.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
4381
+ try {
4382
+ if (typeof target === "string") {
4383
+ const element = await page.$(target);
4384
+ if (!element) {
4385
+ logger6.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
4386
+ return false;
4387
+ }
4388
+ const box = await element.boundingBox();
4389
+ if (!box) {
4390
+ logger6.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
4391
+ return false;
4392
+ }
4393
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
4394
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
4395
+ await cursor.actions.move({ x, y });
4396
+ } else if (target && typeof target.x === "number" && typeof target.y === "number") {
4397
+ await cursor.actions.move(target);
4398
+ } else if (target && typeof target.boundingBox === "function") {
4399
+ const box = await target.boundingBox();
4400
+ if (box) {
4401
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
4402
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
4403
+ await cursor.actions.move({ x, y });
4404
+ }
4266
4405
  }
4406
+ logger6.success("humanMove");
4407
+ return true;
4408
+ } catch (error) {
4409
+ logger6.fail("humanMove", error);
4410
+ throw error;
4267
4411
  }
4268
- await waitJitter(80, 0.4);
4269
- return true;
4270
4412
  },
4413
+ /**
4414
+ * 渐进式滚动到元素可见(仅处理 Y 轴滚动)
4415
+ * 返回 restore 方法,用于将滚动容器恢复到原位置
4416
+ *
4417
+ * @param {import('playwright').Page} page
4418
+ * @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
4419
+ * @param {Object} [options]
4420
+ * @param {number} [options.maxSteps=20] - 最大滚动步数
4421
+ * @param {number} [options.minStep=260] - 单次滚动最小步长
4422
+ * @param {number} [options.maxStep=800] - 单次滚动最大步长
4423
+ * @param {number} [options.maxDurationMs] - 最长耗时上限 (默认随 maxSteps 估算)
4424
+ */
4271
4425
  async humanScroll(page, target, options = {}) {
4272
4426
  const {
4273
4427
  maxSteps = 20,
4274
- minStep = 180,
4275
- maxStep = 520,
4276
- maxDurationMs = maxSteps * 280 + 1200,
4277
- throwOnMissing = false
4428
+ minStep = 260,
4429
+ maxStep = 800,
4430
+ maxDurationMs = maxSteps * 220 + 800
4278
4431
  } = options;
4279
- const targetDesc = describeTarget(target);
4432
+ const targetDesc = typeof target === "string" ? target : "ElementHandle";
4280
4433
  logger6.start("humanScroll", `target=${targetDesc}`);
4281
- const element = await resolveElement(page, target, { throwOnMissing });
4282
- if (!element) {
4283
- logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
4284
- return { element: null, didScroll: false, restore: null };
4434
+ let element;
4435
+ if (typeof target === "string") {
4436
+ element = await page.$(target);
4437
+ if (!element) {
4438
+ logger6.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
4439
+ return { element: null, didScroll: false };
4440
+ }
4441
+ } else {
4442
+ element = target;
4285
4443
  }
4286
- const startTime = Date.now();
4444
+ const cursor = $GetCursor(page);
4287
4445
  let didScroll = false;
4288
- for (let i = 0; i < maxSteps; i += 1) {
4289
- const status = await checkElementVisibility(element);
4290
- if (status.code === "VISIBLE") {
4291
- if (status.isFixed) {
4292
- logger6.info("humanScroll | fixed/sticky \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4293
- } else {
4294
- logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
4446
+ const checkVisibility = async () => {
4447
+ return await element.evaluate((el) => {
4448
+ const rect = el.getBoundingClientRect();
4449
+ if (!rect || rect.width === 0 || rect.height === 0) {
4450
+ return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
4295
4451
  }
4296
- logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4297
- return { element, didScroll, restore: null };
4298
- }
4299
- if (status.code === "ZERO_DIMENSIONS" || status.code === "NOT_INTERACTABLE") {
4300
- logger6.warn(`humanScroll | \u5143\u7D20\u4E0D\u53EF\u6EDA\u52A8\u81F3\u53EF\u70B9\u51FB\u72B6\u6001: ${status.reason || status.code}`);
4301
- return { element, didScroll, restore: null };
4302
- }
4303
- const scrollRect = await getScrollableRect(element);
4304
- if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
4305
- logger6.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"})`);
4306
- return { element, didScroll, restore: null, unscrollable: true };
4307
- }
4308
- if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
4309
- logger6.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
4310
- return { element, didScroll, restore: null, unscrollable: true };
4311
- }
4312
- if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
4313
- const moved = await scrollAwayFromObstruction(element, status);
4314
- if (moved.moved) {
4315
- logger6.debug(`humanScroll | sticky/fixed \u906E\u6321\u8865\u507F\u6EDA\u52A8 top=${Math.round(moved.scrollTop || 0)}`);
4316
- await waitJitter(90, 0.3);
4317
- didScroll = true;
4318
- continue;
4452
+ const cx = rect.left + rect.width / 2;
4453
+ const cy = rect.top + rect.height / 2;
4454
+ const viewH = window.innerHeight;
4455
+ const viewW = window.innerWidth;
4456
+ let isFixed = false;
4457
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
4458
+ const style = window.getComputedStyle(node);
4459
+ if (style && style.position === "fixed") {
4460
+ isFixed = true;
4461
+ break;
4462
+ }
4319
4463
  }
4320
- }
4321
- if (Date.now() - startTime > maxDurationMs) {
4322
- logger6.warn(`humanScroll | mobile timeout (${maxDurationMs}ms, status=${status.code}, direction=${status.direction || "unknown"}, fixed=${Boolean(status.isFixed)})`);
4323
- return { element, didScroll, restore: null };
4324
- }
4325
- const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
4326
- const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
4327
- logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason || status.code} ${status.direction ? `(${status.direction})` : ""}`);
4328
- const distance = stepMin + Math.random() * Math.max(1, stepMax - stepMin);
4329
- let deltaY = status.direction === "up" ? -distance : distance;
4330
- if (status.code === "OBSTRUCTED") {
4331
- if (status.obstruction?.isFixed && status.obstruction.top != null) {
4332
- const obstructionMiddle = (Number(status.obstruction.top || 0) + Number(status.obstruction.bottom || status.obstruction.top || 0)) / 2;
4333
- const visibleMiddle = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4334
- deltaY = obstructionMiddle < visibleMiddle ? -distance : distance;
4335
- } else {
4336
- const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4337
- deltaY = status.cy > halfY ? distance : -distance;
4464
+ if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
4465
+ const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
4466
+ return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH, isFixed };
4338
4467
  }
4339
- }
4340
- const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
4341
- const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4342
- const beforeState = scrollRect ? await element.evaluate((el) => {
4468
+ const pointElement = document.elementFromPoint(cx, cy);
4469
+ if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
4470
+ return {
4471
+ code: "OBSTRUCTED",
4472
+ reason: "\u88AB\u906E\u6321",
4473
+ obstruction: {
4474
+ tag: pointElement.tagName,
4475
+ id: pointElement.id,
4476
+ className: pointElement.className
4477
+ },
4478
+ cy,
4479
+ // Return Center Y for smart direction calculation
4480
+ viewH,
4481
+ isFixed
4482
+ };
4483
+ }
4484
+ return { code: "VISIBLE", isFixed };
4485
+ });
4486
+ };
4487
+ const getScrollableRect2 = async () => {
4488
+ return await element.evaluate((el) => {
4343
4489
  const isScrollable = (node) => {
4344
4490
  const style = window.getComputedStyle(node);
4345
4491
  if (!style) return false;
@@ -4349,260 +4495,260 @@ var MobileHumanize = {
4349
4495
  };
4350
4496
  let current = el;
4351
4497
  while (current && current !== document.body) {
4352
- if (isScrollable(current)) {
4353
- return {
4354
- kind: "element",
4355
- top: current.scrollTop,
4356
- left: current.scrollLeft
4357
- };
4358
- }
4359
- current = current.parentElement;
4360
- }
4361
- return { kind: "window", top: window.scrollY, left: window.scrollX };
4362
- }) : null;
4363
- await dispatchTouchSwipe(page, deltaY, { rect: scrollRect });
4364
- if (scrollRect && beforeWindowState) {
4365
- const afterWindowState = await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY }));
4366
- if (Math.abs(afterWindowState.x - beforeWindowState.x) > 2 || Math.abs(afterWindowState.y - beforeWindowState.y) > 2) {
4367
- await page.evaluate((state2) => window.scrollTo(state2.x, state2.y), beforeWindowState);
4368
- logger6.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
4369
- }
4370
- }
4371
- let afterElementSnapshot = null;
4372
- const readAfterElementSnapshot = async () => {
4373
- if (!afterElementSnapshot) {
4374
- afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4375
- }
4376
- return afterElementSnapshot;
4377
- };
4378
- if (!scrollRect && beforeElementSnapshot) {
4379
- const afterSnapshot = await readAfterElementSnapshot();
4380
- if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4381
- await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4382
- logger6.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"})`);
4383
- return { element, didScroll, restore: null, unscrollable: true };
4384
- }
4385
- }
4386
- if (scrollRect && beforeState) {
4387
- const afterState = await element.evaluate((el) => {
4388
- const isScrollable = (node) => {
4389
- const style = window.getComputedStyle(node);
4390
- if (!style) return false;
4391
- const overflowY = style.overflowY;
4392
- if (!["auto", "scroll", "overlay"].includes(overflowY)) return false;
4393
- return node.scrollHeight > node.clientHeight + 1;
4394
- };
4395
- let current = el;
4396
- while (current && current !== document.body) {
4397
- if (isScrollable(current)) {
4398
- return {
4399
- kind: "element",
4400
- top: current.scrollTop,
4401
- left: current.scrollLeft
4402
- };
4498
+ if (isScrollable(current)) {
4499
+ const rect = current.getBoundingClientRect();
4500
+ if (rect && rect.width > 0 && rect.height > 0) {
4501
+ return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
4403
4502
  }
4404
- current = current.parentElement;
4405
4503
  }
4406
- return { kind: "window", top: window.scrollY, left: window.scrollX };
4407
- });
4408
- const topDelta = Number(afterState.top || 0) - Number(beforeState.top || 0);
4409
- const leftDelta = Number(afterState.left || 0) - Number(beforeState.left || 0);
4410
- const expectedDelta = Number(deltaY || 0);
4411
- const moved = beforeState.kind !== afterState.kind || Math.abs(topDelta) > 2 || Math.abs(leftDelta) > 2;
4412
- if (!moved) {
4413
- const fallback = await scrollScrollableAncestor(element, deltaY);
4414
- logger6.debug(`humanScroll | \u5BB9\u5668\u89E6\u6478\u65E0\u6548\uFF0C\u76F4\u63A5\u6EDA\u52A8 fallback=${fallback.scroller ? "ancestor" : "window"} top=${Math.round(fallback.scrollTop || 0)}`);
4415
- } 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)) {
4416
- const residualDelta = expectedDelta - topDelta;
4417
- if (Math.sign(residualDelta) === Math.sign(expectedDelta) && Math.abs(residualDelta) > 24) {
4418
- const fallback = await scrollScrollableAncestor(element, residualDelta);
4419
- logger6.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)}`);
4504
+ current = current.parentElement;
4505
+ }
4506
+ return null;
4507
+ });
4508
+ };
4509
+ const startTime = Date.now();
4510
+ try {
4511
+ for (let i = 0; i < maxSteps; i++) {
4512
+ if (Date.now() - startTime > maxDurationMs) {
4513
+ logger6.warn(`humanScroll | \u8D85\u65F6\u4FDD\u62A4\u89E6\u53D1 (${maxDurationMs}ms)`);
4514
+ return { element, didScroll };
4515
+ }
4516
+ const status = await checkVisibility();
4517
+ if (status.code === "VISIBLE") {
4518
+ if (status.isFixed) {
4519
+ logger6.info("humanScroll | fixed \u5BB9\u5668\u5185\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4520
+ } else {
4521
+ logger6.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
4420
4522
  }
4523
+ logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4524
+ return { element, didScroll };
4421
4525
  }
4422
- }
4423
- if (scrollRect && beforeElementSnapshot) {
4424
- const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
4425
- if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
4426
- await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
4427
- logger6.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"})`);
4428
- return { element, didScroll, restore: null, unscrollable: true };
4526
+ logger6.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
4527
+ if (status.code === "OBSTRUCTED" && status.obstruction) {
4528
+ logger6.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
4429
4529
  }
4530
+ const scrollRect = await getScrollableRect2();
4531
+ if (!scrollRect && status.isFixed) {
4532
+ logger6.warn("humanScroll | fixed \u5BB9\u5668\u5185\u4E14\u65E0\u53EF\u6EDA\u52A8\u7956\u5148\uFF0C\u8DF3\u8FC7\u6EDA\u52A8");
4533
+ return { element, didScroll };
4534
+ }
4535
+ const stepMin = scrollRect ? Math.min(minStep, Math.max(60, scrollRect.height * 0.4)) : minStep;
4536
+ const stepMax = scrollRect ? Math.min(maxStep, Math.max(stepMin + 40, scrollRect.height * 0.8)) : maxStep;
4537
+ let deltaY = 0;
4538
+ if (status.code === "OUT_OF_VIEWPORT") {
4539
+ if (status.direction === "down") {
4540
+ deltaY = stepMin + Math.random() * (stepMax - stepMin);
4541
+ } else if (status.direction === "up") {
4542
+ deltaY = -(stepMin + Math.random() * (stepMax - stepMin));
4543
+ } else {
4544
+ deltaY = 100;
4545
+ }
4546
+ } else if (status.code === "OBSTRUCTED") {
4547
+ const halfY = scrollRect ? scrollRect.y + scrollRect.height / 2 : status.viewH / 2;
4548
+ const isBottomHalf = status.cy > halfY;
4549
+ const direction = isBottomHalf ? 1 : -1;
4550
+ deltaY = direction * (stepMin + Math.random() * 50);
4551
+ }
4552
+ if (i === 0) {
4553
+ const viewSize = page.viewportSize();
4554
+ if (scrollRect) {
4555
+ const safeX = scrollRect.x + scrollRect.width * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.width * 0.4);
4556
+ const safeY = scrollRect.y + scrollRect.height * 0.5 + (Math.random() - 0.5) * Math.min(80, scrollRect.height * 0.4);
4557
+ await cursor.actions.move({ x: safeX, y: safeY });
4558
+ } else if (viewSize) {
4559
+ const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 80;
4560
+ const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 80;
4561
+ await cursor.actions.move({ x: safeX, y: safeY });
4562
+ }
4563
+ }
4564
+ await page.mouse.wheel(0, deltaY);
4565
+ didScroll = true;
4566
+ await (0, import_delay4.default)(this.jitterMs(20 + Math.random() * 40, 0.2));
4430
4567
  }
4431
- didScroll = true;
4432
- }
4433
- try {
4434
- await element.scrollIntoViewIfNeeded?.();
4435
- await waitJitter(80, 0.3);
4436
- const finalStatus = await checkElementVisibility(element);
4437
- if (finalStatus.code === "VISIBLE") {
4438
- logger6.info("humanScroll | \u539F\u751F scrollIntoViewIfNeeded \u515C\u5E95\u6210\u529F");
4439
- logger6.success("humanScroll", didScroll ? "\u5DF2\u6EDA\u52A8" : "\u65E0\u9700\u6EDA\u52A8");
4440
- return { element, didScroll: true, restore: null };
4441
- }
4442
- } catch (fallbackError) {
4443
- logger6.debug(`humanScroll | native fallback failed: ${fallbackError?.message || fallbackError}`);
4568
+ logger6.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
4569
+ return { element, didScroll };
4570
+ } catch (error) {
4571
+ logger6.fail("humanScroll", error);
4572
+ throw error;
4444
4573
  }
4445
- logger6.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
4446
- return { element, didScroll, restore: null };
4447
4574
  },
4575
+ /**
4576
+ * 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
4577
+ *
4578
+ * @param {import('playwright').Page} page
4579
+ * @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
4580
+ * @param {Object} [options]
4581
+ * @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
4582
+ * @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
4583
+ * @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
4584
+ */
4448
4585
  async humanClick(page, target, options = {}) {
4449
- const {
4450
- reactionDelay = 220,
4451
- throwOnMissing = true,
4452
- scrollIfNeeded = true,
4453
- tapTimeoutMs = DEFAULT_TAP_TIMEOUT_MS,
4454
- mouseFallbackTimeoutMs = DEFAULT_MOUSE_TAP_FALLBACK_TIMEOUT_MS,
4455
- activateFallbackTimeoutMs = DEFAULT_ACTIVATE_FALLBACK_TIMEOUT_MS,
4456
- fallbackDomClick = true,
4457
- fallbackDomClickOnTapError = true
4458
- } = options;
4459
- const targetDesc = describeTarget(target);
4586
+ const cursor = $GetCursor(page);
4587
+ const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
4588
+ const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
4460
4589
  logger6.start("humanClick", `target=${targetDesc}`);
4590
+ const restoreOnce = async () => {
4591
+ if (restoreOnce.restored) return;
4592
+ restoreOnce.restored = true;
4593
+ if (typeof restoreOnce.do !== "function") return;
4594
+ try {
4595
+ await (0, import_delay4.default)(this.jitterMs(1e3));
4596
+ await restoreOnce.do();
4597
+ } catch (restoreError) {
4598
+ logger6.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
4599
+ }
4600
+ };
4461
4601
  try {
4462
4602
  if (target == null) {
4463
- const viewport = resolveViewport(page);
4464
- await waitJitter(reactionDelay, 0.45);
4465
- await tapPoint(page, {
4466
- x: viewport.width * (0.45 + Math.random() * 0.1),
4467
- y: viewport.height * (0.48 + Math.random() * 0.12)
4468
- }, {
4469
- timeoutMs: tapTimeoutMs,
4470
- mouseFallbackTimeoutMs
4471
- });
4472
- logger6.success("humanClick", "Tapped current position");
4603
+ await (0, import_delay4.default)(this.jitterMs(reactionDelay, 0.4));
4604
+ await cursor.actions.click();
4605
+ logger6.success("humanClick", "Clicked current position");
4473
4606
  return true;
4474
4607
  }
4475
- const element = await resolveElement(page, target, { throwOnMissing });
4476
- if (!element) {
4477
- logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
4478
- return false;
4479
- }
4480
- const scrollResult = scrollIfNeeded ? await MobileHumanize.humanScroll(page, element, { throwOnMissing }) : null;
4481
- const status = await checkElementVisibility(element).catch(() => null);
4482
- if (status && status.code !== "VISIBLE") {
4483
- if (fallbackDomClick && (status.code === "OUT_OF_VIEWPORT" || status.code === "OBSTRUCTED") && (status.isFixed || scrollResult?.unscrollable)) {
4484
- let fallback = await withTimeout(
4485
- () => activateElementFallback(element, null, {
4486
- editableOnly: true
4487
- }),
4488
- activateFallbackTimeoutMs,
4489
- "focus fallback"
4490
- ).catch(() => null);
4491
- if (!fallback?.activated) {
4492
- fallback = await withTimeout(
4493
- () => activateElementFallback(element, null, {
4494
- editableOnly: false
4495
- }),
4496
- activateFallbackTimeoutMs,
4497
- "activation fallback"
4498
- ).catch(() => null);
4499
- }
4500
- if (fallback?.activated) {
4501
- logger6.warn(`humanClick: \u4E0D\u53EF\u6EDA\u52A8\u76EE\u6807\u4E0D\u53EF\u7269\u7406\u70B9\u51FB\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
4502
- return true;
4608
+ let element;
4609
+ if (typeof target === "string") {
4610
+ element = await page.$(target);
4611
+ if (!element) {
4612
+ if (throwOnMissing) {
4613
+ throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
4503
4614
  }
4615
+ logger6.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
4616
+ return false;
4504
4617
  }
4505
- const message = `\u5143\u7D20\u4E0D\u53EF\u70B9\u51FB: ${status.reason || status.code}`;
4506
- if (throwOnMissing) throw new Error(message);
4507
- logger6.warn(`humanClick: ${message}\uFF0C\u8DF3\u8FC7\u70B9\u51FB`);
4508
- return false;
4618
+ } else {
4619
+ element = target;
4620
+ }
4621
+ if (scrollIfNeeded) {
4622
+ const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
4623
+ restoreOnce.do = didScroll && restore ? restoreFn : null;
4509
4624
  }
4510
4625
  const box = await element.boundingBox();
4511
4626
  if (!box) {
4512
- if (throwOnMissing) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4627
+ await restoreOnce();
4628
+ if (throwOnMissing) {
4629
+ throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
4630
+ }
4513
4631
  logger6.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
4514
4632
  return false;
4515
4633
  }
4516
- await waitJitter(reactionDelay, 0.45);
4517
- const visibleBox = clipBoxToViewport(box, resolveViewport(page));
4518
- const tapTarget = centerPointInBox(visibleBox);
4519
- try {
4520
- await tapPoint(page, tapTarget, {
4521
- timeoutMs: tapTimeoutMs,
4522
- mouseFallbackTimeoutMs
4523
- });
4524
- } catch (tapError) {
4525
- if (!fallbackDomClickOnTapError) throw tapError;
4526
- const fallback = await withTimeout(
4527
- () => activateElementFallback(element, tapTarget, {
4528
- editableOnly: false
4529
- }),
4530
- activateFallbackTimeoutMs,
4531
- "activation fallback"
4532
- ).catch(() => null);
4533
- if (!fallback?.activated) throw tapError;
4534
- logger6.warn(`humanClick: tap \u5931\u8D25\u540E\u5DF2\u7528 ${fallback.method} \u515C\u5E95: ${tapError?.message || tapError}`);
4535
- }
4536
- await waitJitter(120, 0.35);
4634
+ const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
4635
+ const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
4636
+ await cursor.actions.move({ x, y });
4637
+ await (0, import_delay4.default)(this.jitterMs(reactionDelay, 0.4));
4638
+ await cursor.actions.click();
4639
+ await restoreOnce();
4537
4640
  logger6.success("humanClick");
4538
4641
  return true;
4539
4642
  } catch (error) {
4643
+ await restoreOnce();
4540
4644
  logger6.fail("humanClick", error);
4541
4645
  throw error;
4542
4646
  }
4543
4647
  },
4648
+ /**
4649
+ * 随机延迟一段毫秒数(带 ±30% 抖动)
4650
+ * @param {number} baseMs - 基础延迟毫秒数
4651
+ * @param {number} [jitterPercent=0.3] - 抖动百分比
4652
+ */
4544
4653
  async randomSleep(baseMs, jitterPercent = 0.3) {
4545
- await waitJitter(baseMs, jitterPercent);
4654
+ const ms = this.jitterMs(baseMs, jitterPercent);
4655
+ logger6.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
4656
+ await (0, import_delay4.default)(ms);
4657
+ logger6.success("randomSleep");
4546
4658
  },
4659
+ /**
4660
+ * 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
4661
+ * @param {import('playwright').Page} page
4662
+ * @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
4663
+ */
4547
4664
  async simulateGaze(page, baseDurationMs = 2500) {
4548
- const durationMs = jitterMs(baseDurationMs, 0.4);
4665
+ const cursor = $GetCursor(page);
4666
+ const durationMs = this.jitterMs(baseDurationMs, 0.4);
4667
+ logger6.start("simulateGaze", `duration=${durationMs}ms`);
4549
4668
  const startTime = Date.now();
4669
+ const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
4550
4670
  while (Date.now() - startTime < durationMs) {
4551
- if (Math.random() < 0.28) {
4552
- const distance = 70 + Math.random() * 120;
4553
- await dispatchTouchSwipe(page, Math.random() < 0.7 ? distance : -distance, {
4554
- durationMs: 180,
4555
- steps: 4
4556
- });
4557
- } else {
4558
- await waitJitter(420, 0.55);
4559
- }
4671
+ const x = 100 + Math.random() * (viewportSize.width - 200);
4672
+ const y = 100 + Math.random() * (viewportSize.height - 200);
4673
+ await cursor.actions.move({ x, y });
4674
+ await (0, import_delay4.default)(this.jitterMs(600, 0.5));
4560
4675
  }
4676
+ logger6.success("simulateGaze");
4561
4677
  },
4678
+ /**
4679
+ * 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
4680
+ * @param {import('playwright').Page} page
4681
+ * @param {string} selector - 输入框选择器
4682
+ * @param {string} text - 要输入的文本
4683
+ * @param {Object} [options]
4684
+ * @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
4685
+ * @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
4686
+ * @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
4687
+ */
4562
4688
  async humanType(page, selector, text, options = {}) {
4689
+ logger6.start("humanType", `selector=${selector}, textLen=${text.length}`);
4563
4690
  const {
4564
- baseDelay = 160,
4691
+ baseDelay = 180,
4565
4692
  pauseProbability = 0.08,
4566
- pauseBase = 700
4567
- } = options;
4568
- await MobileHumanize.humanClick(page, selector, { scrollIfNeeded: true });
4569
- await waitJitter(220, 0.45);
4570
- for (let i = 0; i < String(text).length; i += 1) {
4571
- const char = String(text)[i];
4572
- const charDelay = /[,.!?;:,。!?;:]/.test(char) ? jitterMs(baseDelay * 1.45, 0.4) : jitterMs(char === " " ? baseDelay * 0.65 : baseDelay, 0.4);
4573
- await page.keyboard.type(char);
4574
- await waitJitter(charDelay, 0.15);
4575
- if (Math.random() < pauseProbability && i < String(text).length - 1) {
4576
- await waitJitter(pauseBase, 0.5);
4693
+ pauseBase = 800
4694
+ } = options;
4695
+ try {
4696
+ const locator = page.locator(selector);
4697
+ await Humanize.humanClick(page, locator);
4698
+ await (0, import_delay4.default)(this.jitterMs(200, 0.4));
4699
+ for (let i = 0; i < text.length; i++) {
4700
+ const char = text[i];
4701
+ let charDelay;
4702
+ if (char === " ") {
4703
+ charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
4704
+ } else if (/[,.!?;:,。!?;:]/.test(char)) {
4705
+ charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
4706
+ } else {
4707
+ charDelay = this.jitterMs(baseDelay, 0.4);
4708
+ }
4709
+ await page.keyboard.type(char);
4710
+ await (0, import_delay4.default)(charDelay);
4711
+ if (Math.random() < pauseProbability && i < text.length - 1) {
4712
+ const pauseTime = this.jitterMs(pauseBase, 0.5);
4713
+ logger6.debug(`\u505C\u987F ${pauseTime}ms...`);
4714
+ await (0, import_delay4.default)(pauseTime);
4715
+ }
4577
4716
  }
4717
+ logger6.success("humanType");
4718
+ } catch (error) {
4719
+ logger6.fail("humanType", error);
4720
+ throw error;
4578
4721
  }
4579
4722
  },
4723
+ /**
4724
+ * 人类化按键 - 模拟用户在当前焦点或指定目标上按下一次键。
4725
+ * @param {import('playwright').Page} page
4726
+ * @param {string|import('playwright').ElementHandle|import('playwright').Locator} targetOrKey - 目标或按键
4727
+ * @param {string|Object} [maybeKey] - 按键或选项
4728
+ * @param {Object} [options]
4729
+ */
4580
4730
  async humanPress(page, targetOrKey, maybeKey, options = {}) {
4581
4731
  const hasTarget = typeof maybeKey === "string";
4582
4732
  const key = hasTarget ? maybeKey : targetOrKey;
4583
4733
  const pressOptions = hasTarget ? options : maybeKey || options;
4584
4734
  const {
4585
- reactionDelay = 170,
4586
- holdDelay = 42,
4735
+ reactionDelay = 180,
4736
+ holdDelay = 45,
4587
4737
  focusDelay = 180,
4588
4738
  scrollIfNeeded = true,
4589
4739
  throwOnMissing = true,
4590
4740
  keyboardOptions = {}
4591
4741
  } = pressOptions || {};
4592
- const targetDesc = hasTarget ? describeTarget(targetOrKey) : "current focus";
4742
+ const targetDesc = hasTarget ? typeof targetOrKey === "string" ? targetOrKey : "ElementHandle" : "current focus";
4593
4743
  logger6.start("humanPress", `key=${key}, target=${targetDesc}`);
4594
4744
  try {
4595
4745
  if (hasTarget) {
4596
- await MobileHumanize.humanClick(page, targetOrKey, {
4597
- reactionDelay: focusDelay,
4598
- scrollIfNeeded,
4599
- throwOnMissing
4600
- });
4746
+ await this.humanClick(page, targetOrKey, { reactionDelay: focusDelay, scrollIfNeeded, throwOnMissing });
4601
4747
  }
4602
- await waitJitter(reactionDelay, 0.45);
4748
+ await (0, import_delay4.default)(this.jitterMs(reactionDelay, 0.45));
4603
4749
  await page.keyboard.press(key, {
4604
4750
  ...keyboardOptions,
4605
- delay: jitterMs(holdDelay, 0.5)
4751
+ delay: this.jitterMs(holdDelay, 0.5)
4606
4752
  });
4607
4753
  logger6.success("humanPress");
4608
4754
  return true;
@@ -4611,285 +4757,164 @@ var MobileHumanize = {
4611
4757
  throw error;
4612
4758
  }
4613
4759
  },
4760
+ /**
4761
+ * 人类化清空输入框 - 模拟人类删除文本的行为
4762
+ * @param {import('playwright').Page} page
4763
+ * @param {string} selector - 输入框选择器
4764
+ */
4614
4765
  async humanClear(page, selector) {
4615
- const locator = page.locator(selector);
4616
- await MobileHumanize.humanClick(page, locator, { scrollIfNeeded: true });
4617
- await waitJitter(160, 0.4);
4618
- const readValue = async () => {
4619
- try {
4620
- return await locator.inputValue({ timeout: 600 });
4621
- } catch {
4622
- return await locator.evaluate((el) => "value" in el ? String(el.value || "") : String(el.textContent || "")).catch(() => "");
4623
- }
4624
- };
4625
- const currentValue = await readValue();
4626
- if (!currentValue) return;
4627
- await page.keyboard.press("ControlOrMeta+A");
4628
- await waitJitter(90, 0.35);
4629
- await page.keyboard.press("Backspace");
4630
- await waitJitter(120, 0.35);
4631
- if (!await readValue()) return;
4632
- await locator.evaluate((el) => {
4633
- if ("value" in el) {
4634
- el.value = "";
4635
- el.dispatchEvent(new Event("input", { bubbles: true }));
4636
- el.dispatchEvent(new Event("change", { bubbles: true }));
4766
+ logger6.start("humanClear", `selector=${selector}`);
4767
+ try {
4768
+ const locator = page.locator(selector);
4769
+ await locator.click();
4770
+ await (0, import_delay4.default)(this.jitterMs(200, 0.4));
4771
+ const currentValue = await locator.inputValue();
4772
+ if (!currentValue || currentValue.length === 0) {
4773
+ logger6.success("humanClear", "already empty");
4637
4774
  return;
4638
4775
  }
4639
- el.textContent = "";
4640
- el.dispatchEvent(new Event("input", { bubbles: true }));
4641
- });
4776
+ await page.keyboard.press("Meta+A");
4777
+ await (0, import_delay4.default)(this.jitterMs(100, 0.4));
4778
+ await page.keyboard.press("Backspace");
4779
+ logger6.success("humanClear");
4780
+ } catch (error) {
4781
+ logger6.fail("humanClear", error);
4782
+ throw error;
4783
+ }
4642
4784
  },
4785
+ /**
4786
+ * 页面预热浏览 - 模拟人类进入页面后的探索行为
4787
+ * @param {import('playwright').Page} page
4788
+ * @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
4789
+ */
4643
4790
  async warmUpBrowsing(page, baseDuration = 3500) {
4644
- const durationMs = jitterMs(baseDuration, 0.4);
4791
+ const cursor = $GetCursor(page);
4792
+ const durationMs = this.jitterMs(baseDuration, 0.4);
4793
+ logger6.start("warmUpBrowsing", `duration=${durationMs}ms`);
4645
4794
  const startTime = Date.now();
4646
- while (Date.now() - startTime < durationMs) {
4647
- const action = Math.random();
4648
- if (action < 0.5) {
4649
- await dispatchTouchSwipe(page, 120 + Math.random() * 220, {
4650
- durationMs: 240,
4651
- steps: 5
4652
- });
4653
- } else if (action < 0.7) {
4654
- await dispatchTouchSwipe(page, -(80 + Math.random() * 160), {
4655
- durationMs: 220,
4656
- steps: 4
4657
- });
4658
- } else {
4659
- await waitJitter(560, 0.55);
4795
+ const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
4796
+ try {
4797
+ while (Date.now() - startTime < durationMs) {
4798
+ const action = Math.random();
4799
+ if (action < 0.4) {
4800
+ const x = 100 + Math.random() * (viewportSize.width - 200);
4801
+ const y = 100 + Math.random() * (viewportSize.height - 200);
4802
+ await cursor.actions.move({ x, y });
4803
+ await (0, import_delay4.default)(this.jitterMs(350, 0.4));
4804
+ } else if (action < 0.7) {
4805
+ const scrollY = (Math.random() - 0.5) * 200;
4806
+ await page.mouse.wheel(0, scrollY);
4807
+ await (0, import_delay4.default)(this.jitterMs(500, 0.4));
4808
+ } else {
4809
+ await (0, import_delay4.default)(this.jitterMs(800, 0.5));
4810
+ }
4660
4811
  }
4812
+ logger6.success("warmUpBrowsing");
4813
+ } catch (error) {
4814
+ logger6.fail("warmUpBrowsing", error);
4815
+ throw error;
4661
4816
  }
4662
4817
  },
4818
+ /**
4819
+ * 自然滚动 - 带惯性、减速效果和随机抖动
4820
+ * @param {import('playwright').Page} page
4821
+ * @param {'up' | 'down'} [direction='down'] - 滚动方向
4822
+ * @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
4823
+ * @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
4824
+ */
4663
4825
  async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4664
4826
  const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
4665
- const actualDistance = jitterMs(distance, 0.15);
4827
+ const actualDistance = this.jitterMs(distance, 0.15);
4828
+ logger6.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
4666
4829
  const sign = direction === "down" ? 1 : -1;
4667
- for (let i = 0; i < steps; i += 1) {
4668
- const factor = 1 - i / steps * 0.45;
4669
- await dispatchTouchSwipe(page, actualDistance / steps * factor * sign, {
4670
- durationMs: 150 + i * 20,
4671
- steps: 4
4672
- });
4830
+ const stepDistance = actualDistance / steps;
4831
+ try {
4832
+ for (let i = 0; i < steps; i++) {
4833
+ const factor = 1 - i / steps * 0.5;
4834
+ const jitter = 0.9 + Math.random() * 0.2;
4835
+ const scrollAmount = stepDistance * factor * sign * jitter;
4836
+ await page.mouse.wheel(0, scrollAmount);
4837
+ const baseDelay = 60 + i * 25;
4838
+ await (0, import_delay4.default)(this.jitterMs(baseDelay, 0.3));
4839
+ }
4840
+ logger6.success("naturalScroll");
4841
+ } catch (error) {
4842
+ logger6.fail("naturalScroll", error);
4843
+ throw error;
4673
4844
  }
4674
4845
  }
4675
4846
  };
4676
4847
 
4677
4848
  // src/internals/humanize/default.js
4678
- var resolveDeviceFromPage2 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4679
- var resolveDelegate = (page) => {
4680
- return resolveDeviceFromPage2(page) === Device.Mobile ? MobileHumanize : Humanize;
4681
- };
4682
- var callDelegate = (method, page, args) => {
4683
- const delegate = resolveDelegate(page);
4684
- return delegate[method](page, ...args);
4685
- };
4686
- var DefaultHumanize = {
4687
- jitterMs(base, jitterPercent = 0.3) {
4688
- return Humanize.jitterMs(base, jitterPercent);
4689
- },
4690
- initializeCursor(page) {
4691
- return callDelegate("initializeCursor", page, []);
4692
- },
4693
- humanMove(page, target) {
4694
- return callDelegate("humanMove", page, [target]);
4695
- },
4696
- humanScroll(page, target, options = {}) {
4697
- return callDelegate("humanScroll", page, [target, options]);
4698
- },
4699
- humanClick(page, target, options = {}) {
4700
- return callDelegate("humanClick", page, [target, options]);
4701
- },
4702
- randomSleep(pageOrBaseMs, maybeBaseMs, maybeJitterPercent) {
4703
- if (pageOrBaseMs && typeof pageOrBaseMs === "object" && typeof pageOrBaseMs.evaluate === "function") {
4704
- const delegate = resolveDelegate(pageOrBaseMs);
4705
- return delegate.randomSleep(maybeBaseMs, maybeJitterPercent);
4706
- }
4707
- return Humanize.randomSleep(pageOrBaseMs, maybeBaseMs);
4708
- },
4709
- simulateGaze(page, baseDurationMs = 2500) {
4710
- return callDelegate("simulateGaze", page, [baseDurationMs]);
4711
- },
4712
- humanType(page, selector, text, options = {}) {
4713
- return callDelegate("humanType", page, [selector, text, options]);
4714
- },
4715
- humanPress(page, targetOrKey, maybeKey, options = {}) {
4716
- if (typeof maybeKey === "string") {
4717
- return callDelegate("humanPress", page, [targetOrKey, maybeKey, options]);
4718
- }
4719
- return callDelegate("humanPress", page, [targetOrKey, maybeKey || options]);
4720
- },
4721
- humanClear(page, selector) {
4722
- return callDelegate("humanClear", page, [selector]);
4723
- },
4724
- warmUpBrowsing(page, baseDuration = 3500) {
4725
- return callDelegate("warmUpBrowsing", page, [baseDuration]);
4726
- },
4727
- naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4728
- return callDelegate("naturalScroll", page, [direction, distance, baseSteps]);
4729
- }
4730
- };
4731
-
4732
- // src/internals/humanize/cloakbrowser.js
4733
- var import_delay4 = __toESM(require("delay"), 1);
4734
4849
  var resolveDeviceFromPage3 = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
4735
- var isMobilePage = (page) => resolveDeviceFromPage3(page) === Device.Mobile;
4850
+ var resolveDefaultHumanizeDelegate = (page) => resolveDeviceFromPage3(page) === Device.Mobile ? MobileHumanize : Humanize;
4851
+ var DefaultHumanizeContext = Object.freeze({
4852
+ desktopDelegate: Humanize,
4853
+ resolveDelegate: resolveDefaultHumanizeDelegate
4854
+ });
4855
+
4856
+ // src/internals/humanize/index.js
4857
+ var HUMANIZE_DELEGATED_METHODS = Object.freeze({
4858
+ initializeCursor: { enumerable: true },
4859
+ humanMove: { enumerable: true },
4860
+ humanScroll: { enumerable: true },
4861
+ humanClick: { enumerable: true },
4862
+ simulateGaze: { enumerable: true },
4863
+ humanType: { enumerable: true },
4864
+ humanPress: { enumerable: true },
4865
+ humanClear: { enumerable: true },
4866
+ warmUpBrowsing: { enumerable: true },
4867
+ naturalScroll: { enumerable: true }
4868
+ });
4736
4869
  var isPageLike2 = (value) => value && typeof value === "object" && typeof value.evaluate === "function";
4737
- var isPoint2 = (value) => value && typeof value === "object" && Number.isFinite(Number(value.x)) && Number.isFinite(Number(value.y));
4738
- var resolveTarget = (page, target) => typeof target === "string" ? page.locator(target).first() : target;
4739
- var sleep = (base, jitter = 0.3) => (0, import_delay4.default)(jitterMs(base, jitter));
4740
- var FORCE_OPTIONS = Object.freeze({ force: true });
4741
- var DesktopCloakHumanize = {
4742
- jitterMs(base, jitterPercent = 0.3) {
4743
- return jitterMs(base, jitterPercent);
4744
- },
4745
- async initializeCursor(page) {
4746
- return Boolean(page);
4747
- },
4748
- async humanMove(page, target) {
4749
- if (target == null) return false;
4750
- if (isPoint2(target)) {
4751
- await page.mouse.move(Number(target.x), Number(target.y));
4752
- return true;
4753
- }
4754
- await resolveTarget(page, target).hover(FORCE_OPTIONS);
4755
- return true;
4756
- },
4757
- async humanScroll(page, target) {
4758
- const element = resolveTarget(page, target);
4759
- if (!element) return { element: null, didScroll: false, restore: null };
4760
- await element.scrollIntoViewIfNeeded?.();
4761
- return { element, didScroll: true, restore: null };
4762
- },
4763
- async humanClick(page, target) {
4764
- if (target == null) {
4765
- await page.mouse.click(0, 0);
4766
- return true;
4767
- }
4768
- if (isPoint2(target)) {
4769
- await page.mouse.click(Number(target.x), Number(target.y));
4770
- return true;
4771
- }
4772
- if (typeof target === "string") {
4773
- await page.click(target, FORCE_OPTIONS);
4774
- return true;
4775
- }
4776
- await target.click(FORCE_OPTIONS);
4777
- return true;
4778
- },
4779
- async randomSleep(baseMs, jitterPercent = 0.3) {
4780
- await sleep(baseMs, jitterPercent);
4781
- },
4782
- async simulateGaze(page, baseDurationMs = 2500) {
4783
- const durationMs = jitterMs(baseDurationMs, 0.4);
4784
- const startedAt = Date.now();
4785
- const viewport = page.viewportSize() || { width: 1365, height: 900 };
4786
- while (Date.now() - startedAt < durationMs) {
4787
- await page.mouse.move(
4788
- 100 + Math.random() * Math.max(120, viewport.width - 200),
4789
- 100 + Math.random() * Math.max(120, viewport.height - 200)
4790
- );
4791
- await sleep(300, 0.5);
4792
- }
4793
- },
4794
- async humanType(page, selector, text) {
4795
- const target = resolveTarget(page, selector);
4796
- await target.click(FORCE_OPTIONS);
4797
- await page.keyboard.type(text);
4798
- },
4799
- async humanPress(page, targetOrKey, maybeKey) {
4800
- if (typeof maybeKey !== "string") {
4801
- await page.keyboard.press(targetOrKey);
4802
- return true;
4803
- }
4804
- await resolveTarget(page, targetOrKey).click(FORCE_OPTIONS);
4805
- await page.keyboard.press(maybeKey);
4806
- return true;
4807
- },
4808
- async humanClear(page, selector) {
4809
- if (typeof selector === "string") {
4810
- await page.fill(selector, "", FORCE_OPTIONS);
4811
- return;
4812
- }
4813
- await selector.fill("", FORCE_OPTIONS);
4814
- },
4815
- async warmUpBrowsing(page, baseDuration = 3500) {
4816
- await this.simulateGaze(page, Math.min(baseDuration, 900));
4817
- },
4818
- async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4819
- const steps = Math.max(1, Number(baseSteps) || 1);
4820
- const delta = (Number(distance) || 0) / steps * (direction === "down" ? 1 : -1);
4821
- for (let index = 0; index < steps; index += 1) {
4822
- await page.mouse.wheel(0, delta);
4823
- await sleep(60 + index * 20, 0.3);
4824
- }
4825
- }
4826
- };
4827
- var resolveDelegate2 = (page) => isMobilePage(page) ? MobileHumanize : DesktopCloakHumanize;
4828
- var callDelegate2 = (method, page, args) => resolveDelegate2(page)[method](page, ...args);
4829
- var CloakBrowserHumanize = {
4870
+ var createSharedHumanizeMethods = ({ desktopDelegate, resolveDelegate }) => ({
4830
4871
  jitterMs(base, jitterPercent = 0.3) {
4831
- return jitterMs(base, jitterPercent);
4832
- },
4833
- initializeCursor(page) {
4834
- return callDelegate2("initializeCursor", page, []);
4835
- },
4836
- humanMove(page, target) {
4837
- return callDelegate2("humanMove", page, [target]);
4838
- },
4839
- humanScroll(page, target, options = {}) {
4840
- return callDelegate2("humanScroll", page, [target, options]);
4841
- },
4842
- humanClick(page, target, options = {}) {
4843
- return callDelegate2("humanClick", page, [target, options]);
4872
+ return desktopDelegate.jitterMs(base, jitterPercent);
4844
4873
  },
4845
4874
  randomSleep(pageOrBaseMs, maybeBaseMs, maybeJitterPercent) {
4846
4875
  if (isPageLike2(pageOrBaseMs)) {
4847
- return resolveDelegate2(pageOrBaseMs).randomSleep(maybeBaseMs, maybeJitterPercent);
4848
- }
4849
- return DesktopCloakHumanize.randomSleep(pageOrBaseMs, maybeBaseMs);
4850
- },
4851
- simulateGaze(page, baseDurationMs = 2500) {
4852
- return callDelegate2("simulateGaze", page, [baseDurationMs]);
4853
- },
4854
- humanType(page, selector, text, options = {}) {
4855
- return callDelegate2("humanType", page, [selector, text, options]);
4856
- },
4857
- humanPress(page, targetOrKey, maybeKey, options = {}) {
4858
- if (typeof maybeKey === "string") {
4859
- return callDelegate2("humanPress", page, [targetOrKey, maybeKey, options]);
4876
+ return resolveDelegate(pageOrBaseMs).randomSleep(maybeBaseMs, maybeJitterPercent);
4860
4877
  }
4861
- return callDelegate2("humanPress", page, [targetOrKey, maybeKey || options]);
4862
- },
4863
- humanClear(page, selector) {
4864
- return callDelegate2("humanClear", page, [selector]);
4865
- },
4866
- warmUpBrowsing(page, baseDuration = 3500) {
4867
- return callDelegate2("warmUpBrowsing", page, [baseDuration]);
4868
- },
4869
- naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
4870
- return callDelegate2("naturalScroll", page, [direction, distance, baseSteps]);
4878
+ return desktopDelegate.randomSleep(pageOrBaseMs, maybeBaseMs);
4871
4879
  }
4880
+ });
4881
+ var withHumanizeDelegates = (target, resolveDelegate) => withDelegatedProperties(target, {
4882
+ namespace: "Humanize",
4883
+ methods: HUMANIZE_DELEGATED_METHODS,
4884
+ resolveDelegate: (_method, args) => ({
4885
+ delegate: resolveDelegate(args[0]),
4886
+ label: "resolved humanize delegate"
4887
+ })
4888
+ });
4889
+ var createHumanizeExport = (context = {}) => {
4890
+ return Object.assign(
4891
+ createSharedHumanizeMethods(context),
4892
+ withHumanizeDelegates({}, context.resolveDelegate)
4893
+ );
4872
4894
  };
4895
+ var DefaultHumanize = createHumanizeExport(DefaultHumanizeContext);
4896
+ var CloakBrowserHumanize = createHumanizeExport(CloakBrowserHumanizeContext);
4873
4897
 
4874
4898
  // src/humanize.js
4875
4899
  var humanizeStrategies = {
4876
4900
  [Mode.Default]: DefaultHumanize,
4877
4901
  [Mode.CloakBrowser]: CloakBrowserHumanize
4878
4902
  };
4879
- var Humanize2 = createDelegatedFacade("Humanize", humanizeStrategies, [
4880
- "jitterMs",
4881
- "initializeCursor",
4882
- "humanMove",
4883
- "humanScroll",
4884
- "humanClick",
4885
- "randomSleep",
4886
- "simulateGaze",
4887
- "humanType",
4888
- "humanPress",
4889
- "humanClear",
4890
- "warmUpBrowsing",
4891
- "naturalScroll"
4892
- ]);
4903
+ var humanizeFacadeMethods = Object.freeze({
4904
+ jitterMs: { enumerable: true },
4905
+ initializeCursor: { enumerable: true },
4906
+ humanMove: { enumerable: true },
4907
+ humanScroll: { enumerable: true },
4908
+ humanClick: { enumerable: true },
4909
+ randomSleep: { enumerable: true },
4910
+ simulateGaze: { enumerable: true },
4911
+ humanType: { enumerable: true },
4912
+ humanPress: { enumerable: true },
4913
+ humanClear: { enumerable: true },
4914
+ warmUpBrowsing: { enumerable: true },
4915
+ naturalScroll: { enumerable: true }
4916
+ });
4917
+ var Humanize2 = createDelegatedFacade("Humanize", humanizeStrategies, humanizeFacadeMethods);
4893
4918
 
4894
4919
  // src/internals/launch/default.js
4895
4920
  var import_node_child_process = require("node:child_process");
@@ -5579,9 +5604,10 @@ var launchStrategies = {
5579
5604
  [Mode.Default]: DefaultLaunch,
5580
5605
  [Mode.CloakBrowser]: CloakBrowserLaunch
5581
5606
  };
5582
- var Launch = createDelegatedFacade("Launch", launchStrategies, [
5583
- "getPlaywrightCrawlerOptions"
5584
- ]);
5607
+ var launchFacadeMethods = Object.freeze({
5608
+ getPlaywrightCrawlerOptions: { enumerable: true }
5609
+ });
5610
+ var Launch = createDelegatedFacade("Launch", launchStrategies, launchFacadeMethods);
5585
5611
 
5586
5612
  // src/live-view.js
5587
5613
  var import_express = __toESM(require("express"), 1);