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 @@
+
+ 目标邮箱类型
+
+
+
+ 转发邮箱
+
+
获取策略