@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/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\u3002");
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\u3002");
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: cleanupName }) => {
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 (!element) {
2991
- return false;
2992
- }
2993
- if (window[callbackName]) {
2994
- window[callbackName]();
3112
+ if (element) {
3113
+ if (window[callbackName]) {
3114
+ window[callbackName]();
3115
+ }
3116
+ return true;
2995
3117
  }
2996
- return true;
3118
+ return false;
2997
3119
  };
2998
3120
  checkAndReport();
2999
3121
  observer = new MutationObserver((mutations) => {
3000
- const shouldCheck = mutations.some((mutation) => mutation.addedNodes.length > 0);
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[cleanupName] = () => {
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\uFF1A${domSelector}`);
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 !== page.mainFrame()) {
3040
- return;
3041
- }
3042
- const currentUrl = page.url();
3043
- if (currentUrl.includes(urlPattern)) {
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\uFF1A${urlPattern}`);
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\u9A8C\u8BC1\u7801\u76D1\u63A7...");
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