@skrillex1224/playwright-toolkit 2.1.244 → 2.1.246
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 +459 -148
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +459 -148
- package/dist/index.js.map +4 -4
- package/index.d.ts +7 -0
- 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
|
};
|
|
@@ -3802,47 +3826,6 @@ var scrollAwayFromObstruction = async (element, status) => {
|
|
|
3802
3826
|
};
|
|
3803
3827
|
}, status);
|
|
3804
3828
|
};
|
|
3805
|
-
var getElementViewportSnapshot = async (element) => {
|
|
3806
|
-
return element.evaluate((el) => {
|
|
3807
|
-
const rect = el.getBoundingClientRect();
|
|
3808
|
-
return {
|
|
3809
|
-
top: rect.top,
|
|
3810
|
-
bottom: rect.bottom,
|
|
3811
|
-
left: rect.left,
|
|
3812
|
-
right: rect.right,
|
|
3813
|
-
width: rect.width,
|
|
3814
|
-
height: rect.height,
|
|
3815
|
-
scrollX: window.scrollX,
|
|
3816
|
-
scrollY: window.scrollY
|
|
3817
|
-
};
|
|
3818
|
-
});
|
|
3819
|
-
};
|
|
3820
|
-
var isTargetImmobileAfterScroll = (before, after) => {
|
|
3821
|
-
if (!before || !after) return false;
|
|
3822
|
-
const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
|
|
3823
|
-
const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
|
|
3824
|
-
const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
|
|
3825
|
-
const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
|
|
3826
|
-
const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
|
|
3827
|
-
const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
|
|
3828
|
-
if (!rectMoved && !pageMoved) return true;
|
|
3829
|
-
if (pageMoved && !rectMoved) return true;
|
|
3830
|
-
if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
|
|
3831
|
-
return true;
|
|
3832
|
-
}
|
|
3833
|
-
return false;
|
|
3834
|
-
};
|
|
3835
|
-
var restoreWindowFromSnapshot = async (page, before, after) => {
|
|
3836
|
-
if (!before || !after) return;
|
|
3837
|
-
if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
|
|
3838
|
-
return;
|
|
3839
|
-
}
|
|
3840
|
-
await page.evaluate(
|
|
3841
|
-
(state) => window.scrollTo(state.x, state.y),
|
|
3842
|
-
{ x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
|
|
3843
|
-
).catch(() => {
|
|
3844
|
-
});
|
|
3845
|
-
};
|
|
3846
3829
|
var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
|
|
3847
3830
|
const viewport = resolveViewport(page);
|
|
3848
3831
|
const rawRect = options.rect || null;
|
|
@@ -4023,11 +4006,11 @@ var MobileHumanize = {
|
|
|
4023
4006
|
const scrollRect = await getScrollableRect(element);
|
|
4024
4007
|
if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4025
4008
|
logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (direction=${status.direction || "unknown"})`);
|
|
4026
|
-
return { element, didScroll, restore: null
|
|
4009
|
+
return { element, didScroll, restore: null };
|
|
4027
4010
|
}
|
|
4028
4011
|
if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
|
|
4029
4012
|
logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
|
|
4030
|
-
return { element, didScroll, restore: null
|
|
4013
|
+
return { element, didScroll, restore: null };
|
|
4031
4014
|
}
|
|
4032
4015
|
if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
|
|
4033
4016
|
const moved = await scrollAwayFromObstruction(element, status);
|
|
@@ -4058,7 +4041,6 @@ var MobileHumanize = {
|
|
|
4058
4041
|
}
|
|
4059
4042
|
}
|
|
4060
4043
|
const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
|
|
4061
|
-
const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4062
4044
|
const beforeState = scrollRect ? await element.evaluate((el) => {
|
|
4063
4045
|
const isScrollable = (node) => {
|
|
4064
4046
|
const style = window.getComputedStyle(node);
|
|
@@ -4088,21 +4070,6 @@ var MobileHumanize = {
|
|
|
4088
4070
|
logger7.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
|
|
4089
4071
|
}
|
|
4090
4072
|
}
|
|
4091
|
-
let afterElementSnapshot = null;
|
|
4092
|
-
const readAfterElementSnapshot = async () => {
|
|
4093
|
-
if (!afterElementSnapshot) {
|
|
4094
|
-
afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4095
|
-
}
|
|
4096
|
-
return afterElementSnapshot;
|
|
4097
|
-
};
|
|
4098
|
-
if (!scrollRect && beforeElementSnapshot) {
|
|
4099
|
-
const afterSnapshot = await readAfterElementSnapshot();
|
|
4100
|
-
if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
|
|
4101
|
-
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
4102
|
-
logger7.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u9875\u9762\u6EDA\u52A8\u79FB\u52A8\uFF0C\u9875\u9762\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
|
|
4103
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
4104
|
-
}
|
|
4105
|
-
}
|
|
4106
4073
|
if (scrollRect && beforeState) {
|
|
4107
4074
|
const afterState = await element.evaluate((el) => {
|
|
4108
4075
|
const isScrollable = (node) => {
|
|
@@ -4140,14 +4107,6 @@ var MobileHumanize = {
|
|
|
4140
4107
|
}
|
|
4141
4108
|
}
|
|
4142
4109
|
}
|
|
4143
|
-
if (scrollRect && beforeElementSnapshot) {
|
|
4144
|
-
const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4145
|
-
if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
|
|
4146
|
-
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
4147
|
-
logger7.warn(`humanScroll | \u76EE\u6807\u4E0D\u968F\u6EDA\u52A8\u5BB9\u5668\u79FB\u52A8\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u6539\u53D8\u5176\u4F4D\u7F6E (status=${status.code}, direction=${status.direction || "unknown"})`);
|
|
4148
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
4149
|
-
}
|
|
4150
|
-
}
|
|
4151
4110
|
didScroll = true;
|
|
4152
4111
|
}
|
|
4153
4112
|
try {
|
|
@@ -4197,28 +4156,21 @@ var MobileHumanize = {
|
|
|
4197
4156
|
logger7.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
|
|
4198
4157
|
return false;
|
|
4199
4158
|
}
|
|
4200
|
-
|
|
4159
|
+
if (scrollIfNeeded) {
|
|
4160
|
+
await MobileHumanize.humanScroll(page, element, { throwOnMissing });
|
|
4161
|
+
}
|
|
4201
4162
|
const status = await checkElementVisibility(element).catch(() => null);
|
|
4202
4163
|
if (status && status.code !== "VISIBLE") {
|
|
4203
|
-
if (fallbackDomClick &&
|
|
4204
|
-
|
|
4164
|
+
if (fallbackDomClick && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4165
|
+
const fallback = await withTimeout(
|
|
4205
4166
|
() => activateElementFallback(element, null, {
|
|
4206
4167
|
editableOnly: true
|
|
4207
4168
|
}),
|
|
4208
4169
|
activateFallbackTimeoutMs,
|
|
4209
4170
|
"focus fallback"
|
|
4210
4171
|
).catch(() => null);
|
|
4211
|
-
if (!fallback?.activated) {
|
|
4212
|
-
fallback = await withTimeout(
|
|
4213
|
-
() => activateElementFallback(element, null, {
|
|
4214
|
-
editableOnly: false
|
|
4215
|
-
}),
|
|
4216
|
-
activateFallbackTimeoutMs,
|
|
4217
|
-
"activation fallback"
|
|
4218
|
-
).catch(() => null);
|
|
4219
|
-
}
|
|
4220
4172
|
if (fallback?.activated) {
|
|
4221
|
-
logger7.warn(`humanClick: \
|
|
4173
|
+
logger7.warn(`humanClick: fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
|
|
4222
4174
|
return true;
|
|
4223
4175
|
}
|
|
4224
4176
|
}
|
|
@@ -4924,6 +4876,10 @@ var LiveView = {
|
|
|
4924
4876
|
// src/chaptcha.js
|
|
4925
4877
|
import { v4 as uuidv4 } from "uuid";
|
|
4926
4878
|
|
|
4879
|
+
// src/internals/captcha/bytedance.js
|
|
4880
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
4881
|
+
import path2 from "path";
|
|
4882
|
+
|
|
4927
4883
|
// src/internals/captcha/shared.js
|
|
4928
4884
|
var waitForVisible = async (locator, timeout) => {
|
|
4929
4885
|
try {
|
|
@@ -4941,38 +4897,71 @@ var isAnyCaptchaTextVisible = async (frame, texts, timeout) => {
|
|
|
4941
4897
|
if (!text) {
|
|
4942
4898
|
continue;
|
|
4943
4899
|
}
|
|
4944
|
-
const
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4900
|
+
const textLocator = frame.getByText(text, { exact: false });
|
|
4901
|
+
const locatorText = frame.locator(`text=${text}`);
|
|
4902
|
+
const candidateGroups = [textLocator, locatorText];
|
|
4903
|
+
for (const candidateGroup of candidateGroups) {
|
|
4904
|
+
const candidateCount = await candidateGroup.count().catch(() => 0);
|
|
4905
|
+
for (let index = 0; index < candidateCount; index += 1) {
|
|
4906
|
+
const candidate = candidateGroup.nth(index);
|
|
4907
|
+
const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
|
|
4908
|
+
if (isVisible) {
|
|
4909
|
+
return true;
|
|
4910
|
+
}
|
|
4952
4911
|
}
|
|
4953
4912
|
}
|
|
4954
4913
|
}
|
|
4955
4914
|
return false;
|
|
4956
4915
|
};
|
|
4916
|
+
var collectVisibleCandidateIndexes = async (candidateGroup, count, timeout) => {
|
|
4917
|
+
const visibleIndexes = [];
|
|
4918
|
+
for (let index = 0; index < count; index += 1) {
|
|
4919
|
+
const candidate = candidateGroup.nth(index);
|
|
4920
|
+
const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
|
|
4921
|
+
if (isVisible) {
|
|
4922
|
+
visibleIndexes.push(index);
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
return visibleIndexes;
|
|
4926
|
+
};
|
|
4957
4927
|
var clickCaptchaAction = async (frame, texts, options) => {
|
|
4958
4928
|
for (const text of texts || []) {
|
|
4959
|
-
const
|
|
4960
|
-
|
|
4961
|
-
|
|
4929
|
+
const textLocator = frame.getByText(text, { exact: false });
|
|
4930
|
+
const locatorText = frame.locator(`text=${text}`);
|
|
4931
|
+
const [getByTextCount, locatorTextCount] = await Promise.all([
|
|
4932
|
+
textLocator.count().catch(() => 0),
|
|
4933
|
+
locatorText.count().catch(() => 0)
|
|
4934
|
+
]);
|
|
4935
|
+
const [getByTextVisibleIndexes, locatorTextVisibleIndexes] = await Promise.all([
|
|
4936
|
+
collectVisibleCandidateIndexes(textLocator, getByTextCount, options.actionVisibleTimeoutMs),
|
|
4937
|
+
collectVisibleCandidateIndexes(locatorText, locatorTextCount, options.actionVisibleTimeoutMs)
|
|
4938
|
+
]);
|
|
4939
|
+
options.logger?.info(
|
|
4940
|
+
`[CaptchaAction] \u6587\u672C "${text}" \u547D\u4E2D\u6570\u91CF\uFF1AgetByText=${getByTextCount} (visible=${getByTextVisibleIndexes.length}), locator=${locatorTextCount} (visible=${locatorTextVisibleIndexes.length})`
|
|
4941
|
+
);
|
|
4942
|
+
const candidateGroups = [
|
|
4943
|
+
{ label: "getByText", locator: textLocator, count: getByTextCount },
|
|
4944
|
+
{ label: "locator", locator: locatorText, count: locatorTextCount }
|
|
4962
4945
|
];
|
|
4963
|
-
for (const
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4946
|
+
for (const candidateGroup of candidateGroups) {
|
|
4947
|
+
for (let index = 0; index < candidateGroup.count; index += 1) {
|
|
4948
|
+
const candidate = candidateGroup.locator.nth(index);
|
|
4949
|
+
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
4950
|
+
if (!isVisible) {
|
|
4951
|
+
continue;
|
|
4952
|
+
}
|
|
4953
|
+
options.logger?.info(
|
|
4954
|
+
`[CaptchaAction] \u6587\u672C "${text}" \u9009\u62E9 ${candidateGroup.label}[${index}] \u4F5C\u4E3A\u7B2C\u4E00\u4E2A\u53EF\u89C1\u8282\u70B9\u6267\u884C\u70B9\u51FB\u3002`
|
|
4955
|
+
);
|
|
4956
|
+
await DeviceInput.click(options.page, candidate, options);
|
|
4957
|
+
return true;
|
|
4967
4958
|
}
|
|
4968
|
-
await DeviceInput.click(options.page, candidate);
|
|
4969
|
-
return true;
|
|
4970
4959
|
}
|
|
4971
4960
|
}
|
|
4972
4961
|
return false;
|
|
4973
4962
|
};
|
|
4974
|
-
var dragCaptchaAction = async (page, sourceLocator, targetLocator) => {
|
|
4975
|
-
await DeviceInput.drag(page, sourceLocator, targetLocator);
|
|
4963
|
+
var dragCaptchaAction = async (page, sourceLocator, targetLocator, options = {}) => {
|
|
4964
|
+
await DeviceInput.drag(page, sourceLocator, targetLocator, options);
|
|
4976
4965
|
};
|
|
4977
4966
|
|
|
4978
4967
|
// src/internals/captcha/bytedance.js
|
|
@@ -4983,11 +4972,12 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
|
4983
4972
|
containerSelector: "#captcha_container",
|
|
4984
4973
|
iframeSelector: 'iframe[src*="verifycenter"]',
|
|
4985
4974
|
iframeFallbackSelector: "iframe",
|
|
4986
|
-
sourceImageSelector: "
|
|
4987
|
-
dropTargetContainerSelector: "
|
|
4975
|
+
sourceImageSelector: ".img-container .canvas-container",
|
|
4976
|
+
dropTargetContainerSelector: ".drag-area",
|
|
4988
4977
|
dropTargetTexts: ["\u62D6\u62FD\u5230\u8FD9\u91CC"],
|
|
4989
4978
|
refreshTexts: ["\u5237\u65B0"],
|
|
4990
4979
|
submitTexts: ["\u63D0\u4EA4"],
|
|
4980
|
+
guideMaskSelector: ".play-guide-mask",
|
|
4991
4981
|
recognitionSuccessCode: 1e4,
|
|
4992
4982
|
containerVisibleTimeoutMs: 2e3,
|
|
4993
4983
|
iframeVisibleTimeoutMs: 12e3,
|
|
@@ -5010,10 +5000,111 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
|
5010
5000
|
],
|
|
5011
5001
|
recognitionDelayMs: 2e3,
|
|
5012
5002
|
refreshWaitMs: 3e3,
|
|
5013
|
-
submitWaitMs:
|
|
5003
|
+
submitWaitMs: 5e3,
|
|
5004
|
+
submitReadyTimeoutMs: 2500,
|
|
5014
5005
|
retryDelayBaseMs: 2e3,
|
|
5015
|
-
retryDelayStepMs: 1e3
|
|
5006
|
+
retryDelayStepMs: 1e3,
|
|
5007
|
+
sourceImageRowTolerancePx: 24,
|
|
5008
|
+
dragBetweenWaitMs: 250,
|
|
5009
|
+
promptBadgeCountSelector: ".drag-area .photo-badge .badge span",
|
|
5010
|
+
promptSubmitButtonSelector: ".vc-captcha-verify-mobile-button",
|
|
5011
|
+
promptSelectedSourceSelector: ".img-container .canvas-container.selected",
|
|
5012
|
+
promptActiveSourceSelector: ".img-container .canvas-container.active",
|
|
5013
|
+
promptDragMoveSteps: 16,
|
|
5014
|
+
promptDragStepDelayMs: 55,
|
|
5015
|
+
promptDragHoldDelayMs: 240,
|
|
5016
|
+
promptDragBeforeReleaseDelayMs: 180,
|
|
5017
|
+
promptDragAfterReleaseDelayMs: 240,
|
|
5018
|
+
promptDragFinalMoveRepeats: 3,
|
|
5019
|
+
promptDragRetryDelayMs: 250,
|
|
5020
|
+
debugArtifacts: false
|
|
5016
5021
|
});
|
|
5022
|
+
var PROMPT_CAPTCHA_DRAG_PLANS = Object.freeze([
|
|
5023
|
+
{ name: "lower-middle", endXRatio: 0.5, endYRatio: 0.72 },
|
|
5024
|
+
{ name: "center-middle", endXRatio: 0.5, endYRatio: 0.56 },
|
|
5025
|
+
{ name: "upper-middle", endXRatio: 0.5, endYRatio: 0.4 }
|
|
5026
|
+
]);
|
|
5027
|
+
var resolveCaptchaDebugDir = () => path2.resolve(process.cwd(), "storage", "captcha-debug");
|
|
5028
|
+
var rectOf = (rect) => {
|
|
5029
|
+
if (!rect) {
|
|
5030
|
+
return null;
|
|
5031
|
+
}
|
|
5032
|
+
return {
|
|
5033
|
+
x: Number(rect.x.toFixed(2)),
|
|
5034
|
+
y: Number(rect.y.toFixed(2)),
|
|
5035
|
+
width: Number(rect.width.toFixed(2)),
|
|
5036
|
+
height: Number(rect.height.toFixed(2))
|
|
5037
|
+
};
|
|
5038
|
+
};
|
|
5039
|
+
var collectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, extra = null) => {
|
|
5040
|
+
const timestamp = Date.now();
|
|
5041
|
+
const debugDir = resolveCaptchaDebugDir();
|
|
5042
|
+
await mkdir(debugDir, { recursive: true });
|
|
5043
|
+
const baseName = `bytedance-${timestamp}-attempt${attempt}-${phase}`;
|
|
5044
|
+
const iframeScreenshotPath = path2.join(debugDir, `${baseName}-iframe.png`);
|
|
5045
|
+
const pageScreenshotPath = path2.join(debugDir, `${baseName}-page.png`);
|
|
5046
|
+
const htmlPath = path2.join(debugDir, `${baseName}-iframe.html`);
|
|
5047
|
+
const infoPath = path2.join(debugDir, `${baseName}-info.json`);
|
|
5048
|
+
await iframeLocator.screenshot({ path: iframeScreenshotPath }).catch(() => {
|
|
5049
|
+
});
|
|
5050
|
+
await page.screenshot({ path: pageScreenshotPath, fullPage: true }).catch(() => {
|
|
5051
|
+
});
|
|
5052
|
+
const html = await frame.evaluate(() => document.documentElement.outerHTML).catch(() => "");
|
|
5053
|
+
if (html) {
|
|
5054
|
+
await writeFile(htmlPath, html, "utf8");
|
|
5055
|
+
}
|
|
5056
|
+
const info = await frame.evaluate(() => {
|
|
5057
|
+
const toRect = (element) => {
|
|
5058
|
+
const rect = element.getBoundingClientRect();
|
|
5059
|
+
return {
|
|
5060
|
+
x: Number(rect.x.toFixed(2)),
|
|
5061
|
+
y: Number(rect.y.toFixed(2)),
|
|
5062
|
+
width: Number(rect.width.toFixed(2)),
|
|
5063
|
+
height: Number(rect.height.toFixed(2))
|
|
5064
|
+
};
|
|
5065
|
+
};
|
|
5066
|
+
const toItem = (element, index) => ({
|
|
5067
|
+
index,
|
|
5068
|
+
tag: element.tagName,
|
|
5069
|
+
id: element.id || "",
|
|
5070
|
+
className: typeof element.className === "string" ? element.className : "",
|
|
5071
|
+
text: String(element.textContent || "").trim(),
|
|
5072
|
+
rect: toRect(element)
|
|
5073
|
+
});
|
|
5074
|
+
const visibleNodes = Array.from(document.querySelectorAll("body *")).map(toItem).filter((item) => item.rect.width > 0 && item.rect.height > 0);
|
|
5075
|
+
return {
|
|
5076
|
+
title: document.title,
|
|
5077
|
+
bodyText: String(document.body?.innerText || "").trim(),
|
|
5078
|
+
canvasContainers: visibleNodes.filter((item) => item.className.includes("canvas-container")),
|
|
5079
|
+
canvasNodes: visibleNodes.filter((item) => item.tag === "CANVAS"),
|
|
5080
|
+
captchaNodes: visibleNodes.filter((item) => item.id.startsWith("captcha_") || item.className.includes("captcha") || item.className.includes("verify") || /拖拽到这里|刷新|提交/.test(item.text)),
|
|
5081
|
+
visibleTextNodes: visibleNodes.filter((item) => item.text).slice(0, 300)
|
|
5082
|
+
};
|
|
5083
|
+
}).catch(() => null);
|
|
5084
|
+
if (info) {
|
|
5085
|
+
const payload = {
|
|
5086
|
+
capturedAt: new Date(timestamp).toISOString(),
|
|
5087
|
+
pageUrl: page.url(),
|
|
5088
|
+
attempt,
|
|
5089
|
+
phase,
|
|
5090
|
+
iframeScreenshotPath,
|
|
5091
|
+
pageScreenshotPath,
|
|
5092
|
+
htmlPath,
|
|
5093
|
+
info
|
|
5094
|
+
};
|
|
5095
|
+
if (extra != null) {
|
|
5096
|
+
payload.extra = extra;
|
|
5097
|
+
}
|
|
5098
|
+
await writeFile(infoPath, JSON.stringify(payload, null, 2), "utf8");
|
|
5099
|
+
}
|
|
5100
|
+
logger10.info(`\u5DF2\u5199\u51FA\u9A8C\u8BC1\u7801\u8C03\u8BD5\u4EA7\u7269\uFF1A${debugDir}`);
|
|
5101
|
+
};
|
|
5102
|
+
var maybeCollectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, options, extra = null) => {
|
|
5103
|
+
if (!options.debugArtifacts) {
|
|
5104
|
+
return;
|
|
5105
|
+
}
|
|
5106
|
+
await collectCaptchaDebugInfo(page, frame, iframeLocator, attempt, phase, extra);
|
|
5107
|
+
};
|
|
5017
5108
|
var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
5018
5109
|
const serialNumbers = apiResponse?.data?.data?.serial_number;
|
|
5019
5110
|
if (!Array.isArray(serialNumbers)) {
|
|
@@ -5022,7 +5113,7 @@ var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
|
5022
5113
|
return serialNumbers.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 0);
|
|
5023
5114
|
};
|
|
5024
5115
|
var resolveContentFrame = async (page, iframeLocator, options) => {
|
|
5025
|
-
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt
|
|
5116
|
+
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt += 1) {
|
|
5026
5117
|
const iframeHandle = await iframeLocator.elementHandle();
|
|
5027
5118
|
const frame = await iframeHandle?.contentFrame();
|
|
5028
5119
|
if (frame) {
|
|
@@ -5067,20 +5158,15 @@ var getVerifycenterCaptchaContext = async (page, options) => {
|
|
|
5067
5158
|
}
|
|
5068
5159
|
return { iframeLocator, frame };
|
|
5069
5160
|
};
|
|
5070
|
-
var refreshCaptcha = async (page, frame, options) => {
|
|
5071
|
-
const clicked = await clickCaptchaAction(frame, options.refreshTexts, { ...options, page }).catch(() => false);
|
|
5072
|
-
if (!clicked) {
|
|
5073
|
-
logger10.warn("Refresh button not found.");
|
|
5074
|
-
return false;
|
|
5075
|
-
}
|
|
5076
|
-
await page.waitForTimeout(options.refreshWaitMs);
|
|
5077
|
-
return true;
|
|
5078
|
-
};
|
|
5079
5161
|
var findCaptchaDropTarget = async (frame, options) => {
|
|
5162
|
+
const directTarget = frame.locator(options.dropTargetContainerSelector).first();
|
|
5163
|
+
if (await waitForVisible(directTarget, options.actionVisibleTimeoutMs)) {
|
|
5164
|
+
return directTarget;
|
|
5165
|
+
}
|
|
5080
5166
|
for (const text of options.dropTargetTexts) {
|
|
5081
5167
|
const candidates = [
|
|
5082
|
-
frame.
|
|
5083
|
-
frame.
|
|
5168
|
+
frame.getByText(text, { exact: false }).first(),
|
|
5169
|
+
frame.locator(`text=${text}`).first()
|
|
5084
5170
|
];
|
|
5085
5171
|
for (const candidate of candidates) {
|
|
5086
5172
|
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
@@ -5091,10 +5177,112 @@ var findCaptchaDropTarget = async (frame, options) => {
|
|
|
5091
5177
|
}
|
|
5092
5178
|
return null;
|
|
5093
5179
|
};
|
|
5180
|
+
var readPromptCaptchaState = async (frame, options) => frame.evaluate((selectors) => {
|
|
5181
|
+
const toRect = (element) => {
|
|
5182
|
+
if (!element) {
|
|
5183
|
+
return null;
|
|
5184
|
+
}
|
|
5185
|
+
const rect = element.getBoundingClientRect();
|
|
5186
|
+
return {
|
|
5187
|
+
x: Number(rect.x.toFixed(2)),
|
|
5188
|
+
y: Number(rect.y.toFixed(2)),
|
|
5189
|
+
width: Number(rect.width.toFixed(2)),
|
|
5190
|
+
height: Number(rect.height.toFixed(2))
|
|
5191
|
+
};
|
|
5192
|
+
};
|
|
5193
|
+
const badgeNode = document.querySelector(selectors.badgeCountSelector);
|
|
5194
|
+
const submitButton = document.querySelector(selectors.submitButtonSelector);
|
|
5195
|
+
const dragArea = document.querySelector(selectors.dragAreaSelector);
|
|
5196
|
+
const badgeCount = badgeNode ? Number.parseInt(String(badgeNode.textContent || "").trim(), 10) : 0;
|
|
5197
|
+
return {
|
|
5198
|
+
badgeCount: Number.isFinite(badgeCount) ? badgeCount : 0,
|
|
5199
|
+
selectedCount: document.querySelectorAll(selectors.selectedSourceSelector).length,
|
|
5200
|
+
activeCount: document.querySelectorAll(selectors.activeSourceSelector).length,
|
|
5201
|
+
submitDisabled: submitButton ? submitButton.classList.contains("disable") : null,
|
|
5202
|
+
dragAreaActive: dragArea ? dragArea.classList.contains("active") : false,
|
|
5203
|
+
dragAreaRect: toRect(dragArea)
|
|
5204
|
+
};
|
|
5205
|
+
}, {
|
|
5206
|
+
badgeCountSelector: options.promptBadgeCountSelector,
|
|
5207
|
+
submitButtonSelector: options.promptSubmitButtonSelector,
|
|
5208
|
+
selectedSourceSelector: options.promptSelectedSourceSelector,
|
|
5209
|
+
activeSourceSelector: options.promptActiveSourceSelector,
|
|
5210
|
+
dragAreaSelector: options.dropTargetContainerSelector
|
|
5211
|
+
}).catch(() => ({
|
|
5212
|
+
badgeCount: 0,
|
|
5213
|
+
selectedCount: 0,
|
|
5214
|
+
activeCount: 0,
|
|
5215
|
+
submitDisabled: null,
|
|
5216
|
+
dragAreaActive: false,
|
|
5217
|
+
dragAreaRect: null
|
|
5218
|
+
}));
|
|
5219
|
+
var normalizeCaptchaImageIndexes = (serialNumbers, imageCount) => {
|
|
5220
|
+
if (!Array.isArray(serialNumbers) || imageCount <= 0) {
|
|
5221
|
+
return [];
|
|
5222
|
+
}
|
|
5223
|
+
const areAllOneBased = serialNumbers.every((value) => value >= 1 && value <= imageCount);
|
|
5224
|
+
if (areAllOneBased) {
|
|
5225
|
+
return serialNumbers.map((value) => value - 1);
|
|
5226
|
+
}
|
|
5227
|
+
const areAllZeroBased = serialNumbers.every((value) => value >= 0 && value < imageCount);
|
|
5228
|
+
if (areAllZeroBased) {
|
|
5229
|
+
return [...serialNumbers];
|
|
5230
|
+
}
|
|
5231
|
+
return serialNumbers.map((value) => {
|
|
5232
|
+
if (value >= 1 && value <= imageCount) {
|
|
5233
|
+
return value - 1;
|
|
5234
|
+
}
|
|
5235
|
+
if (value >= 0 && value < imageCount) {
|
|
5236
|
+
return value;
|
|
5237
|
+
}
|
|
5238
|
+
return null;
|
|
5239
|
+
}).filter((value) => Number.isInteger(value));
|
|
5240
|
+
};
|
|
5241
|
+
var resolveCaptchaSourceImagesInVisualOrder = async (frame, options) => {
|
|
5242
|
+
const sourceImages = frame.locator(options.sourceImageSelector);
|
|
5243
|
+
const imageCount = await sourceImages.count().catch(() => 0);
|
|
5244
|
+
const sources = [];
|
|
5245
|
+
for (let domIndex = 0; domIndex < imageCount; domIndex += 1) {
|
|
5246
|
+
const locator = sourceImages.nth(domIndex);
|
|
5247
|
+
const box = await locator.boundingBox().catch(() => null);
|
|
5248
|
+
if (!box || box.width <= 0 || box.height <= 0) {
|
|
5249
|
+
continue;
|
|
5250
|
+
}
|
|
5251
|
+
sources.push({
|
|
5252
|
+
domIndex,
|
|
5253
|
+
locator,
|
|
5254
|
+
box
|
|
5255
|
+
});
|
|
5256
|
+
}
|
|
5257
|
+
sources.sort((left, right) => {
|
|
5258
|
+
const deltaY = left.box.y - right.box.y;
|
|
5259
|
+
if (Math.abs(deltaY) > options.sourceImageRowTolerancePx) {
|
|
5260
|
+
return deltaY;
|
|
5261
|
+
}
|
|
5262
|
+
return left.box.x - right.box.x;
|
|
5263
|
+
});
|
|
5264
|
+
return sources;
|
|
5265
|
+
};
|
|
5266
|
+
var refreshCaptcha = async (page, frame, options) => {
|
|
5267
|
+
const clicked = await clickCaptchaAction(frame, options.refreshTexts, {
|
|
5268
|
+
...options,
|
|
5269
|
+
page,
|
|
5270
|
+
logger: logger10,
|
|
5271
|
+
forceMouse: true
|
|
5272
|
+
}).catch(() => false);
|
|
5273
|
+
if (!clicked) {
|
|
5274
|
+
logger10.warn("Refresh button not found.");
|
|
5275
|
+
return false;
|
|
5276
|
+
}
|
|
5277
|
+
await page.waitForTimeout(options.refreshWaitMs);
|
|
5278
|
+
return true;
|
|
5279
|
+
};
|
|
5094
5280
|
var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
5095
5281
|
const deadline = Date.now() + options.challengeReadyTimeoutMs;
|
|
5096
5282
|
let refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
|
|
5097
5283
|
let hasSeenLoading = false;
|
|
5284
|
+
let hasSeenGuideMask = false;
|
|
5285
|
+
let hasLoggedGuideMask = false;
|
|
5098
5286
|
while (Date.now() < deadline) {
|
|
5099
5287
|
const isLoadingVisible = await isAnyCaptchaTextVisible(
|
|
5100
5288
|
frame,
|
|
@@ -5110,8 +5298,17 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5110
5298
|
const sourceImages = frame.locator(options.sourceImageSelector);
|
|
5111
5299
|
const imageCount = await sourceImages.count().catch(() => 0);
|
|
5112
5300
|
const hasVisibleSourceImage = imageCount > 0 ? await sourceImages.first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
|
|
5113
|
-
|
|
5114
|
-
|
|
5301
|
+
const hasVisibleDropTarget = await frame.locator(options.dropTargetContainerSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false);
|
|
5302
|
+
const hasGuideMaskVisible = options.guideMaskSelector ? await frame.locator(options.guideMaskSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
|
|
5303
|
+
hasSeenGuideMask = hasSeenGuideMask || hasGuideMaskVisible;
|
|
5304
|
+
if (hasGuideMaskVisible && !hasLoggedGuideMask) {
|
|
5305
|
+
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");
|
|
5306
|
+
hasLoggedGuideMask = true;
|
|
5307
|
+
}
|
|
5308
|
+
if (!isLoadingVisible && hasVisibleSourceImage && hasVisibleDropTarget && !hasGuideMaskVisible) {
|
|
5309
|
+
logger10.info(
|
|
5310
|
+
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"
|
|
5311
|
+
);
|
|
5115
5312
|
return;
|
|
5116
5313
|
}
|
|
5117
5314
|
if (hasErrorTextVisible) {
|
|
@@ -5121,8 +5318,8 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5121
5318
|
hasSeenLoading = false;
|
|
5122
5319
|
continue;
|
|
5123
5320
|
}
|
|
5124
|
-
if (!hasVisibleSourceImage && Date.now() >= refreshDeadline) {
|
|
5125
|
-
logger10.warn(`\u9A8C\u8BC1\u7801\
|
|
5321
|
+
if ((!hasVisibleSourceImage || !hasVisibleDropTarget) && Date.now() >= refreshDeadline) {
|
|
5322
|
+
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`);
|
|
5126
5323
|
await refreshCaptcha(page, frame, options);
|
|
5127
5324
|
refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
|
|
5128
5325
|
hasSeenLoading = false;
|
|
@@ -5132,6 +5329,69 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5132
5329
|
}
|
|
5133
5330
|
throw new Error("Captcha challenge is still loading and did not become ready in time.");
|
|
5134
5331
|
};
|
|
5332
|
+
var dragPromptCaptchaImage = async (page, frame, iframeLocator, sourceLocator, dropTarget, options, {
|
|
5333
|
+
attempt,
|
|
5334
|
+
visualIndex
|
|
5335
|
+
}) => {
|
|
5336
|
+
const baselineState = await readPromptCaptchaState(frame, options);
|
|
5337
|
+
const dragAttempts = [];
|
|
5338
|
+
for (const plan of PROMPT_CAPTCHA_DRAG_PLANS) {
|
|
5339
|
+
const sourceBox = await sourceLocator.boundingBox().catch(() => null);
|
|
5340
|
+
const targetBox = await dropTarget.boundingBox().catch(() => null);
|
|
5341
|
+
if (!sourceBox || !targetBox) {
|
|
5342
|
+
throw new Error("Unable to resolve prompt captcha drag coordinates.");
|
|
5343
|
+
}
|
|
5344
|
+
const targetOffsetX = (plan.endXRatio - 0.5) * targetBox.width;
|
|
5345
|
+
const targetOffsetY = (plan.endYRatio - 0.5) * targetBox.height;
|
|
5346
|
+
await dragCaptchaAction(page, sourceLocator, dropTarget, {
|
|
5347
|
+
forceMouse: true,
|
|
5348
|
+
targetOffsetX,
|
|
5349
|
+
targetOffsetY,
|
|
5350
|
+
steps: options.promptDragMoveSteps,
|
|
5351
|
+
holdDelayMs: options.promptDragHoldDelayMs,
|
|
5352
|
+
stepDelayMs: options.promptDragStepDelayMs,
|
|
5353
|
+
beforeReleaseDelayMs: options.promptDragBeforeReleaseDelayMs,
|
|
5354
|
+
afterReleaseDelayMs: options.promptDragAfterReleaseDelayMs,
|
|
5355
|
+
finalMoveRepeats: options.promptDragFinalMoveRepeats
|
|
5356
|
+
});
|
|
5357
|
+
const afterState = await readPromptCaptchaState(frame, options);
|
|
5358
|
+
const accepted = afterState.badgeCount > baselineState.badgeCount || afterState.selectedCount > baselineState.selectedCount;
|
|
5359
|
+
const attemptInfo = {
|
|
5360
|
+
planName: plan.name,
|
|
5361
|
+
sourceRect: rectOf(sourceBox),
|
|
5362
|
+
targetRect: rectOf(targetBox),
|
|
5363
|
+
targetOffsetX: Number(targetOffsetX.toFixed(2)),
|
|
5364
|
+
targetOffsetY: Number(targetOffsetY.toFixed(2)),
|
|
5365
|
+
beforeState: baselineState,
|
|
5366
|
+
afterState,
|
|
5367
|
+
accepted
|
|
5368
|
+
};
|
|
5369
|
+
dragAttempts.push(attemptInfo);
|
|
5370
|
+
logger10.info(
|
|
5371
|
+
`\u9A8C\u8BC1\u7801\u62D6\u62FD\u7B2C ${visualIndex + 1} \u5F20\uFF0C\u65B9\u6848 ${plan.name}\uFF0Cbadge ${baselineState.badgeCount} -> ${afterState.badgeCount}\uFF0Cselected ${baselineState.selectedCount} -> ${afterState.selectedCount}`
|
|
5372
|
+
);
|
|
5373
|
+
if (accepted) {
|
|
5374
|
+
return {
|
|
5375
|
+
accepted: true,
|
|
5376
|
+
dragAttempts
|
|
5377
|
+
};
|
|
5378
|
+
}
|
|
5379
|
+
if (options.promptDragRetryDelayMs > 0) {
|
|
5380
|
+
await page.waitForTimeout(options.promptDragRetryDelayMs);
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, `drag-${visualIndex + 1}-failed`, options, {
|
|
5384
|
+
visualIndex,
|
|
5385
|
+
dragAttempts,
|
|
5386
|
+
finalState: await readPromptCaptchaState(frame, options)
|
|
5387
|
+
}).catch((error) => {
|
|
5388
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u62D6\u62FD\u5931\u8D25\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5389
|
+
});
|
|
5390
|
+
return {
|
|
5391
|
+
accepted: false,
|
|
5392
|
+
dragAttempts
|
|
5393
|
+
};
|
|
5394
|
+
};
|
|
5135
5395
|
async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
5136
5396
|
const { callCaptchaRecognitionApi: callCaptchaRecognitionApi2 } = dependencies;
|
|
5137
5397
|
if (typeof callCaptchaRecognitionApi2 !== "function") {
|
|
@@ -5146,7 +5406,7 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5146
5406
|
return false;
|
|
5147
5407
|
}
|
|
5148
5408
|
logger10.info("\u5F53\u524D\u4F7F\u7528\u672Ctool\u2014\u2014\u6D4B\u8BD5\u7248\u672C");
|
|
5149
|
-
for (let attempt = 1; attempt <= config.maxRetries; attempt
|
|
5409
|
+
for (let attempt = 1; attempt <= config.maxRetries; attempt += 1) {
|
|
5150
5410
|
logger10.info(`\u5F00\u59CB\u7B2C ${attempt}/${config.maxRetries} \u6B21 verifycenter \u9A8C\u8BC1\u7801\u8BC6\u522B\u3002`);
|
|
5151
5411
|
try {
|
|
5152
5412
|
const captchaContext = await getVerifycenterCaptchaContext(page, config);
|
|
@@ -5156,6 +5416,16 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5156
5416
|
}
|
|
5157
5417
|
const { iframeLocator, frame } = captchaContext;
|
|
5158
5418
|
await waitForCaptchaChallengeReady(page, frame, config);
|
|
5419
|
+
await maybeCollectCaptchaDebugInfo(
|
|
5420
|
+
page,
|
|
5421
|
+
frame,
|
|
5422
|
+
iframeLocator,
|
|
5423
|
+
attempt,
|
|
5424
|
+
"ready",
|
|
5425
|
+
config
|
|
5426
|
+
).catch((error) => {
|
|
5427
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5428
|
+
});
|
|
5159
5429
|
await page.waitForTimeout(config.recognitionDelayMs);
|
|
5160
5430
|
const screenshotBuffer = await iframeLocator.screenshot();
|
|
5161
5431
|
const apiResponse = await callCaptchaRecognitionApi2({
|
|
@@ -5179,33 +5449,74 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5179
5449
|
await refreshCaptcha(page, frame, config);
|
|
5180
5450
|
continue;
|
|
5181
5451
|
}
|
|
5182
|
-
const
|
|
5183
|
-
const
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5452
|
+
const orderedSourceImages = await resolveCaptchaSourceImagesInVisualOrder(frame, config);
|
|
5453
|
+
const normalizedIndexes = normalizeCaptchaImageIndexes(serialNumbers, orderedSourceImages.length);
|
|
5454
|
+
if (normalizedIndexes.length !== serialNumbers.length) {
|
|
5455
|
+
throw new Error(
|
|
5456
|
+
`Captcha image indexes could not be normalized. raw=${serialNumbers.join(", ")}, count=${orderedSourceImages.length}`
|
|
5457
|
+
);
|
|
5458
|
+
}
|
|
5459
|
+
logger10.info(`\u9A8C\u8BC1\u7801\u89C6\u89C9\u4F4D\u5E8F\u6620\u5C04\uFF1A${normalizedIndexes.map((index) => index + 1).join(", ")}`);
|
|
5460
|
+
for (const imageIndex of normalizedIndexes) {
|
|
5461
|
+
if (imageIndex < 0 || imageIndex >= orderedSourceImages.length) {
|
|
5462
|
+
throw new Error(
|
|
5463
|
+
`Captcha image index ${imageIndex} is out of range. count=${orderedSourceImages.length}`
|
|
5464
|
+
);
|
|
5191
5465
|
}
|
|
5192
|
-
const sourceImage =
|
|
5466
|
+
const sourceImage = orderedSourceImages[imageIndex].locator;
|
|
5193
5467
|
await sourceImage.waitFor({
|
|
5194
5468
|
state: "visible",
|
|
5195
5469
|
timeout: config.sourceImageVisibleTimeoutMs
|
|
5196
5470
|
});
|
|
5197
|
-
await
|
|
5471
|
+
const dragResult = await dragPromptCaptchaImage(
|
|
5472
|
+
page,
|
|
5473
|
+
frame,
|
|
5474
|
+
iframeLocator,
|
|
5475
|
+
sourceImage,
|
|
5476
|
+
dropTarget,
|
|
5477
|
+
config,
|
|
5478
|
+
{
|
|
5479
|
+
attempt,
|
|
5480
|
+
visualIndex: imageIndex
|
|
5481
|
+
}
|
|
5482
|
+
);
|
|
5483
|
+
if (!dragResult.accepted) {
|
|
5484
|
+
throw new Error(`Captcha prompt drag was not accepted for visual index ${imageIndex + 1}.`);
|
|
5485
|
+
}
|
|
5486
|
+
if (config.dragBetweenWaitMs > 0) {
|
|
5487
|
+
await page.waitForTimeout(config.dragBetweenWaitMs);
|
|
5488
|
+
}
|
|
5198
5489
|
}
|
|
5199
|
-
const
|
|
5490
|
+
const beforeSubmitState = await readPromptCaptchaState(frame, config);
|
|
5491
|
+
logger10.info(
|
|
5492
|
+
`\u63D0\u4EA4\u524D\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${beforeSubmitState.badgeCount}, selected=${beforeSubmitState.selectedCount}, submitDisabled=${beforeSubmitState.submitDisabled}`
|
|
5493
|
+
);
|
|
5494
|
+
const submitted = await clickCaptchaAction(frame, config.submitTexts, {
|
|
5495
|
+
...config,
|
|
5496
|
+
page,
|
|
5497
|
+
logger: logger10,
|
|
5498
|
+
forceMouse: true,
|
|
5499
|
+
actionVisibleTimeoutMs: config.submitReadyTimeoutMs
|
|
5500
|
+
}).catch(() => false);
|
|
5200
5501
|
if (!submitted) {
|
|
5201
5502
|
logger10.warn("\u672A\u627E\u5230\u63D0\u4EA4\u6309\u94AE\uFF0C\u53EF\u80FD\u4F1A\u81EA\u52A8\u63D0\u4EA4\u3002");
|
|
5202
5503
|
}
|
|
5203
5504
|
await page.waitForTimeout(config.submitWaitMs);
|
|
5505
|
+
const afterSubmitState = await readPromptCaptchaState(frame, config);
|
|
5506
|
+
logger10.info(
|
|
5507
|
+
`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${afterSubmitState.badgeCount}, selected=${afterSubmitState.selectedCount}, submitDisabled=${afterSubmitState.submitDisabled}`
|
|
5508
|
+
);
|
|
5204
5509
|
const stillVisible = await iframeLocator.isVisible({ timeout: config.containerVisibleTimeoutMs }).catch(() => false);
|
|
5205
5510
|
if (!stillVisible) {
|
|
5206
5511
|
logger10.info("\u9A8C\u8BC1\u7801\u8BC6\u522B\u5E76\u63D0\u4EA4\u6210\u529F\u3002");
|
|
5207
5512
|
return true;
|
|
5208
5513
|
}
|
|
5514
|
+
await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, "submit-still-visible", config, {
|
|
5515
|
+
beforeSubmitState,
|
|
5516
|
+
afterSubmitState
|
|
5517
|
+
}).catch((error) => {
|
|
5518
|
+
logger10.warn(`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5519
|
+
});
|
|
5209
5520
|
logger10.warn("\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801 iframe \u4ECD\u7136\u53EF\u89C1\uFF0C\u51C6\u5907\u5237\u65B0\u540E\u91CD\u8BD5\u3002");
|
|
5210
5521
|
await page.waitForTimeout(2e3);
|
|
5211
5522
|
await refreshCaptcha(page, frame, config);
|
|
@@ -5648,14 +5959,14 @@ var Mutation = {
|
|
|
5648
5959
|
const isFrameElement = tagName === "IFRAME" || tagName === "FRAME";
|
|
5649
5960
|
const nodeName = descriptor?.id || descriptor?.name || "no-id";
|
|
5650
5961
|
let source = "main";
|
|
5651
|
-
let
|
|
5962
|
+
let path3 = `${selector}[${index}]`;
|
|
5652
5963
|
let text = "";
|
|
5653
5964
|
let html = "";
|
|
5654
5965
|
let frameUrl = "";
|
|
5655
5966
|
let readyState = "";
|
|
5656
5967
|
if (isFrameElement) {
|
|
5657
5968
|
source = "iframe";
|
|
5658
|
-
|
|
5969
|
+
path3 = `${selector}[${index}]::iframe(${nodeName})`;
|
|
5659
5970
|
const frame = await handle.contentFrame();
|
|
5660
5971
|
if (frame) {
|
|
5661
5972
|
try {
|
|
@@ -5685,7 +5996,7 @@ var Mutation = {
|
|
|
5685
5996
|
items.push({
|
|
5686
5997
|
selector,
|
|
5687
5998
|
source,
|
|
5688
|
-
path:
|
|
5999
|
+
path: path3,
|
|
5689
6000
|
text,
|
|
5690
6001
|
html,
|
|
5691
6002
|
frameUrl,
|