@skrillex1224/playwright-toolkit 2.1.247 → 2.1.249
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 +457 -168
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +457 -168
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -106,10 +106,10 @@ var createActorInfo = (info) => {
|
|
|
106
106
|
xurl
|
|
107
107
|
};
|
|
108
108
|
};
|
|
109
|
-
const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path:
|
|
109
|
+
const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path4 }) => {
|
|
110
110
|
const safeProtocol = String(protocol2).trim();
|
|
111
111
|
const safeDomain = normalizeDomain(domain2);
|
|
112
|
-
const safePath = normalizePath(
|
|
112
|
+
const safePath = normalizePath(path4);
|
|
113
113
|
return `${safeProtocol}://${safeDomain}${safePath}`;
|
|
114
114
|
};
|
|
115
115
|
const buildIcon = ({ key }) => {
|
|
@@ -117,14 +117,14 @@ var createActorInfo = (info) => {
|
|
|
117
117
|
};
|
|
118
118
|
const protocol = info.protocol || "https";
|
|
119
119
|
const domain = normalizeDomain(info.domain);
|
|
120
|
-
const
|
|
120
|
+
const path3 = normalizePath(info.path);
|
|
121
121
|
const share = normalizeShare2(info.share);
|
|
122
122
|
const device = normalizeDevice(info.device);
|
|
123
123
|
return {
|
|
124
124
|
...info,
|
|
125
125
|
protocol,
|
|
126
126
|
domain,
|
|
127
|
-
path:
|
|
127
|
+
path: path3,
|
|
128
128
|
share,
|
|
129
129
|
device,
|
|
130
130
|
get icon() {
|
|
@@ -1484,7 +1484,7 @@ var normalizeCookies = (value) => {
|
|
|
1484
1484
|
if (!name || !cookieValue || cookieValue === "<nil>") return null;
|
|
1485
1485
|
const domain = String(raw.domain || "").trim();
|
|
1486
1486
|
const url = normalizeHttpUrl(raw.url);
|
|
1487
|
-
const
|
|
1487
|
+
const path3 = String(raw.path || "").trim() || "/";
|
|
1488
1488
|
const sameSite = normalizeCookieSameSite(raw.sameSite);
|
|
1489
1489
|
const expires = normalizeCookieExpires(raw);
|
|
1490
1490
|
const secure = Boolean(raw.secure);
|
|
@@ -1493,7 +1493,7 @@ var normalizeCookies = (value) => {
|
|
|
1493
1493
|
const normalized = {
|
|
1494
1494
|
name,
|
|
1495
1495
|
value: cookieValue,
|
|
1496
|
-
path:
|
|
1496
|
+
path: path3,
|
|
1497
1497
|
...domain ? { domain } : {},
|
|
1498
1498
|
...!domain && url ? { url } : {},
|
|
1499
1499
|
...sameSite ? { sameSite } : {},
|
|
@@ -2564,6 +2564,10 @@ var assertPoint = (point) => {
|
|
|
2564
2564
|
throw new Error(`Invalid input point: ${JSON.stringify(point)}`);
|
|
2565
2565
|
}
|
|
2566
2566
|
};
|
|
2567
|
+
var toFiniteNumber = (value, fallback = 0) => {
|
|
2568
|
+
const number = Number(value);
|
|
2569
|
+
return Number.isFinite(number) ? number : fallback;
|
|
2570
|
+
};
|
|
2567
2571
|
var dispatchMouseMove = (page, point, options = {}) => page.mouse.move(point.x, point.y, options);
|
|
2568
2572
|
var dispatchMouseStart = (page, options = {}) => page.mouse.down(options);
|
|
2569
2573
|
var dispatchMouseEnd = (page, options = {}) => page.mouse.up(options);
|
|
@@ -2583,6 +2587,11 @@ var dragWithMouse = async (page, points, options = {}) => {
|
|
|
2583
2587
|
}, { steps: 2 });
|
|
2584
2588
|
await waitFor(page, options.stepDelayMs ?? 90);
|
|
2585
2589
|
}
|
|
2590
|
+
const finalMoveRepeats = Math.max(0, Math.floor(toFiniteNumber(options.finalMoveRepeats)));
|
|
2591
|
+
for (let repeat = 0; repeat < finalMoveRepeats; repeat += 1) {
|
|
2592
|
+
await dispatchMouseMove(page, points.end, { steps: 1 });
|
|
2593
|
+
await waitFor(page, options.finalMoveDelayMs ?? 35);
|
|
2594
|
+
}
|
|
2586
2595
|
await waitFor(page, options.beforeReleaseDelayMs ?? 100);
|
|
2587
2596
|
await dispatchMouseEnd(page);
|
|
2588
2597
|
await waitFor(page, options.afterReleaseDelayMs ?? 100);
|
|
@@ -2592,6 +2601,7 @@ var dragWithTouch = async (page, points, options = {}) => {
|
|
|
2592
2601
|
let client = null;
|
|
2593
2602
|
try {
|
|
2594
2603
|
client = await page.context().newCDPSession(page);
|
|
2604
|
+
await waitFor(page, options.initialDelayMs ?? 250);
|
|
2595
2605
|
await client.send("Input.dispatchTouchEvent", {
|
|
2596
2606
|
type: "touchStart",
|
|
2597
2607
|
touchPoints: [{ x: points.start.x, y: points.start.y, id: 1 }]
|
|
@@ -2613,6 +2623,14 @@ var dragWithTouch = async (page, points, options = {}) => {
|
|
|
2613
2623
|
});
|
|
2614
2624
|
await waitFor(page, options.stepDelayMs ?? 90);
|
|
2615
2625
|
}
|
|
2626
|
+
const finalMoveRepeats = Math.max(0, Math.floor(toFiniteNumber(options.finalMoveRepeats)));
|
|
2627
|
+
for (let repeat = 0; repeat < finalMoveRepeats; repeat += 1) {
|
|
2628
|
+
await client.send("Input.dispatchTouchEvent", {
|
|
2629
|
+
type: "touchMove",
|
|
2630
|
+
touchPoints: [{ x: points.end.x, y: points.end.y, id: 1 }]
|
|
2631
|
+
});
|
|
2632
|
+
await waitFor(page, options.finalMoveDelayMs ?? 35);
|
|
2633
|
+
}
|
|
2616
2634
|
await waitFor(page, options.beforeReleaseDelayMs ?? 100);
|
|
2617
2635
|
await client.send("Input.dispatchTouchEvent", {
|
|
2618
2636
|
type: "touchEnd",
|
|
@@ -2630,7 +2648,7 @@ var dragWithTouch = async (page, points, options = {}) => {
|
|
|
2630
2648
|
var clickTargetWithDevice = async (page, target, options = {}) => {
|
|
2631
2649
|
const normalizedOptions = normalizeSelectorOptions(options);
|
|
2632
2650
|
const resolvedDevice = resolveDeviceFromPage(page);
|
|
2633
|
-
if (target && resolvedDevice === Device.Mobile && !normalizedOptions.forceClick) {
|
|
2651
|
+
if (target && resolvedDevice === Device.Mobile && !normalizedOptions.forceClick && !normalizedOptions.forceMouse) {
|
|
2634
2652
|
if (typeof target.tap === "function") {
|
|
2635
2653
|
await target.tap(normalizedOptions.tapOptions);
|
|
2636
2654
|
return true;
|
|
@@ -2830,20 +2848,26 @@ var DeviceInput = {
|
|
|
2830
2848
|
throw new Error("Unable to resolve drag coordinates.");
|
|
2831
2849
|
}
|
|
2832
2850
|
const steps = options.steps || 10;
|
|
2851
|
+
const sourceOffsetX = toFiniteNumber(options.sourceOffsetX);
|
|
2852
|
+
const sourceOffsetY = toFiniteNumber(options.sourceOffsetY);
|
|
2853
|
+
const targetOffsetX = toFiniteNumber(options.targetOffsetX);
|
|
2854
|
+
const targetOffsetY = toFiniteNumber(options.targetOffsetY);
|
|
2855
|
+
const sourceCenterX = sourceBox.x + sourceBox.width / 2 + sourceOffsetX;
|
|
2856
|
+
const sourceCenterY = sourceBox.y + sourceBox.height / 2 + sourceOffsetY;
|
|
2833
2857
|
const liftOffsetX = Math.min(18, Math.max(8, sourceBox.width * 0.12));
|
|
2834
2858
|
const liftOffsetY = Math.min(12, Math.max(4, sourceBox.height * 0.08));
|
|
2835
2859
|
const points = {
|
|
2836
2860
|
start: {
|
|
2837
|
-
x:
|
|
2838
|
-
y:
|
|
2861
|
+
x: sourceCenterX,
|
|
2862
|
+
y: sourceCenterY
|
|
2839
2863
|
},
|
|
2840
2864
|
lift: {
|
|
2841
|
-
x:
|
|
2842
|
-
y:
|
|
2865
|
+
x: sourceCenterX + liftOffsetX,
|
|
2866
|
+
y: sourceCenterY + liftOffsetY
|
|
2843
2867
|
},
|
|
2844
2868
|
end: {
|
|
2845
|
-
x: targetBox.x + targetBox.width / 2,
|
|
2846
|
-
y: targetBox.y + targetBox.height / 2
|
|
2869
|
+
x: targetBox.x + targetBox.width / 2 + targetOffsetX,
|
|
2870
|
+
y: targetBox.y + targetBox.height / 2 + targetOffsetY
|
|
2847
2871
|
},
|
|
2848
2872
|
steps
|
|
2849
2873
|
};
|
|
@@ -3379,13 +3403,6 @@ var jitterMs = (base, jitterPercent = 0.3) => {
|
|
|
3379
3403
|
const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
|
|
3380
3404
|
return Math.max(10, Math.round(Number(base || 0) + jitter));
|
|
3381
3405
|
};
|
|
3382
|
-
var randomPointInBox = (box, paddingRatio = 0.24) => {
|
|
3383
|
-
const safePaddingX = Math.max(2, Math.min(box.width * paddingRatio, box.width / 2));
|
|
3384
|
-
const safePaddingY = Math.max(2, Math.min(box.height * paddingRatio, box.height / 2));
|
|
3385
|
-
const x = box.x + safePaddingX + Math.random() * Math.max(1, box.width - safePaddingX * 2);
|
|
3386
|
-
const y = box.y + safePaddingY + Math.random() * Math.max(1, box.height - safePaddingY * 2);
|
|
3387
|
-
return { x, y };
|
|
3388
|
-
};
|
|
3389
3406
|
var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
|
|
3390
3407
|
if (target == null) return null;
|
|
3391
3408
|
let element = target;
|
|
@@ -3430,6 +3447,10 @@ var clipBoxToViewport = (box, viewport) => {
|
|
|
3430
3447
|
}
|
|
3431
3448
|
return box;
|
|
3432
3449
|
};
|
|
3450
|
+
var centerPointInBox = (box) => ({
|
|
3451
|
+
x: box.x + box.width / 2,
|
|
3452
|
+
y: box.y + box.height / 2
|
|
3453
|
+
});
|
|
3433
3454
|
var withTimeout = async (operation, timeoutMs, label) => {
|
|
3434
3455
|
const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
|
|
3435
3456
|
let timeoutId = null;
|
|
@@ -3581,97 +3602,6 @@ var checkElementVisibility = async (element) => {
|
|
|
3581
3602
|
return { code: "VISIBLE", isFixed, positioning };
|
|
3582
3603
|
});
|
|
3583
3604
|
};
|
|
3584
|
-
var resolveSafeTapPoint = async (element) => {
|
|
3585
|
-
return element.evaluate((el) => {
|
|
3586
|
-
const rect = el.getBoundingClientRect();
|
|
3587
|
-
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
3588
|
-
return null;
|
|
3589
|
-
}
|
|
3590
|
-
const viewW = window.innerWidth;
|
|
3591
|
-
const viewH = window.innerHeight;
|
|
3592
|
-
let clipLeft = 0;
|
|
3593
|
-
let clipRight = viewW;
|
|
3594
|
-
let clipTop = 0;
|
|
3595
|
-
let clipBottom = viewH;
|
|
3596
|
-
for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
|
|
3597
|
-
const style = window.getComputedStyle(node);
|
|
3598
|
-
if (!style) continue;
|
|
3599
|
-
const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
|
|
3600
|
-
const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
|
|
3601
|
-
if (!clipsX && !clipsY) continue;
|
|
3602
|
-
const nodeRect = node.getBoundingClientRect();
|
|
3603
|
-
if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
|
|
3604
|
-
if (clipsX) {
|
|
3605
|
-
clipLeft = Math.max(clipLeft, nodeRect.left);
|
|
3606
|
-
clipRight = Math.min(clipRight, nodeRect.right);
|
|
3607
|
-
}
|
|
3608
|
-
if (clipsY) {
|
|
3609
|
-
clipTop = Math.max(clipTop, nodeRect.top);
|
|
3610
|
-
clipBottom = Math.min(clipBottom, nodeRect.bottom);
|
|
3611
|
-
}
|
|
3612
|
-
}
|
|
3613
|
-
const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
|
|
3614
|
-
const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
|
|
3615
|
-
const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
|
|
3616
|
-
const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
|
|
3617
|
-
const visibleWidth = visibleRight - visibleLeft;
|
|
3618
|
-
const visibleHeight = visibleBottom - visibleTop;
|
|
3619
|
-
if (visibleWidth <= 1 || visibleHeight <= 1) {
|
|
3620
|
-
return null;
|
|
3621
|
-
}
|
|
3622
|
-
const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
|
|
3623
|
-
const commonAncestor = (a, b) => {
|
|
3624
|
-
for (let current = a; current && !isRootNode(current); current = current.parentElement) {
|
|
3625
|
-
if (current.contains(b)) return current;
|
|
3626
|
-
}
|
|
3627
|
-
return null;
|
|
3628
|
-
};
|
|
3629
|
-
const sameTapTarget = (pointElement) => {
|
|
3630
|
-
if (!pointElement) return false;
|
|
3631
|
-
if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
|
|
3632
|
-
return true;
|
|
3633
|
-
}
|
|
3634
|
-
const common = commonAncestor(el, pointElement);
|
|
3635
|
-
if (!common) return false;
|
|
3636
|
-
const commonRect = common.getBoundingClientRect?.();
|
|
3637
|
-
if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
|
|
3638
|
-
const commonArea = commonRect.width * commonRect.height;
|
|
3639
|
-
const targetArea = Math.max(1, rect.width * rect.height);
|
|
3640
|
-
const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
|
|
3641
|
-
if (commonArea > maxSharedRegionArea) return false;
|
|
3642
|
-
if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
|
|
3643
|
-
return false;
|
|
3644
|
-
}
|
|
3645
|
-
return common.contains(el) && common.contains(pointElement);
|
|
3646
|
-
};
|
|
3647
|
-
const cx = visibleLeft + visibleWidth / 2;
|
|
3648
|
-
const cy = visibleTop + visibleHeight / 2;
|
|
3649
|
-
const smallX = Math.min(12, Math.max(2, visibleWidth * 0.18));
|
|
3650
|
-
const smallY = Math.min(12, Math.max(2, visibleHeight * 0.18));
|
|
3651
|
-
const points = [
|
|
3652
|
-
{ x: cx, y: cy },
|
|
3653
|
-
{ x: visibleLeft + smallX, y: visibleTop + smallY },
|
|
3654
|
-
{ x: visibleRight - smallX, y: visibleTop + smallY },
|
|
3655
|
-
{ x: visibleLeft + smallX, y: visibleBottom - smallY },
|
|
3656
|
-
{ x: visibleRight - smallX, y: visibleBottom - smallY },
|
|
3657
|
-
{ x: cx, y: visibleTop + Math.min(10, Math.max(2, visibleHeight * 0.2)) },
|
|
3658
|
-
{ x: cx, y: visibleBottom - Math.min(10, Math.max(2, visibleHeight * 0.2)) },
|
|
3659
|
-
{ x: visibleLeft + Math.min(10, Math.max(2, visibleWidth * 0.2)), y: cy },
|
|
3660
|
-
{ x: visibleRight - Math.min(10, Math.max(2, visibleWidth * 0.2)), y: cy }
|
|
3661
|
-
];
|
|
3662
|
-
const safePoints = points.filter((point) => {
|
|
3663
|
-
const pointElement = document.elementFromPoint(point.x, point.y);
|
|
3664
|
-
return sameTapTarget(pointElement);
|
|
3665
|
-
});
|
|
3666
|
-
const candidates = safePoints.length ? safePoints : points;
|
|
3667
|
-
const chosen = candidates[Math.floor(Math.random() * candidates.length)];
|
|
3668
|
-
if (!chosen) return null;
|
|
3669
|
-
return {
|
|
3670
|
-
x: chosen.x,
|
|
3671
|
-
y: chosen.y
|
|
3672
|
-
};
|
|
3673
|
-
});
|
|
3674
|
-
};
|
|
3675
3605
|
var activateElementFallback = async (element, point = null, options = {}) => {
|
|
3676
3606
|
return element.evaluate((el, { innerOptions }) => {
|
|
3677
3607
|
const isEditable = (node) => {
|
|
@@ -4256,8 +4186,8 @@ var MobileHumanize = {
|
|
|
4256
4186
|
return false;
|
|
4257
4187
|
}
|
|
4258
4188
|
await waitJitter(reactionDelay, 0.45);
|
|
4259
|
-
const
|
|
4260
|
-
const tapTarget =
|
|
4189
|
+
const visibleBox = clipBoxToViewport(box, resolveViewport(page));
|
|
4190
|
+
const tapTarget = centerPointInBox(visibleBox);
|
|
4261
4191
|
try {
|
|
4262
4192
|
await tapPoint(page, tapTarget, {
|
|
4263
4193
|
timeoutMs: tapTimeoutMs,
|
|
@@ -4960,6 +4890,10 @@ var LiveView = {
|
|
|
4960
4890
|
// src/chaptcha.js
|
|
4961
4891
|
import { v4 as uuidv4 } from "uuid";
|
|
4962
4892
|
|
|
4893
|
+
// src/internals/captcha/bytedance.js
|
|
4894
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
4895
|
+
import path2 from "path";
|
|
4896
|
+
|
|
4963
4897
|
// src/internals/captcha/shared.js
|
|
4964
4898
|
var waitForVisible = async (locator, timeout) => {
|
|
4965
4899
|
try {
|
|
@@ -4977,38 +4911,71 @@ var isAnyCaptchaTextVisible = async (frame, texts, timeout) => {
|
|
|
4977
4911
|
if (!text) {
|
|
4978
4912
|
continue;
|
|
4979
4913
|
}
|
|
4980
|
-
const
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4914
|
+
const textLocator = frame.getByText(text, { exact: false });
|
|
4915
|
+
const locatorText = frame.locator(`text=${text}`);
|
|
4916
|
+
const candidateGroups = [textLocator, locatorText];
|
|
4917
|
+
for (const candidateGroup of candidateGroups) {
|
|
4918
|
+
const candidateCount = await candidateGroup.count().catch(() => 0);
|
|
4919
|
+
for (let index = 0; index < candidateCount; index += 1) {
|
|
4920
|
+
const candidate = candidateGroup.nth(index);
|
|
4921
|
+
const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
|
|
4922
|
+
if (isVisible) {
|
|
4923
|
+
return true;
|
|
4924
|
+
}
|
|
4988
4925
|
}
|
|
4989
4926
|
}
|
|
4990
4927
|
}
|
|
4991
4928
|
return false;
|
|
4992
4929
|
};
|
|
4930
|
+
var collectVisibleCandidateIndexes = async (candidateGroup, count, timeout) => {
|
|
4931
|
+
const visibleIndexes = [];
|
|
4932
|
+
for (let index = 0; index < count; index += 1) {
|
|
4933
|
+
const candidate = candidateGroup.nth(index);
|
|
4934
|
+
const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
|
|
4935
|
+
if (isVisible) {
|
|
4936
|
+
visibleIndexes.push(index);
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
return visibleIndexes;
|
|
4940
|
+
};
|
|
4993
4941
|
var clickCaptchaAction = async (frame, texts, options) => {
|
|
4994
4942
|
for (const text of texts || []) {
|
|
4995
|
-
const
|
|
4996
|
-
|
|
4997
|
-
|
|
4943
|
+
const textLocator = frame.getByText(text, { exact: false });
|
|
4944
|
+
const locatorText = frame.locator(`text=${text}`);
|
|
4945
|
+
const [getByTextCount, locatorTextCount] = await Promise.all([
|
|
4946
|
+
textLocator.count().catch(() => 0),
|
|
4947
|
+
locatorText.count().catch(() => 0)
|
|
4948
|
+
]);
|
|
4949
|
+
const [getByTextVisibleIndexes, locatorTextVisibleIndexes] = await Promise.all([
|
|
4950
|
+
collectVisibleCandidateIndexes(textLocator, getByTextCount, options.actionVisibleTimeoutMs),
|
|
4951
|
+
collectVisibleCandidateIndexes(locatorText, locatorTextCount, options.actionVisibleTimeoutMs)
|
|
4952
|
+
]);
|
|
4953
|
+
options.logger?.info(
|
|
4954
|
+
`[CaptchaAction] \u6587\u672C "${text}" \u547D\u4E2D\u6570\u91CF\uFF1AgetByText=${getByTextCount} (visible=${getByTextVisibleIndexes.length}), locator=${locatorTextCount} (visible=${locatorTextVisibleIndexes.length})`
|
|
4955
|
+
);
|
|
4956
|
+
const candidateGroups = [
|
|
4957
|
+
{ label: "getByText", locator: textLocator, count: getByTextCount },
|
|
4958
|
+
{ label: "locator", locator: locatorText, count: locatorTextCount }
|
|
4998
4959
|
];
|
|
4999
|
-
for (const
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
4960
|
+
for (const candidateGroup of candidateGroups) {
|
|
4961
|
+
for (let index = 0; index < candidateGroup.count; index += 1) {
|
|
4962
|
+
const candidate = candidateGroup.locator.nth(index);
|
|
4963
|
+
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
4964
|
+
if (!isVisible) {
|
|
4965
|
+
continue;
|
|
4966
|
+
}
|
|
4967
|
+
options.logger?.info(
|
|
4968
|
+
`[CaptchaAction] \u6587\u672C "${text}" \u9009\u62E9 ${candidateGroup.label}[${index}] \u4F5C\u4E3A\u7B2C\u4E00\u4E2A\u53EF\u89C1\u8282\u70B9\u6267\u884C\u70B9\u51FB\u3002`
|
|
4969
|
+
);
|
|
4970
|
+
await DeviceInput.click(options.page, candidate, options);
|
|
4971
|
+
return true;
|
|
5003
4972
|
}
|
|
5004
|
-
await DeviceInput.click(options.page, candidate);
|
|
5005
|
-
return true;
|
|
5006
4973
|
}
|
|
5007
4974
|
}
|
|
5008
4975
|
return false;
|
|
5009
4976
|
};
|
|
5010
|
-
var dragCaptchaAction = async (page, sourceLocator, targetLocator) => {
|
|
5011
|
-
await DeviceInput.drag(page, sourceLocator, targetLocator);
|
|
4977
|
+
var dragCaptchaAction = async (page, sourceLocator, targetLocator, options = {}) => {
|
|
4978
|
+
await DeviceInput.drag(page, sourceLocator, targetLocator, options);
|
|
5012
4979
|
};
|
|
5013
4980
|
|
|
5014
4981
|
// src/internals/captcha/bytedance.js
|
|
@@ -5019,11 +4986,12 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
|
5019
4986
|
containerSelector: "#captcha_container",
|
|
5020
4987
|
iframeSelector: 'iframe[src*="verifycenter"]',
|
|
5021
4988
|
iframeFallbackSelector: "iframe",
|
|
5022
|
-
sourceImageSelector: "
|
|
5023
|
-
dropTargetContainerSelector: "
|
|
4989
|
+
sourceImageSelector: ".img-container .canvas-container",
|
|
4990
|
+
dropTargetContainerSelector: ".drag-area",
|
|
5024
4991
|
dropTargetTexts: ["\u62D6\u62FD\u5230\u8FD9\u91CC"],
|
|
5025
4992
|
refreshTexts: ["\u5237\u65B0"],
|
|
5026
4993
|
submitTexts: ["\u63D0\u4EA4"],
|
|
4994
|
+
guideMaskSelector: ".play-guide-mask",
|
|
5027
4995
|
recognitionSuccessCode: 1e4,
|
|
5028
4996
|
containerVisibleTimeoutMs: 2e3,
|
|
5029
4997
|
iframeVisibleTimeoutMs: 12e3,
|
|
@@ -5046,10 +5014,111 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
|
5046
5014
|
],
|
|
5047
5015
|
recognitionDelayMs: 2e3,
|
|
5048
5016
|
refreshWaitMs: 3e3,
|
|
5049
|
-
submitWaitMs:
|
|
5017
|
+
submitWaitMs: 5e3,
|
|
5018
|
+
submitReadyTimeoutMs: 2500,
|
|
5050
5019
|
retryDelayBaseMs: 2e3,
|
|
5051
|
-
retryDelayStepMs: 1e3
|
|
5020
|
+
retryDelayStepMs: 1e3,
|
|
5021
|
+
sourceImageRowTolerancePx: 24,
|
|
5022
|
+
dragBetweenWaitMs: 250,
|
|
5023
|
+
promptBadgeCountSelector: ".drag-area .photo-badge .badge span",
|
|
5024
|
+
promptSubmitButtonSelector: ".vc-captcha-verify-mobile-button",
|
|
5025
|
+
promptSelectedSourceSelector: ".img-container .canvas-container.selected",
|
|
5026
|
+
promptActiveSourceSelector: ".img-container .canvas-container.active",
|
|
5027
|
+
promptDragMoveSteps: 16,
|
|
5028
|
+
promptDragStepDelayMs: 55,
|
|
5029
|
+
promptDragHoldDelayMs: 240,
|
|
5030
|
+
promptDragBeforeReleaseDelayMs: 180,
|
|
5031
|
+
promptDragAfterReleaseDelayMs: 240,
|
|
5032
|
+
promptDragFinalMoveRepeats: 3,
|
|
5033
|
+
promptDragRetryDelayMs: 250,
|
|
5034
|
+
debugArtifacts: false
|
|
5052
5035
|
});
|
|
5036
|
+
var PROMPT_CAPTCHA_DRAG_PLANS = Object.freeze([
|
|
5037
|
+
{ name: "lower-middle", endXRatio: 0.5, endYRatio: 0.72 },
|
|
5038
|
+
{ name: "center-middle", endXRatio: 0.5, endYRatio: 0.56 },
|
|
5039
|
+
{ name: "upper-middle", endXRatio: 0.5, endYRatio: 0.4 }
|
|
5040
|
+
]);
|
|
5041
|
+
var resolveCaptchaDebugDir = () => path2.resolve(process.cwd(), "storage", "captcha-debug");
|
|
5042
|
+
var rectOf = (rect) => {
|
|
5043
|
+
if (!rect) {
|
|
5044
|
+
return null;
|
|
5045
|
+
}
|
|
5046
|
+
return {
|
|
5047
|
+
x: Number(rect.x.toFixed(2)),
|
|
5048
|
+
y: Number(rect.y.toFixed(2)),
|
|
5049
|
+
width: Number(rect.width.toFixed(2)),
|
|
5050
|
+
height: Number(rect.height.toFixed(2))
|
|
5051
|
+
};
|
|
5052
|
+
};
|
|
5053
|
+
var collectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, extra = null) => {
|
|
5054
|
+
const timestamp = Date.now();
|
|
5055
|
+
const debugDir = resolveCaptchaDebugDir();
|
|
5056
|
+
await mkdir(debugDir, { recursive: true });
|
|
5057
|
+
const baseName = `bytedance-${timestamp}-attempt${attempt}-${phase}`;
|
|
5058
|
+
const iframeScreenshotPath = path2.join(debugDir, `${baseName}-iframe.png`);
|
|
5059
|
+
const pageScreenshotPath = path2.join(debugDir, `${baseName}-page.png`);
|
|
5060
|
+
const htmlPath = path2.join(debugDir, `${baseName}-iframe.html`);
|
|
5061
|
+
const infoPath = path2.join(debugDir, `${baseName}-info.json`);
|
|
5062
|
+
await iframeLocator.screenshot({ path: iframeScreenshotPath }).catch(() => {
|
|
5063
|
+
});
|
|
5064
|
+
await page.screenshot({ path: pageScreenshotPath, fullPage: true }).catch(() => {
|
|
5065
|
+
});
|
|
5066
|
+
const html = await frame.evaluate(() => document.documentElement.outerHTML).catch(() => "");
|
|
5067
|
+
if (html) {
|
|
5068
|
+
await writeFile(htmlPath, html, "utf8");
|
|
5069
|
+
}
|
|
5070
|
+
const info = await frame.evaluate(() => {
|
|
5071
|
+
const toRect = (element) => {
|
|
5072
|
+
const rect = element.getBoundingClientRect();
|
|
5073
|
+
return {
|
|
5074
|
+
x: Number(rect.x.toFixed(2)),
|
|
5075
|
+
y: Number(rect.y.toFixed(2)),
|
|
5076
|
+
width: Number(rect.width.toFixed(2)),
|
|
5077
|
+
height: Number(rect.height.toFixed(2))
|
|
5078
|
+
};
|
|
5079
|
+
};
|
|
5080
|
+
const toItem = (element, index) => ({
|
|
5081
|
+
index,
|
|
5082
|
+
tag: element.tagName,
|
|
5083
|
+
id: element.id || "",
|
|
5084
|
+
className: typeof element.className === "string" ? element.className : "",
|
|
5085
|
+
text: String(element.textContent || "").trim(),
|
|
5086
|
+
rect: toRect(element)
|
|
5087
|
+
});
|
|
5088
|
+
const visibleNodes = Array.from(document.querySelectorAll("body *")).map(toItem).filter((item) => item.rect.width > 0 && item.rect.height > 0);
|
|
5089
|
+
return {
|
|
5090
|
+
title: document.title,
|
|
5091
|
+
bodyText: String(document.body?.innerText || "").trim(),
|
|
5092
|
+
canvasContainers: visibleNodes.filter((item) => item.className.includes("canvas-container")),
|
|
5093
|
+
canvasNodes: visibleNodes.filter((item) => item.tag === "CANVAS"),
|
|
5094
|
+
captchaNodes: visibleNodes.filter((item) => item.id.startsWith("captcha_") || item.className.includes("captcha") || item.className.includes("verify") || /拖拽到这里|刷新|提交/.test(item.text)),
|
|
5095
|
+
visibleTextNodes: visibleNodes.filter((item) => item.text).slice(0, 300)
|
|
5096
|
+
};
|
|
5097
|
+
}).catch(() => null);
|
|
5098
|
+
if (info) {
|
|
5099
|
+
const payload = {
|
|
5100
|
+
capturedAt: new Date(timestamp).toISOString(),
|
|
5101
|
+
pageUrl: page.url(),
|
|
5102
|
+
attempt,
|
|
5103
|
+
phase,
|
|
5104
|
+
iframeScreenshotPath,
|
|
5105
|
+
pageScreenshotPath,
|
|
5106
|
+
htmlPath,
|
|
5107
|
+
info
|
|
5108
|
+
};
|
|
5109
|
+
if (extra != null) {
|
|
5110
|
+
payload.extra = extra;
|
|
5111
|
+
}
|
|
5112
|
+
await writeFile(infoPath, JSON.stringify(payload, null, 2), "utf8");
|
|
5113
|
+
}
|
|
5114
|
+
logger10.info(`\u5DF2\u5199\u51FA\u9A8C\u8BC1\u7801\u8C03\u8BD5\u4EA7\u7269\uFF1A${debugDir}`);
|
|
5115
|
+
};
|
|
5116
|
+
var maybeCollectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, options, extra = null) => {
|
|
5117
|
+
if (!options.debugArtifacts) {
|
|
5118
|
+
return;
|
|
5119
|
+
}
|
|
5120
|
+
await collectCaptchaDebugInfo(page, frame, iframeLocator, attempt, phase, extra);
|
|
5121
|
+
};
|
|
5053
5122
|
var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
5054
5123
|
const serialNumbers = apiResponse?.data?.data?.serial_number;
|
|
5055
5124
|
if (!Array.isArray(serialNumbers)) {
|
|
@@ -5058,7 +5127,7 @@ var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
|
5058
5127
|
return serialNumbers.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 0);
|
|
5059
5128
|
};
|
|
5060
5129
|
var resolveContentFrame = async (page, iframeLocator, options) => {
|
|
5061
|
-
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt
|
|
5130
|
+
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt += 1) {
|
|
5062
5131
|
const iframeHandle = await iframeLocator.elementHandle();
|
|
5063
5132
|
const frame = await iframeHandle?.contentFrame();
|
|
5064
5133
|
if (frame) {
|
|
@@ -5103,20 +5172,15 @@ var getVerifycenterCaptchaContext = async (page, options) => {
|
|
|
5103
5172
|
}
|
|
5104
5173
|
return { iframeLocator, frame };
|
|
5105
5174
|
};
|
|
5106
|
-
var refreshCaptcha = async (page, frame, options) => {
|
|
5107
|
-
const clicked = await clickCaptchaAction(frame, options.refreshTexts, { ...options, page }).catch(() => false);
|
|
5108
|
-
if (!clicked) {
|
|
5109
|
-
logger10.warn("Refresh button not found.");
|
|
5110
|
-
return false;
|
|
5111
|
-
}
|
|
5112
|
-
await page.waitForTimeout(options.refreshWaitMs);
|
|
5113
|
-
return true;
|
|
5114
|
-
};
|
|
5115
5175
|
var findCaptchaDropTarget = async (frame, options) => {
|
|
5176
|
+
const directTarget = frame.locator(options.dropTargetContainerSelector).first();
|
|
5177
|
+
if (await waitForVisible(directTarget, options.actionVisibleTimeoutMs)) {
|
|
5178
|
+
return directTarget;
|
|
5179
|
+
}
|
|
5116
5180
|
for (const text of options.dropTargetTexts) {
|
|
5117
5181
|
const candidates = [
|
|
5118
|
-
frame.
|
|
5119
|
-
frame.
|
|
5182
|
+
frame.getByText(text, { exact: false }).first(),
|
|
5183
|
+
frame.locator(`text=${text}`).first()
|
|
5120
5184
|
];
|
|
5121
5185
|
for (const candidate of candidates) {
|
|
5122
5186
|
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
@@ -5127,10 +5191,112 @@ var findCaptchaDropTarget = async (frame, options) => {
|
|
|
5127
5191
|
}
|
|
5128
5192
|
return null;
|
|
5129
5193
|
};
|
|
5194
|
+
var readPromptCaptchaState = async (frame, options) => frame.evaluate((selectors) => {
|
|
5195
|
+
const toRect = (element) => {
|
|
5196
|
+
if (!element) {
|
|
5197
|
+
return null;
|
|
5198
|
+
}
|
|
5199
|
+
const rect = element.getBoundingClientRect();
|
|
5200
|
+
return {
|
|
5201
|
+
x: Number(rect.x.toFixed(2)),
|
|
5202
|
+
y: Number(rect.y.toFixed(2)),
|
|
5203
|
+
width: Number(rect.width.toFixed(2)),
|
|
5204
|
+
height: Number(rect.height.toFixed(2))
|
|
5205
|
+
};
|
|
5206
|
+
};
|
|
5207
|
+
const badgeNode = document.querySelector(selectors.badgeCountSelector);
|
|
5208
|
+
const submitButton = document.querySelector(selectors.submitButtonSelector);
|
|
5209
|
+
const dragArea = document.querySelector(selectors.dragAreaSelector);
|
|
5210
|
+
const badgeCount = badgeNode ? Number.parseInt(String(badgeNode.textContent || "").trim(), 10) : 0;
|
|
5211
|
+
return {
|
|
5212
|
+
badgeCount: Number.isFinite(badgeCount) ? badgeCount : 0,
|
|
5213
|
+
selectedCount: document.querySelectorAll(selectors.selectedSourceSelector).length,
|
|
5214
|
+
activeCount: document.querySelectorAll(selectors.activeSourceSelector).length,
|
|
5215
|
+
submitDisabled: submitButton ? submitButton.classList.contains("disable") : null,
|
|
5216
|
+
dragAreaActive: dragArea ? dragArea.classList.contains("active") : false,
|
|
5217
|
+
dragAreaRect: toRect(dragArea)
|
|
5218
|
+
};
|
|
5219
|
+
}, {
|
|
5220
|
+
badgeCountSelector: options.promptBadgeCountSelector,
|
|
5221
|
+
submitButtonSelector: options.promptSubmitButtonSelector,
|
|
5222
|
+
selectedSourceSelector: options.promptSelectedSourceSelector,
|
|
5223
|
+
activeSourceSelector: options.promptActiveSourceSelector,
|
|
5224
|
+
dragAreaSelector: options.dropTargetContainerSelector
|
|
5225
|
+
}).catch(() => ({
|
|
5226
|
+
badgeCount: 0,
|
|
5227
|
+
selectedCount: 0,
|
|
5228
|
+
activeCount: 0,
|
|
5229
|
+
submitDisabled: null,
|
|
5230
|
+
dragAreaActive: false,
|
|
5231
|
+
dragAreaRect: null
|
|
5232
|
+
}));
|
|
5233
|
+
var normalizeCaptchaImageIndexes = (serialNumbers, imageCount) => {
|
|
5234
|
+
if (!Array.isArray(serialNumbers) || imageCount <= 0) {
|
|
5235
|
+
return [];
|
|
5236
|
+
}
|
|
5237
|
+
const areAllOneBased = serialNumbers.every((value) => value >= 1 && value <= imageCount);
|
|
5238
|
+
if (areAllOneBased) {
|
|
5239
|
+
return serialNumbers.map((value) => value - 1);
|
|
5240
|
+
}
|
|
5241
|
+
const areAllZeroBased = serialNumbers.every((value) => value >= 0 && value < imageCount);
|
|
5242
|
+
if (areAllZeroBased) {
|
|
5243
|
+
return [...serialNumbers];
|
|
5244
|
+
}
|
|
5245
|
+
return serialNumbers.map((value) => {
|
|
5246
|
+
if (value >= 1 && value <= imageCount) {
|
|
5247
|
+
return value - 1;
|
|
5248
|
+
}
|
|
5249
|
+
if (value >= 0 && value < imageCount) {
|
|
5250
|
+
return value;
|
|
5251
|
+
}
|
|
5252
|
+
return null;
|
|
5253
|
+
}).filter((value) => Number.isInteger(value));
|
|
5254
|
+
};
|
|
5255
|
+
var resolveCaptchaSourceImagesInVisualOrder = async (frame, options) => {
|
|
5256
|
+
const sourceImages = frame.locator(options.sourceImageSelector);
|
|
5257
|
+
const imageCount = await sourceImages.count().catch(() => 0);
|
|
5258
|
+
const sources = [];
|
|
5259
|
+
for (let domIndex = 0; domIndex < imageCount; domIndex += 1) {
|
|
5260
|
+
const locator = sourceImages.nth(domIndex);
|
|
5261
|
+
const box = await locator.boundingBox().catch(() => null);
|
|
5262
|
+
if (!box || box.width <= 0 || box.height <= 0) {
|
|
5263
|
+
continue;
|
|
5264
|
+
}
|
|
5265
|
+
sources.push({
|
|
5266
|
+
domIndex,
|
|
5267
|
+
locator,
|
|
5268
|
+
box
|
|
5269
|
+
});
|
|
5270
|
+
}
|
|
5271
|
+
sources.sort((left, right) => {
|
|
5272
|
+
const deltaY = left.box.y - right.box.y;
|
|
5273
|
+
if (Math.abs(deltaY) > options.sourceImageRowTolerancePx) {
|
|
5274
|
+
return deltaY;
|
|
5275
|
+
}
|
|
5276
|
+
return left.box.x - right.box.x;
|
|
5277
|
+
});
|
|
5278
|
+
return sources;
|
|
5279
|
+
};
|
|
5280
|
+
var refreshCaptcha = async (page, frame, options) => {
|
|
5281
|
+
const clicked = await clickCaptchaAction(frame, options.refreshTexts, {
|
|
5282
|
+
...options,
|
|
5283
|
+
page,
|
|
5284
|
+
logger: logger10,
|
|
5285
|
+
forceMouse: true
|
|
5286
|
+
}).catch(() => false);
|
|
5287
|
+
if (!clicked) {
|
|
5288
|
+
logger10.warn("Refresh button not found.");
|
|
5289
|
+
return false;
|
|
5290
|
+
}
|
|
5291
|
+
await page.waitForTimeout(options.refreshWaitMs);
|
|
5292
|
+
return true;
|
|
5293
|
+
};
|
|
5130
5294
|
var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
5131
5295
|
const deadline = Date.now() + options.challengeReadyTimeoutMs;
|
|
5132
5296
|
let refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
|
|
5133
5297
|
let hasSeenLoading = false;
|
|
5298
|
+
let hasSeenGuideMask = false;
|
|
5299
|
+
let hasLoggedGuideMask = false;
|
|
5134
5300
|
while (Date.now() < deadline) {
|
|
5135
5301
|
const isLoadingVisible = await isAnyCaptchaTextVisible(
|
|
5136
5302
|
frame,
|
|
@@ -5146,8 +5312,17 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5146
5312
|
const sourceImages = frame.locator(options.sourceImageSelector);
|
|
5147
5313
|
const imageCount = await sourceImages.count().catch(() => 0);
|
|
5148
5314
|
const hasVisibleSourceImage = imageCount > 0 ? await sourceImages.first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
|
|
5149
|
-
|
|
5150
|
-
|
|
5315
|
+
const hasVisibleDropTarget = await frame.locator(options.dropTargetContainerSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false);
|
|
5316
|
+
const hasGuideMaskVisible = options.guideMaskSelector ? await frame.locator(options.guideMaskSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
|
|
5317
|
+
hasSeenGuideMask = hasSeenGuideMask || hasGuideMaskVisible;
|
|
5318
|
+
if (hasGuideMaskVisible && !hasLoggedGuideMask) {
|
|
5319
|
+
logger10.info("\u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801\u64CD\u4F5C\u5F15\u5BFC\u5C42\uFF0C\u7B49\u5F85\u5176\u6D88\u5931\u540E\u518D\u5F00\u59CB\u8BC6\u522B\u3002");
|
|
5320
|
+
hasLoggedGuideMask = true;
|
|
5321
|
+
}
|
|
5322
|
+
if (!isLoadingVisible && hasVisibleSourceImage && hasVisibleDropTarget && !hasGuideMaskVisible) {
|
|
5323
|
+
logger10.info(
|
|
5324
|
+
hasSeenGuideMask ? "\u9A8C\u8BC1\u7801\u56FE\u7247\u548C\u62D6\u62FD\u533A\u57DF\u5DF2\u5C31\u7EEA\uFF0C\u5F15\u5BFC\u5C42\u5DF2\u6D88\u5931\u3002" : hasSeenLoading ? "\u9A8C\u8BC1\u7801\u56FE\u7247\u5DF2\u52A0\u8F7D\u5B8C\u6210\u3002" : "\u9A8C\u8BC1\u7801\u56FE\u7247\u5DF2\u5C31\u7EEA\u3002"
|
|
5325
|
+
);
|
|
5151
5326
|
return;
|
|
5152
5327
|
}
|
|
5153
5328
|
if (hasErrorTextVisible) {
|
|
@@ -5157,8 +5332,8 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5157
5332
|
hasSeenLoading = false;
|
|
5158
5333
|
continue;
|
|
5159
5334
|
}
|
|
5160
|
-
if (!hasVisibleSourceImage && Date.now() >= refreshDeadline) {
|
|
5161
|
-
logger10.warn(`\u9A8C\u8BC1\u7801\
|
|
5335
|
+
if ((!hasVisibleSourceImage || !hasVisibleDropTarget) && Date.now() >= refreshDeadline) {
|
|
5336
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u9898\u76EE\u8D85\u8FC7 ${options.challengeReadyRefreshTimeoutMs}ms \u4ECD\u672A\u51C6\u5907\u597D\uFF0C\u5C1D\u8BD5\u5237\u65B0\u9898\u76EE\u3002`);
|
|
5162
5337
|
await refreshCaptcha(page, frame, options);
|
|
5163
5338
|
refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
|
|
5164
5339
|
hasSeenLoading = false;
|
|
@@ -5168,6 +5343,69 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5168
5343
|
}
|
|
5169
5344
|
throw new Error("Captcha challenge is still loading and did not become ready in time.");
|
|
5170
5345
|
};
|
|
5346
|
+
var dragPromptCaptchaImage = async (page, frame, iframeLocator, sourceLocator, dropTarget, options, {
|
|
5347
|
+
attempt,
|
|
5348
|
+
visualIndex
|
|
5349
|
+
}) => {
|
|
5350
|
+
const baselineState = await readPromptCaptchaState(frame, options);
|
|
5351
|
+
const dragAttempts = [];
|
|
5352
|
+
for (const plan of PROMPT_CAPTCHA_DRAG_PLANS) {
|
|
5353
|
+
const sourceBox = await sourceLocator.boundingBox().catch(() => null);
|
|
5354
|
+
const targetBox = await dropTarget.boundingBox().catch(() => null);
|
|
5355
|
+
if (!sourceBox || !targetBox) {
|
|
5356
|
+
throw new Error("Unable to resolve prompt captcha drag coordinates.");
|
|
5357
|
+
}
|
|
5358
|
+
const targetOffsetX = (plan.endXRatio - 0.5) * targetBox.width;
|
|
5359
|
+
const targetOffsetY = (plan.endYRatio - 0.5) * targetBox.height;
|
|
5360
|
+
await dragCaptchaAction(page, sourceLocator, dropTarget, {
|
|
5361
|
+
forceMouse: true,
|
|
5362
|
+
targetOffsetX,
|
|
5363
|
+
targetOffsetY,
|
|
5364
|
+
steps: options.promptDragMoveSteps,
|
|
5365
|
+
holdDelayMs: options.promptDragHoldDelayMs,
|
|
5366
|
+
stepDelayMs: options.promptDragStepDelayMs,
|
|
5367
|
+
beforeReleaseDelayMs: options.promptDragBeforeReleaseDelayMs,
|
|
5368
|
+
afterReleaseDelayMs: options.promptDragAfterReleaseDelayMs,
|
|
5369
|
+
finalMoveRepeats: options.promptDragFinalMoveRepeats
|
|
5370
|
+
});
|
|
5371
|
+
const afterState = await readPromptCaptchaState(frame, options);
|
|
5372
|
+
const accepted = afterState.badgeCount > baselineState.badgeCount || afterState.selectedCount > baselineState.selectedCount;
|
|
5373
|
+
const attemptInfo = {
|
|
5374
|
+
planName: plan.name,
|
|
5375
|
+
sourceRect: rectOf(sourceBox),
|
|
5376
|
+
targetRect: rectOf(targetBox),
|
|
5377
|
+
targetOffsetX: Number(targetOffsetX.toFixed(2)),
|
|
5378
|
+
targetOffsetY: Number(targetOffsetY.toFixed(2)),
|
|
5379
|
+
beforeState: baselineState,
|
|
5380
|
+
afterState,
|
|
5381
|
+
accepted
|
|
5382
|
+
};
|
|
5383
|
+
dragAttempts.push(attemptInfo);
|
|
5384
|
+
logger10.info(
|
|
5385
|
+
`\u9A8C\u8BC1\u7801\u62D6\u62FD\u7B2C ${visualIndex + 1} \u5F20\uFF0C\u65B9\u6848 ${plan.name}\uFF0Cbadge ${baselineState.badgeCount} -> ${afterState.badgeCount}\uFF0Cselected ${baselineState.selectedCount} -> ${afterState.selectedCount}`
|
|
5386
|
+
);
|
|
5387
|
+
if (accepted) {
|
|
5388
|
+
return {
|
|
5389
|
+
accepted: true,
|
|
5390
|
+
dragAttempts
|
|
5391
|
+
};
|
|
5392
|
+
}
|
|
5393
|
+
if (options.promptDragRetryDelayMs > 0) {
|
|
5394
|
+
await page.waitForTimeout(options.promptDragRetryDelayMs);
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
5397
|
+
await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, `drag-${visualIndex + 1}-failed`, options, {
|
|
5398
|
+
visualIndex,
|
|
5399
|
+
dragAttempts,
|
|
5400
|
+
finalState: await readPromptCaptchaState(frame, options)
|
|
5401
|
+
}).catch((error) => {
|
|
5402
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u62D6\u62FD\u5931\u8D25\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5403
|
+
});
|
|
5404
|
+
return {
|
|
5405
|
+
accepted: false,
|
|
5406
|
+
dragAttempts
|
|
5407
|
+
};
|
|
5408
|
+
};
|
|
5171
5409
|
async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
5172
5410
|
const { callCaptchaRecognitionApi: callCaptchaRecognitionApi2 } = dependencies;
|
|
5173
5411
|
if (typeof callCaptchaRecognitionApi2 !== "function") {
|
|
@@ -5182,7 +5420,7 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5182
5420
|
return false;
|
|
5183
5421
|
}
|
|
5184
5422
|
logger10.info("\u5F53\u524D\u4F7F\u7528\u672Ctool\u2014\u2014\u6D4B\u8BD5\u7248\u672C");
|
|
5185
|
-
for (let attempt = 1; attempt <= config.maxRetries; attempt
|
|
5423
|
+
for (let attempt = 1; attempt <= config.maxRetries; attempt += 1) {
|
|
5186
5424
|
logger10.info(`\u5F00\u59CB\u7B2C ${attempt}/${config.maxRetries} \u6B21 verifycenter \u9A8C\u8BC1\u7801\u8BC6\u522B\u3002`);
|
|
5187
5425
|
try {
|
|
5188
5426
|
const captchaContext = await getVerifycenterCaptchaContext(page, config);
|
|
@@ -5192,6 +5430,16 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5192
5430
|
}
|
|
5193
5431
|
const { iframeLocator, frame } = captchaContext;
|
|
5194
5432
|
await waitForCaptchaChallengeReady(page, frame, config);
|
|
5433
|
+
await maybeCollectCaptchaDebugInfo(
|
|
5434
|
+
page,
|
|
5435
|
+
frame,
|
|
5436
|
+
iframeLocator,
|
|
5437
|
+
attempt,
|
|
5438
|
+
"ready",
|
|
5439
|
+
config
|
|
5440
|
+
).catch((error) => {
|
|
5441
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5442
|
+
});
|
|
5195
5443
|
await page.waitForTimeout(config.recognitionDelayMs);
|
|
5196
5444
|
const screenshotBuffer = await iframeLocator.screenshot();
|
|
5197
5445
|
const apiResponse = await callCaptchaRecognitionApi2({
|
|
@@ -5215,33 +5463,74 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5215
5463
|
await refreshCaptcha(page, frame, config);
|
|
5216
5464
|
continue;
|
|
5217
5465
|
}
|
|
5218
|
-
const
|
|
5219
|
-
const
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5466
|
+
const orderedSourceImages = await resolveCaptchaSourceImagesInVisualOrder(frame, config);
|
|
5467
|
+
const normalizedIndexes = normalizeCaptchaImageIndexes(serialNumbers, orderedSourceImages.length);
|
|
5468
|
+
if (normalizedIndexes.length !== serialNumbers.length) {
|
|
5469
|
+
throw new Error(
|
|
5470
|
+
`Captcha image indexes could not be normalized. raw=${serialNumbers.join(", ")}, count=${orderedSourceImages.length}`
|
|
5471
|
+
);
|
|
5472
|
+
}
|
|
5473
|
+
logger10.info(`\u9A8C\u8BC1\u7801\u89C6\u89C9\u4F4D\u5E8F\u6620\u5C04\uFF1A${normalizedIndexes.map((index) => index + 1).join(", ")}`);
|
|
5474
|
+
for (const imageIndex of normalizedIndexes) {
|
|
5475
|
+
if (imageIndex < 0 || imageIndex >= orderedSourceImages.length) {
|
|
5476
|
+
throw new Error(
|
|
5477
|
+
`Captcha image index ${imageIndex} is out of range. count=${orderedSourceImages.length}`
|
|
5478
|
+
);
|
|
5227
5479
|
}
|
|
5228
|
-
const sourceImage =
|
|
5480
|
+
const sourceImage = orderedSourceImages[imageIndex].locator;
|
|
5229
5481
|
await sourceImage.waitFor({
|
|
5230
5482
|
state: "visible",
|
|
5231
5483
|
timeout: config.sourceImageVisibleTimeoutMs
|
|
5232
5484
|
});
|
|
5233
|
-
await
|
|
5485
|
+
const dragResult = await dragPromptCaptchaImage(
|
|
5486
|
+
page,
|
|
5487
|
+
frame,
|
|
5488
|
+
iframeLocator,
|
|
5489
|
+
sourceImage,
|
|
5490
|
+
dropTarget,
|
|
5491
|
+
config,
|
|
5492
|
+
{
|
|
5493
|
+
attempt,
|
|
5494
|
+
visualIndex: imageIndex
|
|
5495
|
+
}
|
|
5496
|
+
);
|
|
5497
|
+
if (!dragResult.accepted) {
|
|
5498
|
+
throw new Error(`Captcha prompt drag was not accepted for visual index ${imageIndex + 1}.`);
|
|
5499
|
+
}
|
|
5500
|
+
if (config.dragBetweenWaitMs > 0) {
|
|
5501
|
+
await page.waitForTimeout(config.dragBetweenWaitMs);
|
|
5502
|
+
}
|
|
5234
5503
|
}
|
|
5235
|
-
const
|
|
5504
|
+
const beforeSubmitState = await readPromptCaptchaState(frame, config);
|
|
5505
|
+
logger10.info(
|
|
5506
|
+
`\u63D0\u4EA4\u524D\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${beforeSubmitState.badgeCount}, selected=${beforeSubmitState.selectedCount}, submitDisabled=${beforeSubmitState.submitDisabled}`
|
|
5507
|
+
);
|
|
5508
|
+
const submitted = await clickCaptchaAction(frame, config.submitTexts, {
|
|
5509
|
+
...config,
|
|
5510
|
+
page,
|
|
5511
|
+
logger: logger10,
|
|
5512
|
+
forceMouse: true,
|
|
5513
|
+
actionVisibleTimeoutMs: config.submitReadyTimeoutMs
|
|
5514
|
+
}).catch(() => false);
|
|
5236
5515
|
if (!submitted) {
|
|
5237
5516
|
logger10.warn("\u672A\u627E\u5230\u63D0\u4EA4\u6309\u94AE\uFF0C\u53EF\u80FD\u4F1A\u81EA\u52A8\u63D0\u4EA4\u3002");
|
|
5238
5517
|
}
|
|
5239
5518
|
await page.waitForTimeout(config.submitWaitMs);
|
|
5519
|
+
const afterSubmitState = await readPromptCaptchaState(frame, config);
|
|
5520
|
+
logger10.info(
|
|
5521
|
+
`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${afterSubmitState.badgeCount}, selected=${afterSubmitState.selectedCount}, submitDisabled=${afterSubmitState.submitDisabled}`
|
|
5522
|
+
);
|
|
5240
5523
|
const stillVisible = await iframeLocator.isVisible({ timeout: config.containerVisibleTimeoutMs }).catch(() => false);
|
|
5241
5524
|
if (!stillVisible) {
|
|
5242
5525
|
logger10.info("\u9A8C\u8BC1\u7801\u8BC6\u522B\u5E76\u63D0\u4EA4\u6210\u529F\u3002");
|
|
5243
5526
|
return true;
|
|
5244
5527
|
}
|
|
5528
|
+
await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, "submit-still-visible", config, {
|
|
5529
|
+
beforeSubmitState,
|
|
5530
|
+
afterSubmitState
|
|
5531
|
+
}).catch((error) => {
|
|
5532
|
+
logger10.warn(`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5533
|
+
});
|
|
5245
5534
|
logger10.warn("\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801 iframe \u4ECD\u7136\u53EF\u89C1\uFF0C\u51C6\u5907\u5237\u65B0\u540E\u91CD\u8BD5\u3002");
|
|
5246
5535
|
await page.waitForTimeout(2e3);
|
|
5247
5536
|
await refreshCaptcha(page, frame, config);
|
|
@@ -5684,14 +5973,14 @@ var Mutation = {
|
|
|
5684
5973
|
const isFrameElement = tagName === "IFRAME" || tagName === "FRAME";
|
|
5685
5974
|
const nodeName = descriptor?.id || descriptor?.name || "no-id";
|
|
5686
5975
|
let source = "main";
|
|
5687
|
-
let
|
|
5976
|
+
let path3 = `${selector}[${index}]`;
|
|
5688
5977
|
let text = "";
|
|
5689
5978
|
let html = "";
|
|
5690
5979
|
let frameUrl = "";
|
|
5691
5980
|
let readyState = "";
|
|
5692
5981
|
if (isFrameElement) {
|
|
5693
5982
|
source = "iframe";
|
|
5694
|
-
|
|
5983
|
+
path3 = `${selector}[${index}]::iframe(${nodeName})`;
|
|
5695
5984
|
const frame = await handle.contentFrame();
|
|
5696
5985
|
if (frame) {
|
|
5697
5986
|
try {
|
|
@@ -5721,7 +6010,7 @@ var Mutation = {
|
|
|
5721
6010
|
items.push({
|
|
5722
6011
|
selector,
|
|
5723
6012
|
source,
|
|
5724
|
-
path:
|
|
6013
|
+
path: path3,
|
|
5725
6014
|
text,
|
|
5726
6015
|
html,
|
|
5727
6016
|
frameUrl,
|