@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.cjs
CHANGED
|
@@ -133,10 +133,10 @@ var createActorInfo = (info) => {
|
|
|
133
133
|
xurl
|
|
134
134
|
};
|
|
135
135
|
};
|
|
136
|
-
const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path:
|
|
136
|
+
const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path4 }) => {
|
|
137
137
|
const safeProtocol = String(protocol2).trim();
|
|
138
138
|
const safeDomain = normalizeDomain(domain2);
|
|
139
|
-
const safePath = normalizePath(
|
|
139
|
+
const safePath = normalizePath(path4);
|
|
140
140
|
return `${safeProtocol}://${safeDomain}${safePath}`;
|
|
141
141
|
};
|
|
142
142
|
const buildIcon = ({ key }) => {
|
|
@@ -144,14 +144,14 @@ var createActorInfo = (info) => {
|
|
|
144
144
|
};
|
|
145
145
|
const protocol = info.protocol || "https";
|
|
146
146
|
const domain = normalizeDomain(info.domain);
|
|
147
|
-
const
|
|
147
|
+
const path3 = normalizePath(info.path);
|
|
148
148
|
const share = normalizeShare2(info.share);
|
|
149
149
|
const device = normalizeDevice(info.device);
|
|
150
150
|
return {
|
|
151
151
|
...info,
|
|
152
152
|
protocol,
|
|
153
153
|
domain,
|
|
154
|
-
path:
|
|
154
|
+
path: path3,
|
|
155
155
|
share,
|
|
156
156
|
device,
|
|
157
157
|
get icon() {
|
|
@@ -1512,7 +1512,7 @@ var normalizeCookies = (value) => {
|
|
|
1512
1512
|
if (!name || !cookieValue || cookieValue === "<nil>") return null;
|
|
1513
1513
|
const domain = String(raw.domain || "").trim();
|
|
1514
1514
|
const url = normalizeHttpUrl(raw.url);
|
|
1515
|
-
const
|
|
1515
|
+
const path3 = String(raw.path || "").trim() || "/";
|
|
1516
1516
|
const sameSite = normalizeCookieSameSite(raw.sameSite);
|
|
1517
1517
|
const expires = normalizeCookieExpires(raw);
|
|
1518
1518
|
const secure = Boolean(raw.secure);
|
|
@@ -1521,7 +1521,7 @@ var normalizeCookies = (value) => {
|
|
|
1521
1521
|
const normalized = {
|
|
1522
1522
|
name,
|
|
1523
1523
|
value: cookieValue,
|
|
1524
|
-
path:
|
|
1524
|
+
path: path3,
|
|
1525
1525
|
...domain ? { domain } : {},
|
|
1526
1526
|
...!domain && url ? { url } : {},
|
|
1527
1527
|
...sameSite ? { sameSite } : {},
|
|
@@ -2592,6 +2592,10 @@ var assertPoint = (point) => {
|
|
|
2592
2592
|
throw new Error(`Invalid input point: ${JSON.stringify(point)}`);
|
|
2593
2593
|
}
|
|
2594
2594
|
};
|
|
2595
|
+
var toFiniteNumber = (value, fallback = 0) => {
|
|
2596
|
+
const number = Number(value);
|
|
2597
|
+
return Number.isFinite(number) ? number : fallback;
|
|
2598
|
+
};
|
|
2595
2599
|
var dispatchMouseMove = (page, point, options = {}) => page.mouse.move(point.x, point.y, options);
|
|
2596
2600
|
var dispatchMouseStart = (page, options = {}) => page.mouse.down(options);
|
|
2597
2601
|
var dispatchMouseEnd = (page, options = {}) => page.mouse.up(options);
|
|
@@ -2611,6 +2615,11 @@ var dragWithMouse = async (page, points, options = {}) => {
|
|
|
2611
2615
|
}, { steps: 2 });
|
|
2612
2616
|
await waitFor(page, options.stepDelayMs ?? 90);
|
|
2613
2617
|
}
|
|
2618
|
+
const finalMoveRepeats = Math.max(0, Math.floor(toFiniteNumber(options.finalMoveRepeats)));
|
|
2619
|
+
for (let repeat = 0; repeat < finalMoveRepeats; repeat += 1) {
|
|
2620
|
+
await dispatchMouseMove(page, points.end, { steps: 1 });
|
|
2621
|
+
await waitFor(page, options.finalMoveDelayMs ?? 35);
|
|
2622
|
+
}
|
|
2614
2623
|
await waitFor(page, options.beforeReleaseDelayMs ?? 100);
|
|
2615
2624
|
await dispatchMouseEnd(page);
|
|
2616
2625
|
await waitFor(page, options.afterReleaseDelayMs ?? 100);
|
|
@@ -2620,6 +2629,7 @@ var dragWithTouch = async (page, points, options = {}) => {
|
|
|
2620
2629
|
let client = null;
|
|
2621
2630
|
try {
|
|
2622
2631
|
client = await page.context().newCDPSession(page);
|
|
2632
|
+
await waitFor(page, options.initialDelayMs ?? 250);
|
|
2623
2633
|
await client.send("Input.dispatchTouchEvent", {
|
|
2624
2634
|
type: "touchStart",
|
|
2625
2635
|
touchPoints: [{ x: points.start.x, y: points.start.y, id: 1 }]
|
|
@@ -2641,6 +2651,14 @@ var dragWithTouch = async (page, points, options = {}) => {
|
|
|
2641
2651
|
});
|
|
2642
2652
|
await waitFor(page, options.stepDelayMs ?? 90);
|
|
2643
2653
|
}
|
|
2654
|
+
const finalMoveRepeats = Math.max(0, Math.floor(toFiniteNumber(options.finalMoveRepeats)));
|
|
2655
|
+
for (let repeat = 0; repeat < finalMoveRepeats; repeat += 1) {
|
|
2656
|
+
await client.send("Input.dispatchTouchEvent", {
|
|
2657
|
+
type: "touchMove",
|
|
2658
|
+
touchPoints: [{ x: points.end.x, y: points.end.y, id: 1 }]
|
|
2659
|
+
});
|
|
2660
|
+
await waitFor(page, options.finalMoveDelayMs ?? 35);
|
|
2661
|
+
}
|
|
2644
2662
|
await waitFor(page, options.beforeReleaseDelayMs ?? 100);
|
|
2645
2663
|
await client.send("Input.dispatchTouchEvent", {
|
|
2646
2664
|
type: "touchEnd",
|
|
@@ -2658,7 +2676,7 @@ var dragWithTouch = async (page, points, options = {}) => {
|
|
|
2658
2676
|
var clickTargetWithDevice = async (page, target, options = {}) => {
|
|
2659
2677
|
const normalizedOptions = normalizeSelectorOptions(options);
|
|
2660
2678
|
const resolvedDevice = resolveDeviceFromPage(page);
|
|
2661
|
-
if (target && resolvedDevice === Device.Mobile && !normalizedOptions.forceClick) {
|
|
2679
|
+
if (target && resolvedDevice === Device.Mobile && !normalizedOptions.forceClick && !normalizedOptions.forceMouse) {
|
|
2662
2680
|
if (typeof target.tap === "function") {
|
|
2663
2681
|
await target.tap(normalizedOptions.tapOptions);
|
|
2664
2682
|
return true;
|
|
@@ -2858,20 +2876,26 @@ var DeviceInput = {
|
|
|
2858
2876
|
throw new Error("Unable to resolve drag coordinates.");
|
|
2859
2877
|
}
|
|
2860
2878
|
const steps = options.steps || 10;
|
|
2879
|
+
const sourceOffsetX = toFiniteNumber(options.sourceOffsetX);
|
|
2880
|
+
const sourceOffsetY = toFiniteNumber(options.sourceOffsetY);
|
|
2881
|
+
const targetOffsetX = toFiniteNumber(options.targetOffsetX);
|
|
2882
|
+
const targetOffsetY = toFiniteNumber(options.targetOffsetY);
|
|
2883
|
+
const sourceCenterX = sourceBox.x + sourceBox.width / 2 + sourceOffsetX;
|
|
2884
|
+
const sourceCenterY = sourceBox.y + sourceBox.height / 2 + sourceOffsetY;
|
|
2861
2885
|
const liftOffsetX = Math.min(18, Math.max(8, sourceBox.width * 0.12));
|
|
2862
2886
|
const liftOffsetY = Math.min(12, Math.max(4, sourceBox.height * 0.08));
|
|
2863
2887
|
const points = {
|
|
2864
2888
|
start: {
|
|
2865
|
-
x:
|
|
2866
|
-
y:
|
|
2889
|
+
x: sourceCenterX,
|
|
2890
|
+
y: sourceCenterY
|
|
2867
2891
|
},
|
|
2868
2892
|
lift: {
|
|
2869
|
-
x:
|
|
2870
|
-
y:
|
|
2893
|
+
x: sourceCenterX + liftOffsetX,
|
|
2894
|
+
y: sourceCenterY + liftOffsetY
|
|
2871
2895
|
},
|
|
2872
2896
|
end: {
|
|
2873
|
-
x: targetBox.x + targetBox.width / 2,
|
|
2874
|
-
y: targetBox.y + targetBox.height / 2
|
|
2897
|
+
x: targetBox.x + targetBox.width / 2 + targetOffsetX,
|
|
2898
|
+
y: targetBox.y + targetBox.height / 2 + targetOffsetY
|
|
2875
2899
|
},
|
|
2876
2900
|
steps
|
|
2877
2901
|
};
|
|
@@ -3407,13 +3431,6 @@ var jitterMs = (base, jitterPercent = 0.3) => {
|
|
|
3407
3431
|
const jitter = Number(base || 0) * Number(jitterPercent || 0) * (Math.random() * 2 - 1);
|
|
3408
3432
|
return Math.max(10, Math.round(Number(base || 0) + jitter));
|
|
3409
3433
|
};
|
|
3410
|
-
var randomPointInBox = (box, paddingRatio = 0.24) => {
|
|
3411
|
-
const safePaddingX = Math.max(2, Math.min(box.width * paddingRatio, box.width / 2));
|
|
3412
|
-
const safePaddingY = Math.max(2, Math.min(box.height * paddingRatio, box.height / 2));
|
|
3413
|
-
const x = box.x + safePaddingX + Math.random() * Math.max(1, box.width - safePaddingX * 2);
|
|
3414
|
-
const y = box.y + safePaddingY + Math.random() * Math.max(1, box.height - safePaddingY * 2);
|
|
3415
|
-
return { x, y };
|
|
3416
|
-
};
|
|
3417
3434
|
var resolveElement = async (page, target, { throwOnMissing = true } = {}) => {
|
|
3418
3435
|
if (target == null) return null;
|
|
3419
3436
|
let element = target;
|
|
@@ -3458,6 +3475,10 @@ var clipBoxToViewport = (box, viewport) => {
|
|
|
3458
3475
|
}
|
|
3459
3476
|
return box;
|
|
3460
3477
|
};
|
|
3478
|
+
var centerPointInBox = (box) => ({
|
|
3479
|
+
x: box.x + box.width / 2,
|
|
3480
|
+
y: box.y + box.height / 2
|
|
3481
|
+
});
|
|
3461
3482
|
var withTimeout = async (operation, timeoutMs, label) => {
|
|
3462
3483
|
const safeTimeoutMs = Math.max(50, Number(timeoutMs || 0));
|
|
3463
3484
|
let timeoutId = null;
|
|
@@ -3609,97 +3630,6 @@ var checkElementVisibility = async (element) => {
|
|
|
3609
3630
|
return { code: "VISIBLE", isFixed, positioning };
|
|
3610
3631
|
});
|
|
3611
3632
|
};
|
|
3612
|
-
var resolveSafeTapPoint = async (element) => {
|
|
3613
|
-
return element.evaluate((el) => {
|
|
3614
|
-
const rect = el.getBoundingClientRect();
|
|
3615
|
-
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
3616
|
-
return null;
|
|
3617
|
-
}
|
|
3618
|
-
const viewW = window.innerWidth;
|
|
3619
|
-
const viewH = window.innerHeight;
|
|
3620
|
-
let clipLeft = 0;
|
|
3621
|
-
let clipRight = viewW;
|
|
3622
|
-
let clipTop = 0;
|
|
3623
|
-
let clipBottom = viewH;
|
|
3624
|
-
for (let node = el.parentElement; node && node !== document.body; node = node.parentElement) {
|
|
3625
|
-
const style = window.getComputedStyle(node);
|
|
3626
|
-
if (!style) continue;
|
|
3627
|
-
const clipsX = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowX);
|
|
3628
|
-
const clipsY = ["auto", "scroll", "overlay", "hidden", "clip"].includes(style.overflowY);
|
|
3629
|
-
if (!clipsX && !clipsY) continue;
|
|
3630
|
-
const nodeRect = node.getBoundingClientRect();
|
|
3631
|
-
if (!nodeRect || nodeRect.width <= 0 || nodeRect.height <= 0) continue;
|
|
3632
|
-
if (clipsX) {
|
|
3633
|
-
clipLeft = Math.max(clipLeft, nodeRect.left);
|
|
3634
|
-
clipRight = Math.min(clipRight, nodeRect.right);
|
|
3635
|
-
}
|
|
3636
|
-
if (clipsY) {
|
|
3637
|
-
clipTop = Math.max(clipTop, nodeRect.top);
|
|
3638
|
-
clipBottom = Math.min(clipBottom, nodeRect.bottom);
|
|
3639
|
-
}
|
|
3640
|
-
}
|
|
3641
|
-
const visibleLeft = Math.max(clipLeft, Math.min(clipRight, rect.left));
|
|
3642
|
-
const visibleRight = Math.max(clipLeft, Math.min(clipRight, rect.right));
|
|
3643
|
-
const visibleTop = Math.max(clipTop, Math.min(clipBottom, rect.top));
|
|
3644
|
-
const visibleBottom = Math.max(clipTop, Math.min(clipBottom, rect.bottom));
|
|
3645
|
-
const visibleWidth = visibleRight - visibleLeft;
|
|
3646
|
-
const visibleHeight = visibleBottom - visibleTop;
|
|
3647
|
-
if (visibleWidth <= 1 || visibleHeight <= 1) {
|
|
3648
|
-
return null;
|
|
3649
|
-
}
|
|
3650
|
-
const isRootNode = (node) => !node || node === document || node === document.body || node === document.documentElement;
|
|
3651
|
-
const commonAncestor = (a, b) => {
|
|
3652
|
-
for (let current = a; current && !isRootNode(current); current = current.parentElement) {
|
|
3653
|
-
if (current.contains(b)) return current;
|
|
3654
|
-
}
|
|
3655
|
-
return null;
|
|
3656
|
-
};
|
|
3657
|
-
const sameTapTarget = (pointElement) => {
|
|
3658
|
-
if (!pointElement) return false;
|
|
3659
|
-
if (pointElement === el || el.contains(pointElement) || pointElement.contains(el)) {
|
|
3660
|
-
return true;
|
|
3661
|
-
}
|
|
3662
|
-
const common = commonAncestor(el, pointElement);
|
|
3663
|
-
if (!common) return false;
|
|
3664
|
-
const commonRect = common.getBoundingClientRect?.();
|
|
3665
|
-
if (!commonRect || commonRect.width <= 0 || commonRect.height <= 0) return false;
|
|
3666
|
-
const commonArea = commonRect.width * commonRect.height;
|
|
3667
|
-
const targetArea = Math.max(1, rect.width * rect.height);
|
|
3668
|
-
const maxSharedRegionArea = Math.max(targetArea * 12, 4096);
|
|
3669
|
-
if (commonArea > maxSharedRegionArea) return false;
|
|
3670
|
-
if (commonRect.width > Math.max(rect.width * 8, 120) || commonRect.height > Math.max(rect.height * 8, 120)) {
|
|
3671
|
-
return false;
|
|
3672
|
-
}
|
|
3673
|
-
return common.contains(el) && common.contains(pointElement);
|
|
3674
|
-
};
|
|
3675
|
-
const cx = visibleLeft + visibleWidth / 2;
|
|
3676
|
-
const cy = visibleTop + visibleHeight / 2;
|
|
3677
|
-
const smallX = Math.min(12, Math.max(2, visibleWidth * 0.18));
|
|
3678
|
-
const smallY = Math.min(12, Math.max(2, visibleHeight * 0.18));
|
|
3679
|
-
const points = [
|
|
3680
|
-
{ x: cx, y: cy },
|
|
3681
|
-
{ x: visibleLeft + smallX, y: visibleTop + smallY },
|
|
3682
|
-
{ x: visibleRight - smallX, y: visibleTop + smallY },
|
|
3683
|
-
{ x: visibleLeft + smallX, y: visibleBottom - smallY },
|
|
3684
|
-
{ x: visibleRight - smallX, y: visibleBottom - smallY },
|
|
3685
|
-
{ x: cx, y: visibleTop + Math.min(10, Math.max(2, visibleHeight * 0.2)) },
|
|
3686
|
-
{ x: cx, y: visibleBottom - Math.min(10, Math.max(2, visibleHeight * 0.2)) },
|
|
3687
|
-
{ x: visibleLeft + Math.min(10, Math.max(2, visibleWidth * 0.2)), y: cy },
|
|
3688
|
-
{ x: visibleRight - Math.min(10, Math.max(2, visibleWidth * 0.2)), y: cy }
|
|
3689
|
-
];
|
|
3690
|
-
const safePoints = points.filter((point) => {
|
|
3691
|
-
const pointElement = document.elementFromPoint(point.x, point.y);
|
|
3692
|
-
return sameTapTarget(pointElement);
|
|
3693
|
-
});
|
|
3694
|
-
const candidates = safePoints.length ? safePoints : points;
|
|
3695
|
-
const chosen = candidates[Math.floor(Math.random() * candidates.length)];
|
|
3696
|
-
if (!chosen) return null;
|
|
3697
|
-
return {
|
|
3698
|
-
x: chosen.x,
|
|
3699
|
-
y: chosen.y
|
|
3700
|
-
};
|
|
3701
|
-
});
|
|
3702
|
-
};
|
|
3703
3633
|
var activateElementFallback = async (element, point = null, options = {}) => {
|
|
3704
3634
|
return element.evaluate((el, { innerOptions }) => {
|
|
3705
3635
|
const isEditable = (node) => {
|
|
@@ -4284,8 +4214,8 @@ var MobileHumanize = {
|
|
|
4284
4214
|
return false;
|
|
4285
4215
|
}
|
|
4286
4216
|
await waitJitter(reactionDelay, 0.45);
|
|
4287
|
-
const
|
|
4288
|
-
const tapTarget =
|
|
4217
|
+
const visibleBox = clipBoxToViewport(box, resolveViewport(page));
|
|
4218
|
+
const tapTarget = centerPointInBox(visibleBox);
|
|
4289
4219
|
try {
|
|
4290
4220
|
await tapPoint(page, tapTarget, {
|
|
4291
4221
|
timeoutMs: tapTimeoutMs,
|
|
@@ -4988,6 +4918,10 @@ var LiveView = {
|
|
|
4988
4918
|
// src/chaptcha.js
|
|
4989
4919
|
var import_uuid = require("uuid");
|
|
4990
4920
|
|
|
4921
|
+
// src/internals/captcha/bytedance.js
|
|
4922
|
+
var import_promises = require("fs/promises");
|
|
4923
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
4924
|
+
|
|
4991
4925
|
// src/internals/captcha/shared.js
|
|
4992
4926
|
var waitForVisible = async (locator, timeout) => {
|
|
4993
4927
|
try {
|
|
@@ -5005,38 +4939,71 @@ var isAnyCaptchaTextVisible = async (frame, texts, timeout) => {
|
|
|
5005
4939
|
if (!text) {
|
|
5006
4940
|
continue;
|
|
5007
4941
|
}
|
|
5008
|
-
const
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
4942
|
+
const textLocator = frame.getByText(text, { exact: false });
|
|
4943
|
+
const locatorText = frame.locator(`text=${text}`);
|
|
4944
|
+
const candidateGroups = [textLocator, locatorText];
|
|
4945
|
+
for (const candidateGroup of candidateGroups) {
|
|
4946
|
+
const candidateCount = await candidateGroup.count().catch(() => 0);
|
|
4947
|
+
for (let index = 0; index < candidateCount; index += 1) {
|
|
4948
|
+
const candidate = candidateGroup.nth(index);
|
|
4949
|
+
const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
|
|
4950
|
+
if (isVisible) {
|
|
4951
|
+
return true;
|
|
4952
|
+
}
|
|
5016
4953
|
}
|
|
5017
4954
|
}
|
|
5018
4955
|
}
|
|
5019
4956
|
return false;
|
|
5020
4957
|
};
|
|
4958
|
+
var collectVisibleCandidateIndexes = async (candidateGroup, count, timeout) => {
|
|
4959
|
+
const visibleIndexes = [];
|
|
4960
|
+
for (let index = 0; index < count; index += 1) {
|
|
4961
|
+
const candidate = candidateGroup.nth(index);
|
|
4962
|
+
const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
|
|
4963
|
+
if (isVisible) {
|
|
4964
|
+
visibleIndexes.push(index);
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
return visibleIndexes;
|
|
4968
|
+
};
|
|
5021
4969
|
var clickCaptchaAction = async (frame, texts, options) => {
|
|
5022
4970
|
for (const text of texts || []) {
|
|
5023
|
-
const
|
|
5024
|
-
|
|
5025
|
-
|
|
4971
|
+
const textLocator = frame.getByText(text, { exact: false });
|
|
4972
|
+
const locatorText = frame.locator(`text=${text}`);
|
|
4973
|
+
const [getByTextCount, locatorTextCount] = await Promise.all([
|
|
4974
|
+
textLocator.count().catch(() => 0),
|
|
4975
|
+
locatorText.count().catch(() => 0)
|
|
4976
|
+
]);
|
|
4977
|
+
const [getByTextVisibleIndexes, locatorTextVisibleIndexes] = await Promise.all([
|
|
4978
|
+
collectVisibleCandidateIndexes(textLocator, getByTextCount, options.actionVisibleTimeoutMs),
|
|
4979
|
+
collectVisibleCandidateIndexes(locatorText, locatorTextCount, options.actionVisibleTimeoutMs)
|
|
4980
|
+
]);
|
|
4981
|
+
options.logger?.info(
|
|
4982
|
+
`[CaptchaAction] \u6587\u672C "${text}" \u547D\u4E2D\u6570\u91CF\uFF1AgetByText=${getByTextCount} (visible=${getByTextVisibleIndexes.length}), locator=${locatorTextCount} (visible=${locatorTextVisibleIndexes.length})`
|
|
4983
|
+
);
|
|
4984
|
+
const candidateGroups = [
|
|
4985
|
+
{ label: "getByText", locator: textLocator, count: getByTextCount },
|
|
4986
|
+
{ label: "locator", locator: locatorText, count: locatorTextCount }
|
|
5026
4987
|
];
|
|
5027
|
-
for (const
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
4988
|
+
for (const candidateGroup of candidateGroups) {
|
|
4989
|
+
for (let index = 0; index < candidateGroup.count; index += 1) {
|
|
4990
|
+
const candidate = candidateGroup.locator.nth(index);
|
|
4991
|
+
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
4992
|
+
if (!isVisible) {
|
|
4993
|
+
continue;
|
|
4994
|
+
}
|
|
4995
|
+
options.logger?.info(
|
|
4996
|
+
`[CaptchaAction] \u6587\u672C "${text}" \u9009\u62E9 ${candidateGroup.label}[${index}] \u4F5C\u4E3A\u7B2C\u4E00\u4E2A\u53EF\u89C1\u8282\u70B9\u6267\u884C\u70B9\u51FB\u3002`
|
|
4997
|
+
);
|
|
4998
|
+
await DeviceInput.click(options.page, candidate, options);
|
|
4999
|
+
return true;
|
|
5031
5000
|
}
|
|
5032
|
-
await DeviceInput.click(options.page, candidate);
|
|
5033
|
-
return true;
|
|
5034
5001
|
}
|
|
5035
5002
|
}
|
|
5036
5003
|
return false;
|
|
5037
5004
|
};
|
|
5038
|
-
var dragCaptchaAction = async (page, sourceLocator, targetLocator) => {
|
|
5039
|
-
await DeviceInput.drag(page, sourceLocator, targetLocator);
|
|
5005
|
+
var dragCaptchaAction = async (page, sourceLocator, targetLocator, options = {}) => {
|
|
5006
|
+
await DeviceInput.drag(page, sourceLocator, targetLocator, options);
|
|
5040
5007
|
};
|
|
5041
5008
|
|
|
5042
5009
|
// src/internals/captcha/bytedance.js
|
|
@@ -5047,11 +5014,12 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
|
5047
5014
|
containerSelector: "#captcha_container",
|
|
5048
5015
|
iframeSelector: 'iframe[src*="verifycenter"]',
|
|
5049
5016
|
iframeFallbackSelector: "iframe",
|
|
5050
|
-
sourceImageSelector: "
|
|
5051
|
-
dropTargetContainerSelector: "
|
|
5017
|
+
sourceImageSelector: ".img-container .canvas-container",
|
|
5018
|
+
dropTargetContainerSelector: ".drag-area",
|
|
5052
5019
|
dropTargetTexts: ["\u62D6\u62FD\u5230\u8FD9\u91CC"],
|
|
5053
5020
|
refreshTexts: ["\u5237\u65B0"],
|
|
5054
5021
|
submitTexts: ["\u63D0\u4EA4"],
|
|
5022
|
+
guideMaskSelector: ".play-guide-mask",
|
|
5055
5023
|
recognitionSuccessCode: 1e4,
|
|
5056
5024
|
containerVisibleTimeoutMs: 2e3,
|
|
5057
5025
|
iframeVisibleTimeoutMs: 12e3,
|
|
@@ -5074,10 +5042,111 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
|
5074
5042
|
],
|
|
5075
5043
|
recognitionDelayMs: 2e3,
|
|
5076
5044
|
refreshWaitMs: 3e3,
|
|
5077
|
-
submitWaitMs:
|
|
5045
|
+
submitWaitMs: 5e3,
|
|
5046
|
+
submitReadyTimeoutMs: 2500,
|
|
5078
5047
|
retryDelayBaseMs: 2e3,
|
|
5079
|
-
retryDelayStepMs: 1e3
|
|
5048
|
+
retryDelayStepMs: 1e3,
|
|
5049
|
+
sourceImageRowTolerancePx: 24,
|
|
5050
|
+
dragBetweenWaitMs: 250,
|
|
5051
|
+
promptBadgeCountSelector: ".drag-area .photo-badge .badge span",
|
|
5052
|
+
promptSubmitButtonSelector: ".vc-captcha-verify-mobile-button",
|
|
5053
|
+
promptSelectedSourceSelector: ".img-container .canvas-container.selected",
|
|
5054
|
+
promptActiveSourceSelector: ".img-container .canvas-container.active",
|
|
5055
|
+
promptDragMoveSteps: 16,
|
|
5056
|
+
promptDragStepDelayMs: 55,
|
|
5057
|
+
promptDragHoldDelayMs: 240,
|
|
5058
|
+
promptDragBeforeReleaseDelayMs: 180,
|
|
5059
|
+
promptDragAfterReleaseDelayMs: 240,
|
|
5060
|
+
promptDragFinalMoveRepeats: 3,
|
|
5061
|
+
promptDragRetryDelayMs: 250,
|
|
5062
|
+
debugArtifacts: false
|
|
5080
5063
|
});
|
|
5064
|
+
var PROMPT_CAPTCHA_DRAG_PLANS = Object.freeze([
|
|
5065
|
+
{ name: "lower-middle", endXRatio: 0.5, endYRatio: 0.72 },
|
|
5066
|
+
{ name: "center-middle", endXRatio: 0.5, endYRatio: 0.56 },
|
|
5067
|
+
{ name: "upper-middle", endXRatio: 0.5, endYRatio: 0.4 }
|
|
5068
|
+
]);
|
|
5069
|
+
var resolveCaptchaDebugDir = () => import_path2.default.resolve(process.cwd(), "storage", "captcha-debug");
|
|
5070
|
+
var rectOf = (rect) => {
|
|
5071
|
+
if (!rect) {
|
|
5072
|
+
return null;
|
|
5073
|
+
}
|
|
5074
|
+
return {
|
|
5075
|
+
x: Number(rect.x.toFixed(2)),
|
|
5076
|
+
y: Number(rect.y.toFixed(2)),
|
|
5077
|
+
width: Number(rect.width.toFixed(2)),
|
|
5078
|
+
height: Number(rect.height.toFixed(2))
|
|
5079
|
+
};
|
|
5080
|
+
};
|
|
5081
|
+
var collectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, extra = null) => {
|
|
5082
|
+
const timestamp = Date.now();
|
|
5083
|
+
const debugDir = resolveCaptchaDebugDir();
|
|
5084
|
+
await (0, import_promises.mkdir)(debugDir, { recursive: true });
|
|
5085
|
+
const baseName = `bytedance-${timestamp}-attempt${attempt}-${phase}`;
|
|
5086
|
+
const iframeScreenshotPath = import_path2.default.join(debugDir, `${baseName}-iframe.png`);
|
|
5087
|
+
const pageScreenshotPath = import_path2.default.join(debugDir, `${baseName}-page.png`);
|
|
5088
|
+
const htmlPath = import_path2.default.join(debugDir, `${baseName}-iframe.html`);
|
|
5089
|
+
const infoPath = import_path2.default.join(debugDir, `${baseName}-info.json`);
|
|
5090
|
+
await iframeLocator.screenshot({ path: iframeScreenshotPath }).catch(() => {
|
|
5091
|
+
});
|
|
5092
|
+
await page.screenshot({ path: pageScreenshotPath, fullPage: true }).catch(() => {
|
|
5093
|
+
});
|
|
5094
|
+
const html = await frame.evaluate(() => document.documentElement.outerHTML).catch(() => "");
|
|
5095
|
+
if (html) {
|
|
5096
|
+
await (0, import_promises.writeFile)(htmlPath, html, "utf8");
|
|
5097
|
+
}
|
|
5098
|
+
const info = await frame.evaluate(() => {
|
|
5099
|
+
const toRect = (element) => {
|
|
5100
|
+
const rect = element.getBoundingClientRect();
|
|
5101
|
+
return {
|
|
5102
|
+
x: Number(rect.x.toFixed(2)),
|
|
5103
|
+
y: Number(rect.y.toFixed(2)),
|
|
5104
|
+
width: Number(rect.width.toFixed(2)),
|
|
5105
|
+
height: Number(rect.height.toFixed(2))
|
|
5106
|
+
};
|
|
5107
|
+
};
|
|
5108
|
+
const toItem = (element, index) => ({
|
|
5109
|
+
index,
|
|
5110
|
+
tag: element.tagName,
|
|
5111
|
+
id: element.id || "",
|
|
5112
|
+
className: typeof element.className === "string" ? element.className : "",
|
|
5113
|
+
text: String(element.textContent || "").trim(),
|
|
5114
|
+
rect: toRect(element)
|
|
5115
|
+
});
|
|
5116
|
+
const visibleNodes = Array.from(document.querySelectorAll("body *")).map(toItem).filter((item) => item.rect.width > 0 && item.rect.height > 0);
|
|
5117
|
+
return {
|
|
5118
|
+
title: document.title,
|
|
5119
|
+
bodyText: String(document.body?.innerText || "").trim(),
|
|
5120
|
+
canvasContainers: visibleNodes.filter((item) => item.className.includes("canvas-container")),
|
|
5121
|
+
canvasNodes: visibleNodes.filter((item) => item.tag === "CANVAS"),
|
|
5122
|
+
captchaNodes: visibleNodes.filter((item) => item.id.startsWith("captcha_") || item.className.includes("captcha") || item.className.includes("verify") || /拖拽到这里|刷新|提交/.test(item.text)),
|
|
5123
|
+
visibleTextNodes: visibleNodes.filter((item) => item.text).slice(0, 300)
|
|
5124
|
+
};
|
|
5125
|
+
}).catch(() => null);
|
|
5126
|
+
if (info) {
|
|
5127
|
+
const payload = {
|
|
5128
|
+
capturedAt: new Date(timestamp).toISOString(),
|
|
5129
|
+
pageUrl: page.url(),
|
|
5130
|
+
attempt,
|
|
5131
|
+
phase,
|
|
5132
|
+
iframeScreenshotPath,
|
|
5133
|
+
pageScreenshotPath,
|
|
5134
|
+
htmlPath,
|
|
5135
|
+
info
|
|
5136
|
+
};
|
|
5137
|
+
if (extra != null) {
|
|
5138
|
+
payload.extra = extra;
|
|
5139
|
+
}
|
|
5140
|
+
await (0, import_promises.writeFile)(infoPath, JSON.stringify(payload, null, 2), "utf8");
|
|
5141
|
+
}
|
|
5142
|
+
logger10.info(`\u5DF2\u5199\u51FA\u9A8C\u8BC1\u7801\u8C03\u8BD5\u4EA7\u7269\uFF1A${debugDir}`);
|
|
5143
|
+
};
|
|
5144
|
+
var maybeCollectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, options, extra = null) => {
|
|
5145
|
+
if (!options.debugArtifacts) {
|
|
5146
|
+
return;
|
|
5147
|
+
}
|
|
5148
|
+
await collectCaptchaDebugInfo(page, frame, iframeLocator, attempt, phase, extra);
|
|
5149
|
+
};
|
|
5081
5150
|
var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
5082
5151
|
const serialNumbers = apiResponse?.data?.data?.serial_number;
|
|
5083
5152
|
if (!Array.isArray(serialNumbers)) {
|
|
@@ -5086,7 +5155,7 @@ var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
|
5086
5155
|
return serialNumbers.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 0);
|
|
5087
5156
|
};
|
|
5088
5157
|
var resolveContentFrame = async (page, iframeLocator, options) => {
|
|
5089
|
-
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt
|
|
5158
|
+
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt += 1) {
|
|
5090
5159
|
const iframeHandle = await iframeLocator.elementHandle();
|
|
5091
5160
|
const frame = await iframeHandle?.contentFrame();
|
|
5092
5161
|
if (frame) {
|
|
@@ -5131,20 +5200,15 @@ var getVerifycenterCaptchaContext = async (page, options) => {
|
|
|
5131
5200
|
}
|
|
5132
5201
|
return { iframeLocator, frame };
|
|
5133
5202
|
};
|
|
5134
|
-
var refreshCaptcha = async (page, frame, options) => {
|
|
5135
|
-
const clicked = await clickCaptchaAction(frame, options.refreshTexts, { ...options, page }).catch(() => false);
|
|
5136
|
-
if (!clicked) {
|
|
5137
|
-
logger10.warn("Refresh button not found.");
|
|
5138
|
-
return false;
|
|
5139
|
-
}
|
|
5140
|
-
await page.waitForTimeout(options.refreshWaitMs);
|
|
5141
|
-
return true;
|
|
5142
|
-
};
|
|
5143
5203
|
var findCaptchaDropTarget = async (frame, options) => {
|
|
5204
|
+
const directTarget = frame.locator(options.dropTargetContainerSelector).first();
|
|
5205
|
+
if (await waitForVisible(directTarget, options.actionVisibleTimeoutMs)) {
|
|
5206
|
+
return directTarget;
|
|
5207
|
+
}
|
|
5144
5208
|
for (const text of options.dropTargetTexts) {
|
|
5145
5209
|
const candidates = [
|
|
5146
|
-
frame.
|
|
5147
|
-
frame.
|
|
5210
|
+
frame.getByText(text, { exact: false }).first(),
|
|
5211
|
+
frame.locator(`text=${text}`).first()
|
|
5148
5212
|
];
|
|
5149
5213
|
for (const candidate of candidates) {
|
|
5150
5214
|
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
@@ -5155,10 +5219,112 @@ var findCaptchaDropTarget = async (frame, options) => {
|
|
|
5155
5219
|
}
|
|
5156
5220
|
return null;
|
|
5157
5221
|
};
|
|
5222
|
+
var readPromptCaptchaState = async (frame, options) => frame.evaluate((selectors) => {
|
|
5223
|
+
const toRect = (element) => {
|
|
5224
|
+
if (!element) {
|
|
5225
|
+
return null;
|
|
5226
|
+
}
|
|
5227
|
+
const rect = element.getBoundingClientRect();
|
|
5228
|
+
return {
|
|
5229
|
+
x: Number(rect.x.toFixed(2)),
|
|
5230
|
+
y: Number(rect.y.toFixed(2)),
|
|
5231
|
+
width: Number(rect.width.toFixed(2)),
|
|
5232
|
+
height: Number(rect.height.toFixed(2))
|
|
5233
|
+
};
|
|
5234
|
+
};
|
|
5235
|
+
const badgeNode = document.querySelector(selectors.badgeCountSelector);
|
|
5236
|
+
const submitButton = document.querySelector(selectors.submitButtonSelector);
|
|
5237
|
+
const dragArea = document.querySelector(selectors.dragAreaSelector);
|
|
5238
|
+
const badgeCount = badgeNode ? Number.parseInt(String(badgeNode.textContent || "").trim(), 10) : 0;
|
|
5239
|
+
return {
|
|
5240
|
+
badgeCount: Number.isFinite(badgeCount) ? badgeCount : 0,
|
|
5241
|
+
selectedCount: document.querySelectorAll(selectors.selectedSourceSelector).length,
|
|
5242
|
+
activeCount: document.querySelectorAll(selectors.activeSourceSelector).length,
|
|
5243
|
+
submitDisabled: submitButton ? submitButton.classList.contains("disable") : null,
|
|
5244
|
+
dragAreaActive: dragArea ? dragArea.classList.contains("active") : false,
|
|
5245
|
+
dragAreaRect: toRect(dragArea)
|
|
5246
|
+
};
|
|
5247
|
+
}, {
|
|
5248
|
+
badgeCountSelector: options.promptBadgeCountSelector,
|
|
5249
|
+
submitButtonSelector: options.promptSubmitButtonSelector,
|
|
5250
|
+
selectedSourceSelector: options.promptSelectedSourceSelector,
|
|
5251
|
+
activeSourceSelector: options.promptActiveSourceSelector,
|
|
5252
|
+
dragAreaSelector: options.dropTargetContainerSelector
|
|
5253
|
+
}).catch(() => ({
|
|
5254
|
+
badgeCount: 0,
|
|
5255
|
+
selectedCount: 0,
|
|
5256
|
+
activeCount: 0,
|
|
5257
|
+
submitDisabled: null,
|
|
5258
|
+
dragAreaActive: false,
|
|
5259
|
+
dragAreaRect: null
|
|
5260
|
+
}));
|
|
5261
|
+
var normalizeCaptchaImageIndexes = (serialNumbers, imageCount) => {
|
|
5262
|
+
if (!Array.isArray(serialNumbers) || imageCount <= 0) {
|
|
5263
|
+
return [];
|
|
5264
|
+
}
|
|
5265
|
+
const areAllOneBased = serialNumbers.every((value) => value >= 1 && value <= imageCount);
|
|
5266
|
+
if (areAllOneBased) {
|
|
5267
|
+
return serialNumbers.map((value) => value - 1);
|
|
5268
|
+
}
|
|
5269
|
+
const areAllZeroBased = serialNumbers.every((value) => value >= 0 && value < imageCount);
|
|
5270
|
+
if (areAllZeroBased) {
|
|
5271
|
+
return [...serialNumbers];
|
|
5272
|
+
}
|
|
5273
|
+
return serialNumbers.map((value) => {
|
|
5274
|
+
if (value >= 1 && value <= imageCount) {
|
|
5275
|
+
return value - 1;
|
|
5276
|
+
}
|
|
5277
|
+
if (value >= 0 && value < imageCount) {
|
|
5278
|
+
return value;
|
|
5279
|
+
}
|
|
5280
|
+
return null;
|
|
5281
|
+
}).filter((value) => Number.isInteger(value));
|
|
5282
|
+
};
|
|
5283
|
+
var resolveCaptchaSourceImagesInVisualOrder = async (frame, options) => {
|
|
5284
|
+
const sourceImages = frame.locator(options.sourceImageSelector);
|
|
5285
|
+
const imageCount = await sourceImages.count().catch(() => 0);
|
|
5286
|
+
const sources = [];
|
|
5287
|
+
for (let domIndex = 0; domIndex < imageCount; domIndex += 1) {
|
|
5288
|
+
const locator = sourceImages.nth(domIndex);
|
|
5289
|
+
const box = await locator.boundingBox().catch(() => null);
|
|
5290
|
+
if (!box || box.width <= 0 || box.height <= 0) {
|
|
5291
|
+
continue;
|
|
5292
|
+
}
|
|
5293
|
+
sources.push({
|
|
5294
|
+
domIndex,
|
|
5295
|
+
locator,
|
|
5296
|
+
box
|
|
5297
|
+
});
|
|
5298
|
+
}
|
|
5299
|
+
sources.sort((left, right) => {
|
|
5300
|
+
const deltaY = left.box.y - right.box.y;
|
|
5301
|
+
if (Math.abs(deltaY) > options.sourceImageRowTolerancePx) {
|
|
5302
|
+
return deltaY;
|
|
5303
|
+
}
|
|
5304
|
+
return left.box.x - right.box.x;
|
|
5305
|
+
});
|
|
5306
|
+
return sources;
|
|
5307
|
+
};
|
|
5308
|
+
var refreshCaptcha = async (page, frame, options) => {
|
|
5309
|
+
const clicked = await clickCaptchaAction(frame, options.refreshTexts, {
|
|
5310
|
+
...options,
|
|
5311
|
+
page,
|
|
5312
|
+
logger: logger10,
|
|
5313
|
+
forceMouse: true
|
|
5314
|
+
}).catch(() => false);
|
|
5315
|
+
if (!clicked) {
|
|
5316
|
+
logger10.warn("Refresh button not found.");
|
|
5317
|
+
return false;
|
|
5318
|
+
}
|
|
5319
|
+
await page.waitForTimeout(options.refreshWaitMs);
|
|
5320
|
+
return true;
|
|
5321
|
+
};
|
|
5158
5322
|
var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
5159
5323
|
const deadline = Date.now() + options.challengeReadyTimeoutMs;
|
|
5160
5324
|
let refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
|
|
5161
5325
|
let hasSeenLoading = false;
|
|
5326
|
+
let hasSeenGuideMask = false;
|
|
5327
|
+
let hasLoggedGuideMask = false;
|
|
5162
5328
|
while (Date.now() < deadline) {
|
|
5163
5329
|
const isLoadingVisible = await isAnyCaptchaTextVisible(
|
|
5164
5330
|
frame,
|
|
@@ -5174,8 +5340,17 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5174
5340
|
const sourceImages = frame.locator(options.sourceImageSelector);
|
|
5175
5341
|
const imageCount = await sourceImages.count().catch(() => 0);
|
|
5176
5342
|
const hasVisibleSourceImage = imageCount > 0 ? await sourceImages.first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
|
|
5177
|
-
|
|
5178
|
-
|
|
5343
|
+
const hasVisibleDropTarget = await frame.locator(options.dropTargetContainerSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false);
|
|
5344
|
+
const hasGuideMaskVisible = options.guideMaskSelector ? await frame.locator(options.guideMaskSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
|
|
5345
|
+
hasSeenGuideMask = hasSeenGuideMask || hasGuideMaskVisible;
|
|
5346
|
+
if (hasGuideMaskVisible && !hasLoggedGuideMask) {
|
|
5347
|
+
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");
|
|
5348
|
+
hasLoggedGuideMask = true;
|
|
5349
|
+
}
|
|
5350
|
+
if (!isLoadingVisible && hasVisibleSourceImage && hasVisibleDropTarget && !hasGuideMaskVisible) {
|
|
5351
|
+
logger10.info(
|
|
5352
|
+
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"
|
|
5353
|
+
);
|
|
5179
5354
|
return;
|
|
5180
5355
|
}
|
|
5181
5356
|
if (hasErrorTextVisible) {
|
|
@@ -5185,8 +5360,8 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5185
5360
|
hasSeenLoading = false;
|
|
5186
5361
|
continue;
|
|
5187
5362
|
}
|
|
5188
|
-
if (!hasVisibleSourceImage && Date.now() >= refreshDeadline) {
|
|
5189
|
-
logger10.warn(`\u9A8C\u8BC1\u7801\
|
|
5363
|
+
if ((!hasVisibleSourceImage || !hasVisibleDropTarget) && Date.now() >= refreshDeadline) {
|
|
5364
|
+
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`);
|
|
5190
5365
|
await refreshCaptcha(page, frame, options);
|
|
5191
5366
|
refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
|
|
5192
5367
|
hasSeenLoading = false;
|
|
@@ -5196,6 +5371,69 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5196
5371
|
}
|
|
5197
5372
|
throw new Error("Captcha challenge is still loading and did not become ready in time.");
|
|
5198
5373
|
};
|
|
5374
|
+
var dragPromptCaptchaImage = async (page, frame, iframeLocator, sourceLocator, dropTarget, options, {
|
|
5375
|
+
attempt,
|
|
5376
|
+
visualIndex
|
|
5377
|
+
}) => {
|
|
5378
|
+
const baselineState = await readPromptCaptchaState(frame, options);
|
|
5379
|
+
const dragAttempts = [];
|
|
5380
|
+
for (const plan of PROMPT_CAPTCHA_DRAG_PLANS) {
|
|
5381
|
+
const sourceBox = await sourceLocator.boundingBox().catch(() => null);
|
|
5382
|
+
const targetBox = await dropTarget.boundingBox().catch(() => null);
|
|
5383
|
+
if (!sourceBox || !targetBox) {
|
|
5384
|
+
throw new Error("Unable to resolve prompt captcha drag coordinates.");
|
|
5385
|
+
}
|
|
5386
|
+
const targetOffsetX = (plan.endXRatio - 0.5) * targetBox.width;
|
|
5387
|
+
const targetOffsetY = (plan.endYRatio - 0.5) * targetBox.height;
|
|
5388
|
+
await dragCaptchaAction(page, sourceLocator, dropTarget, {
|
|
5389
|
+
forceMouse: true,
|
|
5390
|
+
targetOffsetX,
|
|
5391
|
+
targetOffsetY,
|
|
5392
|
+
steps: options.promptDragMoveSteps,
|
|
5393
|
+
holdDelayMs: options.promptDragHoldDelayMs,
|
|
5394
|
+
stepDelayMs: options.promptDragStepDelayMs,
|
|
5395
|
+
beforeReleaseDelayMs: options.promptDragBeforeReleaseDelayMs,
|
|
5396
|
+
afterReleaseDelayMs: options.promptDragAfterReleaseDelayMs,
|
|
5397
|
+
finalMoveRepeats: options.promptDragFinalMoveRepeats
|
|
5398
|
+
});
|
|
5399
|
+
const afterState = await readPromptCaptchaState(frame, options);
|
|
5400
|
+
const accepted = afterState.badgeCount > baselineState.badgeCount || afterState.selectedCount > baselineState.selectedCount;
|
|
5401
|
+
const attemptInfo = {
|
|
5402
|
+
planName: plan.name,
|
|
5403
|
+
sourceRect: rectOf(sourceBox),
|
|
5404
|
+
targetRect: rectOf(targetBox),
|
|
5405
|
+
targetOffsetX: Number(targetOffsetX.toFixed(2)),
|
|
5406
|
+
targetOffsetY: Number(targetOffsetY.toFixed(2)),
|
|
5407
|
+
beforeState: baselineState,
|
|
5408
|
+
afterState,
|
|
5409
|
+
accepted
|
|
5410
|
+
};
|
|
5411
|
+
dragAttempts.push(attemptInfo);
|
|
5412
|
+
logger10.info(
|
|
5413
|
+
`\u9A8C\u8BC1\u7801\u62D6\u62FD\u7B2C ${visualIndex + 1} \u5F20\uFF0C\u65B9\u6848 ${plan.name}\uFF0Cbadge ${baselineState.badgeCount} -> ${afterState.badgeCount}\uFF0Cselected ${baselineState.selectedCount} -> ${afterState.selectedCount}`
|
|
5414
|
+
);
|
|
5415
|
+
if (accepted) {
|
|
5416
|
+
return {
|
|
5417
|
+
accepted: true,
|
|
5418
|
+
dragAttempts
|
|
5419
|
+
};
|
|
5420
|
+
}
|
|
5421
|
+
if (options.promptDragRetryDelayMs > 0) {
|
|
5422
|
+
await page.waitForTimeout(options.promptDragRetryDelayMs);
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, `drag-${visualIndex + 1}-failed`, options, {
|
|
5426
|
+
visualIndex,
|
|
5427
|
+
dragAttempts,
|
|
5428
|
+
finalState: await readPromptCaptchaState(frame, options)
|
|
5429
|
+
}).catch((error) => {
|
|
5430
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u62D6\u62FD\u5931\u8D25\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5431
|
+
});
|
|
5432
|
+
return {
|
|
5433
|
+
accepted: false,
|
|
5434
|
+
dragAttempts
|
|
5435
|
+
};
|
|
5436
|
+
};
|
|
5199
5437
|
async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
5200
5438
|
const { callCaptchaRecognitionApi: callCaptchaRecognitionApi2 } = dependencies;
|
|
5201
5439
|
if (typeof callCaptchaRecognitionApi2 !== "function") {
|
|
@@ -5210,7 +5448,7 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5210
5448
|
return false;
|
|
5211
5449
|
}
|
|
5212
5450
|
logger10.info("\u5F53\u524D\u4F7F\u7528\u672Ctool\u2014\u2014\u6D4B\u8BD5\u7248\u672C");
|
|
5213
|
-
for (let attempt = 1; attempt <= config.maxRetries; attempt
|
|
5451
|
+
for (let attempt = 1; attempt <= config.maxRetries; attempt += 1) {
|
|
5214
5452
|
logger10.info(`\u5F00\u59CB\u7B2C ${attempt}/${config.maxRetries} \u6B21 verifycenter \u9A8C\u8BC1\u7801\u8BC6\u522B\u3002`);
|
|
5215
5453
|
try {
|
|
5216
5454
|
const captchaContext = await getVerifycenterCaptchaContext(page, config);
|
|
@@ -5220,6 +5458,16 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5220
5458
|
}
|
|
5221
5459
|
const { iframeLocator, frame } = captchaContext;
|
|
5222
5460
|
await waitForCaptchaChallengeReady(page, frame, config);
|
|
5461
|
+
await maybeCollectCaptchaDebugInfo(
|
|
5462
|
+
page,
|
|
5463
|
+
frame,
|
|
5464
|
+
iframeLocator,
|
|
5465
|
+
attempt,
|
|
5466
|
+
"ready",
|
|
5467
|
+
config
|
|
5468
|
+
).catch((error) => {
|
|
5469
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5470
|
+
});
|
|
5223
5471
|
await page.waitForTimeout(config.recognitionDelayMs);
|
|
5224
5472
|
const screenshotBuffer = await iframeLocator.screenshot();
|
|
5225
5473
|
const apiResponse = await callCaptchaRecognitionApi2({
|
|
@@ -5243,33 +5491,74 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5243
5491
|
await refreshCaptcha(page, frame, config);
|
|
5244
5492
|
continue;
|
|
5245
5493
|
}
|
|
5246
|
-
const
|
|
5247
|
-
const
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5494
|
+
const orderedSourceImages = await resolveCaptchaSourceImagesInVisualOrder(frame, config);
|
|
5495
|
+
const normalizedIndexes = normalizeCaptchaImageIndexes(serialNumbers, orderedSourceImages.length);
|
|
5496
|
+
if (normalizedIndexes.length !== serialNumbers.length) {
|
|
5497
|
+
throw new Error(
|
|
5498
|
+
`Captcha image indexes could not be normalized. raw=${serialNumbers.join(", ")}, count=${orderedSourceImages.length}`
|
|
5499
|
+
);
|
|
5500
|
+
}
|
|
5501
|
+
logger10.info(`\u9A8C\u8BC1\u7801\u89C6\u89C9\u4F4D\u5E8F\u6620\u5C04\uFF1A${normalizedIndexes.map((index) => index + 1).join(", ")}`);
|
|
5502
|
+
for (const imageIndex of normalizedIndexes) {
|
|
5503
|
+
if (imageIndex < 0 || imageIndex >= orderedSourceImages.length) {
|
|
5504
|
+
throw new Error(
|
|
5505
|
+
`Captcha image index ${imageIndex} is out of range. count=${orderedSourceImages.length}`
|
|
5506
|
+
);
|
|
5255
5507
|
}
|
|
5256
|
-
const sourceImage =
|
|
5508
|
+
const sourceImage = orderedSourceImages[imageIndex].locator;
|
|
5257
5509
|
await sourceImage.waitFor({
|
|
5258
5510
|
state: "visible",
|
|
5259
5511
|
timeout: config.sourceImageVisibleTimeoutMs
|
|
5260
5512
|
});
|
|
5261
|
-
await
|
|
5513
|
+
const dragResult = await dragPromptCaptchaImage(
|
|
5514
|
+
page,
|
|
5515
|
+
frame,
|
|
5516
|
+
iframeLocator,
|
|
5517
|
+
sourceImage,
|
|
5518
|
+
dropTarget,
|
|
5519
|
+
config,
|
|
5520
|
+
{
|
|
5521
|
+
attempt,
|
|
5522
|
+
visualIndex: imageIndex
|
|
5523
|
+
}
|
|
5524
|
+
);
|
|
5525
|
+
if (!dragResult.accepted) {
|
|
5526
|
+
throw new Error(`Captcha prompt drag was not accepted for visual index ${imageIndex + 1}.`);
|
|
5527
|
+
}
|
|
5528
|
+
if (config.dragBetweenWaitMs > 0) {
|
|
5529
|
+
await page.waitForTimeout(config.dragBetweenWaitMs);
|
|
5530
|
+
}
|
|
5262
5531
|
}
|
|
5263
|
-
const
|
|
5532
|
+
const beforeSubmitState = await readPromptCaptchaState(frame, config);
|
|
5533
|
+
logger10.info(
|
|
5534
|
+
`\u63D0\u4EA4\u524D\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${beforeSubmitState.badgeCount}, selected=${beforeSubmitState.selectedCount}, submitDisabled=${beforeSubmitState.submitDisabled}`
|
|
5535
|
+
);
|
|
5536
|
+
const submitted = await clickCaptchaAction(frame, config.submitTexts, {
|
|
5537
|
+
...config,
|
|
5538
|
+
page,
|
|
5539
|
+
logger: logger10,
|
|
5540
|
+
forceMouse: true,
|
|
5541
|
+
actionVisibleTimeoutMs: config.submitReadyTimeoutMs
|
|
5542
|
+
}).catch(() => false);
|
|
5264
5543
|
if (!submitted) {
|
|
5265
5544
|
logger10.warn("\u672A\u627E\u5230\u63D0\u4EA4\u6309\u94AE\uFF0C\u53EF\u80FD\u4F1A\u81EA\u52A8\u63D0\u4EA4\u3002");
|
|
5266
5545
|
}
|
|
5267
5546
|
await page.waitForTimeout(config.submitWaitMs);
|
|
5547
|
+
const afterSubmitState = await readPromptCaptchaState(frame, config);
|
|
5548
|
+
logger10.info(
|
|
5549
|
+
`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${afterSubmitState.badgeCount}, selected=${afterSubmitState.selectedCount}, submitDisabled=${afterSubmitState.submitDisabled}`
|
|
5550
|
+
);
|
|
5268
5551
|
const stillVisible = await iframeLocator.isVisible({ timeout: config.containerVisibleTimeoutMs }).catch(() => false);
|
|
5269
5552
|
if (!stillVisible) {
|
|
5270
5553
|
logger10.info("\u9A8C\u8BC1\u7801\u8BC6\u522B\u5E76\u63D0\u4EA4\u6210\u529F\u3002");
|
|
5271
5554
|
return true;
|
|
5272
5555
|
}
|
|
5556
|
+
await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, "submit-still-visible", config, {
|
|
5557
|
+
beforeSubmitState,
|
|
5558
|
+
afterSubmitState
|
|
5559
|
+
}).catch((error) => {
|
|
5560
|
+
logger10.warn(`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5561
|
+
});
|
|
5273
5562
|
logger10.warn("\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801 iframe \u4ECD\u7136\u53EF\u89C1\uFF0C\u51C6\u5907\u5237\u65B0\u540E\u91CD\u8BD5\u3002");
|
|
5274
5563
|
await page.waitForTimeout(2e3);
|
|
5275
5564
|
await refreshCaptcha(page, frame, config);
|
|
@@ -5712,14 +6001,14 @@ var Mutation = {
|
|
|
5712
6001
|
const isFrameElement = tagName === "IFRAME" || tagName === "FRAME";
|
|
5713
6002
|
const nodeName = descriptor?.id || descriptor?.name || "no-id";
|
|
5714
6003
|
let source = "main";
|
|
5715
|
-
let
|
|
6004
|
+
let path3 = `${selector}[${index}]`;
|
|
5716
6005
|
let text = "";
|
|
5717
6006
|
let html = "";
|
|
5718
6007
|
let frameUrl = "";
|
|
5719
6008
|
let readyState = "";
|
|
5720
6009
|
if (isFrameElement) {
|
|
5721
6010
|
source = "iframe";
|
|
5722
|
-
|
|
6011
|
+
path3 = `${selector}[${index}]::iframe(${nodeName})`;
|
|
5723
6012
|
const frame = await handle.contentFrame();
|
|
5724
6013
|
if (frame) {
|
|
5725
6014
|
try {
|
|
@@ -5749,7 +6038,7 @@ var Mutation = {
|
|
|
5749
6038
|
items.push({
|
|
5750
6039
|
selector,
|
|
5751
6040
|
source,
|
|
5752
|
-
path:
|
|
6041
|
+
path: path3,
|
|
5753
6042
|
text,
|
|
5754
6043
|
html,
|
|
5755
6044
|
frameUrl,
|