@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.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
|
};
|
|
@@ -3830,47 +3854,6 @@ var scrollAwayFromObstruction = async (element, status) => {
|
|
|
3830
3854
|
};
|
|
3831
3855
|
}, status);
|
|
3832
3856
|
};
|
|
3833
|
-
var getElementViewportSnapshot = async (element) => {
|
|
3834
|
-
return element.evaluate((el) => {
|
|
3835
|
-
const rect = el.getBoundingClientRect();
|
|
3836
|
-
return {
|
|
3837
|
-
top: rect.top,
|
|
3838
|
-
bottom: rect.bottom,
|
|
3839
|
-
left: rect.left,
|
|
3840
|
-
right: rect.right,
|
|
3841
|
-
width: rect.width,
|
|
3842
|
-
height: rect.height,
|
|
3843
|
-
scrollX: window.scrollX,
|
|
3844
|
-
scrollY: window.scrollY
|
|
3845
|
-
};
|
|
3846
|
-
});
|
|
3847
|
-
};
|
|
3848
|
-
var isTargetImmobileAfterScroll = (before, after) => {
|
|
3849
|
-
if (!before || !after) return false;
|
|
3850
|
-
const rectDeltaY = Number(after.top || 0) - Number(before.top || 0);
|
|
3851
|
-
const rectDeltaX = Number(after.left || 0) - Number(before.left || 0);
|
|
3852
|
-
const scrollDeltaY = Number(after.scrollY || 0) - Number(before.scrollY || 0);
|
|
3853
|
-
const scrollDeltaX = Number(after.scrollX || 0) - Number(before.scrollX || 0);
|
|
3854
|
-
const rectMoved = Math.abs(rectDeltaY) > 3 || Math.abs(rectDeltaX) > 3;
|
|
3855
|
-
const pageMoved = Math.abs(scrollDeltaY) > 3 || Math.abs(scrollDeltaX) > 3;
|
|
3856
|
-
if (!rectMoved && !pageMoved) return true;
|
|
3857
|
-
if (pageMoved && !rectMoved) return true;
|
|
3858
|
-
if (Math.abs(scrollDeltaY) > 12 && Math.abs(rectDeltaY) < Math.min(12, Math.abs(scrollDeltaY) * 0.2)) {
|
|
3859
|
-
return true;
|
|
3860
|
-
}
|
|
3861
|
-
return false;
|
|
3862
|
-
};
|
|
3863
|
-
var restoreWindowFromSnapshot = async (page, before, after) => {
|
|
3864
|
-
if (!before || !after) return;
|
|
3865
|
-
if (Math.abs(Number(after.scrollX || 0) - Number(before.scrollX || 0)) <= 2 && Math.abs(Number(after.scrollY || 0) - Number(before.scrollY || 0)) <= 2) {
|
|
3866
|
-
return;
|
|
3867
|
-
}
|
|
3868
|
-
await page.evaluate(
|
|
3869
|
-
(state) => window.scrollTo(state.x, state.y),
|
|
3870
|
-
{ x: Number(before.scrollX || 0), y: Number(before.scrollY || 0) }
|
|
3871
|
-
).catch(() => {
|
|
3872
|
-
});
|
|
3873
|
-
};
|
|
3874
3857
|
var dispatchTouchSwipe = async (page, deltaY, options = {}) => {
|
|
3875
3858
|
const viewport = resolveViewport(page);
|
|
3876
3859
|
const rawRect = options.rect || null;
|
|
@@ -4051,11 +4034,11 @@ var MobileHumanize = {
|
|
|
4051
4034
|
const scrollRect = await getScrollableRect(element);
|
|
4052
4035
|
if (!scrollRect && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4053
4036
|
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"})`);
|
|
4054
|
-
return { element, didScroll, restore: null
|
|
4037
|
+
return { element, didScroll, restore: null };
|
|
4055
4038
|
}
|
|
4056
4039
|
if (!scrollRect && status.isFixed && status.code === "OBSTRUCTED") {
|
|
4057
4040
|
logger7.warn(`humanScroll | fixed/sticky \u76EE\u6807\u88AB\u906E\u6321\uFF0C\u6EDA\u52A8\u65E0\u6CD5\u89E3\u9664 (${status.obstruction?.tag || "unknown"})`);
|
|
4058
|
-
return { element, didScroll, restore: null
|
|
4041
|
+
return { element, didScroll, restore: null };
|
|
4059
4042
|
}
|
|
4060
4043
|
if (scrollRect && status.code === "OBSTRUCTED" && status.obstruction?.isFixed) {
|
|
4061
4044
|
const moved = await scrollAwayFromObstruction(element, status);
|
|
@@ -4086,7 +4069,6 @@ var MobileHumanize = {
|
|
|
4086
4069
|
}
|
|
4087
4070
|
}
|
|
4088
4071
|
const beforeWindowState = scrollRect ? await page.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) : null;
|
|
4089
|
-
const beforeElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4090
4072
|
const beforeState = scrollRect ? await element.evaluate((el) => {
|
|
4091
4073
|
const isScrollable = (node) => {
|
|
4092
4074
|
const style = window.getComputedStyle(node);
|
|
@@ -4116,21 +4098,6 @@ var MobileHumanize = {
|
|
|
4116
4098
|
logger7.debug(`humanScroll | \u7A97\u53E3\u6EDA\u52A8\u56DE\u6536 from=${Math.round(afterWindowState.y)} to=${Math.round(beforeWindowState.y)}`);
|
|
4117
4099
|
}
|
|
4118
4100
|
}
|
|
4119
|
-
let afterElementSnapshot = null;
|
|
4120
|
-
const readAfterElementSnapshot = async () => {
|
|
4121
|
-
if (!afterElementSnapshot) {
|
|
4122
|
-
afterElementSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4123
|
-
}
|
|
4124
|
-
return afterElementSnapshot;
|
|
4125
|
-
};
|
|
4126
|
-
if (!scrollRect && beforeElementSnapshot) {
|
|
4127
|
-
const afterSnapshot = await readAfterElementSnapshot();
|
|
4128
|
-
if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
|
|
4129
|
-
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
4130
|
-
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"})`);
|
|
4131
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
4132
|
-
}
|
|
4133
|
-
}
|
|
4134
4101
|
if (scrollRect && beforeState) {
|
|
4135
4102
|
const afterState = await element.evaluate((el) => {
|
|
4136
4103
|
const isScrollable = (node) => {
|
|
@@ -4168,14 +4135,6 @@ var MobileHumanize = {
|
|
|
4168
4135
|
}
|
|
4169
4136
|
}
|
|
4170
4137
|
}
|
|
4171
|
-
if (scrollRect && beforeElementSnapshot) {
|
|
4172
|
-
const afterSnapshot = await getElementViewportSnapshot(element).catch(() => null);
|
|
4173
|
-
if (isTargetImmobileAfterScroll(beforeElementSnapshot, afterSnapshot)) {
|
|
4174
|
-
await restoreWindowFromSnapshot(page, beforeElementSnapshot, afterSnapshot);
|
|
4175
|
-
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"})`);
|
|
4176
|
-
return { element, didScroll, restore: null, unscrollable: true };
|
|
4177
|
-
}
|
|
4178
|
-
}
|
|
4179
4138
|
didScroll = true;
|
|
4180
4139
|
}
|
|
4181
4140
|
try {
|
|
@@ -4225,28 +4184,21 @@ var MobileHumanize = {
|
|
|
4225
4184
|
logger7.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${targetDesc}`);
|
|
4226
4185
|
return false;
|
|
4227
4186
|
}
|
|
4228
|
-
|
|
4187
|
+
if (scrollIfNeeded) {
|
|
4188
|
+
await MobileHumanize.humanScroll(page, element, { throwOnMissing });
|
|
4189
|
+
}
|
|
4229
4190
|
const status = await checkElementVisibility(element).catch(() => null);
|
|
4230
4191
|
if (status && status.code !== "VISIBLE") {
|
|
4231
|
-
if (fallbackDomClick &&
|
|
4232
|
-
|
|
4192
|
+
if (fallbackDomClick && status.isFixed && status.code === "OUT_OF_VIEWPORT") {
|
|
4193
|
+
const fallback = await withTimeout(
|
|
4233
4194
|
() => activateElementFallback(element, null, {
|
|
4234
4195
|
editableOnly: true
|
|
4235
4196
|
}),
|
|
4236
4197
|
activateFallbackTimeoutMs,
|
|
4237
4198
|
"focus fallback"
|
|
4238
4199
|
).catch(() => null);
|
|
4239
|
-
if (!fallback?.activated) {
|
|
4240
|
-
fallback = await withTimeout(
|
|
4241
|
-
() => activateElementFallback(element, null, {
|
|
4242
|
-
editableOnly: false
|
|
4243
|
-
}),
|
|
4244
|
-
activateFallbackTimeoutMs,
|
|
4245
|
-
"activation fallback"
|
|
4246
|
-
).catch(() => null);
|
|
4247
|
-
}
|
|
4248
4200
|
if (fallback?.activated) {
|
|
4249
|
-
logger7.warn(`humanClick: \
|
|
4201
|
+
logger7.warn(`humanClick: fixed/sticky \u76EE\u6807\u4E0D\u5728\u89C6\u53E3\u5185\uFF0C\u5DF2\u7528 ${fallback.method} \u6FC0\u6D3B`);
|
|
4250
4202
|
return true;
|
|
4251
4203
|
}
|
|
4252
4204
|
}
|
|
@@ -4952,6 +4904,10 @@ var LiveView = {
|
|
|
4952
4904
|
// src/chaptcha.js
|
|
4953
4905
|
var import_uuid = require("uuid");
|
|
4954
4906
|
|
|
4907
|
+
// src/internals/captcha/bytedance.js
|
|
4908
|
+
var import_promises = require("fs/promises");
|
|
4909
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
4910
|
+
|
|
4955
4911
|
// src/internals/captcha/shared.js
|
|
4956
4912
|
var waitForVisible = async (locator, timeout) => {
|
|
4957
4913
|
try {
|
|
@@ -4969,38 +4925,71 @@ var isAnyCaptchaTextVisible = async (frame, texts, timeout) => {
|
|
|
4969
4925
|
if (!text) {
|
|
4970
4926
|
continue;
|
|
4971
4927
|
}
|
|
4972
|
-
const
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4928
|
+
const textLocator = frame.getByText(text, { exact: false });
|
|
4929
|
+
const locatorText = frame.locator(`text=${text}`);
|
|
4930
|
+
const candidateGroups = [textLocator, locatorText];
|
|
4931
|
+
for (const candidateGroup of candidateGroups) {
|
|
4932
|
+
const candidateCount = await candidateGroup.count().catch(() => 0);
|
|
4933
|
+
for (let index = 0; index < candidateCount; index += 1) {
|
|
4934
|
+
const candidate = candidateGroup.nth(index);
|
|
4935
|
+
const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
|
|
4936
|
+
if (isVisible) {
|
|
4937
|
+
return true;
|
|
4938
|
+
}
|
|
4980
4939
|
}
|
|
4981
4940
|
}
|
|
4982
4941
|
}
|
|
4983
4942
|
return false;
|
|
4984
4943
|
};
|
|
4944
|
+
var collectVisibleCandidateIndexes = async (candidateGroup, count, timeout) => {
|
|
4945
|
+
const visibleIndexes = [];
|
|
4946
|
+
for (let index = 0; index < count; index += 1) {
|
|
4947
|
+
const candidate = candidateGroup.nth(index);
|
|
4948
|
+
const isVisible = await candidate.isVisible({ timeout }).catch(() => false);
|
|
4949
|
+
if (isVisible) {
|
|
4950
|
+
visibleIndexes.push(index);
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
return visibleIndexes;
|
|
4954
|
+
};
|
|
4985
4955
|
var clickCaptchaAction = async (frame, texts, options) => {
|
|
4986
4956
|
for (const text of texts || []) {
|
|
4987
|
-
const
|
|
4988
|
-
|
|
4989
|
-
|
|
4957
|
+
const textLocator = frame.getByText(text, { exact: false });
|
|
4958
|
+
const locatorText = frame.locator(`text=${text}`);
|
|
4959
|
+
const [getByTextCount, locatorTextCount] = await Promise.all([
|
|
4960
|
+
textLocator.count().catch(() => 0),
|
|
4961
|
+
locatorText.count().catch(() => 0)
|
|
4962
|
+
]);
|
|
4963
|
+
const [getByTextVisibleIndexes, locatorTextVisibleIndexes] = await Promise.all([
|
|
4964
|
+
collectVisibleCandidateIndexes(textLocator, getByTextCount, options.actionVisibleTimeoutMs),
|
|
4965
|
+
collectVisibleCandidateIndexes(locatorText, locatorTextCount, options.actionVisibleTimeoutMs)
|
|
4966
|
+
]);
|
|
4967
|
+
options.logger?.info(
|
|
4968
|
+
`[CaptchaAction] \u6587\u672C "${text}" \u547D\u4E2D\u6570\u91CF\uFF1AgetByText=${getByTextCount} (visible=${getByTextVisibleIndexes.length}), locator=${locatorTextCount} (visible=${locatorTextVisibleIndexes.length})`
|
|
4969
|
+
);
|
|
4970
|
+
const candidateGroups = [
|
|
4971
|
+
{ label: "getByText", locator: textLocator, count: getByTextCount },
|
|
4972
|
+
{ label: "locator", locator: locatorText, count: locatorTextCount }
|
|
4990
4973
|
];
|
|
4991
|
-
for (const
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4974
|
+
for (const candidateGroup of candidateGroups) {
|
|
4975
|
+
for (let index = 0; index < candidateGroup.count; index += 1) {
|
|
4976
|
+
const candidate = candidateGroup.locator.nth(index);
|
|
4977
|
+
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
4978
|
+
if (!isVisible) {
|
|
4979
|
+
continue;
|
|
4980
|
+
}
|
|
4981
|
+
options.logger?.info(
|
|
4982
|
+
`[CaptchaAction] \u6587\u672C "${text}" \u9009\u62E9 ${candidateGroup.label}[${index}] \u4F5C\u4E3A\u7B2C\u4E00\u4E2A\u53EF\u89C1\u8282\u70B9\u6267\u884C\u70B9\u51FB\u3002`
|
|
4983
|
+
);
|
|
4984
|
+
await DeviceInput.click(options.page, candidate, options);
|
|
4985
|
+
return true;
|
|
4995
4986
|
}
|
|
4996
|
-
await DeviceInput.click(options.page, candidate);
|
|
4997
|
-
return true;
|
|
4998
4987
|
}
|
|
4999
4988
|
}
|
|
5000
4989
|
return false;
|
|
5001
4990
|
};
|
|
5002
|
-
var dragCaptchaAction = async (page, sourceLocator, targetLocator) => {
|
|
5003
|
-
await DeviceInput.drag(page, sourceLocator, targetLocator);
|
|
4991
|
+
var dragCaptchaAction = async (page, sourceLocator, targetLocator, options = {}) => {
|
|
4992
|
+
await DeviceInput.drag(page, sourceLocator, targetLocator, options);
|
|
5004
4993
|
};
|
|
5005
4994
|
|
|
5006
4995
|
// src/internals/captcha/bytedance.js
|
|
@@ -5011,11 +5000,12 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
|
5011
5000
|
containerSelector: "#captcha_container",
|
|
5012
5001
|
iframeSelector: 'iframe[src*="verifycenter"]',
|
|
5013
5002
|
iframeFallbackSelector: "iframe",
|
|
5014
|
-
sourceImageSelector: "
|
|
5015
|
-
dropTargetContainerSelector: "
|
|
5003
|
+
sourceImageSelector: ".img-container .canvas-container",
|
|
5004
|
+
dropTargetContainerSelector: ".drag-area",
|
|
5016
5005
|
dropTargetTexts: ["\u62D6\u62FD\u5230\u8FD9\u91CC"],
|
|
5017
5006
|
refreshTexts: ["\u5237\u65B0"],
|
|
5018
5007
|
submitTexts: ["\u63D0\u4EA4"],
|
|
5008
|
+
guideMaskSelector: ".play-guide-mask",
|
|
5019
5009
|
recognitionSuccessCode: 1e4,
|
|
5020
5010
|
containerVisibleTimeoutMs: 2e3,
|
|
5021
5011
|
iframeVisibleTimeoutMs: 12e3,
|
|
@@ -5038,10 +5028,111 @@ var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
|
5038
5028
|
],
|
|
5039
5029
|
recognitionDelayMs: 2e3,
|
|
5040
5030
|
refreshWaitMs: 3e3,
|
|
5041
|
-
submitWaitMs:
|
|
5031
|
+
submitWaitMs: 5e3,
|
|
5032
|
+
submitReadyTimeoutMs: 2500,
|
|
5042
5033
|
retryDelayBaseMs: 2e3,
|
|
5043
|
-
retryDelayStepMs: 1e3
|
|
5034
|
+
retryDelayStepMs: 1e3,
|
|
5035
|
+
sourceImageRowTolerancePx: 24,
|
|
5036
|
+
dragBetweenWaitMs: 250,
|
|
5037
|
+
promptBadgeCountSelector: ".drag-area .photo-badge .badge span",
|
|
5038
|
+
promptSubmitButtonSelector: ".vc-captcha-verify-mobile-button",
|
|
5039
|
+
promptSelectedSourceSelector: ".img-container .canvas-container.selected",
|
|
5040
|
+
promptActiveSourceSelector: ".img-container .canvas-container.active",
|
|
5041
|
+
promptDragMoveSteps: 16,
|
|
5042
|
+
promptDragStepDelayMs: 55,
|
|
5043
|
+
promptDragHoldDelayMs: 240,
|
|
5044
|
+
promptDragBeforeReleaseDelayMs: 180,
|
|
5045
|
+
promptDragAfterReleaseDelayMs: 240,
|
|
5046
|
+
promptDragFinalMoveRepeats: 3,
|
|
5047
|
+
promptDragRetryDelayMs: 250,
|
|
5048
|
+
debugArtifacts: false
|
|
5044
5049
|
});
|
|
5050
|
+
var PROMPT_CAPTCHA_DRAG_PLANS = Object.freeze([
|
|
5051
|
+
{ name: "lower-middle", endXRatio: 0.5, endYRatio: 0.72 },
|
|
5052
|
+
{ name: "center-middle", endXRatio: 0.5, endYRatio: 0.56 },
|
|
5053
|
+
{ name: "upper-middle", endXRatio: 0.5, endYRatio: 0.4 }
|
|
5054
|
+
]);
|
|
5055
|
+
var resolveCaptchaDebugDir = () => import_path2.default.resolve(process.cwd(), "storage", "captcha-debug");
|
|
5056
|
+
var rectOf = (rect) => {
|
|
5057
|
+
if (!rect) {
|
|
5058
|
+
return null;
|
|
5059
|
+
}
|
|
5060
|
+
return {
|
|
5061
|
+
x: Number(rect.x.toFixed(2)),
|
|
5062
|
+
y: Number(rect.y.toFixed(2)),
|
|
5063
|
+
width: Number(rect.width.toFixed(2)),
|
|
5064
|
+
height: Number(rect.height.toFixed(2))
|
|
5065
|
+
};
|
|
5066
|
+
};
|
|
5067
|
+
var collectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, extra = null) => {
|
|
5068
|
+
const timestamp = Date.now();
|
|
5069
|
+
const debugDir = resolveCaptchaDebugDir();
|
|
5070
|
+
await (0, import_promises.mkdir)(debugDir, { recursive: true });
|
|
5071
|
+
const baseName = `bytedance-${timestamp}-attempt${attempt}-${phase}`;
|
|
5072
|
+
const iframeScreenshotPath = import_path2.default.join(debugDir, `${baseName}-iframe.png`);
|
|
5073
|
+
const pageScreenshotPath = import_path2.default.join(debugDir, `${baseName}-page.png`);
|
|
5074
|
+
const htmlPath = import_path2.default.join(debugDir, `${baseName}-iframe.html`);
|
|
5075
|
+
const infoPath = import_path2.default.join(debugDir, `${baseName}-info.json`);
|
|
5076
|
+
await iframeLocator.screenshot({ path: iframeScreenshotPath }).catch(() => {
|
|
5077
|
+
});
|
|
5078
|
+
await page.screenshot({ path: pageScreenshotPath, fullPage: true }).catch(() => {
|
|
5079
|
+
});
|
|
5080
|
+
const html = await frame.evaluate(() => document.documentElement.outerHTML).catch(() => "");
|
|
5081
|
+
if (html) {
|
|
5082
|
+
await (0, import_promises.writeFile)(htmlPath, html, "utf8");
|
|
5083
|
+
}
|
|
5084
|
+
const info = await frame.evaluate(() => {
|
|
5085
|
+
const toRect = (element) => {
|
|
5086
|
+
const rect = element.getBoundingClientRect();
|
|
5087
|
+
return {
|
|
5088
|
+
x: Number(rect.x.toFixed(2)),
|
|
5089
|
+
y: Number(rect.y.toFixed(2)),
|
|
5090
|
+
width: Number(rect.width.toFixed(2)),
|
|
5091
|
+
height: Number(rect.height.toFixed(2))
|
|
5092
|
+
};
|
|
5093
|
+
};
|
|
5094
|
+
const toItem = (element, index) => ({
|
|
5095
|
+
index,
|
|
5096
|
+
tag: element.tagName,
|
|
5097
|
+
id: element.id || "",
|
|
5098
|
+
className: typeof element.className === "string" ? element.className : "",
|
|
5099
|
+
text: String(element.textContent || "").trim(),
|
|
5100
|
+
rect: toRect(element)
|
|
5101
|
+
});
|
|
5102
|
+
const visibleNodes = Array.from(document.querySelectorAll("body *")).map(toItem).filter((item) => item.rect.width > 0 && item.rect.height > 0);
|
|
5103
|
+
return {
|
|
5104
|
+
title: document.title,
|
|
5105
|
+
bodyText: String(document.body?.innerText || "").trim(),
|
|
5106
|
+
canvasContainers: visibleNodes.filter((item) => item.className.includes("canvas-container")),
|
|
5107
|
+
canvasNodes: visibleNodes.filter((item) => item.tag === "CANVAS"),
|
|
5108
|
+
captchaNodes: visibleNodes.filter((item) => item.id.startsWith("captcha_") || item.className.includes("captcha") || item.className.includes("verify") || /拖拽到这里|刷新|提交/.test(item.text)),
|
|
5109
|
+
visibleTextNodes: visibleNodes.filter((item) => item.text).slice(0, 300)
|
|
5110
|
+
};
|
|
5111
|
+
}).catch(() => null);
|
|
5112
|
+
if (info) {
|
|
5113
|
+
const payload = {
|
|
5114
|
+
capturedAt: new Date(timestamp).toISOString(),
|
|
5115
|
+
pageUrl: page.url(),
|
|
5116
|
+
attempt,
|
|
5117
|
+
phase,
|
|
5118
|
+
iframeScreenshotPath,
|
|
5119
|
+
pageScreenshotPath,
|
|
5120
|
+
htmlPath,
|
|
5121
|
+
info
|
|
5122
|
+
};
|
|
5123
|
+
if (extra != null) {
|
|
5124
|
+
payload.extra = extra;
|
|
5125
|
+
}
|
|
5126
|
+
await (0, import_promises.writeFile)(infoPath, JSON.stringify(payload, null, 2), "utf8");
|
|
5127
|
+
}
|
|
5128
|
+
logger10.info(`\u5DF2\u5199\u51FA\u9A8C\u8BC1\u7801\u8C03\u8BD5\u4EA7\u7269\uFF1A${debugDir}`);
|
|
5129
|
+
};
|
|
5130
|
+
var maybeCollectCaptchaDebugInfo = async (page, frame, iframeLocator, attempt, phase, options, extra = null) => {
|
|
5131
|
+
if (!options.debugArtifacts) {
|
|
5132
|
+
return;
|
|
5133
|
+
}
|
|
5134
|
+
await collectCaptchaDebugInfo(page, frame, iframeLocator, attempt, phase, extra);
|
|
5135
|
+
};
|
|
5045
5136
|
var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
5046
5137
|
const serialNumbers = apiResponse?.data?.data?.serial_number;
|
|
5047
5138
|
if (!Array.isArray(serialNumbers)) {
|
|
@@ -5050,7 +5141,7 @@ var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
|
5050
5141
|
return serialNumbers.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 0);
|
|
5051
5142
|
};
|
|
5052
5143
|
var resolveContentFrame = async (page, iframeLocator, options) => {
|
|
5053
|
-
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt
|
|
5144
|
+
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt += 1) {
|
|
5054
5145
|
const iframeHandle = await iframeLocator.elementHandle();
|
|
5055
5146
|
const frame = await iframeHandle?.contentFrame();
|
|
5056
5147
|
if (frame) {
|
|
@@ -5095,20 +5186,15 @@ var getVerifycenterCaptchaContext = async (page, options) => {
|
|
|
5095
5186
|
}
|
|
5096
5187
|
return { iframeLocator, frame };
|
|
5097
5188
|
};
|
|
5098
|
-
var refreshCaptcha = async (page, frame, options) => {
|
|
5099
|
-
const clicked = await clickCaptchaAction(frame, options.refreshTexts, { ...options, page }).catch(() => false);
|
|
5100
|
-
if (!clicked) {
|
|
5101
|
-
logger10.warn("Refresh button not found.");
|
|
5102
|
-
return false;
|
|
5103
|
-
}
|
|
5104
|
-
await page.waitForTimeout(options.refreshWaitMs);
|
|
5105
|
-
return true;
|
|
5106
|
-
};
|
|
5107
5189
|
var findCaptchaDropTarget = async (frame, options) => {
|
|
5190
|
+
const directTarget = frame.locator(options.dropTargetContainerSelector).first();
|
|
5191
|
+
if (await waitForVisible(directTarget, options.actionVisibleTimeoutMs)) {
|
|
5192
|
+
return directTarget;
|
|
5193
|
+
}
|
|
5108
5194
|
for (const text of options.dropTargetTexts) {
|
|
5109
5195
|
const candidates = [
|
|
5110
|
-
frame.
|
|
5111
|
-
frame.
|
|
5196
|
+
frame.getByText(text, { exact: false }).first(),
|
|
5197
|
+
frame.locator(`text=${text}`).first()
|
|
5112
5198
|
];
|
|
5113
5199
|
for (const candidate of candidates) {
|
|
5114
5200
|
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
@@ -5119,10 +5205,112 @@ var findCaptchaDropTarget = async (frame, options) => {
|
|
|
5119
5205
|
}
|
|
5120
5206
|
return null;
|
|
5121
5207
|
};
|
|
5208
|
+
var readPromptCaptchaState = async (frame, options) => frame.evaluate((selectors) => {
|
|
5209
|
+
const toRect = (element) => {
|
|
5210
|
+
if (!element) {
|
|
5211
|
+
return null;
|
|
5212
|
+
}
|
|
5213
|
+
const rect = element.getBoundingClientRect();
|
|
5214
|
+
return {
|
|
5215
|
+
x: Number(rect.x.toFixed(2)),
|
|
5216
|
+
y: Number(rect.y.toFixed(2)),
|
|
5217
|
+
width: Number(rect.width.toFixed(2)),
|
|
5218
|
+
height: Number(rect.height.toFixed(2))
|
|
5219
|
+
};
|
|
5220
|
+
};
|
|
5221
|
+
const badgeNode = document.querySelector(selectors.badgeCountSelector);
|
|
5222
|
+
const submitButton = document.querySelector(selectors.submitButtonSelector);
|
|
5223
|
+
const dragArea = document.querySelector(selectors.dragAreaSelector);
|
|
5224
|
+
const badgeCount = badgeNode ? Number.parseInt(String(badgeNode.textContent || "").trim(), 10) : 0;
|
|
5225
|
+
return {
|
|
5226
|
+
badgeCount: Number.isFinite(badgeCount) ? badgeCount : 0,
|
|
5227
|
+
selectedCount: document.querySelectorAll(selectors.selectedSourceSelector).length,
|
|
5228
|
+
activeCount: document.querySelectorAll(selectors.activeSourceSelector).length,
|
|
5229
|
+
submitDisabled: submitButton ? submitButton.classList.contains("disable") : null,
|
|
5230
|
+
dragAreaActive: dragArea ? dragArea.classList.contains("active") : false,
|
|
5231
|
+
dragAreaRect: toRect(dragArea)
|
|
5232
|
+
};
|
|
5233
|
+
}, {
|
|
5234
|
+
badgeCountSelector: options.promptBadgeCountSelector,
|
|
5235
|
+
submitButtonSelector: options.promptSubmitButtonSelector,
|
|
5236
|
+
selectedSourceSelector: options.promptSelectedSourceSelector,
|
|
5237
|
+
activeSourceSelector: options.promptActiveSourceSelector,
|
|
5238
|
+
dragAreaSelector: options.dropTargetContainerSelector
|
|
5239
|
+
}).catch(() => ({
|
|
5240
|
+
badgeCount: 0,
|
|
5241
|
+
selectedCount: 0,
|
|
5242
|
+
activeCount: 0,
|
|
5243
|
+
submitDisabled: null,
|
|
5244
|
+
dragAreaActive: false,
|
|
5245
|
+
dragAreaRect: null
|
|
5246
|
+
}));
|
|
5247
|
+
var normalizeCaptchaImageIndexes = (serialNumbers, imageCount) => {
|
|
5248
|
+
if (!Array.isArray(serialNumbers) || imageCount <= 0) {
|
|
5249
|
+
return [];
|
|
5250
|
+
}
|
|
5251
|
+
const areAllOneBased = serialNumbers.every((value) => value >= 1 && value <= imageCount);
|
|
5252
|
+
if (areAllOneBased) {
|
|
5253
|
+
return serialNumbers.map((value) => value - 1);
|
|
5254
|
+
}
|
|
5255
|
+
const areAllZeroBased = serialNumbers.every((value) => value >= 0 && value < imageCount);
|
|
5256
|
+
if (areAllZeroBased) {
|
|
5257
|
+
return [...serialNumbers];
|
|
5258
|
+
}
|
|
5259
|
+
return serialNumbers.map((value) => {
|
|
5260
|
+
if (value >= 1 && value <= imageCount) {
|
|
5261
|
+
return value - 1;
|
|
5262
|
+
}
|
|
5263
|
+
if (value >= 0 && value < imageCount) {
|
|
5264
|
+
return value;
|
|
5265
|
+
}
|
|
5266
|
+
return null;
|
|
5267
|
+
}).filter((value) => Number.isInteger(value));
|
|
5268
|
+
};
|
|
5269
|
+
var resolveCaptchaSourceImagesInVisualOrder = async (frame, options) => {
|
|
5270
|
+
const sourceImages = frame.locator(options.sourceImageSelector);
|
|
5271
|
+
const imageCount = await sourceImages.count().catch(() => 0);
|
|
5272
|
+
const sources = [];
|
|
5273
|
+
for (let domIndex = 0; domIndex < imageCount; domIndex += 1) {
|
|
5274
|
+
const locator = sourceImages.nth(domIndex);
|
|
5275
|
+
const box = await locator.boundingBox().catch(() => null);
|
|
5276
|
+
if (!box || box.width <= 0 || box.height <= 0) {
|
|
5277
|
+
continue;
|
|
5278
|
+
}
|
|
5279
|
+
sources.push({
|
|
5280
|
+
domIndex,
|
|
5281
|
+
locator,
|
|
5282
|
+
box
|
|
5283
|
+
});
|
|
5284
|
+
}
|
|
5285
|
+
sources.sort((left, right) => {
|
|
5286
|
+
const deltaY = left.box.y - right.box.y;
|
|
5287
|
+
if (Math.abs(deltaY) > options.sourceImageRowTolerancePx) {
|
|
5288
|
+
return deltaY;
|
|
5289
|
+
}
|
|
5290
|
+
return left.box.x - right.box.x;
|
|
5291
|
+
});
|
|
5292
|
+
return sources;
|
|
5293
|
+
};
|
|
5294
|
+
var refreshCaptcha = async (page, frame, options) => {
|
|
5295
|
+
const clicked = await clickCaptchaAction(frame, options.refreshTexts, {
|
|
5296
|
+
...options,
|
|
5297
|
+
page,
|
|
5298
|
+
logger: logger10,
|
|
5299
|
+
forceMouse: true
|
|
5300
|
+
}).catch(() => false);
|
|
5301
|
+
if (!clicked) {
|
|
5302
|
+
logger10.warn("Refresh button not found.");
|
|
5303
|
+
return false;
|
|
5304
|
+
}
|
|
5305
|
+
await page.waitForTimeout(options.refreshWaitMs);
|
|
5306
|
+
return true;
|
|
5307
|
+
};
|
|
5122
5308
|
var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
5123
5309
|
const deadline = Date.now() + options.challengeReadyTimeoutMs;
|
|
5124
5310
|
let refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
|
|
5125
5311
|
let hasSeenLoading = false;
|
|
5312
|
+
let hasSeenGuideMask = false;
|
|
5313
|
+
let hasLoggedGuideMask = false;
|
|
5126
5314
|
while (Date.now() < deadline) {
|
|
5127
5315
|
const isLoadingVisible = await isAnyCaptchaTextVisible(
|
|
5128
5316
|
frame,
|
|
@@ -5138,8 +5326,17 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5138
5326
|
const sourceImages = frame.locator(options.sourceImageSelector);
|
|
5139
5327
|
const imageCount = await sourceImages.count().catch(() => 0);
|
|
5140
5328
|
const hasVisibleSourceImage = imageCount > 0 ? await sourceImages.first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
|
|
5141
|
-
|
|
5142
|
-
|
|
5329
|
+
const hasVisibleDropTarget = await frame.locator(options.dropTargetContainerSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false);
|
|
5330
|
+
const hasGuideMaskVisible = options.guideMaskSelector ? await frame.locator(options.guideMaskSelector).first().isVisible({ timeout: options.loadingIndicatorVisibleTimeoutMs }).catch(() => false) : false;
|
|
5331
|
+
hasSeenGuideMask = hasSeenGuideMask || hasGuideMaskVisible;
|
|
5332
|
+
if (hasGuideMaskVisible && !hasLoggedGuideMask) {
|
|
5333
|
+
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");
|
|
5334
|
+
hasLoggedGuideMask = true;
|
|
5335
|
+
}
|
|
5336
|
+
if (!isLoadingVisible && hasVisibleSourceImage && hasVisibleDropTarget && !hasGuideMaskVisible) {
|
|
5337
|
+
logger10.info(
|
|
5338
|
+
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"
|
|
5339
|
+
);
|
|
5143
5340
|
return;
|
|
5144
5341
|
}
|
|
5145
5342
|
if (hasErrorTextVisible) {
|
|
@@ -5149,8 +5346,8 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5149
5346
|
hasSeenLoading = false;
|
|
5150
5347
|
continue;
|
|
5151
5348
|
}
|
|
5152
|
-
if (!hasVisibleSourceImage && Date.now() >= refreshDeadline) {
|
|
5153
|
-
logger10.warn(`\u9A8C\u8BC1\u7801\
|
|
5349
|
+
if ((!hasVisibleSourceImage || !hasVisibleDropTarget) && Date.now() >= refreshDeadline) {
|
|
5350
|
+
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`);
|
|
5154
5351
|
await refreshCaptcha(page, frame, options);
|
|
5155
5352
|
refreshDeadline = Date.now() + options.challengeReadyRefreshTimeoutMs;
|
|
5156
5353
|
hasSeenLoading = false;
|
|
@@ -5160,6 +5357,69 @@ var waitForCaptchaChallengeReady = async (page, frame, options) => {
|
|
|
5160
5357
|
}
|
|
5161
5358
|
throw new Error("Captcha challenge is still loading and did not become ready in time.");
|
|
5162
5359
|
};
|
|
5360
|
+
var dragPromptCaptchaImage = async (page, frame, iframeLocator, sourceLocator, dropTarget, options, {
|
|
5361
|
+
attempt,
|
|
5362
|
+
visualIndex
|
|
5363
|
+
}) => {
|
|
5364
|
+
const baselineState = await readPromptCaptchaState(frame, options);
|
|
5365
|
+
const dragAttempts = [];
|
|
5366
|
+
for (const plan of PROMPT_CAPTCHA_DRAG_PLANS) {
|
|
5367
|
+
const sourceBox = await sourceLocator.boundingBox().catch(() => null);
|
|
5368
|
+
const targetBox = await dropTarget.boundingBox().catch(() => null);
|
|
5369
|
+
if (!sourceBox || !targetBox) {
|
|
5370
|
+
throw new Error("Unable to resolve prompt captcha drag coordinates.");
|
|
5371
|
+
}
|
|
5372
|
+
const targetOffsetX = (plan.endXRatio - 0.5) * targetBox.width;
|
|
5373
|
+
const targetOffsetY = (plan.endYRatio - 0.5) * targetBox.height;
|
|
5374
|
+
await dragCaptchaAction(page, sourceLocator, dropTarget, {
|
|
5375
|
+
forceMouse: true,
|
|
5376
|
+
targetOffsetX,
|
|
5377
|
+
targetOffsetY,
|
|
5378
|
+
steps: options.promptDragMoveSteps,
|
|
5379
|
+
holdDelayMs: options.promptDragHoldDelayMs,
|
|
5380
|
+
stepDelayMs: options.promptDragStepDelayMs,
|
|
5381
|
+
beforeReleaseDelayMs: options.promptDragBeforeReleaseDelayMs,
|
|
5382
|
+
afterReleaseDelayMs: options.promptDragAfterReleaseDelayMs,
|
|
5383
|
+
finalMoveRepeats: options.promptDragFinalMoveRepeats
|
|
5384
|
+
});
|
|
5385
|
+
const afterState = await readPromptCaptchaState(frame, options);
|
|
5386
|
+
const accepted = afterState.badgeCount > baselineState.badgeCount || afterState.selectedCount > baselineState.selectedCount;
|
|
5387
|
+
const attemptInfo = {
|
|
5388
|
+
planName: plan.name,
|
|
5389
|
+
sourceRect: rectOf(sourceBox),
|
|
5390
|
+
targetRect: rectOf(targetBox),
|
|
5391
|
+
targetOffsetX: Number(targetOffsetX.toFixed(2)),
|
|
5392
|
+
targetOffsetY: Number(targetOffsetY.toFixed(2)),
|
|
5393
|
+
beforeState: baselineState,
|
|
5394
|
+
afterState,
|
|
5395
|
+
accepted
|
|
5396
|
+
};
|
|
5397
|
+
dragAttempts.push(attemptInfo);
|
|
5398
|
+
logger10.info(
|
|
5399
|
+
`\u9A8C\u8BC1\u7801\u62D6\u62FD\u7B2C ${visualIndex + 1} \u5F20\uFF0C\u65B9\u6848 ${plan.name}\uFF0Cbadge ${baselineState.badgeCount} -> ${afterState.badgeCount}\uFF0Cselected ${baselineState.selectedCount} -> ${afterState.selectedCount}`
|
|
5400
|
+
);
|
|
5401
|
+
if (accepted) {
|
|
5402
|
+
return {
|
|
5403
|
+
accepted: true,
|
|
5404
|
+
dragAttempts
|
|
5405
|
+
};
|
|
5406
|
+
}
|
|
5407
|
+
if (options.promptDragRetryDelayMs > 0) {
|
|
5408
|
+
await page.waitForTimeout(options.promptDragRetryDelayMs);
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5411
|
+
await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, `drag-${visualIndex + 1}-failed`, options, {
|
|
5412
|
+
visualIndex,
|
|
5413
|
+
dragAttempts,
|
|
5414
|
+
finalState: await readPromptCaptchaState(frame, options)
|
|
5415
|
+
}).catch((error) => {
|
|
5416
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u62D6\u62FD\u5931\u8D25\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5417
|
+
});
|
|
5418
|
+
return {
|
|
5419
|
+
accepted: false,
|
|
5420
|
+
dragAttempts
|
|
5421
|
+
};
|
|
5422
|
+
};
|
|
5163
5423
|
async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
5164
5424
|
const { callCaptchaRecognitionApi: callCaptchaRecognitionApi2 } = dependencies;
|
|
5165
5425
|
if (typeof callCaptchaRecognitionApi2 !== "function") {
|
|
@@ -5174,7 +5434,7 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5174
5434
|
return false;
|
|
5175
5435
|
}
|
|
5176
5436
|
logger10.info("\u5F53\u524D\u4F7F\u7528\u672Ctool\u2014\u2014\u6D4B\u8BD5\u7248\u672C");
|
|
5177
|
-
for (let attempt = 1; attempt <= config.maxRetries; attempt
|
|
5437
|
+
for (let attempt = 1; attempt <= config.maxRetries; attempt += 1) {
|
|
5178
5438
|
logger10.info(`\u5F00\u59CB\u7B2C ${attempt}/${config.maxRetries} \u6B21 verifycenter \u9A8C\u8BC1\u7801\u8BC6\u522B\u3002`);
|
|
5179
5439
|
try {
|
|
5180
5440
|
const captchaContext = await getVerifycenterCaptchaContext(page, config);
|
|
@@ -5184,6 +5444,16 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5184
5444
|
}
|
|
5185
5445
|
const { iframeLocator, frame } = captchaContext;
|
|
5186
5446
|
await waitForCaptchaChallengeReady(page, frame, config);
|
|
5447
|
+
await maybeCollectCaptchaDebugInfo(
|
|
5448
|
+
page,
|
|
5449
|
+
frame,
|
|
5450
|
+
iframeLocator,
|
|
5451
|
+
attempt,
|
|
5452
|
+
"ready",
|
|
5453
|
+
config
|
|
5454
|
+
).catch((error) => {
|
|
5455
|
+
logger10.warn(`\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5456
|
+
});
|
|
5187
5457
|
await page.waitForTimeout(config.recognitionDelayMs);
|
|
5188
5458
|
const screenshotBuffer = await iframeLocator.screenshot();
|
|
5189
5459
|
const apiResponse = await callCaptchaRecognitionApi2({
|
|
@@ -5207,33 +5477,74 @@ async function solveCaptcha(page, options = {}, dependencies = {}) {
|
|
|
5207
5477
|
await refreshCaptcha(page, frame, config);
|
|
5208
5478
|
continue;
|
|
5209
5479
|
}
|
|
5210
|
-
const
|
|
5211
|
-
const
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5480
|
+
const orderedSourceImages = await resolveCaptchaSourceImagesInVisualOrder(frame, config);
|
|
5481
|
+
const normalizedIndexes = normalizeCaptchaImageIndexes(serialNumbers, orderedSourceImages.length);
|
|
5482
|
+
if (normalizedIndexes.length !== serialNumbers.length) {
|
|
5483
|
+
throw new Error(
|
|
5484
|
+
`Captcha image indexes could not be normalized. raw=${serialNumbers.join(", ")}, count=${orderedSourceImages.length}`
|
|
5485
|
+
);
|
|
5486
|
+
}
|
|
5487
|
+
logger10.info(`\u9A8C\u8BC1\u7801\u89C6\u89C9\u4F4D\u5E8F\u6620\u5C04\uFF1A${normalizedIndexes.map((index) => index + 1).join(", ")}`);
|
|
5488
|
+
for (const imageIndex of normalizedIndexes) {
|
|
5489
|
+
if (imageIndex < 0 || imageIndex >= orderedSourceImages.length) {
|
|
5490
|
+
throw new Error(
|
|
5491
|
+
`Captcha image index ${imageIndex} is out of range. count=${orderedSourceImages.length}`
|
|
5492
|
+
);
|
|
5219
5493
|
}
|
|
5220
|
-
const sourceImage =
|
|
5494
|
+
const sourceImage = orderedSourceImages[imageIndex].locator;
|
|
5221
5495
|
await sourceImage.waitFor({
|
|
5222
5496
|
state: "visible",
|
|
5223
5497
|
timeout: config.sourceImageVisibleTimeoutMs
|
|
5224
5498
|
});
|
|
5225
|
-
await
|
|
5499
|
+
const dragResult = await dragPromptCaptchaImage(
|
|
5500
|
+
page,
|
|
5501
|
+
frame,
|
|
5502
|
+
iframeLocator,
|
|
5503
|
+
sourceImage,
|
|
5504
|
+
dropTarget,
|
|
5505
|
+
config,
|
|
5506
|
+
{
|
|
5507
|
+
attempt,
|
|
5508
|
+
visualIndex: imageIndex
|
|
5509
|
+
}
|
|
5510
|
+
);
|
|
5511
|
+
if (!dragResult.accepted) {
|
|
5512
|
+
throw new Error(`Captcha prompt drag was not accepted for visual index ${imageIndex + 1}.`);
|
|
5513
|
+
}
|
|
5514
|
+
if (config.dragBetweenWaitMs > 0) {
|
|
5515
|
+
await page.waitForTimeout(config.dragBetweenWaitMs);
|
|
5516
|
+
}
|
|
5226
5517
|
}
|
|
5227
|
-
const
|
|
5518
|
+
const beforeSubmitState = await readPromptCaptchaState(frame, config);
|
|
5519
|
+
logger10.info(
|
|
5520
|
+
`\u63D0\u4EA4\u524D\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${beforeSubmitState.badgeCount}, selected=${beforeSubmitState.selectedCount}, submitDisabled=${beforeSubmitState.submitDisabled}`
|
|
5521
|
+
);
|
|
5522
|
+
const submitted = await clickCaptchaAction(frame, config.submitTexts, {
|
|
5523
|
+
...config,
|
|
5524
|
+
page,
|
|
5525
|
+
logger: logger10,
|
|
5526
|
+
forceMouse: true,
|
|
5527
|
+
actionVisibleTimeoutMs: config.submitReadyTimeoutMs
|
|
5528
|
+
}).catch(() => false);
|
|
5228
5529
|
if (!submitted) {
|
|
5229
5530
|
logger10.warn("\u672A\u627E\u5230\u63D0\u4EA4\u6309\u94AE\uFF0C\u53EF\u80FD\u4F1A\u81EA\u52A8\u63D0\u4EA4\u3002");
|
|
5230
5531
|
}
|
|
5231
5532
|
await page.waitForTimeout(config.submitWaitMs);
|
|
5533
|
+
const afterSubmitState = await readPromptCaptchaState(frame, config);
|
|
5534
|
+
logger10.info(
|
|
5535
|
+
`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u72B6\u6001\uFF1Abadge=${afterSubmitState.badgeCount}, selected=${afterSubmitState.selectedCount}, submitDisabled=${afterSubmitState.submitDisabled}`
|
|
5536
|
+
);
|
|
5232
5537
|
const stillVisible = await iframeLocator.isVisible({ timeout: config.containerVisibleTimeoutMs }).catch(() => false);
|
|
5233
5538
|
if (!stillVisible) {
|
|
5234
5539
|
logger10.info("\u9A8C\u8BC1\u7801\u8BC6\u522B\u5E76\u63D0\u4EA4\u6210\u529F\u3002");
|
|
5235
5540
|
return true;
|
|
5236
5541
|
}
|
|
5542
|
+
await maybeCollectCaptchaDebugInfo(page, frame, iframeLocator, attempt, "submit-still-visible", config, {
|
|
5543
|
+
beforeSubmitState,
|
|
5544
|
+
afterSubmitState
|
|
5545
|
+
}).catch((error) => {
|
|
5546
|
+
logger10.warn(`\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801\u8C03\u8BD5\u6293\u53D6\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
5547
|
+
});
|
|
5237
5548
|
logger10.warn("\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801 iframe \u4ECD\u7136\u53EF\u89C1\uFF0C\u51C6\u5907\u5237\u65B0\u540E\u91CD\u8BD5\u3002");
|
|
5238
5549
|
await page.waitForTimeout(2e3);
|
|
5239
5550
|
await refreshCaptcha(page, frame, config);
|
|
@@ -5676,14 +5987,14 @@ var Mutation = {
|
|
|
5676
5987
|
const isFrameElement = tagName === "IFRAME" || tagName === "FRAME";
|
|
5677
5988
|
const nodeName = descriptor?.id || descriptor?.name || "no-id";
|
|
5678
5989
|
let source = "main";
|
|
5679
|
-
let
|
|
5990
|
+
let path3 = `${selector}[${index}]`;
|
|
5680
5991
|
let text = "";
|
|
5681
5992
|
let html = "";
|
|
5682
5993
|
let frameUrl = "";
|
|
5683
5994
|
let readyState = "";
|
|
5684
5995
|
if (isFrameElement) {
|
|
5685
5996
|
source = "iframe";
|
|
5686
|
-
|
|
5997
|
+
path3 = `${selector}[${index}]::iframe(${nodeName})`;
|
|
5687
5998
|
const frame = await handle.contentFrame();
|
|
5688
5999
|
if (frame) {
|
|
5689
6000
|
try {
|
|
@@ -5713,7 +6024,7 @@ var Mutation = {
|
|
|
5713
6024
|
items.push({
|
|
5714
6025
|
selector,
|
|
5715
6026
|
source,
|
|
5716
|
-
path:
|
|
6027
|
+
path: path3,
|
|
5717
6028
|
text,
|
|
5718
6029
|
html,
|
|
5719
6030
|
frameUrl,
|