feat: add configurable operation delay

This commit is contained in:
root
2026-05-08 12:50:20 -04:00
parent 872f382815
commit c23461261b
44 changed files with 4770 additions and 193 deletions
+8 -1
View File
@@ -15,6 +15,7 @@
isActionEnabled,
isVisibleElement,
log,
performOperationWithDelay: injectedPerformOperationWithDelay,
routeErrorPattern = null,
simulateClick,
sleep,
@@ -122,6 +123,10 @@
}
async function recoverAuthRetryPage(options = {}) {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
const performOperationWithDelay = injectedPerformOperationWithDelay
|| rootScope.CodexOperationDelay?.performOperationWithDelay
|| (async (_metadata, operation) => operation());
const {
logLabel = '',
maxClickAttempts = 5,
@@ -173,7 +178,9 @@
if (typeof humanPause === 'function') {
await humanPause(300, 800);
}
simulateClick(retryState.retryButton);
await performOperationWithDelay({ stepKey: options.stepKey || '', kind: 'click', label: 'auth-retry-click' }, async () => {
simulateClick(retryState.retryButton);
});
const recoveryResult = await waitForRetryPageRecoveryAfterClick({
pathPatterns,
pollIntervalMs,
+7 -5
View File
@@ -95,11 +95,13 @@ async function fetchDuckEmail(payload = {}) {
for (let attempt = 1; attempt <= 2; attempt++) {
await humanPause(500, 1300);
if (typeof simulateClick === 'function') {
simulateClick(generatorButton);
} else {
generatorButton.click();
}
await window.CodexOperationDelay.performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'click', label: 'duck-generate-address' }, async () => {
if (typeof simulateClick === 'function') {
simulateClick(generatorButton);
} else {
generatorButton.click();
}
});
log(`Duck 邮箱:已点击“生成 Duck 私有地址”按钮(${attempt}/2`);
try {
+77 -15
View File
@@ -35,6 +35,12 @@ if (document.documentElement.getAttribute(GOPAY_FLOW_LISTENER_SENTINEL) !== '1')
console.log('[MultiPage:gopay-flow] 消息监听已存在,跳过重复注册');
}
async function performGoPayOperationWithDelay(metadata, operation) {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
}
async function handleGoPayCommand(message) {
switch (message.type) {
case 'GOPAY_GET_STATE':
@@ -622,20 +628,33 @@ function fillVisibleOtpInputs(code = '') {
}
async function submitGoPayPhone(payload = {}) {
const delayOperation = typeof performGoPayOperationWithDelay === 'function'
? performGoPayOperationWithDelay
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
await waitForDocumentComplete();
const countryCode = normalizeGoPayCountryCode(payload.countryCode || payload.gopayCountryCode || '+86');
const phone = normalizeGoPayNationalPhone(payload.phone || payload.gopayPhone || '', countryCode);
if (!phone) {
throw new Error('GoPay 手机号为空,请先在侧边栏配置。');
}
const countryResult = await ensureGoPayCountryCode(countryCode);
const input = await waitUntil(() => findPhoneInput(), {
label: 'GoPay 手机号输入框',
intervalMs: 250,
timeoutMs: 15000,
});
fillInput(input, phone);
const clickResult = await clickContinueIfPresent();
const { countryResult, clickResult } = await delayOperation({ stepKey: 'gopay-approve', kind: 'submit', label: 'submit-phone' }, async () => {
const nextCountryResult = await ensureGoPayCountryCode(countryCode);
fillInput(input, phone);
const nextClickResult = await clickContinueIfPresent();
return {
countryResult: nextCountryResult,
clickResult: nextClickResult,
};
});
return {
phoneSubmitted: true,
countryCode,
@@ -647,17 +666,27 @@ async function submitGoPayPhone(payload = {}) {
}
async function submitGoPayOtp(payload = {}) {
const delayOperation = typeof performGoPayOperationWithDelay === 'function'
? performGoPayOperationWithDelay
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
await waitForDocumentComplete();
const code = normalizeOtp(payload.code || payload.otp || '');
if (!code) {
throw new Error('GoPay WhatsApp 验证码为空。');
}
const filled = await waitUntil(() => fillVisibleOtpInputs(code), {
label: 'GoPay 验证码输入框',
intervalMs: 250,
timeoutMs: 15000,
const { filled, clickResult } = await delayOperation({ stepKey: 'gopay-approve', kind: 'submit', label: 'submit-otp' }, async () => {
const filledOtp = await waitUntil(() => fillVisibleOtpInputs(code), {
label: 'GoPay 验证码输入框',
intervalMs: 250,
timeoutMs: 15000,
});
const continueResult = await clickContinueIfPresent();
return { filled: filledOtp, clickResult: continueResult };
});
const clickResult = await clickContinueIfPresent();
return {
otpSubmitted: Boolean(filled),
clicked: Boolean(clickResult.clicked),
@@ -667,17 +696,27 @@ async function submitGoPayOtp(payload = {}) {
}
async function submitGoPayPin(payload = {}) {
const delayOperation = typeof performGoPayOperationWithDelay === 'function'
? performGoPayOperationWithDelay
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
await waitForDocumentComplete();
const pin = normalizeOtp(payload.pin || payload.gopayPin || '');
if (!pin) {
throw new Error('GoPay PIN 为空,请先在侧边栏配置。');
}
const filled = await waitUntil(() => fillVisiblePinInputs(pin), {
label: 'GoPay PIN 输入框',
intervalMs: 250,
timeoutMs: 15000,
const { filled, clickResult } = await delayOperation({ stepKey: 'gopay-approve', kind: 'submit', label: 'submit-pin' }, async () => {
const filledPin = await waitUntil(() => fillVisiblePinInputs(pin), {
label: 'GoPay PIN 输入框',
intervalMs: 250,
timeoutMs: 15000,
});
const continueResult = await clickContinueIfPresent();
return { filled: filledPin, clickResult: continueResult };
});
const clickResult = await clickContinueIfPresent();
return {
pinSubmitted: Boolean(filled),
clicked: Boolean(clickResult.clicked),
@@ -722,18 +761,41 @@ function getGoPayPayNowTarget() {
}
async function clickGoPayContinue() {
const delayOperation = typeof performGoPayOperationWithDelay === 'function'
? performGoPayOperationWithDelay
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
await waitForDocumentComplete();
const clickResult = await clickContinueIfPresent({ afterMs: 1200 });
const button = findContinueButton();
if (!button) {
return { clicked: false, clickTarget: '' };
}
const clickResult = await delayOperation({ stepKey: 'gopay-approve', kind: 'click', label: 'click-continue' }, async () => {
await humanClickElement(button, { afterMs: 1200 });
return { clicked: true, target: describeElement(button) };
});
return { clicked: Boolean(clickResult.clicked), clickTarget: clickResult.target || '' };
}
async function clickGoPayPayNow() {
const delayOperation = typeof performGoPayOperationWithDelay === 'function'
? performGoPayOperationWithDelay
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
await waitForDocumentComplete();
const button = findPayNowButton();
if (!button) {
return { clicked: false, clickTarget: '' };
}
await humanClickElement(button, { afterMs: 1500 });
await delayOperation({ stepKey: 'gopay-approve', kind: 'click', label: 'click-pay-now' }, async () => {
await humanClickElement(button, { afterMs: 1500 });
});
return { clicked: true, clickTarget: describeElement(button) };
}
+17 -4
View File
@@ -501,7 +501,9 @@ async function ensureAgreementChecked() {
if (isCheckboxChecked(checkbox)) {
continue;
}
simulateClick(checkbox);
await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'click', label: 'mail2925-agreement-checkbox' }, async () => {
simulateClick(checkbox);
});
changed = true;
await sleep(120);
}
@@ -1052,6 +1054,11 @@ async function waitForMail2925View(targetView, timeoutMs = 45000) {
return detectMail2925ViewState();
}
async function performOperationWithDelay(metadata, operation) {
const gate = window.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
}
async function ensureMail2925Session(payload = {}) {
const email = String(payload?.email || '').trim();
const password = String(payload?.password || '');
@@ -1135,13 +1142,19 @@ async function ensureMail2925Session(payload = {}) {
}
await ensureAgreementChecked();
fillInput(emailInput, email);
await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'fill', label: 'mail2925-login-email' }, async () => {
fillInput(emailInput, email);
});
await sleep(150);
fillInput(passwordInput, password);
await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'fill', label: 'mail2925-login-password' }, async () => {
fillInput(passwordInput, password);
});
await sleep(200);
await sleep(1000);
log(`步骤 0:2925 已定位到登录表单,准备点击“登录”,当前地址 ${location.href}`, 'info');
simulateClick(loginButton);
await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'submit', label: 'mail2925-login-submit' }, async () => {
simulateClick(loginButton);
});
log(`步骤 0:2925 已点击“登录”,点击后地址 ${location.href}`, 'info');
const finalState = await waitForMail2925View('mailbox', 40000);
+98
View File
@@ -0,0 +1,98 @@
(function attachOperationDelay(root) {
const OPERATION_DELAY_MS = 2000;
const SETTING_RESTORE_FALLBACK_MS = 50;
const EXCLUDED_STEP_KEYS = new Set(['confirm-oauth', 'platform-verify']);
let operationDelayEnabled = true;
let operationDelaySettingReady = null;
let operationDelaySettingRevision = 0;
function normalizeOperationDelayEnabled(value) {
return typeof value === 'boolean' ? value : true;
}
function getOperationDelayEnabled() {
return operationDelayEnabled;
}
async function refreshOperationDelaySetting() {
const restoreRevision = ++operationDelaySettingRevision;
const ready = (async () => {
let nextEnabled = true;
try {
const data = await root.chrome?.storage?.local?.get?.(['operationDelayEnabled']);
nextEnabled = normalizeOperationDelayEnabled(data?.operationDelayEnabled);
} catch {
nextEnabled = true;
}
if (operationDelaySettingRevision === restoreRevision) {
operationDelayEnabled = nextEnabled;
}
return operationDelayEnabled;
})();
operationDelaySettingReady = ready;
try {
return await ready;
} finally {
if (operationDelaySettingReady === ready) {
operationDelaySettingReady = null;
}
}
}
function shouldDelayOperation(metadata = {}) {
if (metadata.skipOperationDelay === true) return false;
if (EXCLUDED_STEP_KEYS.has(String(metadata.stepKey || '').trim())) return false;
const enabled = Object.prototype.hasOwnProperty.call(metadata, 'enabled')
? normalizeOperationDelayEnabled(metadata.enabled)
: getOperationDelayEnabled();
if (enabled === false) return false;
return true;
}
function waitForOperationDelaySettingFallback() {
return new Promise((resolve) => {
const schedule = root.setTimeout || (typeof setTimeout === 'function' ? setTimeout : null);
if (typeof schedule === 'function') {
schedule(() => resolve(getOperationDelayEnabled()), SETTING_RESTORE_FALLBACK_MS);
return;
}
resolve(getOperationDelayEnabled());
});
}
async function resolveOperationDelayEnabled(metadata = {}, options = {}) {
if (typeof options.getEnabled === 'function') {
return normalizeOperationDelayEnabled(options.getEnabled());
}
if (Object.prototype.hasOwnProperty.call(metadata, 'enabled')) {
return normalizeOperationDelayEnabled(metadata.enabled);
}
if (operationDelaySettingReady) {
return normalizeOperationDelayEnabled(await Promise.race([
operationDelaySettingReady,
waitForOperationDelaySettingFallback(),
]));
}
return getOperationDelayEnabled();
}
async function performOperationWithDelay(metadata = {}, operation, options = {}) {
const result = await operation();
const enabled = await resolveOperationDelayEnabled(metadata, options);
if (shouldDelayOperation({ ...metadata, enabled })) {
const wait = options.sleep || root.sleep;
await wait(OPERATION_DELAY_MS);
}
return result;
}
root.chrome?.storage?.onChanged?.addListener?.((changes, areaName) => {
if (areaName === 'local' && Object.prototype.hasOwnProperty.call(changes, 'operationDelayEnabled')) {
operationDelaySettingRevision += 1;
operationDelayEnabled = normalizeOperationDelayEnabled(changes.operationDelayEnabled?.newValue);
}
});
refreshOperationDelaySetting().catch(() => { operationDelayEnabled = true; });
root.CodexOperationDelay = { OPERATION_DELAY_MS, normalizeOperationDelayEnabled, refreshOperationDelaySetting, getOperationDelayEnabled, shouldDelayOperation, performOperationWithDelay };
})(typeof self !== 'undefined' ? self : globalThis);
+107 -33
View File
@@ -31,6 +31,12 @@ if (document.documentElement.getAttribute(PAYPAL_FLOW_LISTENER_SENTINEL) !== '1'
console.log('[MultiPage:paypal-flow] 消息监听已存在,跳过重复注册');
}
async function performPayPalOperationWithDelay(metadata, operation) {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
}
async function handlePayPalCommand(message) {
switch (message.type) {
case 'PAYPAL_GET_STATE':
@@ -142,15 +148,50 @@ function findInputByPatterns(patterns) {
}
function findEmailInput() {
return findInputByPatterns([
const isPasswordCandidate = (input) => {
const type = String(input?.getAttribute?.('type') || input?.type || '').trim().toLowerCase();
const metadataText = normalizeText([
input?.textContent,
input?.getAttribute?.('aria-label'),
input?.getAttribute?.('title'),
input?.getAttribute?.('placeholder'),
input?.getAttribute?.('name'),
input?.id,
].filter(Boolean).join(' '));
return type === 'password' || /password|pass|密码/i.test(metadataText);
};
const inputs = getVisibleControls('input')
.filter((input) => {
const type = String(input.getAttribute('type') || input.type || '').trim().toLowerCase();
return isEnabledControl(input)
&& !['hidden', 'checkbox', 'radio', 'submit', 'button', 'file'].includes(type)
&& !isPasswordCandidate(input);
});
return inputs.find((input) => [
/email|login|user|账号|邮箱/i,
]) || getVisibleControls('input[type="email"]').find(isVisibleElement) || null;
].some((pattern) => pattern.test(getActionText(input))))
|| getVisibleControls('input[type="email"]').find((input) => isVisibleElement(input) && !isPasswordCandidate(input))
|| null;
}
function findPasswordInput() {
return findInputByPatterns([
/password|pass|密码/i,
]) || getVisibleControls('input[type="password"]').find(isVisibleElement) || null;
const inputs = getVisibleControls('input')
.filter((input) => {
const type = String(input.getAttribute('type') || input.type || '').trim().toLowerCase();
return isEnabledControl(input) && !['hidden', 'checkbox', 'radio', 'submit', 'button', 'file'].includes(type);
});
return inputs.find((input) => {
const type = String(input.getAttribute('type') || input.type || '').trim().toLowerCase();
const metadataText = normalizeText([
input?.textContent,
input?.getAttribute?.('aria-label'),
input?.getAttribute?.('title'),
input?.getAttribute?.('placeholder'),
input?.getAttribute?.('name'),
input?.id,
].filter(Boolean).join(' '));
return type === 'password' || /password|pass|密码/i.test(metadataText);
}) || getVisibleControls('input[type="password"]').find(isVisibleElement) || null;
}
function findLoginNextButton() {
@@ -240,6 +281,13 @@ function refillPayPalEmailInput(emailInput, email) {
}
async function submitPayPalLogin(payload = {}) {
const delayOperation = typeof performPayPalOperationWithDelay === 'function'
? performPayPalOperationWithDelay
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
await waitForDocumentComplete();
const email = normalizeText(payload.email || '');
@@ -253,8 +301,10 @@ async function submitPayPalLogin(payload = {}) {
const emailNextButton = findEmailNextButton();
if (emailInput && emailNextButton && isEnabledControl(emailNextButton) && (!passwordInput || !findPasswordLoginButton())) {
refillPayPalEmailInput(emailInput, email);
simulateClick(emailNextButton);
await delayOperation({ stepKey: 'paypal-approve', kind: 'submit', label: 'paypal-email' }, async () => {
refillPayPalEmailInput(emailInput, email);
simulateClick(emailNextButton);
});
return {
submitted: false,
phase: 'email_submitted',
@@ -263,16 +313,18 @@ async function submitPayPalLogin(payload = {}) {
}
if (!passwordInput && emailInput && email) {
refillPayPalEmailInput(emailInput, email);
const nextButton = await waitUntil(() => {
const button = findEmailNextButton() || findLoginNextButton();
return button && isEnabledControl(button) ? button : null;
}, {
intervalMs: 250,
timeoutMs: 8000,
timeoutMessage: 'PayPal email page did not expose a clickable next/continue button.',
await delayOperation({ stepKey: 'paypal-approve', kind: 'submit', label: 'paypal-email' }, async () => {
refillPayPalEmailInput(emailInput, email);
const nextButton = await waitUntil(() => {
const button = findEmailNextButton() || findLoginNextButton();
return button && isEnabledControl(button) ? button : null;
}, {
intervalMs: 250,
timeoutMs: 8000,
timeoutMessage: 'PayPal email page did not expose a clickable next/continue button.',
});
simulateClick(nextButton);
});
simulateClick(nextButton);
return {
submitted: false,
phase: 'email_submitted',
@@ -281,7 +333,9 @@ async function submitPayPalLogin(payload = {}) {
} else if (!passwordInput && emailInput && !email) {
throw new Error('PayPal 账号为空,请先在侧边栏配置。');
} else if (emailInput && email) {
refillPayPalEmailInput(emailInput, email);
await delayOperation({ stepKey: 'paypal-approve', kind: 'fill', label: 'paypal-email' }, async () => {
refillPayPalEmailInput(emailInput, email);
});
}
passwordInput = passwordInput || await waitUntil(() => findPasswordInput(), {
@@ -289,22 +343,24 @@ async function submitPayPalLogin(payload = {}) {
timeoutMs: 8000,
timeoutMessage: 'PayPal password page did not expose a password input.',
});
fillInput(passwordInput, password);
await sleep(1000);
await delayOperation({ stepKey: 'paypal-approve', kind: 'submit', label: 'paypal-password' }, async () => {
fillInput(passwordInput, password);
await sleep(1000);
const loginButton = await waitUntil(() => {
const button = findClickableByText([
/login|log\s*in|sign\s*in|continue/i,
/登录|登入|继续/i,
]);
return button && isEnabledControl(button) ? button : null;
}, {
intervalMs: 250,
timeoutMs: 8000,
timeoutMessage: 'PayPal password page did not expose a clickable login/continue button.',
const loginButton = await waitUntil(() => {
const button = findClickableByText([
/login|log\s*in|sign\s*in|continue/i,
/登录|登入|继续/i,
]);
return button && isEnabledControl(button) ? button : null;
}, {
intervalMs: 250,
timeoutMs: 8000,
timeoutMessage: 'PayPal password page did not expose a clickable login/continue button.',
});
simulateClick(loginButton);
});
simulateClick(loginButton);
return {
submitted: true,
phase: 'password_submitted',
@@ -313,6 +369,13 @@ async function submitPayPalLogin(payload = {}) {
}
async function dismissPayPalPrompts() {
const delayOperation = typeof performPayPalOperationWithDelay === 'function'
? performPayPalOperationWithDelay
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
await waitForDocumentComplete();
const buttons = findPasskeyPromptButtons();
let clicked = 0;
@@ -320,7 +383,9 @@ async function dismissPayPalPrompts() {
if (!isVisibleElement(button) || !isEnabledControl(button)) {
continue;
}
simulateClick(button);
await delayOperation({ stepKey: 'paypal-approve', kind: 'click', label: 'paypal-dismiss-prompt' }, async () => {
simulateClick(button);
});
clicked += 1;
await sleep(500);
}
@@ -331,6 +396,13 @@ async function dismissPayPalPrompts() {
}
async function clickPayPalApprove() {
const delayOperation = typeof performPayPalOperationWithDelay === 'function'
? performPayPalOperationWithDelay
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
await waitForDocumentComplete();
await dismissPayPalPrompts().catch(() => ({ clicked: 0 }));
@@ -342,7 +414,9 @@ async function clickPayPalApprove() {
};
}
simulateClick(button);
await delayOperation({ stepKey: 'paypal-approve', kind: 'click', label: 'paypal-approve' }, async () => {
simulateClick(button);
});
return {
clicked: true,
buttonText: getActionText(button),
+38 -11
View File
@@ -13,6 +13,7 @@
isConsentReady,
isPhoneVerificationPageReady,
isVisibleElement,
performOperationWithDelay: injectedPerformOperationWithDelay,
simulateClick,
sleep,
throwIfStopped,
@@ -34,6 +35,11 @@
let lastPhoneRoute405RecoveryFailedAt = 0;
let activePhoneResendPromise = null;
async function performOperationWithDelay(metadata, operation) {
const gate = injectedPerformOperationWithDelay || rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
}
function dispatchInputEvents(element) {
if (!element) return;
element.dispatchEvent(new Event('input', { bubbles: true }));
@@ -334,10 +340,15 @@
}
const selectedOption = getSelectedCountryOption();
if (selectedOption && isSameCountryOption(selectedOption, targetOption)) {
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'select', label: 'phone-country-select' }, async () => {
dispatchInputEvents(select);
});
return true;
}
select.value = String(targetOption.value || '');
dispatchInputEvents(select);
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'select', label: 'phone-country-select' }, async () => {
select.value = String(targetOption.value || '');
dispatchInputEvents(select);
});
await sleep(250);
const nextSelectedOption = getSelectedCountryOption();
return Boolean(nextSelectedOption && isSameCountryOption(nextSelectedOption, targetOption));
@@ -616,7 +627,9 @@
}
clicked += 1;
await humanPause(200, 500);
simulateClick(retryButton);
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'click', label: 'phone-route-retry' }, async () => {
simulateClick(retryButton);
});
await sleep(1000);
continue;
}
@@ -715,13 +728,19 @@
}
await humanPause(250, 700);
fillInput(phoneInput, nationalPhoneNumber);
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'fill', label: 'phone-number' }, async () => {
fillInput(phoneInput, nationalPhoneNumber);
});
if (hiddenPhoneNumberInput) {
hiddenPhoneNumberInput.value = phoneNumber;
dispatchInputEvents(hiddenPhoneNumberInput);
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'hidden-sync', label: 'phone-number-hidden-sync' }, async () => {
hiddenPhoneNumberInput.value = phoneNumber;
dispatchInputEvents(hiddenPhoneNumberInput);
});
}
await sleep(250);
simulateClick(submitButton);
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'submit', label: 'phone-number-submit' }, async () => {
simulateClick(submitButton);
});
return waitForPhoneVerificationReady();
}
@@ -797,9 +816,13 @@
}
await humanPause(250, 700);
fillInput(codeInput, code);
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'fill', label: 'phone-verification-code' }, async () => {
fillInput(codeInput, code);
});
await sleep(250);
simulateClick(submitButton);
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'submit', label: 'phone-verification-submit' }, async () => {
simulateClick(submitButton);
});
if (is405MethodNotAllowedPage()) {
await recoverPhoneRoute405(12000);
}
@@ -850,7 +873,9 @@
const resendButton = getPhoneVerificationResendButton({ allowDisabled: true });
if (resendButton && isActionEnabled(resendButton)) {
await humanPause(250, 700);
simulateClick(resendButton);
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'click', label: 'phone-verification-resend' }, async () => {
simulateClick(resendButton);
});
await sleep(1000);
if (is405MethodNotAllowedPage()) {
await recoverRoute405WithinResend();
@@ -902,7 +927,9 @@
throw new Error('The auth page is not currently on phone verification or add-phone page.');
}
location.assign('/add-phone');
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'navigation', label: 'phone-return-add-phone' }, async () => {
location.assign('/add-phone');
});
await waitForAddPhoneReady(timeout);
return {
addPhonePage: true,
+38 -13
View File
@@ -60,6 +60,12 @@ const PAYMENT_METHOD_CONFIGS = {
},
};
async function performOperationWithDelay(metadata, operation) {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
}
if (document.documentElement.getAttribute(PLUS_CHECKOUT_LISTENER_SENTINEL) !== '1') {
document.documentElement.setAttribute(PLUS_CHECKOUT_LISTENER_SENTINEL, '1');
@@ -688,7 +694,9 @@ async function selectPaymentMethod(method = PLUS_PAYMENT_METHOD_PAYPAL) {
intervalMs: 250,
});
console.info(`[MultiPage:plus-checkout] ${config.label} target selected`, summarizeElementForDebug(target));
simulateClick(target);
await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'select', label: 'select-payment-method' }, async () => {
simulateClick(target);
});
log(`Plus Checkout:已点击 ${config.label} 付款方式,正在确认选中状态。`);
if (!await waitForPaymentMethodActive(config.id)) {
@@ -1417,8 +1425,6 @@ async function fillPlusBillingAndSubmit(payload = {}) {
async function fillPlusBillingAddress(payload = {}) {
await waitForDocumentComplete();
await fillFullName(payload.fullName || '');
const countryText = readCountryText();
const seed = payload.addressSeed || {
query: 'Berlin Mitte',
@@ -1434,10 +1440,21 @@ async function fillPlusBillingAddress(payload = {}) {
const fields = getStructuredAddressFields();
const useDirectStructuredBranch = Boolean(seed.skipAutocomplete || isDropdownStructuredAddressForm(fields));
if (!useDirectStructuredBranch) {
selected = await selectAddressSuggestion(seed);
await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'fill', label: 'fill-address-query' }, async () => {
await fillFullName(payload.fullName || '');
await fillAddressQuery(seed);
});
selected = await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'select', label: 'select-address-suggestion' }, async () => (
clickAddressSuggestion(seed)
));
}
const structuredAddress = await ensureStructuredAddress(seed, {
overwrite: useDirectStructuredBranch,
const structuredAddress = await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'fill', label: 'fill-billing-address' }, async () => {
if (useDirectStructuredBranch) {
await fillFullName(payload.fullName || '');
}
return ensureStructuredAddress(seed, {
overwrite: useDirectStructuredBranch,
});
});
return {
@@ -1449,9 +1466,11 @@ async function fillPlusBillingAddress(payload = {}) {
async function fillPlusAddressQuery(payload = {}) {
await waitForDocumentComplete();
await fillFullName(payload.fullName || '');
const seed = payload.addressSeed || {};
await fillAddressQuery(seed);
await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'fill', label: 'fill-address-query' }, async () => {
await fillFullName(payload.fullName || '');
await fillAddressQuery(seed);
});
return {
countryText: readCountryText(),
queryFilled: true,
@@ -1460,7 +1479,9 @@ async function fillPlusAddressQuery(payload = {}) {
async function selectPlusAddressSuggestion(payload = {}) {
await waitForDocumentComplete();
const selected = await clickAddressSuggestion(payload.addressSeed || {});
const selected = await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'select', label: 'select-address-suggestion' }, async () => (
clickAddressSuggestion(payload.addressSeed || {})
));
return {
selectedAddressText: selected.selectedText,
suggestionIndex: selected.suggestionIndex,
@@ -1469,9 +1490,11 @@ async function selectPlusAddressSuggestion(payload = {}) {
async function ensurePlusStructuredBillingAddress(payload = {}) {
await waitForDocumentComplete();
const structuredAddress = await ensureStructuredAddress(payload.addressSeed || {}, {
overwrite: Boolean(payload.overwriteStructuredAddress),
});
const structuredAddress = await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'fill', label: 'fill-billing-address' }, async () => (
ensureStructuredAddress(payload.addressSeed || {}, {
overwrite: Boolean(payload.overwriteStructuredAddress),
})
));
return {
countryText: readCountryText(),
structuredAddress,
@@ -1494,7 +1517,9 @@ async function clickPlusSubscribe(payload = {}) {
});
await sleep(Math.max(0, Math.floor(Number(payload.beforeClickDelayMs) || 0)));
await humanLikeClick(subscribeButton);
await performOperationWithDelay({ stepKey: 'plus-checkout-billing', kind: 'submit', label: 'click-subscribe' }, async () => {
await humanLikeClick(subscribeButton);
});
return {
clicked: true,
};
+344 -96
View File
@@ -6,6 +6,14 @@ console.log('[MultiPage:signup-page] Content script loaded on', location.href);
const SIGNUP_PAGE_LISTENER_SENTINEL = 'data-multipage-signup-page-listener';
function getOperationDelayRunner() {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function'
? gate
: async (_metadata, operation) => operation();
}
if (document.documentElement.getAttribute(SIGNUP_PAGE_LISTENER_SENTINEL) !== '1') {
document.documentElement.setAttribute(SIGNUP_PAGE_LISTENER_SENTINEL, '1');
@@ -251,6 +259,13 @@ function isEmailVerificationPage() {
}
async function resendVerificationCode(step, timeout = 45000) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
if (step === 8) {
await waitForLoginVerificationPageReady(10000, step);
}
@@ -275,7 +290,9 @@ async function resendVerificationCode(step, timeout = 45000) {
if (action && isActionEnabled(action)) {
log(`步骤 ${step}:重新发送验证码按钮已可用。`);
await humanPause(350, 900);
simulateClick(action);
await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'click', label: 'resend-verification-code' }, async () => {
simulateClick(action);
});
await sleep(1200);
// After clicking resend, check if 405 error appeared
@@ -955,6 +972,13 @@ function logSignupPasswordDiagnostics(context, level = 'warn') {
}
async function waitForSignupEntryState(options = {}) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const {
timeout = 15000,
autoOpenEntry = false,
@@ -995,7 +1019,9 @@ async function waitForSignupEntryState(options = {}) {
}
log('步骤 2:检测到手机号输入模式,正在切换到邮箱输入模式...');
await humanPause(350, 900);
simulateClick(snapshot.switchToEmailTrigger);
await performOperationWithDelay({ stepKey: 'signup-entry', kind: 'click', label: 'switch-to-signup-email' }, async () => {
simulateClick(snapshot.switchToEmailTrigger);
});
} else if (!snapshot.switchToEmailTrigger && !loggedMissingSwitchToEmail) {
loggedMissingSwitchToEmail = true;
log('步骤 2:检测到手机号输入模式,但暂未识别到“改用邮箱/继续使用电子邮件地址登录”按钮,继续等待界面稳定...', 'warn');
@@ -1023,7 +1049,9 @@ async function waitForSignupEntryState(options = {}) {
}
log('步骤 2:正在点击官网注册入口...');
await humanPause(350, 900);
simulateClick(snapshot.signupTrigger);
await performOperationWithDelay({ stepKey: 'signup-entry', kind: 'click', label: 'open-signup-entry' }, async () => {
simulateClick(snapshot.signupTrigger);
});
}
}
@@ -1093,6 +1121,13 @@ async function ensureSignupPasswordPageReady(timeout = 20000) {
}
async function fillSignupEmailAndContinue(email, step) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
if (!email) throw new Error(`未提供邮箱地址,步骤 ${step} 无法继续。`);
const normalizedEmail = String(email || '').trim().toLowerCase();
@@ -1123,7 +1158,9 @@ async function fillSignupEmailAndContinue(email, step) {
log(`步骤 ${step}:正在填写邮箱:${email}`);
await humanPause(500, 1400);
fillInput(snapshot.emailInput, email);
await performOperationWithDelay({ stepKey: step === 2 ? 'signup-entry' : 'fill-password', kind: 'fill', label: 'signup-email' }, async () => {
fillInput(snapshot.emailInput, email);
});
log(`步骤 ${step}:邮箱已填写`);
const continueButton = snapshot.continueButton || getSignupEmailContinueButton({ allowDisabled: true });
@@ -1132,16 +1169,18 @@ async function fillSignupEmailAndContinue(email, step) {
}
log(`步骤 ${step}:邮箱已准备提交,正在前往密码页...`);
window.setTimeout(() => {
try {
throwIfStopped();
try {
await sleep(120);
throwIfStopped();
await performOperationWithDelay({ stepKey: step === 2 ? 'signup-entry' : 'fill-password', kind: 'submit', label: 'submit-signup-email' }, async () => {
simulateClick(continueButton);
} catch (error) {
if (!isStopError(error)) {
console.error('[MultiPage:signup-page] deferred signup email submit failed:', error?.message || error);
}
});
} catch (error) {
if (!isStopError(error)) {
console.error('[MultiPage:signup-page] signup email submit failed:', error?.message || error);
}
}, 120);
throw error;
}
return {
submitted: true,
@@ -1655,6 +1694,13 @@ function findSignupPhoneCountryOptionByPhoneNumber(phoneInput, phoneNumber) {
}
async function trySelectSignupPhoneCountryOption(select, targetOption, phoneInput = getSignupPhoneInput(), options = {}) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
if (!select || !targetOption) {
return false;
}
@@ -1662,12 +1708,16 @@ async function trySelectSignupPhoneCountryOption(select, targetOption, phoneInpu
? (select.options?.[select.selectedIndex] || null)
: null;
if (selectedOption && isSameSignupCountryOption(selectedOption, targetOption)) {
dispatchSignupPhoneFieldEvents(select);
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'select', label: 'signup-phone-country-select' }, async () => {
dispatchSignupPhoneFieldEvents(select);
});
await sleep(120);
return isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options);
}
select.value = String(targetOption.value || '');
dispatchSignupPhoneFieldEvents(select);
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'select', label: 'signup-phone-country-select' }, async () => {
select.value = String(targetOption.value || '');
dispatchSignupPhoneFieldEvents(select);
});
await sleep(250);
return isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options);
}
@@ -1722,6 +1772,13 @@ function findSignupPhoneCountryListboxOption(targetOption, options = {}) {
}
async function trySelectSignupPhoneCountryListboxOption(phoneInput, targetOption, options = {}) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const button = getSignupPhoneCountryButton(phoneInput);
if (!button) {
return false;
@@ -1802,7 +1859,9 @@ async function trySelectSignupPhoneCountryListboxOption(phoneInput, targetOption
return scrolled;
};
simulateClick(button);
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'open-signup-phone-country-listbox' }, async () => {
simulateClick(button);
});
await sleep(200);
resetListboxScroll();
@@ -1812,7 +1871,9 @@ async function trySelectSignupPhoneCountryListboxOption(phoneInput, targetOption
throwIfStopped();
const option = findSignupPhoneCountryListboxOption(targetOption, options);
if (option) {
simulateClick(option);
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'select', label: 'signup-phone-country-listbox-option' }, async () => {
simulateClick(option);
});
await sleep(450);
if (isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options)) {
return true;
@@ -2108,6 +2169,13 @@ function getLoginPhoneInputCandidateDiagnostics(limit = 12) {
}
async function fillLoginPhoneInputAndConfirm(phoneInput, options = {}) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const {
phoneNumber = '',
dialCode = '',
@@ -2132,7 +2200,9 @@ async function fillLoginPhoneInputAndConfirm(phoneInput, options = {}) {
const fillCandidates = getLoginPhoneFillCandidates(phoneNumber, dialCode, currentInput);
for (const attemptedValue of fillCandidates) {
currentInput.focus?.();
fillInput(currentInput, attemptedValue);
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-phone-number' }, async () => {
fillInput(currentInput, attemptedValue);
});
lastVerification = await waitForPhoneInputValue(currentInput, inputValue, {
resolvePhoneInput,
phoneNumber,
@@ -2142,7 +2212,9 @@ async function fillLoginPhoneInputAndConfirm(phoneInput, options = {}) {
});
if (lastVerification.ok) {
const verifiedInput = lastVerification.input || currentInput;
const hiddenSync = syncPhoneHiddenFormValue(verifiedInput, { phoneNumber, dialCode, inputValue });
const hiddenSync = await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'hidden-sync', label: 'login-phone-hidden-sync' }, async () => (
syncPhoneHiddenFormValue(verifiedInput, { phoneNumber, dialCode, inputValue })
));
const expectedHiddenDigits = normalizePhoneDigits(phoneNumber) || `${normalizePhoneDigits(dialCode)}${normalizePhoneDigits(inputValue)}`;
if (hiddenSync && expectedHiddenDigits && normalizePhoneDigits(hiddenSync.value) !== expectedHiddenDigits) {
throw new Error(`\u6b65\u9aa4 ${visibleStep}\uff1a\u624b\u673a\u53f7\u9690\u85cf\u63d0\u4ea4\u5b57\u6bb5\u540c\u6b65\u5931\u8d25\uff0c\u671f\u671b ${expectedHiddenDigits}\uff0c\u5b9e\u9645 ${normalizePhoneDigits(hiddenSync.value) || '\u7a7a'}\u3002`);
@@ -2198,6 +2270,13 @@ function resolveSignupPhoneDialCode(phoneInput, options = {}) {
}
async function waitForSignupPhoneEntryState(options = {}) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const {
timeout = 20000,
step = 2,
@@ -2226,14 +2305,18 @@ async function waitForSignupPhoneEntryState(options = {}) {
lastSwitchToPhoneAt = Date.now();
log(`步骤 ${step}:检测到邮箱输入模式,正在切换到手机号注册入口...`);
await humanPause(350, 900);
simulateClick(switchToPhone);
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'switch-to-signup-phone' }, async () => {
simulateClick(switchToPhone);
});
} else {
const moreOptionsTrigger = findSignupMoreOptionsTrigger();
if (moreOptionsTrigger && Date.now() - lastMoreOptionsClickAt >= 1500) {
lastMoreOptionsClickAt = Date.now();
log(`步骤 ${step}:手机号入口可能隐藏在更多选项中,正在展开更多选项...`);
await humanPause(350, 900);
simulateClick(moreOptionsTrigger);
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'signup-phone-more-options' }, async () => {
simulateClick(moreOptionsTrigger);
});
} else if (!switchToPhone && !slowSnapshotLogged && Date.now() - start >= 5000) {
slowSnapshotLogged = true;
log(`步骤 ${step}:尚未找到手机号入口,页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn');
@@ -2248,7 +2331,9 @@ async function waitForSignupPhoneEntryState(options = {}) {
lastTriggerClickAt = Date.now();
log(`步骤 ${step}:正在点击官网注册入口...`);
await humanPause(350, 900);
simulateClick(snapshot.signupTrigger);
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'open-signup-entry' }, async () => {
simulateClick(snapshot.signupTrigger);
});
}
await sleep(250);
continue;
@@ -2268,6 +2353,13 @@ async function waitForSignupPhoneEntryState(options = {}) {
}
async function submitSignupPhoneNumberAndContinue(payload = {}) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const phoneNumber = String(payload.phoneNumber || '').trim();
const countryLabel = String(payload.countryLabel || '').trim();
if (!phoneNumber) {
@@ -2312,11 +2404,15 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) {
log(`步骤 2:正在填写手机号:${phoneNumber}`);
await humanPause(500, 1400);
fillInput(snapshot.phoneInput, inputValue);
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'fill', label: 'signup-phone-number' }, async () => {
fillInput(snapshot.phoneInput, inputValue);
});
const hiddenPhoneNumberInput = getSignupPhoneHiddenNumberInput(snapshot.phoneInput);
const e164PhoneNumber = toE164PhoneNumber(phoneNumber, dialCode);
if (hiddenPhoneNumberInput && e164PhoneNumber) {
fillInput(hiddenPhoneNumberInput, e164PhoneNumber);
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'hidden-sync', label: 'signup-phone-hidden-sync' }, async () => {
fillInput(hiddenPhoneNumberInput, e164PhoneNumber);
});
}
log(`步骤 2:手机号已填写:${phoneNumber}${dialCode ? `(区号 +${dialCode},本地号 ${inputValue}` : ''}`);
@@ -2326,16 +2422,18 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) {
}
log('步骤 2:手机号已准备提交,正在前往下一页...');
window.setTimeout(() => {
try {
throwIfStopped();
try {
await sleep(120);
throwIfStopped();
await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'submit', label: 'submit-signup-phone' }, async () => {
simulateClick(continueButton);
} catch (error) {
if (!isStopError(error)) {
console.error('[MultiPage:signup-page] deferred signup phone submit failed:', error?.message || error);
}
});
} catch (error) {
if (!isStopError(error)) {
console.error('[MultiPage:signup-page] signup phone submit failed:', error?.message || error);
}
}, 120);
throw error;
}
return {
submitted: true,
@@ -2363,6 +2461,13 @@ async function step2_clickRegister(payload = {}) {
// ============================================================
async function step3_fillEmailPassword(payload) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const { email, password } = payload;
if (!password) throw new Error('未提供密码,步骤 3 需要可用密码。');
const normalizedEmail = String(email || '').trim().toLowerCase();
@@ -2426,7 +2531,9 @@ async function step3_fillEmailPassword(payload) {
}
await humanPause(600, 1500);
fillInput(snapshot.passwordInput, password);
await performOperationWithDelay({ stepKey: 'fill-password', kind: 'fill', label: 'signup-password' }, async () => {
fillInput(snapshot.passwordInput, password);
});
log('步骤 3:密码已填写');
const submitBtn = snapshot.submitButton
@@ -2439,8 +2546,6 @@ async function step3_fillEmailPassword(payload) {
logSignupPasswordDiagnostics('步骤 3:当前密码页同时存在一次性验证码入口', 'info');
}
// Report complete BEFORE submit, because submit causes page navigation
// which kills the content script connection
const signupVerificationRequestedAt = submitBtn ? Date.now() : null;
const completionPayload = {
email,
@@ -2450,6 +2555,7 @@ async function step3_fillEmailPassword(payload) {
signupVerificationRequestedAt,
deferredSubmit: Boolean(submitBtn),
};
reportComplete(3, completionPayload);
if (submitBtn) {
@@ -2458,7 +2564,9 @@ async function step3_fillEmailPassword(payload) {
throwIfStopped();
await sleep(500);
await humanPause(500, 1300);
simulateClick(submitBtn);
await performOperationWithDelay({ stepKey: 'fill-password', kind: 'submit', label: 'submit-signup-password' }, async () => {
simulateClick(submitBtn);
});
log('步骤 3:表单已提交');
} catch (error) {
if (!isStopError(error)) {
@@ -3282,6 +3390,13 @@ function getCurrentAuthRetryPageState(flow = 'auth') {
}
async function recoverCurrentAuthRetryPage(payload = {}) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const {
flow = 'auth',
logLabel = '',
@@ -3300,6 +3415,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
maxClickAttempts,
pathPatterns: resolvedPathPatterns,
step,
stepKey: step === 8 || flow === 'login' ? 'oauth-login' : 'fetch-signup-code',
timeoutMs,
waitAfterClickMs,
});
@@ -3332,7 +3448,9 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
clickCount += 1;
log(`${logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`}(第 ${clickCount} 次)...`, 'warn');
await humanPause(300, 800);
simulateClick(retryState.retryButton);
await performOperationWithDelay({ stepKey: step === 8 || flow === 'login' ? 'oauth-login' : 'fetch-signup-code', kind: 'click', label: 'auth-retry-click' }, async () => {
simulateClick(retryState.retryButton);
});
const settleStart = Date.now();
while (Date.now() - settleStart < waitAfterClickMs) {
throwIfStopped();
@@ -4369,28 +4487,37 @@ function throwForStep6FatalState(snapshot, visibleStep = 7) {
async function triggerLoginSubmitAction(button, fallbackField) {
const form = button?.form || fallbackField?.form || button?.closest?.('form') || fallbackField?.closest?.('form') || null;
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
await humanPause(400, 1100);
if (button && isActionEnabled(button)) {
simulateClick(button);
return;
}
if (form && typeof form.requestSubmit === 'function') {
if (button && button.form === form) {
form.requestSubmit(button);
} else {
form.requestSubmit();
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'submit', label: 'login-submit' }, async () => {
if (button && isActionEnabled(button)) {
simulateClick(button);
return;
}
return;
}
if (button && typeof button.click === 'function') {
button.click();
return;
}
if (form && typeof form.requestSubmit === 'function') {
if (button && button.form === form) {
form.requestSubmit(button);
} else {
form.requestSubmit();
}
return;
}
throw new Error('未找到可用的登录提交按钮。URL: ' + location.href);
if (button && typeof button.click === 'function') {
button.click();
return;
}
throw new Error('未找到可用的登录提交按钮。URL: ' + location.href);
});
}
function isSignupPasswordErrorPage() {
@@ -4480,6 +4607,13 @@ async function waitForSignupVerificationTransition(timeout = 5000) {
}
async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const { password } = payload;
const prepareSource = String(payload?.prepareSource || '').trim() || 'step4_execute';
const prepareLogLabel = String(payload?.prepareLogLabel || '').trim()
@@ -4554,13 +4688,17 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
if ((snapshot.passwordInput.value || '') !== password) {
log(`${prepareLogLabel}:页面仍停留在密码页,正在重新填写密码...`, 'warn');
await humanPause(450, 1100);
fillInput(snapshot.passwordInput, password);
await performOperationWithDelay({ stepKey: 'fill-password', kind: 'fill', label: 'retry-signup-password' }, async () => {
fillInput(snapshot.passwordInput, password);
});
}
if (snapshot.submitButton && isActionEnabled(snapshot.submitButton)) {
log(`${prepareLogLabel}:页面仍停留在密码页,正在重新点击“继续”(第 ${recoveryRound}/${maxRecoveryRounds} 次)...`, 'warn');
await humanPause(350, 900);
simulateClick(snapshot.submitButton);
await performOperationWithDelay({ stepKey: 'fill-password', kind: 'submit', label: 'retry-submit-signup-password' }, async () => {
simulateClick(snapshot.submitButton);
});
await sleep(1200);
continue;
}
@@ -4763,6 +4901,13 @@ async function waitForSplitVerificationInputsFilled(inputs, code, timeout = 2500
async function fillVerificationCode(step, payload) {
const { code, signupProfile } = payload;
if (!code) throw new Error('未提供验证码。');
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
if (step === 4) {
const postVerificationState = getStep4PostVerificationState();
@@ -4854,17 +4999,18 @@ async function fillVerificationCode(step, payload) {
if (splitInputs?.length >= 6) {
log(`步骤 ${step}:发现分开的单字符验证码输入框,正在逐个填写...`);
for (let i = 0; i < 6 && i < splitInputs.length; i++) {
const targetInput = splitInputs[i];
try {
targetInput.focus?.();
} catch {}
fillInput(splitInputs[i], code[i]);
try {
targetInput.dispatchEvent(new KeyboardEvent('keyup', { key: code[i], bubbles: true }));
} catch {}
await sleep(100);
}
await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'grouped-code', label: 'split-code' }, async () => {
for (let i = 0; i < 6 && i < splitInputs.length; i++) {
const targetInput = splitInputs[i];
try {
targetInput.focus?.();
} catch {}
fillInput(splitInputs[i], code[i]);
try {
targetInput.dispatchEvent(new KeyboardEvent('keyup', { key: code[i], bubbles: true }));
} catch {}
}
});
const filled = await waitForSplitVerificationInputsFilled(splitInputs, code, 2500);
if (!filled) {
const current = Array.from(splitInputs)
@@ -4880,7 +5026,9 @@ async function fillVerificationCode(step, payload) {
const splitSubmitBtn = await waitForVerificationSubmitButton(splitInputs[0], 2000).catch(() => null);
if (splitSubmitBtn) {
await humanPause(450, 1200);
simulateClick(splitSubmitBtn);
await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'submit', label: 'submit-code' }, async () => {
simulateClick(splitSubmitBtn);
});
log(`步骤 ${step}:分格验证码已提交`);
} else {
log(`步骤 ${step}:分格验证码页面未找到可点击提交按钮,继续等待页面自动推进。`, 'info');
@@ -4906,7 +5054,9 @@ async function fillVerificationCode(step, payload) {
throw new Error('未找到验证码输入框。URL: ' + location.href);
}
fillInput(codeInput, code);
await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'fill', label: 'verification-code' }, async () => {
fillInput(codeInput, code);
});
log(`步骤 ${step}:验证码已填写`);
// Submit
@@ -4915,7 +5065,9 @@ async function fillVerificationCode(step, payload) {
if (submitBtn) {
await humanPause(450, 1200);
simulateClick(submitBtn);
await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'submit', label: 'submit-code' }, async () => {
simulateClick(submitBtn);
});
log(`步骤 ${step}:验证码已提交`);
} else {
log(`步骤 ${step}:未找到可提交的验证码按钮,先等待页面自动推进或反馈结果。`, 'warn');
@@ -5199,6 +5351,13 @@ async function waitForPhoneLoginEntrySwitchTransition(timeout = 10000) {
}
async function step6OpenLoginEntry(payload, snapshot) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
const preferPhoneLogin = String(payload?.loginIdentifierType || '').trim() === 'phone' || (!payload?.email && payload?.phoneNumber);
@@ -5215,7 +5374,9 @@ async function step6OpenLoginEntry(payload, snapshot) {
log(`检测到登录入口页,正在点击 "${getActionText(trigger).slice(0, 80)}"...`, 'info', { step: visibleStep, stepKey: 'oauth-login' });
await humanPause(350, 900);
simulateClick(trigger);
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'open-login-entry' }, async () => {
simulateClick(trigger);
});
const nextSnapshot = await waitForLoginEntryOpenTransition();
if (nextSnapshot.state === 'email_page') {
@@ -5267,6 +5428,13 @@ async function step6OpenLoginEntry(payload, snapshot) {
}
async function step6SwitchToOneTimeCodeLogin(payload, snapshot) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
const switchTrigger = snapshot?.switchTrigger || findOneTimeCodeLoginTrigger();
if (!switchTrigger || !isActionEnabled(switchTrigger)) {
@@ -5278,7 +5446,9 @@ async function step6SwitchToOneTimeCodeLogin(payload, snapshot) {
log('已检测到一次性验证码登录入口,准备切换...', 'info', { step: visibleStep, stepKey: 'oauth-login' });
const loginVerificationRequestedAt = Date.now();
await humanPause(350, 900);
simulateClick(switchTrigger);
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'switch-one-time-code-login' }, async () => {
simulateClick(switchTrigger);
});
log('已点击一次性验证码登录', 'info', { step: visibleStep, stepKey: 'oauth-login' });
await sleep(1200);
const result = await waitForStep6SwitchTransition(loginVerificationRequestedAt, 10000, { visibleStep });
@@ -5305,6 +5475,13 @@ async function step6SwitchToOneTimeCodeLogin(payload, snapshot) {
}
async function step6LoginFromPhonePage(payload, snapshot) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
const phoneInput = currentSnapshot.phoneInput || getLoginPhoneInput();
@@ -5358,7 +5535,9 @@ async function step6LoginFromPhonePage(payload, snapshot) {
await sleep(500);
const verifiedPhoneInput = fillResult.input || phoneInput;
const hiddenSync = syncPhoneHiddenFormValue(verifiedPhoneInput, { phoneNumber, dialCode, inputValue });
const hiddenSync = await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'hidden-sync', label: 'login-phone-pre-submit-hidden-sync' }, async () => (
syncPhoneHiddenFormValue(verifiedPhoneInput, { phoneNumber, dialCode, inputValue })
));
const submitButton = getLoginSubmitButton({ allowDisabled: true }) || currentSnapshot.submitButton;
const preSubmitRenderedValue = getPhoneInputRenderedValue(verifiedPhoneInput);
const preSubmitHiddenInput = hiddenSync?.input || getPhoneHiddenValueInput(verifiedPhoneInput);
@@ -5416,6 +5595,13 @@ async function step6LoginFromPhonePage(payload, snapshot) {
}
async function switchFromEmailPageToPhoneLogin(payload, snapshot) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
let currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
let phoneEntryTrigger = currentSnapshot.phoneEntryTrigger || findLoginPhoneEntryTrigger();
@@ -5427,7 +5613,9 @@ async function switchFromEmailPageToPhoneLogin(payload, snapshot) {
stepKey: 'oauth-login',
});
await humanPause(350, 900);
simulateClick(moreOptionsTrigger);
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'login-more-options' }, async () => {
simulateClick(moreOptionsTrigger);
});
await sleep(800);
currentSnapshot = normalizeStep6Snapshot(inspectLoginAuthState());
phoneEntryTrigger = currentSnapshot.phoneEntryTrigger || findLoginPhoneEntryTrigger();
@@ -5442,7 +5630,9 @@ async function switchFromEmailPageToPhoneLogin(payload, snapshot) {
log(`步骤 ${visibleStep}:当前在邮箱入口,正在切换到手机号登录...`, 'info', { step: visibleStep, stepKey: 'oauth-login' });
await humanPause(350, 900);
simulateClick(phoneEntryTrigger);
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'switch-phone-login' }, async () => {
simulateClick(phoneEntryTrigger);
});
const nextSnapshot = normalizeStep6Snapshot(await waitForPhoneLoginEntrySwitchTransition(20000));
if (nextSnapshot.state === 'phone_entry_page') {
return step6LoginFromPhonePage(payload, nextSnapshot);
@@ -5491,6 +5681,13 @@ async function switchFromEmailPageToPhoneLogin(payload, snapshot) {
}
async function step6LoginFromPasswordPage(payload, snapshot) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
const hasPassword = Boolean(String(payload?.password || '').trim());
@@ -5509,7 +5706,9 @@ async function step6LoginFromPasswordPage(payload, snapshot) {
log('已进入密码页,准备填写密码...', 'info', { step: visibleStep, stepKey: 'oauth-login' });
await humanPause(550, 1450);
fillInput(currentSnapshot.passwordInput, payload.password);
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-password' }, async () => {
fillInput(currentSnapshot.passwordInput, payload.password);
});
log('已填写密码', 'info', { step: visibleStep, stepKey: 'oauth-login' });
await sleep(500);
@@ -5560,6 +5759,13 @@ async function step6LoginFromPasswordPage(payload, snapshot) {
}
async function step6LoginFromEmailPage(payload, snapshot) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7;
const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState());
if (String(payload?.loginIdentifierType || '').trim() === 'phone' && payload?.phoneNumber) {
@@ -5572,7 +5778,9 @@ async function step6LoginFromEmailPage(payload, snapshot) {
if ((emailInput.value || '').trim() !== payload.email) {
await humanPause(500, 1400);
fillInput(emailInput, payload.email);
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-email' }, async () => {
fillInput(emailInput, payload.email);
});
log('已填写邮箱', 'info', { step: visibleStep, stepKey: 'oauth-login' });
} else {
log('邮箱已在输入框中,准备提交...', 'info', { step: visibleStep, stepKey: 'oauth-login' });
@@ -5783,6 +5991,13 @@ async function waitForAddEmailSubmitOutcome(timeout = 45000) {
}
async function submitAddEmailAndContinue(payload = {}) {
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const email = String(payload.email || '').trim().toLowerCase();
if (!email) {
throw new Error('未提供邮箱地址,无法添加邮箱。');
@@ -5795,7 +6010,9 @@ async function submitAddEmailAndContinue(payload = {}) {
}
await humanPause(500, 1400);
fillInput(emailInput, email);
await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'add-email' }, async () => {
fillInput(emailInput, email);
});
log(`步骤 8:已填写邮箱:${email}`);
await sleep(500);
@@ -6087,6 +6304,13 @@ async function waitForCombinedSignupVerificationProfilePage(timeout = 2500) {
async function step5_fillNameBirthday(payload) {
const { firstName, lastName, age, year, month, day, prefillOnly = false } = payload;
if (!firstName || !lastName) throw new Error('未提供姓名数据。');
const performOperationWithDelay = typeof getOperationDelayRunner === 'function'
? getOperationDelayRunner()
: async (metadata, operation) => {
const rootScope = typeof window !== 'undefined' ? window : globalThis;
const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay;
return typeof gate === 'function' ? gate(metadata, operation) : operation();
};
const resolvedAge = age ?? (year ? new Date().getFullYear() - Number(year) : null);
const hasBirthdayData = [year, month, day].every(value => value != null && !Number.isNaN(Number(value)));
@@ -6113,7 +6337,9 @@ async function step5_fillNameBirthday(payload) {
throw new Error('未找到姓名输入框。URL: ' + location.href);
}
await humanPause(500, 1300);
fillInput(nameInput, fullName);
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-name' }, async () => {
fillInput(nameInput, fullName);
});
log(`步骤 5:姓名已填写:${fullName}`);
let birthdayMode = false;
@@ -6183,11 +6409,17 @@ async function step5_fillNameBirthday(payload) {
log('步骤 5:检测到 React Aria 下拉生日字段,正在填写生日...');
await humanPause(450, 1100);
await setReactAriaBirthdaySelect(yearReactSelect, year);
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'select', label: 'select-birthday-year' }, async () => {
await setReactAriaBirthdaySelect(yearReactSelect, year);
});
await humanPause(250, 650);
await setReactAriaBirthdaySelect(monthReactSelect, month);
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'select', label: 'select-birthday-month' }, async () => {
await setReactAriaBirthdaySelect(monthReactSelect, month);
});
await humanPause(250, 650);
await setReactAriaBirthdaySelect(dayReactSelect, day);
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'select', label: 'select-birthday-day' }, async () => {
await setReactAriaBirthdaySelect(dayReactSelect, day);
});
if (hiddenBirthday) {
const start = Date.now();
@@ -6228,20 +6460,28 @@ async function step5_fillNameBirthday(payload) {
}
await humanPause(450, 1100);
await setSpinButton(yearSpinner, year);
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday-year' }, async () => {
await setSpinButton(yearSpinner, year);
});
await humanPause(250, 650);
await setSpinButton(monthSpinner, String(month).padStart(2, '0'));
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday-month' }, async () => {
await setSpinButton(monthSpinner, String(month).padStart(2, '0'));
});
await humanPause(250, 650);
await setSpinButton(daySpinner, String(day).padStart(2, '0'));
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday-day' }, async () => {
await setSpinButton(daySpinner, String(day).padStart(2, '0'));
});
log(`步骤 5:生日已填写:${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`);
}
const hiddenBirthday = document.querySelector('input[name="birthday"]');
if (hiddenBirthday) {
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
hiddenBirthday.value = dateStr;
hiddenBirthday.dispatchEvent(new Event('input', { bubbles: true }));
hiddenBirthday.dispatchEvent(new Event('change', { bubbles: true }));
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'hidden-sync', label: 'profile-dom-sync' }, async () => {
hiddenBirthday.value = dateStr;
hiddenBirthday.dispatchEvent(new Event('input', { bubbles: true }));
hiddenBirthday.dispatchEvent(new Event('change', { bubbles: true }));
});
log(`步骤 5:已设置隐藏生日输入框:${dateStr}`);
}
} else if (ageInput) {
@@ -6249,7 +6489,9 @@ async function step5_fillNameBirthday(payload) {
throw new Error('检测到年龄字段,但未提供年龄数据。');
}
await humanPause(500, 1300);
fillInput(ageInput, String(resolvedAge));
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday' }, async () => {
fillInput(ageInput, String(resolvedAge));
});
log(`步骤 5:年龄已填写:${resolvedAge}`);
} else {
throw new Error('未找到生日或年龄输入项。URL: ' + location.href);
@@ -6261,15 +6503,19 @@ async function step5_fillNameBirthday(payload) {
if (!isStep5CheckboxChecked(allConsentCheckbox)) {
const checkboxLabel = allConsentCheckbox.closest('label');
await humanPause(500, 1500);
if (checkboxLabel && isVisibleElement(checkboxLabel)) {
simulateClick(checkboxLabel);
} else {
simulateClick(allConsentCheckbox);
}
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'click', label: 'accept-profile-consent' }, async () => {
if (checkboxLabel && isVisibleElement(checkboxLabel)) {
simulateClick(checkboxLabel);
} else {
simulateClick(allConsentCheckbox);
}
});
await sleep(250);
if (!isStep5CheckboxChecked(allConsentCheckbox)) {
allConsentCheckbox.click();
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'click', label: 'accept-profile-consent-fallback' }, async () => {
allConsentCheckbox.click();
});
await sleep(250);
}
@@ -6303,7 +6549,9 @@ async function step5_fillNameBirthday(payload) {
}
await humanPause(500, 1300);
simulateClick(completeBtn);
await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'submit', label: 'submit-profile' }, async () => {
simulateClick(completeBtn);
});
const completionPayload = getStep5DirectCompletionPayload({ isAgeMode });
reportComplete(5, completionPayload);