@skrillex1224/playwright-toolkit 2.1.58 → 2.1.60
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/README.md +17 -18
- package/dist/index.cjs +369 -88
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +369 -88
- package/dist/index.js.map +3 -3
- package/index.d.ts +0 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,24 +82,23 @@ await Actor.exit();
|
|
|
82
82
|
|
|
83
83
|
### API 一览
|
|
84
84
|
|
|
85
|
-
| 模块 | 方法
|
|
86
|
-
| ----------- |
|
|
87
|
-
| `Launch` | `getAdvancedLaunchOptions()`
|
|
88
|
-
| `Launch` | `getLaunchOptions()`
|
|
89
|
-
| `Launch` | `getFingerprintGeneratorOptions()`
|
|
90
|
-
| `AntiCheat` | `applyPage(page, options?)`
|
|
91
|
-
| `AntiCheat` | `applyContext(context, options?)`
|
|
92
|
-
| `AntiCheat` | `syncViewportWithScreen(page)`
|
|
93
|
-
| `AntiCheat` | `getTlsFingerprintOptions(userAgent?)`
|
|
94
|
-
| `Humanize` | `initializeCursor(page)`
|
|
95
|
-
| `Humanize` | `jitterMs(base, jitterPercent?)`
|
|
96
|
-
| `Humanize` | `humanType(page, selector, text, options?)`
|
|
97
|
-
| `Humanize` | `humanClick(page, selector, options?)`
|
|
98
|
-
| `Humanize` | `warmUpBrowsing(page, baseDuration?)`
|
|
99
|
-
| `Humanize` | `
|
|
100
|
-
| `Humanize` | `
|
|
101
|
-
| `
|
|
102
|
-
| `Captcha` | `useCaptchaMonitor(page, options)` | 验证码监控 |
|
|
85
|
+
| 模块 | 方法 | 说明 |
|
|
86
|
+
| ----------- | ------------------------------------------- | -------------------------------------- |
|
|
87
|
+
| `Launch` | `getAdvancedLaunchOptions()` | 增强版启动参数 |
|
|
88
|
+
| `Launch` | `getLaunchOptions()` | 基础启动参数 |
|
|
89
|
+
| `Launch` | `getFingerprintGeneratorOptions()` | 指纹生成器选项 |
|
|
90
|
+
| `AntiCheat` | `applyPage(page, options?)` | 应用时区/语言/权限/视口 |
|
|
91
|
+
| `AntiCheat` | `applyContext(context, options?)` | 仅应用 Context 设置 |
|
|
92
|
+
| `AntiCheat` | `syncViewportWithScreen(page)` | 同步视口与屏幕 |
|
|
93
|
+
| `AntiCheat` | `getTlsFingerprintOptions(userAgent?)` | got-scraping TLS 指纹 |
|
|
94
|
+
| `Humanize` | `initializeCursor(page)` | 初始化 Cursor (必须先调用) |
|
|
95
|
+
| `Humanize` | `jitterMs(base, jitterPercent?)` | 生成带抖动的毫秒数 (同步,返回 number) |
|
|
96
|
+
| `Humanize` | `humanType(page, selector, text, options?)` | 人类化输入 (baseDelay=180ms ±40%) |
|
|
97
|
+
| `Humanize` | `humanClick(page, selector, options?)` | 人类化点击 (reactionDelay=250ms ±40%) |
|
|
98
|
+
| `Humanize` | `warmUpBrowsing(page, baseDuration?)` | 页面预热 (3500ms ±40%) |
|
|
99
|
+
| `Humanize` | `simulateGaze(page, baseDurationMs?)` | 模拟注视 (2500ms ±40%) |
|
|
100
|
+
| `Humanize` | `randomSleep(baseMs, jitterPercent?)` | 随机延迟 (±30% 抖动) |
|
|
101
|
+
| `Captcha` | `useCaptchaMonitor(page, options)` | 验证码监控 |
|
|
103
102
|
|
|
104
103
|
---
|
|
105
104
|
|
package/dist/index.cjs
CHANGED
|
@@ -534,6 +534,59 @@ var import_delay2 = __toESM(require("delay"), 1);
|
|
|
534
534
|
var import_ghost_cursor_playwright = require("ghost-cursor-playwright");
|
|
535
535
|
var logger5 = createInternalLogger("Humanize");
|
|
536
536
|
var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
|
|
537
|
+
var VISIBILITY_CODE = {
|
|
538
|
+
VISIBLE: "VISIBLE",
|
|
539
|
+
OUT_OF_VIEWPORT: "OUT_OF_VIEWPORT",
|
|
540
|
+
OBSTRUCTED: "OBSTRUCTED",
|
|
541
|
+
ZERO_DIMENSIONS: "ZERO_DIMENSIONS",
|
|
542
|
+
HIDDEN: "HIDDEN",
|
|
543
|
+
DETACHED: "DETACHED"
|
|
544
|
+
};
|
|
545
|
+
var VISIBILITY_REASON = {
|
|
546
|
+
OUT_OF_VIEWPORT: "\u4E0D\u5728\u89C6\u53E3\u5185",
|
|
547
|
+
OBSTRUCTED: "\u88AB\u906E\u6321",
|
|
548
|
+
ZERO_DIMENSIONS: "\u5C3A\u5BF8\u4E3A\u96F6",
|
|
549
|
+
HIDDEN: "\u5143\u7D20\u4E0D\u53EF\u89C1",
|
|
550
|
+
DETACHED: "\u5143\u7D20\u5DF2\u88AB\u79FB\u9664"
|
|
551
|
+
};
|
|
552
|
+
var SCROLL_DIRECTION = {
|
|
553
|
+
UP: "up",
|
|
554
|
+
DOWN: "down",
|
|
555
|
+
UNKNOWN: "unknown"
|
|
556
|
+
};
|
|
557
|
+
var DEFAULT_SCROLL_OPTIONS = {
|
|
558
|
+
maxSteps: 20,
|
|
559
|
+
minStep: 150,
|
|
560
|
+
maxStep: 400,
|
|
561
|
+
maxDurationMs: 3500
|
|
562
|
+
};
|
|
563
|
+
var SAME_POSITION_THRESHOLD_PX = 2;
|
|
564
|
+
var SAME_POSITION_LIMIT = 3;
|
|
565
|
+
var SAFE_MOUSE_JITTER_PX = 100;
|
|
566
|
+
var isElementHandleLike = (value) => value && typeof value.evaluate === "function" && typeof value.boundingBox === "function";
|
|
567
|
+
var isLocatorLike = (value) => value && typeof value.elementHandle === "function";
|
|
568
|
+
async function resolveElementHandle(page, target) {
|
|
569
|
+
if (!target) return { element: null, owned: false };
|
|
570
|
+
if (typeof target === "string") {
|
|
571
|
+
const element = await page.$(target);
|
|
572
|
+
return { element, owned: true };
|
|
573
|
+
}
|
|
574
|
+
if (isLocatorLike(target)) {
|
|
575
|
+
const element = await target.elementHandle();
|
|
576
|
+
return { element, owned: true };
|
|
577
|
+
}
|
|
578
|
+
if (isElementHandleLike(target)) {
|
|
579
|
+
return { element: target, owned: false };
|
|
580
|
+
}
|
|
581
|
+
return { element: null, owned: false };
|
|
582
|
+
}
|
|
583
|
+
async function disposeElementHandle(element, owned) {
|
|
584
|
+
if (!element || !owned || typeof element.dispose !== "function") return;
|
|
585
|
+
try {
|
|
586
|
+
await element.dispose();
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
}
|
|
537
590
|
function $GetCursor(page) {
|
|
538
591
|
const cursor = $CursorWeakMap.get(page);
|
|
539
592
|
if (!cursor) {
|
|
@@ -614,99 +667,353 @@ var Humanize = {
|
|
|
614
667
|
* 返回 restore 方法,用于将滚动容器恢复到原位置
|
|
615
668
|
*
|
|
616
669
|
* @param {import('playwright').Page} page
|
|
617
|
-
* @param {string|import('playwright').ElementHandle} target - CSS
|
|
670
|
+
* @param {string|import('playwright').ElementHandle|import('playwright').Locator} target - CSS 选择器、元素句柄或 Locator
|
|
618
671
|
* @param {Object} [options]
|
|
619
|
-
* @param {number} [options.maxSteps=
|
|
620
|
-
* @param {number} [options.minStep=
|
|
621
|
-
* @param {number} [options.maxStep=
|
|
672
|
+
* @param {number} [options.maxSteps=20] - 最大滚动步数
|
|
673
|
+
* @param {number} [options.minStep=150] - 单次滚动最小步长
|
|
674
|
+
* @param {number} [options.maxStep=400] - 单次滚动最大步长
|
|
675
|
+
* @param {number} [options.maxDurationMs=3500] - 最大滚动耗时上限
|
|
622
676
|
*/
|
|
623
677
|
async humanScroll(page, target, options = {}) {
|
|
624
|
-
const {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
element
|
|
678
|
+
const {
|
|
679
|
+
maxSteps = DEFAULT_SCROLL_OPTIONS.maxSteps,
|
|
680
|
+
minStep = DEFAULT_SCROLL_OPTIONS.minStep,
|
|
681
|
+
maxStep = DEFAULT_SCROLL_OPTIONS.maxStep,
|
|
682
|
+
maxDurationMs = DEFAULT_SCROLL_OPTIONS.maxDurationMs
|
|
683
|
+
} = options;
|
|
684
|
+
const targetDesc = typeof target === "string" ? target : isLocatorLike(target) ? "Locator" : "ElementHandle";
|
|
685
|
+
logger5.info(`humanScroll | \u5F00\u59CB\u6EDA\u52A8\u76EE\u6807: ${targetDesc}`);
|
|
686
|
+
const { element } = await resolveElementHandle(page, target);
|
|
687
|
+
if (!element) {
|
|
688
|
+
logger5.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${targetDesc}`);
|
|
689
|
+
return { element: null, didScroll: false, restore: null };
|
|
636
690
|
}
|
|
637
691
|
const cursor = $GetCursor(page);
|
|
638
692
|
let didScroll = false;
|
|
693
|
+
let lastRect = null;
|
|
694
|
+
let samePositionCount = 0;
|
|
695
|
+
const startAt = Date.now();
|
|
696
|
+
const visibilityPayload = {
|
|
697
|
+
codes: VISIBILITY_CODE,
|
|
698
|
+
reasons: VISIBILITY_REASON,
|
|
699
|
+
directions: SCROLL_DIRECTION
|
|
700
|
+
};
|
|
701
|
+
const scrollDeltas = /* @__PURE__ */ new Map();
|
|
702
|
+
const scrollTargetRects = /* @__PURE__ */ new Map();
|
|
703
|
+
let windowIndex = null;
|
|
704
|
+
const moveMouseToRect = async (rect) => {
|
|
705
|
+
if (!rect || !Number.isFinite(rect.left) || !Number.isFinite(rect.top)) return false;
|
|
706
|
+
const width = Number.isFinite(rect.width) ? rect.width : Math.max(0, (rect.right ?? rect.left) - rect.left);
|
|
707
|
+
const height = Number.isFinite(rect.height) ? rect.height : Math.max(0, (rect.bottom ?? rect.top) - rect.top);
|
|
708
|
+
const viewSize = page.viewportSize();
|
|
709
|
+
const viewW = viewSize?.width ?? null;
|
|
710
|
+
const viewH = viewSize?.height ?? null;
|
|
711
|
+
if (viewW && viewH) {
|
|
712
|
+
const rectRight = rect.right ?? rect.left + width;
|
|
713
|
+
const rectBottom = rect.bottom ?? rect.top + height;
|
|
714
|
+
if (rectBottom < 0 || rect.top > viewH || rectRight < 0 || rect.left > viewW) {
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const rawX = rect.left + width / 2;
|
|
719
|
+
const rawY = rect.top + height / 2;
|
|
720
|
+
if (!Number.isFinite(rawX) || !Number.isFinite(rawY)) return false;
|
|
721
|
+
const padding = 6;
|
|
722
|
+
let x = rawX;
|
|
723
|
+
let y = rawY;
|
|
724
|
+
if (viewW && viewH) {
|
|
725
|
+
const maxX = Math.max(padding, viewW - padding);
|
|
726
|
+
const maxY = Math.max(padding, viewH - padding);
|
|
727
|
+
x = Math.min(Math.max(rawX, padding), maxX);
|
|
728
|
+
y = Math.min(Math.max(rawY, padding), maxY);
|
|
729
|
+
}
|
|
730
|
+
x += (Math.random() - 0.5) * 4;
|
|
731
|
+
y += (Math.random() - 0.5) * 4;
|
|
732
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return false;
|
|
733
|
+
await page.mouse.move(x, y, { steps: 6 }).catch(() => {
|
|
734
|
+
});
|
|
735
|
+
return true;
|
|
736
|
+
};
|
|
737
|
+
const wheelByChunks = async (delta, rect, isWindowTarget) => {
|
|
738
|
+
let remaining = delta;
|
|
739
|
+
const stepBase = Math.min(maxStep, 400);
|
|
740
|
+
while (Math.abs(remaining) > 1) {
|
|
741
|
+
const step = Math.abs(remaining) > stepBase ? Math.sign(remaining) * stepBase : remaining;
|
|
742
|
+
if (!isWindowTarget) {
|
|
743
|
+
await moveMouseToRect(rect);
|
|
744
|
+
}
|
|
745
|
+
await page.mouse.wheel(0, step);
|
|
746
|
+
remaining -= step;
|
|
747
|
+
await (0, import_delay2.default)(this.jitterMs(60, 0.35));
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
const restore = async () => {
|
|
751
|
+
if (scrollDeltas.size === 0) return;
|
|
752
|
+
try {
|
|
753
|
+
for (const [index, delta] of scrollDeltas.entries()) {
|
|
754
|
+
if (!delta) continue;
|
|
755
|
+
const rect = scrollTargetRects.get(index);
|
|
756
|
+
const isWindowTarget = windowIndex !== null && index === windowIndex;
|
|
757
|
+
await wheelByChunks(-delta, rect, isWindowTarget);
|
|
758
|
+
}
|
|
759
|
+
} catch (err) {
|
|
760
|
+
logger5.warn(`humanScroll | restore failed: ${err?.message || err}`);
|
|
761
|
+
}
|
|
762
|
+
};
|
|
639
763
|
const checkVisibility = async () => {
|
|
640
|
-
return await element.evaluate((el) => {
|
|
764
|
+
return await element.evaluate((el, payload) => {
|
|
765
|
+
const overflowRe = /(auto|scroll|overlay)/i;
|
|
766
|
+
const isScrollable = (node) => {
|
|
767
|
+
if (!node || node === document.body || node === document.documentElement) return false;
|
|
768
|
+
const style2 = window.getComputedStyle(node);
|
|
769
|
+
const overflowY = style2.overflowY;
|
|
770
|
+
return overflowRe.test(overflowY) && node.scrollHeight > node.clientHeight + 1;
|
|
771
|
+
};
|
|
772
|
+
const getScrollableAncestors = (node) => {
|
|
773
|
+
const list = [];
|
|
774
|
+
let current = node?.parentElement;
|
|
775
|
+
while (current && current !== document.body && current !== document.documentElement) {
|
|
776
|
+
if (isScrollable(current)) {
|
|
777
|
+
list.push(current);
|
|
778
|
+
}
|
|
779
|
+
current = current.parentElement;
|
|
780
|
+
}
|
|
781
|
+
return list;
|
|
782
|
+
};
|
|
783
|
+
if (!el || !el.isConnected) {
|
|
784
|
+
return { code: payload.codes.DETACHED, reason: payload.reasons.DETACHED };
|
|
785
|
+
}
|
|
786
|
+
const style = window.getComputedStyle(el);
|
|
787
|
+
if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0) {
|
|
788
|
+
return { code: payload.codes.HIDDEN, reason: payload.reasons.HIDDEN };
|
|
789
|
+
}
|
|
641
790
|
const rect = el.getBoundingClientRect();
|
|
642
791
|
if (!rect || rect.width === 0 || rect.height === 0) {
|
|
643
|
-
return { code:
|
|
792
|
+
return { code: payload.codes.ZERO_DIMENSIONS, reason: payload.reasons.ZERO_DIMENSIONS };
|
|
644
793
|
}
|
|
794
|
+
const hasFixedAncestor = (() => {
|
|
795
|
+
let node = el;
|
|
796
|
+
while (node && node !== document.body && node !== document.documentElement) {
|
|
797
|
+
const position = window.getComputedStyle(node).position;
|
|
798
|
+
if (position === "fixed") return true;
|
|
799
|
+
node = node.parentElement;
|
|
800
|
+
}
|
|
801
|
+
return false;
|
|
802
|
+
})();
|
|
803
|
+
const ancestors = getScrollableAncestors(el);
|
|
804
|
+
const targets = ancestors.map((node) => ({
|
|
805
|
+
isWindow: false,
|
|
806
|
+
rect: node.getBoundingClientRect(),
|
|
807
|
+
canScroll: node.scrollHeight > node.clientHeight + 1
|
|
808
|
+
}));
|
|
809
|
+
const windowTarget = {
|
|
810
|
+
isWindow: true,
|
|
811
|
+
rect: { top: 0, left: 0, right: window.innerWidth, bottom: window.innerHeight, width: window.innerWidth, height: window.innerHeight },
|
|
812
|
+
canScroll: document.documentElement.scrollHeight > window.innerHeight
|
|
813
|
+
};
|
|
814
|
+
targets.push(windowTarget);
|
|
815
|
+
const windowIndex2 = targets.length - 1;
|
|
645
816
|
const cx = rect.left + rect.width / 2;
|
|
646
817
|
const cy = rect.top + rect.height / 2;
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
818
|
+
const promoteToVisibleTarget = (index) => {
|
|
819
|
+
let idx = index;
|
|
820
|
+
while (idx < targets.length - 1) {
|
|
821
|
+
const current = targets[idx];
|
|
822
|
+
const parent = targets[idx + 1];
|
|
823
|
+
const c = current.rect;
|
|
824
|
+
const p = parent.rect;
|
|
825
|
+
const outOfParent = c.bottom < p.top || c.top > p.bottom || c.right < p.left || c.left > p.right;
|
|
826
|
+
if (outOfParent) {
|
|
827
|
+
idx += 1;
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
return idx;
|
|
833
|
+
};
|
|
834
|
+
let outIndex = -1;
|
|
835
|
+
let direction = payload.directions.UNKNOWN;
|
|
836
|
+
for (let i = 0; i < targets.length; i += 1) {
|
|
837
|
+
const bounds = targets[i].rect;
|
|
838
|
+
if (cy < bounds.top) {
|
|
839
|
+
outIndex = i;
|
|
840
|
+
direction = payload.directions.UP;
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
if (cy > bounds.bottom) {
|
|
844
|
+
outIndex = i;
|
|
845
|
+
direction = payload.directions.DOWN;
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
if (cx < bounds.left || cx > bounds.right) {
|
|
849
|
+
outIndex = i;
|
|
850
|
+
direction = payload.directions.UNKNOWN;
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (outIndex !== -1) {
|
|
855
|
+
let scrollTargetIndex = promoteToVisibleTarget(outIndex);
|
|
856
|
+
let target2 = targets[scrollTargetIndex];
|
|
857
|
+
if (!target2.isWindow) {
|
|
858
|
+
const targetRect = target2.rect;
|
|
859
|
+
const outOfWindow = targetRect.bottom < 0 || targetRect.top > window.innerHeight || targetRect.right < 0 || targetRect.left > window.innerWidth;
|
|
860
|
+
if (outOfWindow) {
|
|
861
|
+
scrollTargetIndex = windowIndex2;
|
|
862
|
+
target2 = windowTarget;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
code: payload.codes.OUT_OF_VIEWPORT,
|
|
867
|
+
reason: payload.reasons.OUT_OF_VIEWPORT,
|
|
868
|
+
direction,
|
|
869
|
+
rect,
|
|
870
|
+
viewH: target2.rect.height,
|
|
871
|
+
viewW: target2.rect.width,
|
|
872
|
+
cy,
|
|
873
|
+
cx,
|
|
874
|
+
scrollTargetIndex,
|
|
875
|
+
isWindow: target2.isWindow,
|
|
876
|
+
canScroll: target2.canScroll,
|
|
877
|
+
isFixed: hasFixedAncestor,
|
|
878
|
+
targetRect: target2.rect,
|
|
879
|
+
windowIndex: windowIndex2
|
|
880
|
+
};
|
|
652
881
|
}
|
|
653
882
|
const pointElement = document.elementFromPoint(cx, cy);
|
|
654
883
|
if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
|
|
884
|
+
let fallbackIndex = promoteToVisibleTarget(targets.length > 1 ? 0 : targets.length - 1);
|
|
885
|
+
let target2 = targets[fallbackIndex];
|
|
886
|
+
if (!target2.isWindow) {
|
|
887
|
+
const targetRect = target2.rect;
|
|
888
|
+
const outOfWindow = targetRect.bottom < 0 || targetRect.top > window.innerHeight || targetRect.right < 0 || targetRect.left > window.innerWidth;
|
|
889
|
+
if (outOfWindow) {
|
|
890
|
+
fallbackIndex = windowIndex2;
|
|
891
|
+
target2 = windowTarget;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
655
894
|
return {
|
|
656
|
-
code:
|
|
657
|
-
reason:
|
|
895
|
+
code: payload.codes.OBSTRUCTED,
|
|
896
|
+
reason: payload.reasons.OBSTRUCTED,
|
|
658
897
|
obstruction: {
|
|
659
898
|
tag: pointElement.tagName,
|
|
660
899
|
id: pointElement.id,
|
|
661
900
|
className: pointElement.className
|
|
662
901
|
},
|
|
902
|
+
rect,
|
|
903
|
+
viewH: target2.rect.height,
|
|
904
|
+
viewW: target2.rect.width,
|
|
663
905
|
cy,
|
|
664
|
-
|
|
665
|
-
|
|
906
|
+
cx,
|
|
907
|
+
scrollTargetIndex: fallbackIndex,
|
|
908
|
+
isWindow: target2.isWindow,
|
|
909
|
+
canScroll: target2.canScroll,
|
|
910
|
+
isFixed: hasFixedAncestor,
|
|
911
|
+
targetRect: target2.rect,
|
|
912
|
+
windowIndex: windowIndex2
|
|
666
913
|
};
|
|
667
914
|
}
|
|
668
|
-
return {
|
|
669
|
-
|
|
915
|
+
return {
|
|
916
|
+
code: payload.codes.VISIBLE,
|
|
917
|
+
rect,
|
|
918
|
+
viewH: windowTarget.rect.height,
|
|
919
|
+
viewW: windowTarget.rect.width,
|
|
920
|
+
cy,
|
|
921
|
+
cx,
|
|
922
|
+
scrollTargetIndex: targets.length - 1,
|
|
923
|
+
isWindow: true,
|
|
924
|
+
canScroll: windowTarget.canScroll,
|
|
925
|
+
isFixed: hasFixedAncestor,
|
|
926
|
+
targetRect: windowTarget.rect,
|
|
927
|
+
windowIndex: windowIndex2
|
|
928
|
+
};
|
|
929
|
+
}, visibilityPayload);
|
|
670
930
|
};
|
|
671
931
|
try {
|
|
672
932
|
for (let i = 0; i < maxSteps; i++) {
|
|
933
|
+
if (Date.now() - startAt > maxDurationMs) {
|
|
934
|
+
logger5.warn(`humanScroll | \u26A0\uFE0F \u8D85\u8FC7\u6700\u5927\u8017\u65F6 ${maxDurationMs}ms\uFF0C\u63D0\u524D\u7ED3\u675F`);
|
|
935
|
+
return { element, didScroll, restore };
|
|
936
|
+
}
|
|
673
937
|
const status = await checkVisibility();
|
|
674
|
-
if (status.
|
|
675
|
-
|
|
676
|
-
|
|
938
|
+
if (windowIndex === null && typeof status.windowIndex === "number") {
|
|
939
|
+
windowIndex = status.windowIndex;
|
|
940
|
+
}
|
|
941
|
+
if (typeof status.scrollTargetIndex === "number" && status.targetRect) {
|
|
942
|
+
scrollTargetRects.set(status.scrollTargetIndex, status.targetRect);
|
|
943
|
+
}
|
|
944
|
+
if (status.code === VISIBILITY_CODE.VISIBLE) {
|
|
945
|
+
logger5.info("humanScroll | \u5143\u7D20\u5DF2\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
|
|
946
|
+
return { element, didScroll, restore };
|
|
947
|
+
}
|
|
948
|
+
if (status.code === VISIBILITY_CODE.DETACHED || status.code === VISIBILITY_CODE.HIDDEN || status.code === VISIBILITY_CODE.ZERO_DIMENSIONS) {
|
|
949
|
+
logger5.warn(`humanScroll | ${status.reason || "\u5143\u7D20\u4E0D\u53EF\u7528"}`);
|
|
950
|
+
return { element, didScroll, restore };
|
|
951
|
+
}
|
|
952
|
+
const directionStr = status.direction ? `(${status.direction})` : "";
|
|
953
|
+
logger5.info(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${directionStr}`);
|
|
954
|
+
const currentRect = status.rect;
|
|
955
|
+
if (i > 0 && lastRect && currentRect) {
|
|
956
|
+
const dy = Math.abs(currentRect.top - lastRect.top);
|
|
957
|
+
const dx = Math.abs(currentRect.left - lastRect.left);
|
|
958
|
+
if (dy < SAME_POSITION_THRESHOLD_PX && dx < SAME_POSITION_THRESHOLD_PX) {
|
|
959
|
+
samePositionCount++;
|
|
960
|
+
logger5.info(`humanScroll | \u26A0\uFE0F \u8B66\u544A: \u6EDA\u52A8\u540E\u4F4D\u7F6E\u672A\u53D8 (${samePositionCount}/${SAME_POSITION_LIMIT}). dy=${dy}`);
|
|
961
|
+
if (samePositionCount >= SAME_POSITION_LIMIT) {
|
|
962
|
+
logger5.warn("humanScroll | \u26A0\uFE0F \u68C0\u6D4B\u5230\u65E0\u6548\u6EDA\u52A8 (\u5143\u7D20\u53EF\u80FD\u662F Fixed \u6216\u4F4D\u4E8E\u4E0D\u53EF\u6EDA\u52A8\u7684\u5BB9\u5668)\uFF0C\u5F3A\u5236\u505C\u6B62\uFF0C\u5C1D\u8BD5\u76F4\u63A5\u4EA4\u4E92");
|
|
963
|
+
return { element, didScroll, restore };
|
|
964
|
+
}
|
|
965
|
+
} else {
|
|
966
|
+
samePositionCount = 0;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (currentRect) lastRect = currentRect;
|
|
970
|
+
if (status.code === VISIBILITY_CODE.OBSTRUCTED && status.obstruction) {
|
|
971
|
+
logger5.info(`humanScroll | \u88AB\u906E\u6321: <${status.obstruction.tag}.${status.obstruction.className}>`);
|
|
677
972
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
973
|
+
if (status.code === VISIBILITY_CODE.OBSTRUCTED && status.isFixed) {
|
|
974
|
+
logger5.warn("humanScroll | \u5143\u7D20\u4E3A Fixed \u4E14\u88AB\u906E\u6321\uFF0C\u505C\u6B62\u6EDA\u52A8\u5C1D\u8BD5");
|
|
975
|
+
return { element, didScroll, restore };
|
|
976
|
+
}
|
|
977
|
+
if (!status.canScroll) {
|
|
978
|
+
logger5.warn("humanScroll | \u5F53\u524D\u5BB9\u5668\u4E0D\u53EF\u6EDA\u52A8\uFF0C\u505C\u6B62\u6EDA\u52A8\u5C1D\u8BD5");
|
|
979
|
+
return { element, didScroll, restore };
|
|
681
980
|
}
|
|
682
981
|
let deltaY = 0;
|
|
683
|
-
if (status.code ===
|
|
684
|
-
if (status.direction ===
|
|
982
|
+
if (status.code === VISIBILITY_CODE.OUT_OF_VIEWPORT) {
|
|
983
|
+
if (status.direction === SCROLL_DIRECTION.DOWN) {
|
|
685
984
|
deltaY = minStep + Math.random() * (maxStep - minStep);
|
|
686
|
-
} else if (status.direction ===
|
|
985
|
+
} else if (status.direction === SCROLL_DIRECTION.UP) {
|
|
687
986
|
deltaY = -(minStep + Math.random() * (maxStep - minStep));
|
|
688
987
|
} else {
|
|
689
988
|
deltaY = 100;
|
|
690
989
|
}
|
|
691
|
-
} else if (status.code ===
|
|
990
|
+
} else if (status.code === VISIBILITY_CODE.OBSTRUCTED) {
|
|
692
991
|
const isBottomHalf = status.cy > status.viewH / 2;
|
|
693
|
-
const
|
|
694
|
-
deltaY =
|
|
992
|
+
const dir = isBottomHalf ? 1 : -1;
|
|
993
|
+
deltaY = dir * (minStep + Math.random() * 50);
|
|
695
994
|
}
|
|
696
995
|
if (i === 0 || Math.random() < 0.2) {
|
|
697
996
|
const viewSize = page.viewportSize();
|
|
698
997
|
if (viewSize) {
|
|
699
|
-
const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) *
|
|
700
|
-
const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) *
|
|
701
|
-
await cursor.actions.move({ x: safeX, y: safeY })
|
|
998
|
+
const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * SAFE_MOUSE_JITTER_PX;
|
|
999
|
+
const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * SAFE_MOUSE_JITTER_PX;
|
|
1000
|
+
await cursor.actions.move({ x: safeX, y: safeY }).catch(() => {
|
|
1001
|
+
});
|
|
702
1002
|
}
|
|
703
1003
|
}
|
|
1004
|
+
if (!status.isWindow) {
|
|
1005
|
+
await moveMouseToRect(status.targetRect);
|
|
1006
|
+
}
|
|
704
1007
|
await page.mouse.wheel(0, deltaY);
|
|
1008
|
+
if (typeof status.scrollTargetIndex === "number") {
|
|
1009
|
+
const prev = scrollDeltas.get(status.scrollTargetIndex) || 0;
|
|
1010
|
+
scrollDeltas.set(status.scrollTargetIndex, prev + deltaY);
|
|
1011
|
+
}
|
|
705
1012
|
didScroll = true;
|
|
706
|
-
await (0, import_delay2.default)(this.jitterMs(
|
|
1013
|
+
await (0, import_delay2.default)(this.jitterMs(150 + Math.random() * 100, 0.2));
|
|
707
1014
|
}
|
|
708
|
-
logger5.warn(`humanScroll | \
|
|
709
|
-
return { element, didScroll };
|
|
1015
|
+
logger5.warn(`humanScroll | \u26A0\uFE0F \u8FBE\u5230\u6700\u5927\u6B65\u6570 (${maxSteps}) \u4ECD\u65E0\u6CD5\u5B8C\u5168\u53EF\u89C1\uFF0C\u653E\u5F03\u6EDA\u52A8`);
|
|
1016
|
+
return { element, didScroll, restore };
|
|
710
1017
|
} catch (error) {
|
|
711
1018
|
logger5.fail("humanScroll", error);
|
|
712
1019
|
throw error;
|
|
@@ -716,7 +1023,7 @@ var Humanize = {
|
|
|
716
1023
|
* 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
|
|
717
1024
|
*
|
|
718
1025
|
* @param {import('playwright').Page} page
|
|
719
|
-
* @param {string|import('playwright').ElementHandle} [target] - CSS
|
|
1026
|
+
* @param {string|import('playwright').ElementHandle|import('playwright').Locator} [target] - CSS 选择器、元素句柄或 Locator。如果为空,则点击当前鼠标位置
|
|
720
1027
|
* @param {Object} [options]
|
|
721
1028
|
* @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
|
|
722
1029
|
* @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
|
|
@@ -725,8 +1032,10 @@ var Humanize = {
|
|
|
725
1032
|
async humanClick(page, target, options = {}) {
|
|
726
1033
|
const cursor = $GetCursor(page);
|
|
727
1034
|
const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
|
|
728
|
-
const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
|
|
1035
|
+
const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : isLocatorLike(target) ? "Locator" : "ElementHandle";
|
|
729
1036
|
logger5.start("humanClick", `target=${targetDesc}`);
|
|
1037
|
+
let element = null;
|
|
1038
|
+
let owned = false;
|
|
730
1039
|
const restoreOnce = async () => {
|
|
731
1040
|
if (restoreOnce.restored) return;
|
|
732
1041
|
restoreOnce.restored = true;
|
|
@@ -745,18 +1054,15 @@ var Humanize = {
|
|
|
745
1054
|
logger5.success("humanClick", "Clicked current position");
|
|
746
1055
|
return true;
|
|
747
1056
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
755
|
-
logger5.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
|
|
756
|
-
return false;
|
|
1057
|
+
const resolved = await resolveElementHandle(page, target);
|
|
1058
|
+
element = resolved.element;
|
|
1059
|
+
owned = resolved.owned;
|
|
1060
|
+
if (!element) {
|
|
1061
|
+
if (throwOnMissing) {
|
|
1062
|
+
throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${targetDesc}`);
|
|
757
1063
|
}
|
|
758
|
-
|
|
759
|
-
|
|
1064
|
+
logger5.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
|
|
1065
|
+
return false;
|
|
760
1066
|
}
|
|
761
1067
|
if (scrollIfNeeded) {
|
|
762
1068
|
const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
|
|
@@ -765,6 +1071,7 @@ var Humanize = {
|
|
|
765
1071
|
const box = await element.boundingBox();
|
|
766
1072
|
if (!box) {
|
|
767
1073
|
await restoreOnce();
|
|
1074
|
+
await disposeElementHandle(element, owned);
|
|
768
1075
|
if (throwOnMissing) {
|
|
769
1076
|
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
|
|
770
1077
|
}
|
|
@@ -777,10 +1084,12 @@ var Humanize = {
|
|
|
777
1084
|
await (0, import_delay2.default)(this.jitterMs(reactionDelay, 0.4));
|
|
778
1085
|
await cursor.actions.click();
|
|
779
1086
|
await restoreOnce();
|
|
1087
|
+
await disposeElementHandle(element, owned);
|
|
780
1088
|
logger5.success("humanClick");
|
|
781
1089
|
return true;
|
|
782
1090
|
} catch (error) {
|
|
783
1091
|
await restoreOnce();
|
|
1092
|
+
await disposeElementHandle(element, owned);
|
|
784
1093
|
logger5.fail("humanClick", error);
|
|
785
1094
|
throw error;
|
|
786
1095
|
}
|
|
@@ -917,34 +1226,6 @@ var Humanize = {
|
|
|
917
1226
|
logger5.fail("warmUpBrowsing", error);
|
|
918
1227
|
throw error;
|
|
919
1228
|
}
|
|
920
|
-
},
|
|
921
|
-
/**
|
|
922
|
-
* 自然滚动 - 带惯性、减速效果和随机抖动
|
|
923
|
-
* @param {import('playwright').Page} page
|
|
924
|
-
* @param {'up' | 'down'} [direction='down'] - 滚动方向
|
|
925
|
-
* @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
|
|
926
|
-
* @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
|
|
927
|
-
*/
|
|
928
|
-
async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
|
|
929
|
-
const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
|
|
930
|
-
const actualDistance = this.jitterMs(distance, 0.15);
|
|
931
|
-
logger5.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
|
|
932
|
-
const sign = direction === "down" ? 1 : -1;
|
|
933
|
-
const stepDistance = actualDistance / steps;
|
|
934
|
-
try {
|
|
935
|
-
for (let i = 0; i < steps; i++) {
|
|
936
|
-
const factor = 1 - i / steps * 0.5;
|
|
937
|
-
const jitter = 0.9 + Math.random() * 0.2;
|
|
938
|
-
const scrollAmount = stepDistance * factor * sign * jitter;
|
|
939
|
-
await page.mouse.wheel(0, scrollAmount);
|
|
940
|
-
const baseDelay = 60 + i * 25;
|
|
941
|
-
await (0, import_delay2.default)(this.jitterMs(baseDelay, 0.3));
|
|
942
|
-
}
|
|
943
|
-
logger5.success("naturalScroll");
|
|
944
|
-
} catch (error) {
|
|
945
|
-
logger5.fail("naturalScroll", error);
|
|
946
|
-
throw error;
|
|
947
|
-
}
|
|
948
1229
|
}
|
|
949
1230
|
};
|
|
950
1231
|
|