From fb69e587c1ffea885b16cdc5cd5eddd4fb57aecf Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Wed, 8 Apr 2026 14:32:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E6=AD=A5=E9=AA=A4=20?= =?UTF-8?q?5=20=E7=9A=84=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=94=9F=E6=97=A5=E9=80=89=E6=8B=A9=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96=E6=8F=90=E4=BA=A4=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/signup-page.js | 166 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 158 insertions(+), 8 deletions(-) diff --git a/content/signup-page.js b/content/signup-page.js index ed383e2..5ed6ba9 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -356,6 +356,7 @@ const INVALID_VERIFICATION_CODE_PATTERN = /代码不正确|验证码不正确| const VERIFICATION_PAGE_PATTERN = /检查您的收件箱|输入我们刚刚向|重新发送电子邮件|重新发送验证码|验证码|代码不正确|email\s+verification/i; const OAUTH_CONSENT_PAGE_PATTERN = /使用\s*ChatGPT\s*登录到\s*Codex|login\s+to\s+codex|log\s+in\s+to\s+codex|authorize|授权/i; const ADD_PHONE_PAGE_PATTERN = /add[\s-]*phone|添加手机号|手机号码|手机号|phone\s+number|telephone/i; +const STEP5_SUBMIT_ERROR_PATTERN = /无法根据该信息创建帐户|请重试|unable\s+to\s+create\s+(?:your\s+)?account|couldn'?t\s+create\s+(?:your\s+)?account|something\s+went\s+wrong|invalid\s+(?:birthday|birth|date)|生日|出生日期/i; const SIGNUP_PASSWORD_ERROR_TITLE_PATTERN = /糟糕,出错了|something\s+went\s+wrong|oops/i; const SIGNUP_PASSWORD_ERROR_DETAIL_PATTERN = /operation\s+timed\s+out|timed\s+out|请求超时|操作超时/i; @@ -448,6 +449,118 @@ function isStep8Ready() { return OAUTH_CONSENT_PAGE_PATTERN.test(getPageTextSnapshot()); } +function normalizeInlineText(text) { + return (text || '').replace(/\s+/g, ' ').trim(); +} + +function findBirthdayReactAriaSelect(labelText) { + const normalizedLabel = normalizeInlineText(labelText); + const roots = document.querySelectorAll('.react-aria-Select'); + + for (const root of roots) { + const labelEl = Array.from(root.querySelectorAll('span')).find((el) => normalizeInlineText(el.textContent) === normalizedLabel); + if (!labelEl) continue; + + const item = root.closest('[class*="selectItem"], ._selectItem_ppsls_113') || root.parentElement; + const nativeSelect = item?.querySelector('[data-testid="hidden-select-container"] select') || null; + const button = root.querySelector('button[aria-haspopup="listbox"]') || null; + const valueEl = root.querySelector('.react-aria-SelectValue') || null; + + return { root, item, labelEl, nativeSelect, button, valueEl }; + } + + return null; +} + +async function setReactAriaBirthdaySelect(control, value) { + if (!control?.nativeSelect) { + throw new Error('未找到可写入的生日下拉框。'); + } + + const desiredValue = String(value); + const option = Array.from(control.nativeSelect.options).find((item) => item.value === desiredValue); + if (!option) { + throw new Error(`生日下拉框中不存在值 ${desiredValue}。`); + } + + control.nativeSelect.value = desiredValue; + option.selected = true; + control.nativeSelect.dispatchEvent(new Event('input', { bubbles: true })); + control.nativeSelect.dispatchEvent(new Event('change', { bubbles: true })); + await sleep(120); +} + +function getStep5ErrorText() { + const messages = []; + const selectors = [ + '.react-aria-FieldError', + '[slot="errorMessage"]', + '[id$="-error"]', + '[id$="-errors"]', + '[role="alert"]', + '[aria-live="assertive"]', + '[aria-live="polite"]', + '[class*="error"]', + ]; + + for (const selector of selectors) { + document.querySelectorAll(selector).forEach((el) => { + if (!isVisibleElement(el)) return; + const text = normalizeInlineText(el.textContent); + if (text) { + messages.push(text); + } + }); + } + + const invalidField = Array.from(document.querySelectorAll('[aria-invalid="true"], [data-invalid="true"]')) + .find((el) => isVisibleElement(el)); + if (invalidField) { + const wrapper = invalidField.closest('form, fieldset, [data-rac], div'); + if (wrapper) { + const text = normalizeInlineText(wrapper.textContent); + if (text) { + messages.push(text); + } + } + } + + return messages.find((text) => STEP5_SUBMIT_ERROR_PATTERN.test(text)) || ''; +} + +async function waitForStep5SubmitOutcome(timeout = 15000) { + const start = Date.now(); + + while (Date.now() - start < timeout) { + throwIfStopped(); + + const errorText = getStep5ErrorText(); + if (errorText) { + return { invalidProfile: true, errorText }; + } + + if (isAddPhonePageReady()) { + return { success: true, addPhonePage: true }; + } + + if (isStep8Ready()) { + return { success: true }; + } + + await sleep(150); + } + + const errorText = getStep5ErrorText(); + if (errorText) { + return { invalidProfile: true, errorText }; + } + + return { + invalidProfile: true, + errorText: '提交后未进入下一阶段,请检查生日是否真正被页面接受。', + }; +} + function isSignupPasswordPage() { return /\/create-account\/password(?:[/?#]|$)/i.test(location.pathname || ''); } @@ -864,6 +977,36 @@ async function step5_fillNameBirthday(payload) { const yearSpinner = document.querySelector('[role="spinbutton"][data-type="year"]'); const monthSpinner = document.querySelector('[role="spinbutton"][data-type="month"]'); const daySpinner = document.querySelector('[role="spinbutton"][data-type="day"]'); + const yearReactSelect = findBirthdayReactAriaSelect('年'); + const monthReactSelect = findBirthdayReactAriaSelect('月'); + const dayReactSelect = findBirthdayReactAriaSelect('天'); + + if (yearReactSelect?.nativeSelect && monthReactSelect?.nativeSelect && dayReactSelect?.nativeSelect) { + const desiredDate = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + const hiddenBirthday = document.querySelector('input[name="birthday"]'); + + log('步骤 5:检测到 React Aria 下拉生日字段,正在填写生日...'); + await humanPause(450, 1100); + await setReactAriaBirthdaySelect(yearReactSelect, year); + await humanPause(250, 650); + await setReactAriaBirthdaySelect(monthReactSelect, month); + await humanPause(250, 650); + await setReactAriaBirthdaySelect(dayReactSelect, day); + + if (hiddenBirthday) { + const start = Date.now(); + while (Date.now() - start < 2000) { + if ((hiddenBirthday.value || '') === desiredDate) break; + await sleep(100); + } + + if ((hiddenBirthday.value || '') !== desiredDate) { + throw new Error(`生日值未成功写入页面。期望 ${desiredDate},实际 ${(hiddenBirthday.value || '空')}。`); + } + } + + log(`步骤 5:React Aria 生日已填写:${desiredDate}`); + } if (yearSpinner && monthSpinner && daySpinner) { log('步骤 5:检测到生日字段,正在填写生日...'); @@ -901,6 +1044,7 @@ async function step5_fillNameBirthday(payload) { 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 })); log(`步骤 5:已设置隐藏生日输入框:${dateStr}`); } @@ -919,13 +1063,19 @@ async function step5_fillNameBirthday(payload) { await sleep(500); const completeBtn = document.querySelector('button[type="submit"]') || await waitForElementByText('button', /完成|create|continue|finish|done|agree/i, 5000).catch(() => null); - - // Report complete BEFORE submit (page navigates to add-phone after this) - reportComplete(5); - - if (completeBtn) { - await humanPause(500, 1300); - simulateClick(completeBtn); - log('步骤 5:已点击“完成帐户创建”'); + if (!completeBtn) { + throw new Error('未找到“完成帐户创建”按钮。URL: ' + location.href); } + + await humanPause(500, 1300); + simulateClick(completeBtn); + log('步骤 5:已点击“完成帐户创建”,正在等待页面结果...'); + + const outcome = await waitForStep5SubmitOutcome(); + if (outcome.invalidProfile) { + throw new Error(`步骤 5:${outcome.errorText}`); + } + + log(`步骤 5:资料已通过。`, 'ok'); + reportComplete(5, { addPhonePage: Boolean(outcome.addPhonePage) }); }