From 2d1bad3642aa76fe2605927b3d29ce348e8baadc Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Tue, 28 Apr 2026 15:31:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(paypal-flow):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=82=AE=E7=AE=B1=E8=BE=93=E5=85=A5=E6=A1=86=E9=87=8D=E5=A1=AB?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/paypal-flow.js | 24 ++++-- docs/使用教程.md | 136 +++++++++++++++++++++++++++++- sidepanel/sidepanel.html | 2 +- sidepanel/sidepanel.js | 7 +- tests/paypal-flow-content.test.js | 105 +++++++++++++++++++++++ 项目完整链路说明.md | 2 +- 项目文件结构说明.md | 4 +- 7 files changed, 262 insertions(+), 18 deletions(-) diff --git a/content/paypal-flow.js b/content/paypal-flow.js index 9f34e51..7a505e6 100644 --- a/content/paypal-flow.js +++ b/content/paypal-flow.js @@ -227,6 +227,18 @@ function getPayPalLoginPhase(emailInput, passwordInput) { return ''; } +function refillPayPalEmailInput(emailInput, email) { + if (!emailInput) return; + if (typeof emailInput.focus === 'function') { + emailInput.focus(); + } + fillInput(emailInput, ''); + fillInput(emailInput, email); + if (typeof emailInput.blur === 'function') { + emailInput.blur(); + } +} + async function submitPayPalLogin(payload = {}) { await waitForDocumentComplete(); @@ -241,9 +253,7 @@ async function submitPayPalLogin(payload = {}) { const emailNextButton = findEmailNextButton(); if (emailInput && emailNextButton && isEnabledControl(emailNextButton) && (!passwordInput || !findPasswordLoginButton())) { - if (normalizeText(emailInput.value || '') !== email) { - fillInput(emailInput, email); - } + refillPayPalEmailInput(emailInput, email); simulateClick(emailNextButton); return { submitted: false, @@ -253,9 +263,7 @@ async function submitPayPalLogin(payload = {}) { } if (!passwordInput && emailInput && email) { - if (normalizeText(emailInput.value || '') !== email) { - fillInput(emailInput, email); - } + refillPayPalEmailInput(emailInput, email); const nextButton = await waitUntil(() => { const button = findEmailNextButton() || findLoginNextButton(); return button && isEnabledControl(button) ? button : null; @@ -272,8 +280,8 @@ async function submitPayPalLogin(payload = {}) { }; } else if (!passwordInput && emailInput && !email) { throw new Error('PayPal 账号为空,请先在侧边栏配置。'); - } else if (emailInput && email && normalizeText(emailInput.value || '') !== email) { - fillInput(emailInput, email); + } else if (emailInput && email) { + refillPayPalEmailInput(emailInput, email); } passwordInput = passwordInput || await waitUntil(() => findPasswordInput(), { diff --git a/docs/使用教程.md b/docs/使用教程.md index 0512821..f9721a6 100644 --- a/docs/使用教程.md +++ b/docs/使用教程.md @@ -1,6 +1,6 @@ # Codex 注册扩展相关项目、更新、邮箱、PayPal 与 Clash Verge 配置教程 -本教程用于说明相关项目地址、扩展更新方式、`Cloudflare Temp Email`、`iCloud 隐私邮箱` 与 `QQ 邮箱` 的使用方法、`PayPal` 注册绑卡流程、节点检测与纯净度检查,以及 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 的 `🔁 非港轮询` 配置方法。 +本教程用于说明相关项目地址、扩展更新方式、`Cloudflare Temp Email`、`iCloud 隐私邮箱` 与 `QQ 邮箱` 的使用方法、`PayPal` 注册绑卡流程、扩展内置动态 IP 代理、节点检测与纯净度检查,以及 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 的 `🔁 非港轮询` 配置方法。 ## 适用场景 @@ -11,6 +11,7 @@ - 需要临时切换 `QQ 邮箱` 地址继续使用 - 需要使用 `网易邮箱`、`网易邮箱大师` 注册多个邮箱或替身邮箱 - 需要注册并使用 `PayPal` 个人账户 +- 需要在扩展内使用 `711Proxy` 动态 IP,并在自动流程中按轮次切换出口 - 需要检查当前节点的出口 IP、纯净度、泄露情况和访问速度 - 需要在 `Clash Verge` 中启用 `🔁 非港轮询` @@ -26,6 +27,7 @@ - 手机端已安装 `网易邮箱` 或 `网易邮箱大师` - 一个可正常接收短信的手机号 - 一张可在线支付的借记卡或信用卡 +- 如需使用扩展内置动态 IP,需准备 `711Proxy` 的 Host、Port、代理账号和代理密码 - 如需部署 `cpa`,部署环境必须可以访问 `OpenAI` - 已安装 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev),并已导入可用订阅 @@ -426,7 +428,113 @@ - **PayPal 登录后页面无法继续跳转** 稍等片刻让页面加载完毕。如果长时间未响应,检查浏览器是否有弹窗被隐藏,或尝试刷新页面。 -### 第九部分:节点检测与纯净度检查网站 +### 第九部分:扩展内置动态 IP 代理使用教程 + +本部分说明扩展侧边栏里的 `IP代理` 功能。 +它和 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 这类系统代理不一样:扩展会通过浏览器代理 API 给当前浏览器设置 PAC 代理,并自动回填代理鉴权。当前首版只开放 `711Proxy` 的账号密码模式,`API 拉取` 和多账号列表入口暂未开放。 + +#### 一、什么时候需要打开 `IP代理` + +如果你已经在 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 或其他客户端里稳定使用节点,可以继续使用外部代理。 +如果你希望在扩展里直接控制动态 IP、检查当前出口、按成功轮次自动切换,或者不想依赖系统全局代理,就使用这里的 `IP代理`。 + +开启 `IP代理` 后,扩展会接管浏览器代理。 +关闭 `IP代理` 后,浏览器会恢复默认代理或外部全局代理。 + +#### 二、注册或登录 `711Proxy` + +在侧边栏打开 `IP代理` 后,`代理服务` 当前固定为 `711Proxy(首版)`。 +点击旁边的 `注册` 按钮,按页面提示完成注册或登录。 + +在网站中完成注册、登录和套餐准备后,进入代理信息页面,找到可用于账号密码代理的 `Host`、`Port`、`Username` 和 `Password`。 + +#### 三、填写固定账号代理 + +1. 在扩展侧边栏打开 `IP代理` 开关。 +2. `代理模式` 保持 `账号密码`。 +3. 填写 `代理 Host`,例如 `global.rotgb.711proxy.com`。 +4. 填写 `代理 Port`,例如 `10000`。 +5. `代理协议` 一般保持 `HTTP`,除非你的代理信息明确要求 `HTTPS`、`SOCKS5` 或 `SOCKS4`。 +6. 填写 `代理账号` 和 `代理密码`。 +7. 按需填写 `地区参数`,例如 `US`、`DE`、`HK`。 +8. 按需填写 `会话(session)` 和 `时长(life)`。 + +`地区参数` 只接受两个字母的国家或地区代码。 +`会话(session)` 会被追加或写回到用户名里的 `session-xxxx`。 +`时长(life)` 会被追加或写回到用户名里的 `sessTime-xx`,711Proxy 的有效范围按 `5-180` 分钟使用更稳。 + +如果你的 `711Proxy` 用户名里已经带了 `region-US`、`session-xxxx` 或 `sessTime-30`,扩展会优先识别用户名里的值。 +如果你在表单里修改地区、session 或时长,扩展会把它们同步回最终生效的代理用户名。 + +#### 四、同步、下一条、Change 和检测出口 + +填写或修改代理信息后,不要只保存后就直接开跑。建议按下面顺序操作: + +1. 点击 `同步` + 扩展会保存当前配置,生成代理规则,应用到浏览器,并自动做一次出口检测。 + 这是修改 Host、Port、账号、密码、地区、session 或时长后最常用的按钮。 + +2. 查看 `代理状态` + 状态卡会显示当前代理、当前出口 IP、出口地区和诊断详情。 + 如果显示 `当前代理` 和 `当前出口`,说明扩展已经能看到代理出口。 + +3. 点击 `检测出口` + 它只重新检测当前出口,不主动更换节点。 + 如果你怀疑检测结果没刷新,或者刚刚切换过外部网络,可以点它复测。 + +4. 点击 `检查IP` + 它会打开 `https://ipinfo.io/what-is-my-ip`,用于人工确认网页看到的公网 IP。 + +5. 点击 `下一条` + 当前首版主要是固定账号模式。如果只有一条可用代理,`下一条` 会重绑当前节点并复测,不保证一定换出口。 + 如果后续开放多账号列表或 API 池,`下一条` 才会真正切到池里的下一条。 + +6. 点击 `Change` + `Change` 只支持 `711Proxy + 账号密码模式 + 用户名包含 session` 的场景。 + 它会保持当前 session,强制重绑代理链路并刷新出口,适合想换出口但不想改整条账号配置时使用。 + +#### 五、任务切换阈值 + +`任务切换阈值` 表示自动流程成功多少轮后尝试自动切换一次代理。 +默认是 `20` 轮,可填写 `1-500`。 + +需要注意: + +- 当前只有一条固定账号时,即使命中阈值,也会跳过自动切换。 +- 如果后续有多条可切换代理,命中阈值后会自动执行 `下一条`。 +- 这个阈值只影响自动流程成功后的切换节奏,不会替代你手动点击 `同步` 或 `检测出口`。 + +#### 六、状态卡怎么看 + +常见状态含义如下: + +- `未开启`:扩展没有接管浏览器代理,继续使用默认网络或外部全局代理。 +- `当前代理:host:port;当前出口:x.x.x.x`:代理已经应用,并检测到出口 IP。 +- `已启用,但账号模式没有可用代理`:Host/Port 为空或格式不完整,需要先补齐代理信息。 +- `连通性失败`:代理应用了,但没有检测到可用出口,优先检查 Host、Port、协议、账号和密码。 +- `地区校验未通过`:检测到的出口地区和你填写的地区参数不一致。711Proxy 场景下扩展会保留接管并给出警告,你需要决定是否继续使用这个出口。 +- `当前链路未收到 407 代理鉴权挑战`:代理服务没有按浏览器扩展代理 API 预期返回标准鉴权挑战,可能导致账号密码无法自动回填。可以尝试 `Change`、关闭再开启 `IP代理`、换 session,或者更换代理节点。 + +如果代理不可用,扩展会启用目标站点阻断保护,避免在代理失效时继续直连访问目标站点。修好代理后重新点击 `同步` 或 `检测出口` 即可恢复。 + +#### 七、推荐使用顺序 + +首次配置建议按这个顺序: + +1. 点击 `注册` 打开 `711Proxy`,准备代理账号信息。 +2. 打开扩展侧边栏的 `IP代理`。 +3. 填写 Host、Port、协议、账号、密码。 +4. 填写地区参数,例如 `US`。 +5. 填写 session 和时长,例如 session 为随机前缀、时长为 `30`。 +6. 点击 `同步`。 +7. 确认 `代理状态` 里有出口 IP。 +8. 点击 `检查IP`,人工确认网页检测结果。 +9. 再继续执行注册、登录或自动流程。 + +如果后续要换出口,优先尝试 `Change`。 +如果 `Change` 不可用,再调整 session 或代理账号后点击 `同步`。 + +### 第十部分:节点检测与纯净度检查网站 本部分用于检查当前代理节点的出口 IP、归属地、风险标签、泄露情况和网站访问速度。 建议每次切换节点后都重新打开这些网站检查一次。 @@ -460,7 +568,7 @@ 接着打开 [IPPure](https://ippure.com/) 检查纯净度、黑名单、代理识别和泄露情况。 最后用 [TCPTest 网站测速](https://www.tcptest.cn/?cckey=e62f31db) 测试目标网站的访问速度和连通性。 -### 第十部分:配置 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 的 `🔁 非港轮询` +### 第十一部分:配置 [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev) 的 `🔁 非港轮询` #### 第一步:添加扩展脚本 @@ -569,7 +677,7 @@ function main(config, profileName) { 5. 确认左侧 `设置` 中的 `系统代理`(`System Proxy`)已经开启。 ![2026 04 25 003703](https://apikey.qzz.io/content-assets/library/2026/04/20260424-163745--2026-04-25-003703--21e892504b82.png) -### 第十一部分:订阅节点与自建推荐 +### 第十二部分:订阅节点与自建推荐 如果你还没有订阅节点或想要寻找稳定、便宜的科学上网方式,可以参考以下三种方案获取。 @@ -661,6 +769,23 @@ function main(config, profileName) { 请先确认脚本已经完整粘贴并保存,然后回到 `代理` 页面重新查看。若仍未出现,请继续确认当前订阅可用、`代理模式` 为 `规则模式`,并且 `系统代理` 已开启。 +### 扩展 `IP代理` 开启后,为什么状态还是没有出口 IP? + +先确认 Host、Port、协议、代理账号和代理密码都已填写正确。 +修改这些字段后,需要点击 `同步` 才会重新应用代理并检测出口。 +如果诊断提示没有收到 `407` 代理鉴权挑战,可以尝试点击 `Change`、关闭再开启 `IP代理`、更换 session,或者换一条代理节点。 + +### `Change` 按钮为什么点不了? + +`Change` 只在 `711Proxy` 的账号密码模式下可用,并且当前生效用户名必须包含 `session-xxxx` 这类 session 参数。 +如果没有 session,请先在 `会话(session)` 中填写一个随机前缀,再点击 `同步`。 + +### `IP代理` 和 Clash Verge 应该同时开吗? + +通常二选一即可。 +如果开启扩展内置 `IP代理`,浏览器会优先使用扩展设置的 PAC 代理;如果关闭它,浏览器会回到默认网络或外部全局代理。 +排查问题时建议先只保留一种代理方式,确认出口 IP 稳定后再继续流程。 + ### 网站测速结果好,是否代表节点纯净? 不代表。 @@ -688,6 +813,9 @@ function main(config, profileName) { - 中国大陆居民注册 `PayPal` 个人账户时,姓名请按身份证上的中文姓名填写,不要使用拼音。 - `PayPal` 官方说明,绑定新卡时可能会向发卡行发送最高 `1 USD` 或等值货币的临时授权验证,所以完全没有余额的卡也可能因为授权失败而无法绑定。 - 为了控制风险,更稳妥的做法是使用单独管理、余额较低的借记卡,并在绑卡后及时检查 `钱包` 页面和右上角通知。 +- 使用扩展内置 `IP代理` 时,修改 Host、Port、账号、密码、地区、session 或时长后,请点击 `同步` 让新配置真正生效。 +- `711Proxy` 的 `Change` 需要用户名中包含 session;没有 session 时请先填写 `会话(session)` 并同步。 +- 扩展 `IP代理` 报连通性失败时,不要直接继续自动流程,先用 `检测出口` 和 `检查IP` 确认网页看到的出口 IP。 - 检查节点时,先确认出口 IP,再看风险标签和泄露检测,最后再看网站测速。 - [TCPTest 网站测速](https://www.tcptest.cn/?cckey=e62f31db) 主要用于判断访问速度和连通性,不要单独用它判断节点纯净度。 - [IPPure](https://ippure.com/) 如果提示 `WebRTC` 或 `DNS` 泄露,请优先检查浏览器和代理客户端的相关设置。 diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 7db0a30..40e9a1a 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -219,7 +219,7 @@ diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 69e94f0..08bdbf5 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -748,7 +748,7 @@ const IP_PROXY_SERVICE_LOGIN_CONFIGS = { '711proxy': { label: '711Proxy', url: 'https://www.711proxy.com/signup?code=AD2497', - buttonLabel: '登录', + buttonLabel: '注册', }, }; @@ -3844,9 +3844,10 @@ function updateIpProxyServiceLoginButtonState(options = {}) { ? Boolean(options.enabled) : Boolean(getSelectedIpProxyEnabled()); btnIpProxyServiceLogin.disabled = !enabled || !loginUrl; - btnIpProxyServiceLogin.textContent = loginConfig?.buttonLabel || '登录'; + const buttonLabel = loginConfig?.buttonLabel || '登录'; + btnIpProxyServiceLogin.textContent = buttonLabel; btnIpProxyServiceLogin.title = loginUrl - ? `打开 ${loginConfig?.label || service} 登录页` + ? `打开 ${loginConfig?.label || service} ${buttonLabel}页` : '当前代理服务没有可跳转的登录页'; } diff --git a/tests/paypal-flow-content.test.js b/tests/paypal-flow-content.test.js index d2f0ffd..f5995f1 100644 --- a/tests/paypal-flow-content.test.js +++ b/tests/paypal-flow-content.test.js @@ -145,6 +145,57 @@ return { `)(document, window); } +function createSubmitApi(overrides = {}) { + const bindings = { + waitForDocumentComplete: async () => {}, + normalizeText: (text = '') => String(text || '').replace(/\s+/g, ' ').trim(), + findPasswordInput: () => null, + findEmailInput: () => null, + findEmailNextButton: () => null, + isEnabledControl: () => true, + findPasswordLoginButton: () => null, + fillInput: () => {}, + simulateClick: () => {}, + waitUntil: async (predicate) => predicate(), + findLoginNextButton: () => null, + sleep: async () => {}, + ...overrides, + }; + + return new Function( + 'waitForDocumentComplete', + 'normalizeText', + 'findPasswordInput', + 'findEmailInput', + 'findEmailNextButton', + 'isEnabledControl', + 'findPasswordLoginButton', + 'fillInput', + 'simulateClick', + 'waitUntil', + 'findLoginNextButton', + 'sleep', + ` +${extractFunction('refillPayPalEmailInput')} +${extractFunction('submitPayPalLogin')} +return { refillPayPalEmailInput, submitPayPalLogin }; +` + )( + bindings.waitForDocumentComplete, + bindings.normalizeText, + bindings.findPasswordInput, + bindings.findEmailInput, + bindings.findEmailNextButton, + bindings.isEnabledControl, + bindings.findPasswordLoginButton, + bindings.fillInput, + bindings.simulateClick, + bindings.waitUntil, + bindings.findLoginNextButton, + bindings.sleep + ); +} + test('PayPal email page ignores hidden pre-rendered password input', () => { const hiddenPanel = createElement({ attrs: { 'aria-hidden': 'true' } }); const emailInput = createElement({ @@ -203,3 +254,57 @@ test('PayPal combined login page still sees visible password input', () => { assert.equal(api.findPasswordLoginButton(), loginButton); assert.equal(api.getPayPalLoginPhase(emailInput, passwordInput), 'login_combined'); }); + +test('PayPal email submit refills a prefilled email before clicking next', async () => { + const emailInput = createElement({ + tag: 'input', + type: 'text', + id: 'login_email', + name: 'login_email', + value: 'user@example.com', + placeholder: 'Email', + }); + const nextButton = createElement({ + tag: 'button', + id: 'btnNext', + text: 'Next', + }); + const fillValues = []; + const clicked = []; + let focusCount = 0; + let blurCount = 0; + + emailInput.focus = () => { + focusCount += 1; + }; + emailInput.blur = () => { + blurCount += 1; + }; + + const api = createSubmitApi({ + findEmailInput: () => emailInput, + findEmailNextButton: () => nextButton, + fillInput: (element, value) => { + fillValues.push(value); + element.value = value; + }, + simulateClick: (element) => { + clicked.push(element); + }, + }); + + const result = await api.submitPayPalLogin({ + email: 'user@example.com', + password: 'secret', + }); + + assert.deepEqual(fillValues, ['', 'user@example.com']); + assert.equal(focusCount, 1); + assert.equal(blurCount, 1); + assert.deepEqual(clicked, [nextButton]); + assert.deepEqual(result, { + submitted: false, + phase: 'email_submitted', + awaiting: 'password_page', + }); +}); diff --git a/项目完整链路说明.md b/项目完整链路说明.md index 77acb92..909a646 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -500,7 +500,7 @@ Plus 模式可见步骤: 1. 第 1~5 步:沿用普通注册入口、邮箱、密码、注册验证码、资料填写链路。 2. 第 6 步 `创建 Plus Checkout`:打开已登录 ChatGPT 页面,通过 `/api/auth/session` 读取 accessToken,再请求 `https://chatgpt.com/backend-api/payments/checkout` 创建 `chatgptplusplan` 的 checkout session,并打开 `https://chatgpt.com/checkout/openai_ie/{checkout_session_id}`。 3. 第 7 步 `填写账单并提交订阅`:选择 PayPal,生成账单全名,从 `data/address-sources.js` 读取同国家 seed query,触发 checkout 内置 Google 地址推荐,选择推荐项并校验地址第 1 行、城市、州/省、邮编等结构化字段,再点击“订阅”。运行时会按 Stripe iframe 拆分执行:付款方式在 `elements-inner-payment` frame,账单地址在 `elements-inner-address` frame,Google 推荐在 `elements-inner-autocompl` frame 时单独点击推荐项。 -4. 第 8 步 `PayPal 登录与授权`:在 PayPal 页面填写 `paypalEmail / paypalPassword`,登录前固定等待 1 秒,关闭可见通行密钥提示,点击“同意并继续”。 +4. 第 8 步 `PayPal 登录与授权`:在 PayPal 页面填写 `paypalEmail / paypalPassword`。当页面处于账号输入阶段时,内容脚本会固定对邮箱输入框执行 `focus -> clear -> fill -> blur -> click next`,即使文本框里已经预填了同一个账号,也会重新触发输入事件,避免 PayPal 因跳过重填而停在邮箱页;登录前固定等待 1 秒,关闭可见通行密钥提示,点击“同意并继续”。 5. 第 9 步 `订阅回跳确认`:等待 PayPal 授权后回跳到 ChatGPT / OpenAI 页面,页面加载完成后固定等待 1 秒。 6. 第 10 步:复用原 Step 7 OAuth 登录执行器,但状态和日志按 Plus 可见第 10 步记录。 7. 第 11 步:复用原 Step 8 登录验证码执行器,但状态和日志按 Plus 可见第 11 步记录。 diff --git a/项目文件结构说明.md b/项目文件结构说明.md index db94a0d..15be872 100644 --- a/项目文件结构说明.md +++ b/项目文件结构说明.md @@ -86,7 +86,7 @@ - `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。 - `content/mail-163.js`:163 / 163 VIP / 126 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。 - `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路;当后台显式开启 `mail2925MatchTargetEmail` 时,会对邮件里显式出现的目标邮箱做弱匹配,避免 `receive` 模式误捞别人的验证码。 -- `content/paypal-flow.js`:PayPal 页面脚本,负责识别登录表单、填写 PayPal 账号密码、处理页面内可见通行密钥提示,并点击 PayPal 授权页的“同意并继续”按钮。 +- `content/paypal-flow.js`:PayPal 页面脚本,负责识别登录表单、填写 PayPal 账号密码、在账号页固定执行 `focus -> clear -> fill -> blur` 重新触发邮箱输入事件、处理页面内可见通行密钥提示,并点击 PayPal 授权页的“同意并继续”按钮。 - `content/phone-auth.js`:认证页手机号验证脚本,负责识别 `add-phone / phone-verification` 页面、选择国家、填写手机号、提交短信验证码、触发重发短信,以及把“回到 add-phone / 进入 OAuth 同意页”的结果反馈给后台。 - `content/plus-checkout.js`:ChatGPT Plus checkout 页面脚本,负责读取 `/api/auth/session` 创建 Plus checkout、选择 PayPal、填写账单姓名、触发 Google 地址推荐、校验结构化地址字段并点击订阅。 - `content/qq-mail.js`:QQ 邮箱轮询脚本,负责网页邮箱验证码读取。 @@ -202,6 +202,8 @@ - `tests/background-verification-flow-module.test.js`:测试验证码流程模块已接入且导出工厂。 - `tests/phone-auth-country-match.test.js`:测试认证页手机号国家选择在页面本地化时,仍能用 HeroSMS 的英文国家名匹配对应国家选项。 - `tests/phone-verification-flow.test.js`:测试手机号验证共享流程对 HeroSMS 的取号、复用、重发、换号与 Step 7 重开错误分支。 +- `tests/paypal-approve-detection.test.js`:测试 Plus 第 8 步后台执行器对 PayPal 标签页发现、分离式账号/密码页识别、联合登录页识别,以及登录后直接离开 PayPal 页的分支判断。 +- `tests/paypal-flow-content.test.js`:测试 PayPal 内容脚本对可见邮箱/密码输入框的识别,并覆盖邮箱页即使已预填相同账号也会先清空再重填后继续下一步。 - `tests/plus-checkout-billing-tab-resolution.test.js`:测试 Plus 第 7 步可直接接管当前 checkout 标签页,并把 PayPal、账单地址、Google 地址推荐和订阅按钮操作路由到对应 Stripe iframe。 - `tests/cloudflare-temp-email-provider.test.js`:测试 Cloudflare Temp Email provider 的轮询与目标邮箱选择逻辑。 - `tests/cloudflare-temp-email-utils.test.js`:测试 Cloudflare Temp Email 工具层的 URL、域名、邮件解析逻辑。