@skrillex1224/playwright-toolkit 2.1.197 → 2.1.199
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -255
- package/browser.d.ts +8 -8
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +182 -288
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +182 -288
- package/dist/index.js.map +3 -3
- package/dist/internals/proxy-meter.js +627 -549
- package/index.d.ts +628 -657
- package/package.json +66 -66
package/dist/index.js
CHANGED
|
@@ -393,9 +393,6 @@ function createInternalLogger(moduleName, explicitLogger) {
|
|
|
393
393
|
const message = error instanceof Error ? error.message : error;
|
|
394
394
|
baseLogger.error(`${methodName} \u5931\u8D25: ${message}`);
|
|
395
395
|
},
|
|
396
|
-
error(message) {
|
|
397
|
-
baseLogger.error(message);
|
|
398
|
-
},
|
|
399
396
|
debug(message) {
|
|
400
397
|
baseLogger.debug(message);
|
|
401
398
|
},
|
|
@@ -574,9 +571,31 @@ var logger2 = createInternalLogger("ProxyMeter");
|
|
|
574
571
|
var MAX_TOP_DOMAINS = 20;
|
|
575
572
|
var FLUSH_INTERVAL_MS = 2e3;
|
|
576
573
|
var DEFAULT_DEBUG_MAX_EVENTS = 400;
|
|
574
|
+
var PROXY_METER_READY_TIMEOUT_MS = 5e3;
|
|
575
|
+
var PROXY_METER_STABLE_WINDOW_MS = 2e3;
|
|
576
|
+
var PROXY_METER_POLL_INTERVAL_MS = 100;
|
|
577
|
+
var PROXY_METER_CONNECT_TIMEOUT_MS = 200;
|
|
577
578
|
var runtime = null;
|
|
578
579
|
var cleanupInstalled = false;
|
|
579
580
|
var observedDomainResourceTypes = /* @__PURE__ */ new Map();
|
|
581
|
+
var sleepSignal = new Int32Array(new SharedArrayBuffer(4));
|
|
582
|
+
var portProbeScript = [
|
|
583
|
+
'const net = require("net");',
|
|
584
|
+
"const port = Number(process.argv[1] || 0);",
|
|
585
|
+
"const timeout = Number(process.argv[2] || 200);",
|
|
586
|
+
"if (!Number.isFinite(port) || port <= 0) process.exit(2);",
|
|
587
|
+
'const socket = net.connect({ host: "127.0.0.1", port });',
|
|
588
|
+
"let done = false;",
|
|
589
|
+
"const finish = (code) => {",
|
|
590
|
+
" if (done) return;",
|
|
591
|
+
" done = true;",
|
|
592
|
+
" try { socket.destroy(); } catch {}",
|
|
593
|
+
" process.exit(code);",
|
|
594
|
+
"};",
|
|
595
|
+
'socket.once("connect", () => finish(0));',
|
|
596
|
+
'socket.once("error", () => finish(1));',
|
|
597
|
+
"socket.setTimeout(timeout, () => finish(1));"
|
|
598
|
+
].join("");
|
|
580
599
|
var toSafeInt = (value) => {
|
|
581
600
|
const num = Number(value);
|
|
582
601
|
if (!Number.isFinite(num) || num <= 0) return 0;
|
|
@@ -675,6 +694,17 @@ var ensureLogPath = () => {
|
|
|
675
694
|
const label = runId ? `proxy-meter-${runId}-${suffix}.json` : `proxy-meter-${process.pid}-${suffix}.json`;
|
|
676
695
|
return path.join(baseDir, label);
|
|
677
696
|
};
|
|
697
|
+
var ensureStatePath = () => {
|
|
698
|
+
const baseDir = resolveLogDir();
|
|
699
|
+
if (!existsSync(baseDir)) {
|
|
700
|
+
try {
|
|
701
|
+
mkdirSync(baseDir, { recursive: true });
|
|
702
|
+
} catch {
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const suffix = `${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
|
|
706
|
+
return path.join(baseDir, `proxy-meter-state-${process.pid}-${suffix}.json`);
|
|
707
|
+
};
|
|
678
708
|
var readSnapshot = (logPath) => {
|
|
679
709
|
if (!logPath || !existsSync(logPath)) return null;
|
|
680
710
|
try {
|
|
@@ -685,6 +715,86 @@ var readSnapshot = (logPath) => {
|
|
|
685
715
|
return null;
|
|
686
716
|
}
|
|
687
717
|
};
|
|
718
|
+
var readState = (statePath) => {
|
|
719
|
+
if (!statePath || !existsSync(statePath)) return null;
|
|
720
|
+
try {
|
|
721
|
+
const raw = readFileSync(statePath, "utf8");
|
|
722
|
+
if (!raw) return null;
|
|
723
|
+
return JSON.parse(raw);
|
|
724
|
+
} catch {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
var sleepSync = (durationMs) => {
|
|
729
|
+
const timeout = Math.max(0, Number(durationMs) || 0);
|
|
730
|
+
if (timeout <= 0) return;
|
|
731
|
+
Atomics.wait(sleepSignal, 0, 0, timeout);
|
|
732
|
+
};
|
|
733
|
+
var canConnectToPort = (port, timeoutMs = PROXY_METER_CONNECT_TIMEOUT_MS) => {
|
|
734
|
+
const result = spawnSync(process.execPath, ["-e", portProbeScript, String(port), String(timeoutMs)], {
|
|
735
|
+
stdio: "ignore",
|
|
736
|
+
timeout: timeoutMs + 100
|
|
737
|
+
});
|
|
738
|
+
return result.status === 0;
|
|
739
|
+
};
|
|
740
|
+
var waitForProxyMeterReady = ({ port, statePath }) => {
|
|
741
|
+
const startedAt = Date.now();
|
|
742
|
+
const listenDeadline = startedAt + PROXY_METER_READY_TIMEOUT_MS;
|
|
743
|
+
let firstConnectAt = 0;
|
|
744
|
+
let stableHealthy = true;
|
|
745
|
+
while (Date.now() < listenDeadline) {
|
|
746
|
+
const lifecycle = readState(statePath);
|
|
747
|
+
if (lifecycle?.status === "exited") {
|
|
748
|
+
return {
|
|
749
|
+
ok: false,
|
|
750
|
+
reason: "child_exit_before_ready",
|
|
751
|
+
childExitCode: lifecycle.exitCode,
|
|
752
|
+
latencyMs: Date.now() - startedAt
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
if (canConnectToPort(port)) {
|
|
756
|
+
firstConnectAt = Date.now();
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
sleepSync(PROXY_METER_POLL_INTERVAL_MS);
|
|
760
|
+
}
|
|
761
|
+
if (!firstConnectAt) {
|
|
762
|
+
return {
|
|
763
|
+
ok: false,
|
|
764
|
+
reason: "listen_timeout",
|
|
765
|
+
childExitCode: readState(statePath)?.exitCode ?? null,
|
|
766
|
+
latencyMs: Date.now() - startedAt
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
const stableDeadline = firstConnectAt + PROXY_METER_STABLE_WINDOW_MS;
|
|
770
|
+
while (Date.now() < stableDeadline) {
|
|
771
|
+
const lifecycle = readState(statePath);
|
|
772
|
+
if (lifecycle?.status === "exited") {
|
|
773
|
+
return {
|
|
774
|
+
ok: false,
|
|
775
|
+
reason: "stabilize_timeout",
|
|
776
|
+
childExitCode: lifecycle.exitCode,
|
|
777
|
+
latencyMs: Date.now() - startedAt
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
if (!canConnectToPort(port)) {
|
|
781
|
+
stableHealthy = false;
|
|
782
|
+
}
|
|
783
|
+
sleepSync(PROXY_METER_POLL_INTERVAL_MS);
|
|
784
|
+
}
|
|
785
|
+
if (!stableHealthy || !canConnectToPort(port)) {
|
|
786
|
+
return {
|
|
787
|
+
ok: false,
|
|
788
|
+
reason: "stabilize_timeout",
|
|
789
|
+
childExitCode: readState(statePath)?.exitCode ?? null,
|
|
790
|
+
latencyMs: Date.now() - startedAt
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
return {
|
|
794
|
+
ok: true,
|
|
795
|
+
latencyMs: Date.now() - startedAt
|
|
796
|
+
};
|
|
797
|
+
};
|
|
688
798
|
var normalizeDomainRows = (hosts) => {
|
|
689
799
|
const rows = [];
|
|
690
800
|
let requestCount = 0;
|
|
@@ -833,15 +943,23 @@ var startProxyMeter = (options = {}) => {
|
|
|
833
943
|
const upstreamUrl = String(options.proxyUrl || "").trim();
|
|
834
944
|
if (!upstreamUrl) return null;
|
|
835
945
|
if (runtime && runtime.proc) {
|
|
946
|
+
const previousStatePath = runtime.statePath;
|
|
836
947
|
try {
|
|
837
948
|
runtime.proc.kill("SIGTERM");
|
|
838
949
|
} catch {
|
|
839
950
|
}
|
|
840
951
|
runtime = null;
|
|
952
|
+
if (previousStatePath) {
|
|
953
|
+
try {
|
|
954
|
+
rmSync(previousStatePath, { force: true });
|
|
955
|
+
} catch {
|
|
956
|
+
}
|
|
957
|
+
}
|
|
841
958
|
}
|
|
842
959
|
observedDomainResourceTypes = /* @__PURE__ */ new Map();
|
|
843
960
|
const port = pickFreePort();
|
|
844
961
|
const logPath = ensureLogPath();
|
|
962
|
+
const statePath = ensureStatePath();
|
|
845
963
|
const scriptPath = resolveScriptPath();
|
|
846
964
|
const debugMode = Boolean(options.debugMode);
|
|
847
965
|
const debugMaxEvents = Math.max(10, toSafeInt(options.debugMaxEvents) || DEFAULT_DEBUG_MAX_EVENTS);
|
|
@@ -852,7 +970,8 @@ var startProxyMeter = (options = {}) => {
|
|
|
852
970
|
PROXY_METER_UPSTREAM: upstreamUrl,
|
|
853
971
|
PROXY_METER_FLUSH_MS: String(FLUSH_INTERVAL_MS),
|
|
854
972
|
PROXY_METER_DEBUG: debugMode ? "1" : "0",
|
|
855
|
-
PROXY_METER_DEBUG_MAX_EVENTS: String(debugMaxEvents)
|
|
973
|
+
PROXY_METER_DEBUG_MAX_EVENTS: String(debugMaxEvents),
|
|
974
|
+
PROXY_METER_STATE: statePath
|
|
856
975
|
};
|
|
857
976
|
const child = spawn(process.execPath, [scriptPath], {
|
|
858
977
|
env,
|
|
@@ -867,8 +986,27 @@ var startProxyMeter = (options = {}) => {
|
|
|
867
986
|
proc: child,
|
|
868
987
|
port,
|
|
869
988
|
logPath,
|
|
989
|
+
statePath,
|
|
870
990
|
startedAt: Date.now()
|
|
871
991
|
};
|
|
992
|
+
const readiness = waitForProxyMeterReady({ port, statePath });
|
|
993
|
+
if (!readiness.ok) {
|
|
994
|
+
logger2.warn(
|
|
995
|
+
`[proxy-meter] startup failed reason=${readiness.reason} latency_ms=${readiness.latencyMs} exit_code=${readiness.childExitCode ?? "unknown"}`
|
|
996
|
+
);
|
|
997
|
+
try {
|
|
998
|
+
child.kill("SIGTERM");
|
|
999
|
+
} catch {
|
|
1000
|
+
}
|
|
1001
|
+
runtime = null;
|
|
1002
|
+
try {
|
|
1003
|
+
rmSync(statePath, { force: true });
|
|
1004
|
+
} catch {
|
|
1005
|
+
}
|
|
1006
|
+
throw new Error(`proxy-meter startup failed: ${readiness.reason}`);
|
|
1007
|
+
}
|
|
1008
|
+
runtime.startedAt = Date.now() - readiness.latencyMs;
|
|
1009
|
+
logger2.info(`[proxy-meter] ready latency_ms=${readiness.latencyMs} stable_window_ms=${PROXY_METER_STABLE_WINDOW_MS}`);
|
|
872
1010
|
registerCleanup();
|
|
873
1011
|
return { server: `http://127.0.0.1:${port}` };
|
|
874
1012
|
};
|
|
@@ -880,9 +1018,15 @@ var recordProxyMeterResourceType = (requestUrl, resourceType) => {
|
|
|
880
1018
|
};
|
|
881
1019
|
var stopProxyMeter = async () => {
|
|
882
1020
|
if (!runtime) return null;
|
|
883
|
-
const { proc, logPath } = runtime;
|
|
1021
|
+
const { proc, logPath, statePath } = runtime;
|
|
884
1022
|
if (!proc || proc.killed) {
|
|
885
1023
|
runtime = null;
|
|
1024
|
+
if (statePath) {
|
|
1025
|
+
try {
|
|
1026
|
+
rmSync(statePath, { force: true });
|
|
1027
|
+
} catch {
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
886
1030
|
return logPath || null;
|
|
887
1031
|
}
|
|
888
1032
|
await new Promise((resolve) => {
|
|
@@ -904,6 +1048,12 @@ var stopProxyMeter = async () => {
|
|
|
904
1048
|
}
|
|
905
1049
|
});
|
|
906
1050
|
runtime = null;
|
|
1051
|
+
if (statePath) {
|
|
1052
|
+
try {
|
|
1053
|
+
rmSync(statePath, { force: true });
|
|
1054
|
+
} catch {
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
907
1057
|
return logPath || null;
|
|
908
1058
|
};
|
|
909
1059
|
var getProxyMeterSnapshot = async (options = {}) => {
|
|
@@ -2927,41 +3077,13 @@ var LiveView = {
|
|
|
2927
3077
|
// src/captcha-monitor.js
|
|
2928
3078
|
import { v4 as uuidv4 } from "uuid";
|
|
2929
3079
|
var logger9 = createInternalLogger("Captcha");
|
|
2930
|
-
var DEFAULT_BYTEDANCE_CAPTCHA_TOKEN = "eKJvBfwfN0YRav0-VD_44E2VBSfm7l0YtddUQ7cFySI";
|
|
2931
|
-
var DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS = Object.freeze({
|
|
2932
|
-
token: DEFAULT_BYTEDANCE_CAPTCHA_TOKEN,
|
|
2933
|
-
apiUrl: "https://api.jfbym.com/api/YmServer/customApi",
|
|
2934
|
-
apiType: "31234",
|
|
2935
|
-
maxRetries: 3,
|
|
2936
|
-
containerSelector: "#captcha_container",
|
|
2937
|
-
iframeSelector: 'iframe[src*="verifycenter"]',
|
|
2938
|
-
iframeFallbackSelector: "iframe",
|
|
2939
|
-
sourceImageSelector: "div.canvas-container",
|
|
2940
|
-
dropTargetContainerSelector: "#captcha_verify_image div",
|
|
2941
|
-
dropTargetTexts: ["\u62D6\u62FD\u5230\u8FD9\u91CC"],
|
|
2942
|
-
refreshTexts: ["\u5237\u65B0"],
|
|
2943
|
-
submitTexts: ["\u63D0\u4EA4"],
|
|
2944
|
-
recognitionSuccessCode: 1e4,
|
|
2945
|
-
containerVisibleTimeoutMs: 2e3,
|
|
2946
|
-
iframeVisibleTimeoutMs: 12e3,
|
|
2947
|
-
iframeFallbackVisibleTimeoutMs: 4e3,
|
|
2948
|
-
contentFrameResolveRetries: 5,
|
|
2949
|
-
contentFrameResolveDelayMs: 500,
|
|
2950
|
-
actionVisibleTimeoutMs: 1500,
|
|
2951
|
-
sourceImageVisibleTimeoutMs: 3e3,
|
|
2952
|
-
recognitionDelayMs: 2e3,
|
|
2953
|
-
refreshWaitMs: 3e3,
|
|
2954
|
-
submitWaitMs: 3e3,
|
|
2955
|
-
retryDelayBaseMs: 2e3,
|
|
2956
|
-
retryDelayStepMs: 1e3
|
|
2957
|
-
});
|
|
2958
3080
|
function useCaptchaMonitor(page, options) {
|
|
2959
3081
|
const { domSelector, urlPattern, onDetected } = options;
|
|
2960
3082
|
if (!domSelector && !urlPattern) {
|
|
2961
|
-
throw new Error("[CaptchaMonitor] \u5FC5\u987B\u63D0\u4F9B domSelector \u6216 urlPattern\
|
|
3083
|
+
throw new Error("[CaptchaMonitor] \u5FC5\u987B\u63D0\u4F9B domSelector \u6216 urlPattern \u81F3\u5C11\u4E00\u4E2A");
|
|
2962
3084
|
}
|
|
2963
3085
|
if (!onDetected || typeof onDetected !== "function") {
|
|
2964
|
-
throw new Error("[CaptchaMonitor] onDetected \u5FC5\u987B\u662F\u51FD\u6570
|
|
3086
|
+
throw new Error("[CaptchaMonitor] onDetected \u5FC5\u987B\u662F\u4E00\u4E2A\u51FD\u6570");
|
|
2965
3087
|
}
|
|
2966
3088
|
let isStopped = false;
|
|
2967
3089
|
let isHandling = false;
|
|
@@ -2982,22 +3104,28 @@ function useCaptchaMonitor(page, options) {
|
|
|
2982
3104
|
const cleanerName = `__c_cleaner_${uuidv4().replace(/-/g, "_")}`;
|
|
2983
3105
|
page.exposeFunction(exposedFunctionName, triggerDetected).catch(() => {
|
|
2984
3106
|
});
|
|
2985
|
-
page.addInitScript(({ selector, callbackName, cleanerName:
|
|
3107
|
+
page.addInitScript(({ selector, callbackName, cleanerName: cleanerName2 }) => {
|
|
2986
3108
|
(() => {
|
|
2987
3109
|
let observer = null;
|
|
2988
3110
|
const checkAndReport = () => {
|
|
2989
3111
|
const element = document.querySelector(selector);
|
|
2990
|
-
if (
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3112
|
+
if (element) {
|
|
3113
|
+
if (window[callbackName]) {
|
|
3114
|
+
window[callbackName]();
|
|
3115
|
+
}
|
|
3116
|
+
return true;
|
|
2995
3117
|
}
|
|
2996
|
-
return
|
|
3118
|
+
return false;
|
|
2997
3119
|
};
|
|
2998
3120
|
checkAndReport();
|
|
2999
3121
|
observer = new MutationObserver((mutations) => {
|
|
3000
|
-
|
|
3122
|
+
let shouldCheck = false;
|
|
3123
|
+
for (const mutation of mutations) {
|
|
3124
|
+
if (mutation.addedNodes.length > 0) {
|
|
3125
|
+
shouldCheck = true;
|
|
3126
|
+
break;
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3001
3129
|
if (shouldCheck && observer) {
|
|
3002
3130
|
checkAndReport();
|
|
3003
3131
|
}
|
|
@@ -3013,7 +3141,7 @@ function useCaptchaMonitor(page, options) {
|
|
|
3013
3141
|
} else {
|
|
3014
3142
|
mountObserver();
|
|
3015
3143
|
}
|
|
3016
|
-
window[
|
|
3144
|
+
window[cleanerName2] = () => {
|
|
3017
3145
|
if (observer) {
|
|
3018
3146
|
observer.disconnect();
|
|
3019
3147
|
observer = null;
|
|
@@ -3021,7 +3149,7 @@ function useCaptchaMonitor(page, options) {
|
|
|
3021
3149
|
};
|
|
3022
3150
|
})();
|
|
3023
3151
|
}, { selector: domSelector, callbackName: exposedFunctionName, cleanerName });
|
|
3024
|
-
logger9.success("useCaptchaMonitor", `DOM \u76D1\u63A7\u5DF2\u542F\u7528
|
|
3152
|
+
logger9.success("useCaptchaMonitor", `DOM \u76D1\u63A7\u5DF2\u542F\u7528: ${domSelector}`);
|
|
3025
3153
|
cleanupFns.push(async () => {
|
|
3026
3154
|
try {
|
|
3027
3155
|
await page.evaluate((name) => {
|
|
@@ -3030,29 +3158,28 @@ function useCaptchaMonitor(page, options) {
|
|
|
3030
3158
|
delete window[name];
|
|
3031
3159
|
}
|
|
3032
3160
|
}, cleanerName);
|
|
3033
|
-
} catch {
|
|
3161
|
+
} catch (e) {
|
|
3034
3162
|
}
|
|
3035
3163
|
});
|
|
3036
3164
|
}
|
|
3037
3165
|
if (urlPattern) {
|
|
3038
3166
|
frameHandler = async (frame) => {
|
|
3039
|
-
if (frame
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
await triggerDetected();
|
|
3167
|
+
if (frame === page.mainFrame()) {
|
|
3168
|
+
const currentUrl = page.url();
|
|
3169
|
+
if (currentUrl.includes(urlPattern)) {
|
|
3170
|
+
await triggerDetected();
|
|
3171
|
+
}
|
|
3045
3172
|
}
|
|
3046
3173
|
};
|
|
3047
3174
|
page.on("framenavigated", frameHandler);
|
|
3048
|
-
logger9.success("useCaptchaMonitor", `URL \u76D1\u63A7\u5DF2\u542F\u7528
|
|
3175
|
+
logger9.success("useCaptchaMonitor", `URL \u76D1\u63A7\u5DF2\u542F\u7528: ${urlPattern}`);
|
|
3049
3176
|
cleanupFns.push(async () => {
|
|
3050
3177
|
page.off("framenavigated", frameHandler);
|
|
3051
3178
|
});
|
|
3052
3179
|
}
|
|
3053
3180
|
return {
|
|
3054
3181
|
stop: async () => {
|
|
3055
|
-
logger9.info("\u6B63\u5728\u505C\u6B62\
|
|
3182
|
+
logger9.info("useCaptchaMonitor", "\u6B63\u5728\u505C\u6B62\u76D1\u63A7...");
|
|
3056
3183
|
for (const fn of cleanupFns) {
|
|
3057
3184
|
await fn();
|
|
3058
3185
|
}
|
|
@@ -3060,241 +3187,8 @@ function useCaptchaMonitor(page, options) {
|
|
|
3060
3187
|
}
|
|
3061
3188
|
};
|
|
3062
3189
|
}
|
|
3063
|
-
var callCaptchaRecognitionApi = async (imageBase64, { apiUrl, apiType, token }) => {
|
|
3064
|
-
const response = await fetch(apiUrl, {
|
|
3065
|
-
method: "POST",
|
|
3066
|
-
headers: {
|
|
3067
|
-
"Content-Type": "application/json"
|
|
3068
|
-
},
|
|
3069
|
-
body: JSON.stringify({
|
|
3070
|
-
type: apiType,
|
|
3071
|
-
image: imageBase64,
|
|
3072
|
-
token
|
|
3073
|
-
})
|
|
3074
|
-
});
|
|
3075
|
-
if (!response.ok) {
|
|
3076
|
-
throw new Error(`Captcha API request failed with status ${response.status}`);
|
|
3077
|
-
}
|
|
3078
|
-
return await response.json();
|
|
3079
|
-
};
|
|
3080
|
-
var extractCaptchaSerialNumbers = (apiResponse) => {
|
|
3081
|
-
const serialNumbers = apiResponse?.data?.data?.serial_number;
|
|
3082
|
-
if (!Array.isArray(serialNumbers)) {
|
|
3083
|
-
return [];
|
|
3084
|
-
}
|
|
3085
|
-
return serialNumbers.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 0);
|
|
3086
|
-
};
|
|
3087
|
-
var waitForVisible = async (locator, timeout) => {
|
|
3088
|
-
try {
|
|
3089
|
-
await locator.waitFor({
|
|
3090
|
-
state: "visible",
|
|
3091
|
-
timeout
|
|
3092
|
-
});
|
|
3093
|
-
return true;
|
|
3094
|
-
} catch {
|
|
3095
|
-
return false;
|
|
3096
|
-
}
|
|
3097
|
-
};
|
|
3098
|
-
var resolveContentFrame = async (page, iframeLocator, options) => {
|
|
3099
|
-
for (let attempt = 1; attempt <= options.contentFrameResolveRetries; attempt++) {
|
|
3100
|
-
const iframeHandle = await iframeLocator.elementHandle();
|
|
3101
|
-
const frame = await iframeHandle?.contentFrame();
|
|
3102
|
-
if (frame) {
|
|
3103
|
-
return frame;
|
|
3104
|
-
}
|
|
3105
|
-
if (attempt < options.contentFrameResolveRetries) {
|
|
3106
|
-
await page.waitForTimeout(options.contentFrameResolveDelayMs);
|
|
3107
|
-
}
|
|
3108
|
-
}
|
|
3109
|
-
return null;
|
|
3110
|
-
};
|
|
3111
|
-
var getVerifycenterCaptchaContext = async (page, options) => {
|
|
3112
|
-
const captchaContainer = page.locator(options.containerSelector).first();
|
|
3113
|
-
const isContainerVisible = await waitForVisible(
|
|
3114
|
-
captchaContainer,
|
|
3115
|
-
options.containerVisibleTimeoutMs
|
|
3116
|
-
);
|
|
3117
|
-
if (!isContainerVisible) {
|
|
3118
|
-
return null;
|
|
3119
|
-
}
|
|
3120
|
-
logger9.info("\u68C0\u6D4B\u5230\u9A8C\u8BC1\u7801\u5BB9\u5668\uFF0C\u5F00\u59CB\u7B49\u5F85 iframe \u52A0\u8F7D\u3002");
|
|
3121
|
-
let iframeLocator = page.locator(options.iframeSelector).first();
|
|
3122
|
-
let isIframeVisible = await waitForVisible(
|
|
3123
|
-
iframeLocator,
|
|
3124
|
-
options.iframeVisibleTimeoutMs
|
|
3125
|
-
);
|
|
3126
|
-
if (!isIframeVisible) {
|
|
3127
|
-
logger9.warn("\u672A\u5728\u9884\u671F\u9009\u62E9\u5668\u4E2D\u627E\u5230 verifycenter iframe\uFF0C\u5C1D\u8BD5\u5BB9\u5668\u5185\u4EFB\u610F iframe\u3002");
|
|
3128
|
-
iframeLocator = captchaContainer.locator(options.iframeFallbackSelector).first();
|
|
3129
|
-
isIframeVisible = await waitForVisible(
|
|
3130
|
-
iframeLocator,
|
|
3131
|
-
options.iframeFallbackVisibleTimeoutMs
|
|
3132
|
-
);
|
|
3133
|
-
}
|
|
3134
|
-
if (!isIframeVisible) {
|
|
3135
|
-
throw new Error("verifycenter iframe not found inside captcha container.");
|
|
3136
|
-
}
|
|
3137
|
-
logger9.info("\u9A8C\u8BC1\u7801 iframe \u5DF2\u53EF\u89C1\uFF0C\u5F00\u59CB\u89E3\u6790\u5185\u5BB9 frame\u3002");
|
|
3138
|
-
const frame = await resolveContentFrame(page, iframeLocator, options);
|
|
3139
|
-
if (!frame) {
|
|
3140
|
-
throw new Error("Failed to resolve verifycenter iframe content frame.");
|
|
3141
|
-
}
|
|
3142
|
-
return { iframeLocator, frame };
|
|
3143
|
-
};
|
|
3144
|
-
var clickCaptchaAction = async (frame, texts, options) => {
|
|
3145
|
-
for (const text of texts) {
|
|
3146
|
-
const candidates = [
|
|
3147
|
-
frame.getByText(text, { exact: false }).first(),
|
|
3148
|
-
frame.locator(`text=${text}`).first()
|
|
3149
|
-
];
|
|
3150
|
-
for (const candidate of candidates) {
|
|
3151
|
-
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
3152
|
-
if (!isVisible) {
|
|
3153
|
-
continue;
|
|
3154
|
-
}
|
|
3155
|
-
await candidate.click();
|
|
3156
|
-
return true;
|
|
3157
|
-
}
|
|
3158
|
-
}
|
|
3159
|
-
return false;
|
|
3160
|
-
};
|
|
3161
|
-
var findCaptchaDropTarget = async (frame, options) => {
|
|
3162
|
-
for (const text of options.dropTargetTexts) {
|
|
3163
|
-
const candidates = [
|
|
3164
|
-
frame.locator(options.dropTargetContainerSelector).filter({ hasText: text }).first(),
|
|
3165
|
-
frame.getByText(text, { exact: false }).first()
|
|
3166
|
-
];
|
|
3167
|
-
for (const candidate of candidates) {
|
|
3168
|
-
const isVisible = await waitForVisible(candidate, options.actionVisibleTimeoutMs);
|
|
3169
|
-
if (isVisible) {
|
|
3170
|
-
return candidate;
|
|
3171
|
-
}
|
|
3172
|
-
}
|
|
3173
|
-
}
|
|
3174
|
-
return null;
|
|
3175
|
-
};
|
|
3176
|
-
var dragCaptchaWithMouse = async (page, sourceLocator, targetLocator) => {
|
|
3177
|
-
const sourceBox = await sourceLocator.boundingBox();
|
|
3178
|
-
const targetBox = await targetLocator.boundingBox();
|
|
3179
|
-
if (!sourceBox || !targetBox) {
|
|
3180
|
-
throw new Error("Unable to resolve captcha drag coordinates.");
|
|
3181
|
-
}
|
|
3182
|
-
const startX = sourceBox.x + sourceBox.width / 2;
|
|
3183
|
-
const startY = sourceBox.y + sourceBox.height / 2;
|
|
3184
|
-
const endX = targetBox.x + targetBox.width / 2;
|
|
3185
|
-
const endY = targetBox.y + targetBox.height / 2;
|
|
3186
|
-
const steps = 10;
|
|
3187
|
-
const liftOffsetX = Math.min(18, Math.max(8, sourceBox.width * 0.12));
|
|
3188
|
-
const liftOffsetY = Math.min(12, Math.max(4, sourceBox.height * 0.08));
|
|
3189
|
-
await page.mouse.move(startX, startY, { steps: 8 });
|
|
3190
|
-
await page.waitForTimeout(250);
|
|
3191
|
-
await page.mouse.down();
|
|
3192
|
-
await page.waitForTimeout(350);
|
|
3193
|
-
await page.mouse.move(startX + liftOffsetX, startY + liftOffsetY, { steps: 6 });
|
|
3194
|
-
await page.waitForTimeout(250);
|
|
3195
|
-
for (let step = 1; step <= steps; step++) {
|
|
3196
|
-
const progress = step / steps;
|
|
3197
|
-
const easedProgress = 1 - (1 - progress) * (1 - progress);
|
|
3198
|
-
const currentX = startX + liftOffsetX + (endX - startX - liftOffsetX) * easedProgress;
|
|
3199
|
-
const currentY = startY + liftOffsetY + (endY - startY - liftOffsetY) * easedProgress;
|
|
3200
|
-
await page.mouse.move(currentX, currentY, { steps: 2 });
|
|
3201
|
-
await page.waitForTimeout(90);
|
|
3202
|
-
}
|
|
3203
|
-
await page.waitForTimeout(100);
|
|
3204
|
-
await page.mouse.up();
|
|
3205
|
-
await page.waitForTimeout(100);
|
|
3206
|
-
};
|
|
3207
|
-
var refreshCaptcha = async (page, frame, options) => {
|
|
3208
|
-
const clicked = await clickCaptchaAction(frame, options.refreshTexts, options).catch(() => false);
|
|
3209
|
-
if (!clicked) {
|
|
3210
|
-
logger9.warn("Refresh button not found.");
|
|
3211
|
-
return false;
|
|
3212
|
-
}
|
|
3213
|
-
await page.waitForTimeout(options.refreshWaitMs);
|
|
3214
|
-
return true;
|
|
3215
|
-
};
|
|
3216
|
-
async function solveBytedanceCaptcha(page, options = {}) {
|
|
3217
|
-
const config = {
|
|
3218
|
-
...DEFAULT_BYTEDANCE_CAPTCHA_OPTIONS,
|
|
3219
|
-
...options
|
|
3220
|
-
};
|
|
3221
|
-
if (!config.token) {
|
|
3222
|
-
logger9.warn("\u7F3A\u5C11\u9A8C\u8BC1\u7801 token\uFF0C\u8DF3\u8FC7\u81EA\u52A8\u8BC6\u522B\u3002");
|
|
3223
|
-
return false;
|
|
3224
|
-
}
|
|
3225
|
-
for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
|
|
3226
|
-
logger9.info(`\u5F00\u59CB\u7B2C ${attempt}/${config.maxRetries} \u6B21 verifycenter \u9A8C\u8BC1\u7801\u8BC6\u522B\u3002`);
|
|
3227
|
-
try {
|
|
3228
|
-
const captchaContext = await getVerifycenterCaptchaContext(page, config);
|
|
3229
|
-
if (!captchaContext) {
|
|
3230
|
-
logger9.info("Captcha container is not visible anymore.");
|
|
3231
|
-
return true;
|
|
3232
|
-
}
|
|
3233
|
-
const { iframeLocator, frame } = captchaContext;
|
|
3234
|
-
await page.waitForTimeout(config.recognitionDelayMs);
|
|
3235
|
-
const screenshotBuffer = await iframeLocator.screenshot();
|
|
3236
|
-
const apiResponse = await callCaptchaRecognitionApi(
|
|
3237
|
-
screenshotBuffer.toString("base64"),
|
|
3238
|
-
config
|
|
3239
|
-
);
|
|
3240
|
-
const serialNumbers = extractCaptchaSerialNumbers(apiResponse);
|
|
3241
|
-
if (apiResponse?.code !== config.recognitionSuccessCode || serialNumbers.length === 0) {
|
|
3242
|
-
logger9.warn(
|
|
3243
|
-
`\u9A8C\u8BC1\u7801\u8BC6\u522B\u5931\u8D25\u3002code=${apiResponse?.code}, msg=${apiResponse?.msg || "unknown"}`
|
|
3244
|
-
);
|
|
3245
|
-
await refreshCaptcha(page, frame, config);
|
|
3246
|
-
continue;
|
|
3247
|
-
}
|
|
3248
|
-
logger9.info(`\u9A8C\u8BC1\u7801\u8BC6\u522B\u6210\u529F\uFF0C\u5E8F\u53F7\uFF1A${serialNumbers.join(", ")}`);
|
|
3249
|
-
const dropTarget = await findCaptchaDropTarget(frame, config);
|
|
3250
|
-
if (!dropTarget) {
|
|
3251
|
-
logger9.warn("\u672A\u627E\u5230\u9A8C\u8BC1\u7801\u62D6\u62FD\u76EE\u6807\u533A\u57DF\u3002");
|
|
3252
|
-
await refreshCaptcha(page, frame, config);
|
|
3253
|
-
continue;
|
|
3254
|
-
}
|
|
3255
|
-
const sourceImages = frame.locator(config.sourceImageSelector);
|
|
3256
|
-
const imageCount = await sourceImages.count();
|
|
3257
|
-
for (const rawIndex of serialNumbers) {
|
|
3258
|
-
let imageIndex = rawIndex;
|
|
3259
|
-
if (imageIndex >= imageCount && imageIndex > 0 && imageIndex - 1 < imageCount) {
|
|
3260
|
-
imageIndex -= 1;
|
|
3261
|
-
}
|
|
3262
|
-
if (imageIndex < 0 || imageIndex >= imageCount) {
|
|
3263
|
-
throw new Error(`Captcha image index ${rawIndex} is out of range. count=${imageCount}`);
|
|
3264
|
-
}
|
|
3265
|
-
const sourceImage = sourceImages.nth(imageIndex);
|
|
3266
|
-
await sourceImage.waitFor({
|
|
3267
|
-
state: "visible",
|
|
3268
|
-
timeout: config.sourceImageVisibleTimeoutMs
|
|
3269
|
-
});
|
|
3270
|
-
await dragCaptchaWithMouse(page, sourceImage, dropTarget);
|
|
3271
|
-
}
|
|
3272
|
-
const submitted = await clickCaptchaAction(frame, config.submitTexts, config).catch(() => false);
|
|
3273
|
-
if (!submitted) {
|
|
3274
|
-
logger9.warn("\u672A\u627E\u5230\u63D0\u4EA4\u6309\u94AE\uFF0C\u53EF\u80FD\u4F1A\u81EA\u52A8\u63D0\u4EA4\u3002");
|
|
3275
|
-
}
|
|
3276
|
-
await page.waitForTimeout(config.submitWaitMs);
|
|
3277
|
-
const stillVisible = await iframeLocator.isVisible({ timeout: config.containerVisibleTimeoutMs }).catch(() => false);
|
|
3278
|
-
if (!stillVisible) {
|
|
3279
|
-
logger9.info("\u9A8C\u8BC1\u7801\u8BC6\u522B\u5E76\u63D0\u4EA4\u6210\u529F\u3002");
|
|
3280
|
-
return true;
|
|
3281
|
-
}
|
|
3282
|
-
logger9.warn("\u63D0\u4EA4\u540E\u9A8C\u8BC1\u7801 iframe \u4ECD\u7136\u53EF\u89C1\uFF0C\u51C6\u5907\u5237\u65B0\u540E\u91CD\u8BD5\u3002");
|
|
3283
|
-
await page.waitForTimeout(2e3);
|
|
3284
|
-
await refreshCaptcha(page, frame, config);
|
|
3285
|
-
} catch (error) {
|
|
3286
|
-
logger9.error(`\u7B2C ${attempt}/${config.maxRetries} \u6B21\u9A8C\u8BC1\u7801\u8BC6\u522B\u5931\u8D25\uFF1A${error?.message || error}`);
|
|
3287
|
-
}
|
|
3288
|
-
if (attempt < config.maxRetries) {
|
|
3289
|
-
await page.waitForTimeout(config.retryDelayBaseMs + attempt * config.retryDelayStepMs);
|
|
3290
|
-
}
|
|
3291
|
-
}
|
|
3292
|
-
logger9.error(`\u91CD\u8BD5 ${config.maxRetries} \u6B21\u540E\uFF0C\u9A8C\u8BC1\u7801\u4ECD\u672A\u8BC6\u522B\u6210\u529F\u3002`);
|
|
3293
|
-
return false;
|
|
3294
|
-
}
|
|
3295
3190
|
var Captcha = {
|
|
3296
|
-
useCaptchaMonitor
|
|
3297
|
-
solveBytedanceCaptcha
|
|
3191
|
+
useCaptchaMonitor
|
|
3298
3192
|
};
|
|
3299
3193
|
|
|
3300
3194
|
// src/mutation.js
|