diff --git a/README.md b/README.md index 7cf5f51..af19001 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ - 至少准备一种验证码接收方式: - DuckDuckGo `@duck.com` + QQ / 163 / Inbucket 转发 - Cloudflare 自定义域邮箱前缀 + QQ / 163 / Inbucket 转发 + - iCloud Hide My Email,可选择直接从 iCloud 收件箱收码,或转发到 QQ / 163 / 163 VIP / 126 / Gmail 后收码 - 手动填写一个可收信邮箱 - 如果使用 `QQ` / `163` / `163 VIP` / `126` / `Inbucket`,对应页面需要提前能正常打开 @@ -592,6 +593,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 支持: - `Hotmail`(远程服务 / 本地助手) +- `iCloud` 收件箱,或 iCloud Hide My Email 转发到 QQ / 163 / 163 VIP / 126 / Gmail 后收码 - `content/qq-mail.js` - `content/mail-163.js`(163 / 163 VIP / 126) - `content/inbucket-mail.js` @@ -773,7 +775,7 @@ content/utils.js 通用工具:等待元素、点击、日志、停 content/vps-panel.js CPA 面板步骤:内部 OAuth 刷新 / Step 10 content/signup-page.js ChatGPT 官网 + OpenAI 注册/登录页步骤:Step 1 / 2 / 3 / 5 / 7 / 9 hotmail-utils.js Hotmail 收信相关通用辅助 -mail-provider-utils.js 网页邮箱 provider 配置辅助 +mail-provider-utils.js 网页邮箱 provider 与 iCloud 转发收码配置辅助 content/duck-mail.js Duck 邮箱自动获取 content/qq-mail.js QQ 邮箱验证码轮询 content/mail-163.js 163 / 163 VIP / 126 邮箱验证码轮询 diff --git a/background.js b/background.js index 9d3e61e..8190aae 100644 --- a/background.js +++ b/background.js @@ -39,6 +39,7 @@ importScripts( 'luckmail-utils.js', 'cloudflare-temp-email-utils.js', 'icloud-utils.js', + 'mail-provider-utils.js', 'content/activation-utils.js' ); @@ -145,6 +146,11 @@ const { pickReusableIcloudAlias, toNormalizedEmailSet, } = self.IcloudUtils; +const { + getIcloudForwardMailConfig: getSharedIcloudForwardMailConfig, + normalizeIcloudForwardMailProvider, + normalizeIcloudTargetMailboxType, +} = self.MailProviderUtils; const { isRecoverableStep9AuthFailure, } = self.MultiPageActivationUtils; @@ -369,6 +375,8 @@ const PERSISTED_SETTING_DEFAULTS = { customEmailPool: [], autoDeleteUsedIcloudAlias: false, icloudHostPreference: 'auto', + icloudTargetMailboxType: 'icloud-inbox', + icloudForwardMailProvider: 'qq', icloudFetchMode: 'reuse_existing', accountRunHistoryTextEnabled: true, accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL, @@ -1105,6 +1113,10 @@ function normalizePersistentSettingValue(key, value) { return Boolean(value); case 'icloudHostPreference': return normalizeIcloudHost(value) || 'auto'; + case 'icloudTargetMailboxType': + return normalizeIcloudTargetMailboxType(value); + case 'icloudForwardMailProvider': + return normalizeIcloudForwardMailProvider(value); case 'icloudFetchMode': return normalizeIcloudFetchMode(value); case 'accountRunHistoryHelperBaseUrl': @@ -7317,6 +7329,17 @@ function getMailConfig(state) { const configuredHost = getConfiguredIcloudHostPreference(state) || normalizeIcloudHost(state?.preferredIcloudHost) || 'icloud.com'; + const targetMailboxType = normalizeIcloudTargetMailboxType(state?.icloudTargetMailboxType); + const useForwardMailbox = targetMailboxType === 'forward-mailbox'; + if (useForwardMailbox) { + const forwardProvider = normalizeIcloudForwardMailProvider(state?.icloudForwardMailProvider); + const forwardConfig = getSharedIcloudForwardMailConfig(forwardProvider); + return { + ...forwardConfig, + label: `iCloud 转发(${forwardConfig.label})`, + icloudForwarding: true, + }; + } const loginUrl = getIcloudLoginUrlForHost(configuredHost) || 'https://www.icloud.com/'; const mailUrl = getIcloudMailUrlForHost(configuredHost) || loginUrl; return { diff --git a/content/signup-page.js b/content/signup-page.js index 9c6caf6..f36207b 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -114,6 +114,9 @@ const VERIFICATION_CODE_INPUT_SELECTOR = [ ].join(', '); const ONE_TIME_CODE_LOGIN_PATTERN = /使用一次性验证码登录|改用(?:一次性)?验证码(?:登录)?|使用验证码登录|一次性验证码|验证码登录|one[-\s]*time\s*(?:passcode|password|code)|use\s+(?:a\s+)?one[-\s]*time\s*(?:passcode|password|code)(?:\s+instead)?|use\s+(?:a\s+)?code(?:\s+instead)?|sign\s+in\s+with\s+(?:email|code)|email\s+(?:me\s+)?(?:a\s+)?code/i; +const LOGIN_ENTRY_ACTION_PATTERN = /(?:^|\b)(?:log\s*in|sign\s*in|continue\s+(?:with|using)\s+(?:email|chatgpt)|use\s+(?:an?\s+)?email|email\s+address)(?:\b|$)|登录|登陆|邮箱|电子邮件/i; +const LOGIN_EXTERNAL_IDP_PATTERN = /google|microsoft|apple|sso|single\s+sign[-\s]*on|企业|工作区|workspace/i; +const LOGIN_CODE_ONLY_ACTION_PATTERN = /one[-\s]*time|passcode|use\s+(?:a\s+)?code|验证码|一次性/i; const RESEND_VERIFICATION_CODE_PATTERN = /重新发送(?:验证码)?|再次发送(?:验证码)?|重发(?:验证码)?|未收到(?:验证码|邮件)|resend(?:\s+code)?|send\s+(?:a\s+)?new\s+code|send\s+(?:it\s+)?again|request\s+(?:a\s+)?new\s+code|didn'?t\s+receive/i; @@ -1719,12 +1722,32 @@ function getLoginSubmitButton({ allowDisabled = false } = {}) { }) || null; } +function findLoginEntryTrigger() { + const candidates = Array.from(document.querySelectorAll( + 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]' + )).filter((el) => isVisibleElement(el) && isActionEnabled(el)); + + const preferred = candidates.find((el) => { + const text = getActionText(el); + if (!text || LOGIN_CODE_ONLY_ACTION_PATTERN.test(text) || LOGIN_EXTERNAL_IDP_PATTERN.test(text)) return false; + return /continue\s+(?:with|using)\s+email|use\s+(?:an?\s+)?email|email\s+address|邮箱|电子邮件/i.test(text); + }); + if (preferred) return preferred; + + return candidates.find((el) => { + const text = getActionText(el); + if (!text || LOGIN_CODE_ONLY_ACTION_PATTERN.test(text) || LOGIN_EXTERNAL_IDP_PATTERN.test(text)) return false; + return LOGIN_ENTRY_ACTION_PATTERN.test(text); + }) || null; +} + function inspectLoginAuthState() { const retryState = getLoginTimeoutErrorPageState(); const verificationTarget = getVerificationCodeTarget(); const passwordInput = getLoginPasswordInput(); const emailInput = getLoginEmailInput(); const switchTrigger = findOneTimeCodeLoginTrigger(); + const loginEntryTrigger = findLoginEntryTrigger(); const submitButton = getLoginSubmitButton({ allowDisabled: true }); const verificationVisible = isVerificationPageStillVisible(); const addPhonePage = isAddPhonePageReady(); @@ -1746,6 +1769,7 @@ function inspectLoginAuthState() { emailInput, submitButton, switchTrigger, + loginEntryTrigger, verificationVisible, addPhonePage, phoneVerificationPage, @@ -1810,6 +1834,13 @@ function inspectLoginAuthState() { }; } + if (loginEntryTrigger) { + return { + ...baseState, + state: 'entry_page', + }; + } + return baseState; } @@ -1828,6 +1859,7 @@ function serializeLoginAuthState(snapshot) { hasEmailInput: Boolean(snapshot?.emailInput), hasSubmitButton: Boolean(snapshot?.submitButton), hasSwitchTrigger: Boolean(snapshot?.switchTrigger), + hasLoginEntryTrigger: Boolean(snapshot?.loginEntryTrigger), verificationVisible: Boolean(snapshot?.verificationVisible), addPhonePage: Boolean(snapshot?.addPhonePage), phoneVerificationPage: Boolean(snapshot?.phoneVerificationPage), @@ -1849,6 +1881,8 @@ function getLoginAuthStateLabel(snapshot) { return '登录超时报错页'; case 'oauth_consent_page': return 'OAuth 授权页'; + case 'entry_page': + return '登录入口页'; case 'add_phone_page': return '手机号页'; default: @@ -2816,6 +2850,71 @@ async function waitForStep6SwitchTransition(loginVerificationRequestedAt, timeou return transition; } +async function waitForLoginEntryOpenTransition(timeout = 10000) { + const start = Date.now(); + let snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); + + while (Date.now() - start < timeout) { + throwIfStopped(); + snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); + if (snapshot.state !== 'unknown' && snapshot.state !== 'entry_page') { + return snapshot; + } + await sleep(250); + } + + return snapshot; +} + +async function step6OpenLoginEntry(payload, snapshot) { + const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState()); + const trigger = currentSnapshot.loginEntryTrigger || findLoginEntryTrigger(); + if (!trigger || !isActionEnabled(trigger)) { + return createStep6RecoverableResult('missing_login_entry_trigger', currentSnapshot, { + message: '当前登录入口页没有可点击的邮箱登录入口。', + }); + } + + log(`步骤 7:检测到登录入口页,正在点击 "${getActionText(trigger).slice(0, 80)}"...`); + await humanPause(350, 900); + simulateClick(trigger); + const nextSnapshot = await waitForLoginEntryOpenTransition(); + + if (nextSnapshot.state === 'email_page') { + return step6LoginFromEmailPage(payload, nextSnapshot); + } + if (nextSnapshot.state === 'password_page') { + return step6LoginFromPasswordPage(payload, nextSnapshot); + } + if (nextSnapshot.state === 'verification_page') { + return finalizeStep6VerificationReady({ + logLabel: '步骤 7 收尾', + loginVerificationRequestedAt: null, + via: 'entry_open_verification_page', + }); + } + if (nextSnapshot.state === 'oauth_consent_page') { + return createStep6OAuthConsentSuccessResult(nextSnapshot, { + via: 'entry_open_oauth_consent_page', + }); + } + if (nextSnapshot.state === 'login_timeout_error_page') { + const transition = await createStep6LoginTimeoutRecoveryTransition( + 'login_timeout_after_entry_open', + nextSnapshot, + '点击登录入口后进入登录超时报错页。' + ); + if (transition.action === 'done') return transition.result; + if (transition.action === 'email') return step6LoginFromEmailPage(payload, transition.snapshot); + if (transition.action === 'password') return step6LoginFromPasswordPage(payload, transition.snapshot); + return transition.result; + } + + return createStep6RecoverableResult('login_entry_open_stalled', nextSnapshot, { + message: '点击登录入口后仍未进入邮箱/密码/验证码页。', + }); +} + async function step6SwitchToOneTimeCodeLogin(payload, snapshot) { const switchTrigger = snapshot?.switchTrigger || findOneTimeCodeLoginTrigger(); if (!switchTrigger || !isActionEnabled(switchTrigger)) { @@ -3024,6 +3123,10 @@ async function step6_login(payload) { return step6LoginFromPasswordPage(payload, snapshot); } + if (snapshot.state === 'entry_page') { + return step6OpenLoginEntry(payload, snapshot); + } + throwForStep6FatalState(snapshot); throw new Error(`无法识别当前登录页面状态。URL: ${snapshot?.url || location.href}`); } diff --git a/mail-provider-utils.js b/mail-provider-utils.js index cccb000..6bd3960 100644 --- a/mail-provider-utils.js +++ b/mail-provider-utils.js @@ -1,5 +1,15 @@ const HOTMAIL_PROVIDER = 'hotmail-api'; +const GMAIL_PROVIDER = 'gmail'; const NETEASE_LIST_PATH = '/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D'; +const ICLOUD_TARGET_MAILBOX_TYPE_INBOX = 'icloud-inbox'; +const ICLOUD_TARGET_MAILBOX_TYPE_FORWARD = 'forward-mailbox'; +const ICLOUD_FORWARD_MAIL_PROVIDER_OPTIONS = [ + { value: 'qq', label: 'QQ 邮箱' }, + { value: '163', label: '163 邮箱' }, + { value: '163-vip', label: '163 VIP 邮箱' }, + { value: '126', label: '126 邮箱' }, + { value: GMAIL_PROVIDER, label: 'Gmail 邮箱' }, +]; function normalizeMailProvider(value = '') { const normalized = String(value || '').trim().toLowerCase(); @@ -16,6 +26,38 @@ function normalizeMailProvider(value = '') { } } +function normalizeIcloudTargetMailboxType(value = '') { + return String(value || '').trim().toLowerCase() === ICLOUD_TARGET_MAILBOX_TYPE_FORWARD + ? ICLOUD_TARGET_MAILBOX_TYPE_FORWARD + : ICLOUD_TARGET_MAILBOX_TYPE_INBOX; +} + +function normalizeIcloudForwardMailProvider(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + return ICLOUD_FORWARD_MAIL_PROVIDER_OPTIONS.some((option) => option.value === normalized) + ? normalized + : 'qq'; +} + +function getIcloudForwardMailProviderOptions() { + return ICLOUD_FORWARD_MAIL_PROVIDER_OPTIONS.map((option) => ({ ...option })); +} + +function getIcloudForwardMailConfig(provider = 'qq') { + const normalizedProvider = normalizeIcloudForwardMailProvider(provider); + if (normalizedProvider === GMAIL_PROVIDER) { + return { + source: 'gmail-mail', + url: 'https://mail.google.com/mail/u/0/#inbox', + label: 'Gmail 邮箱', + inject: ['content/activation-utils.js', 'content/utils.js', 'content/gmail-mail.js'], + injectSource: 'gmail-mail', + }; + } + + return getMailProviderConfig({ mailProvider: normalizedProvider }); +} + function getMailProviderConfig(state = {}, options = {}) { const provider = normalizeMailProvider(state.mailProvider); const normalizeInbucketOrigin = options.normalizeInbucketOrigin || (() => ''); @@ -66,8 +108,13 @@ function getMailProviderConfig(state = {}, options = {}) { } const api = { + GMAIL_PROVIDER, HOTMAIL_PROVIDER, + getIcloudForwardMailConfig, + getIcloudForwardMailProviderOptions, getMailProviderConfig, + normalizeIcloudForwardMailProvider, + normalizeIcloudTargetMailboxType, normalizeMailProvider, }; diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index adf2fa0..7a49664 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -685,6 +685,23 @@ +
+ 目标邮箱类型 + +
+
获取策略