diff --git a/background.js b/background.js index 9e872d3..c2813f6 100644 --- a/background.js +++ b/background.js @@ -76,6 +76,7 @@ importScripts( 'background/freemail-provider.js', 'background/outlook-email-plus-provider.js', 'background/cloudmail-provider.js', + 'background/mail-api-provider.js', 'background/moemail-provider.js', 'background/yydsmail-provider.js', 'icloud-utils.js', @@ -555,6 +556,7 @@ const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email'; const CLOUDFLARE_TEMP_EMAIL_GENERATOR = 'cloudflare-temp-email'; const CLOUD_MAIL_PROVIDER = 'cloudmail'; const CLOUD_MAIL_GENERATOR = 'cloudmail'; +const MAIL_API_PROVIDER = 'mail-api'; const FREEMAIL_PROVIDER = 'freemail'; const FREEMAIL_GENERATOR = 'freemail'; const MOEMAIL_PROVIDER = 'moemail'; @@ -1297,6 +1299,12 @@ const PERSISTED_SETTING_DEFAULTS = { cloudMailReceiveMailbox: '', cloudMailDomain: '', cloudMailDomains: [], + mailApiBaseUrl: '', + mailApiKey: '', + mailApiGroupId: '', + mailApiLeaseDays: 30, + mailApiLeases: {}, + currentMailApiEmail: '', freemailBaseUrl: '', freemailAdminUsername: '', freemailAdminPassword: '', @@ -3081,6 +3089,29 @@ async function markCurrentRegistrationAccountUsed(state = {}, options = {}) { } } + if (isMailApiProvider(latestState)) { + const email = String(latestState.email || latestState.currentMailApiEmail || '').trim().toLowerCase(); + if (email) { + const now = Date.now(); + const leaseDays = mailApiProvider?.normalizeMailApiLeaseDays + ? mailApiProvider.normalizeMailApiLeaseDays(latestState.mailApiLeaseDays) + : 30; + const nextLeases = { + ...(mailApiProvider?.normalizeMailApiLeases ? mailApiProvider.normalizeMailApiLeases(latestState.mailApiLeases) : {}), + [email]: { + email, + expiresAt: now + leaseDays * 24 * 60 * 60 * 1000, + lastUsedAt: now, + }, + }; + const updates = { mailApiLeases: nextLeases, currentMailApiEmail: email }; + await setPersistentSettings(updates); + await setState(updates); + await addLog(`${reasonPrefix}:Mail API 邮箱 ${email} 已设置 ${leaseDays} 天有效期。`, options.level || 'warn'); + updated = true; + } + } + if (String(latestState.mailProvider || '').trim().toLowerCase() === '2925' && latestState.currentMail2925AccountId) { await patchMail2925Account(latestState.currentMail2925AccountId, { lastUsedAt: Date.now(), @@ -3365,6 +3396,7 @@ function normalizeMailProvider(value = '') { case LUCKMAIL_PROVIDER: case CLOUDFLARE_TEMP_EMAIL_PROVIDER: case CLOUD_MAIL_PROVIDER: + case MAIL_API_PROVIDER: case FREEMAIL_PROVIDER: case MOEMAIL_PROVIDER: case YYDSMAIL_PROVIDER: @@ -3606,6 +3638,22 @@ const { pollCloudMailVerificationCode, resolveCloudMailPollTargetEmail, } = cloudMailProvider; +const mailApiProvider = self.MultiPageBackgroundMailApiProvider.createMailApiProvider({ + addLog, + getState, + normalizeHotmailMailApiMessages, + persistRegistrationEmailState, + pickVerificationMessageWithTimeFallback, + setEmailState, + setPersistentSettings, + setState, + sleepWithStop, + throwIfStopped, +}); +const { + ensureMailApiAccountForFlow, + pollMailApiVerificationCode, +} = mailApiProvider; const freemailProvider = self.MultiPageBackgroundFreemailProvider.createFreemailProvider({ addLog, buildFreemailHeaders, @@ -4252,6 +4300,9 @@ function normalizePersistentSettingValue(key, value) { if (normalizedMailProvider === CLOUD_MAIL_PROVIDER) { return CLOUD_MAIL_PROVIDER; } + if (normalizedMailProvider === MAIL_API_PROVIDER) { + return MAIL_API_PROVIDER; + } if (normalizedMailProvider === FREEMAIL_PROVIDER) { return FREEMAIL_PROVIDER; } @@ -4358,6 +4409,26 @@ function normalizePersistentSettingValue(key, value) { return normalizeCloudMailDomain(value); case 'cloudMailDomains': return normalizeCloudMailDomains(value); + case 'mailApiBaseUrl': + return mailApiProvider?.normalizeMailApiBaseUrl + ? mailApiProvider.normalizeMailApiBaseUrl(value) + : String(value || '').trim(); + case 'mailApiKey': + return String(value || '').trim(); + case 'mailApiGroupId': + return mailApiProvider?.normalizeMailApiGroupId + ? mailApiProvider.normalizeMailApiGroupId(value) + : String(value || '').trim(); + case 'mailApiLeaseDays': + return mailApiProvider?.normalizeMailApiLeaseDays + ? mailApiProvider.normalizeMailApiLeaseDays(value) + : 30; + case 'mailApiLeases': + return mailApiProvider?.normalizeMailApiLeases + ? mailApiProvider.normalizeMailApiLeases(value) + : {}; + case 'currentMailApiEmail': + return String(value || '').trim().toLowerCase(); case 'freemailBaseUrl': return normalizeFreemailBaseUrl(value); case 'freemailAdminUsername': @@ -5971,6 +6042,13 @@ function isLuckmailProvider(stateOrProvider) { return provider === LUCKMAIL_PROVIDER; } +function isMailApiProvider(stateOrProvider) { + const provider = typeof stateOrProvider === 'string' + ? stateOrProvider + : stateOrProvider?.mailProvider; + return provider === MAIL_API_PROVIDER; +} + function isOutlookEmailPlusProvider(stateOrProvider) { const provider = typeof stateOrProvider === 'string' ? stateOrProvider @@ -7104,6 +7182,7 @@ function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) { function shouldUseCustomRegistrationEmail(state = {}) { return isCustomMailProvider(state) || (!isHotmailProvider(state) + && !isMailApiProvider(state) && !isGeneratedAliasProvider(state) && normalizeEmailGenerator(state.emailGenerator) === 'custom'); } @@ -7273,6 +7352,7 @@ function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) { function shouldUseCustomRegistrationEmail(state = {}) { return isCustomMailProvider(state) || (!isHotmailProvider(state) + && !isMailApiProvider(state) && !isGeneratedAliasProvider(state) && normalizeEmailGenerator(state.emailGenerator) === 'custom'); } @@ -10847,6 +10927,7 @@ function getSourceLabel(source) { 'luckmail-api': 'LuckMail(API 购邮)', 'cloudflare-temp-email': 'Cloudflare Temp Email', 'cloudmail': 'Cloud Mail', + 'mail-api': 'Mail API', 'freemail': 'freemail', 'plus-checkout': 'Plus Checkout', 'paypal-flow': 'PayPal 授权页', @@ -14640,6 +14721,12 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) { return purchase.email_address; } + if (isMailApiProvider(currentState)) { + const account = await ensureMailApiAccountForFlow({ allowReuse: false }); + await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:Mail API 邮箱已就绪:${account.email}(第 ${attemptRuns} 次尝试)===`, 'ok'); + return account.email; + } + if (isGeneratedAliasProvider(currentState)) { if (currentState.mailProvider === GMAIL_PROVIDER) { if (!currentState.emailPrefix) { @@ -14771,6 +14858,12 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) { return purchase.email_address; } + if (isMailApiProvider(currentState)) { + const account = await ensureMailApiAccountForFlow({ allowReuse: false }); + await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:Mail API 邮箱已就绪:${account.email}(第 ${attemptRuns} 次尝试)===`, 'ok'); + return account.email; + } + if (isGeneratedAliasProvider(currentState)) { if (isReusableGeneratedAliasEmail(currentState)) { await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:当前已复用 ${currentState.email},将直接继续执行(第 ${attemptRuns} 次尝试)===`, 'info'); @@ -15634,6 +15727,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe ensureHotmailAccountForFlow, ensureMail2925AccountForFlow, ensureLuckmailPurchaseForFlow, + ensureMailApiAccountForFlow, fetchGeneratedEmail, getTabId, isGeneratedAliasProvider, @@ -15650,6 +15744,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe isRetryableContentScriptTransportError, isHotmailProvider, isLuckmailProvider, + isMailApiProvider, isSignupPasswordPageUrl, isTabAlive, persistRegistrationEmailState, @@ -15680,6 +15775,7 @@ const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.create closeConflictingTabsForSource, CLOUDFLARE_TEMP_EMAIL_PROVIDER, CLOUD_MAIL_PROVIDER, + MAIL_API_PROVIDER, FREEMAIL_PROVIDER, ICLOUD_API_PROVIDER, MOEMAIL_PROVIDER, @@ -15705,6 +15801,7 @@ const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.create MAIL_2925_VERIFICATION_MAX_ATTEMPTS, pollCloudflareTempEmailVerificationCode, pollCloudMailVerificationCode, + pollMailApiVerificationCode, pollFreemailVerificationCode, pollIcloudApiVerificationCode, pollMoemailVerificationCode, @@ -15853,6 +15950,7 @@ const step4Executor = self.MultiPageBackgroundStep4?.createStep4Executor({ LUCKMAIL_PROVIDER, CLOUDFLARE_TEMP_EMAIL_PROVIDER, CLOUD_MAIL_PROVIDER, + MAIL_API_PROVIDER, FREEMAIL_PROVIDER, MOEMAIL_PROVIDER, YYDSMAIL_PROVIDER, @@ -15914,6 +16012,7 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({ chrome, CLOUDFLARE_TEMP_EMAIL_PROVIDER, CLOUD_MAIL_PROVIDER, + MAIL_API_PROVIDER, FREEMAIL_PROVIDER, MOEMAIL_PROVIDER, YYDSMAIL_PROVIDER, @@ -16559,6 +16658,9 @@ function getMailConfig(state) { if (provider === 'cloudmail') { return { provider: 'cloudmail', label: 'Cloud Mail' }; } + if (provider === MAIL_API_PROVIDER) { + return { provider: MAIL_API_PROVIDER, label: 'Mail API' }; + } if (provider === FREEMAIL_PROVIDER) { return { provider: FREEMAIL_PROVIDER, label: 'freemail' }; } diff --git a/background/logging-status.js b/background/logging-status.js index 6dd73c8..5801ab1 100644 --- a/background/logging-status.js +++ b/background/logging-status.js @@ -35,6 +35,7 @@ 'luckmail-api': 'LuckMail(API 购邮)', 'cloudflare-temp-email': 'Cloudflare Temp Email', 'cloudmail': 'Cloud Mail', + 'mail-api': 'Mail API', 'freemail': 'freemail', 'moemail': 'MoeMail', 'yydsmail': 'YYDS Mail', diff --git a/background/mail-api-provider.js b/background/mail-api-provider.js new file mode 100644 index 0000000..9a9f340 --- /dev/null +++ b/background/mail-api-provider.js @@ -0,0 +1,386 @@ +(function mailApiProviderModule(root, factory) { + root.MultiPageBackgroundMailApiProvider = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createMailApiProviderModule() { + const DEFAULT_MAIL_API_LEASE_DAYS = 30; + const MS_PER_DAY = 24 * 60 * 60 * 1000; + + function createMailApiProvider(deps = {}) { + const { + addLog = async () => {}, + fetchImpl = typeof fetch === 'function' ? fetch.bind(globalThis) : null, + getState = async () => ({}), + normalizeHotmailMailApiMessages = (messages) => (Array.isArray(messages) ? messages : []), + persistRegistrationEmailState = null, + pickVerificationMessageWithTimeFallback, + setEmailState = async () => {}, + setPersistentSettings = async () => {}, + setState = async () => {}, + sleepWithStop = async () => {}, + throwIfStopped = () => {}, + } = deps; + + function normalizeMailApiBaseUrl(value = '') { + const rawValue = String(value || '').trim(); + if (!rawValue) return ''; + const candidate = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(rawValue) ? rawValue : `https://${rawValue}`; + try { + const parsed = new URL(candidate); + if (!['http:', 'https:'].includes(parsed.protocol)) return ''; + parsed.hash = ''; + parsed.search = ''; + const pathname = parsed.pathname === '/' ? '' : parsed.pathname.replace(/\/+$/g, ''); + return `${parsed.origin}${pathname}`; + } catch { + return ''; + } + } + + function normalizeMailApiGroupId(value = '') { + const rawValue = String(value || '').trim(); + if (!rawValue) return ''; + const numeric = Number(rawValue); + return Number.isSafeInteger(numeric) && numeric > 0 ? String(numeric) : ''; + } + + function normalizeMailApiLeaseDays(value, fallback = DEFAULT_MAIL_API_LEASE_DAYS) { + const rawValue = String(value ?? '').trim(); + const fallbackValue = Math.max(1, Math.min(365, Math.floor(Number(fallback) || DEFAULT_MAIL_API_LEASE_DAYS))); + if (!rawValue) return fallbackValue; + const numeric = Number(rawValue); + if (!Number.isFinite(numeric)) return fallbackValue; + return Math.max(1, Math.min(365, Math.floor(numeric))); + } + + function normalizeMailApiAddress(value = '') { + return String(value || '').trim().toLowerCase(); + } + + function normalizeMailApiLeases(value = {}) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return {}; + } + const leases = {}; + for (const [rawEmail, rawLease] of Object.entries(value)) { + const email = normalizeMailApiAddress(rawLease?.email || rawEmail); + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + continue; + } + const expiresAt = Number(rawLease?.expiresAt ?? rawLease?.leaseExpiresAt ?? rawLease); + if (!Number.isFinite(expiresAt) || expiresAt <= 0) { + continue; + } + leases[email] = { + email, + expiresAt, + lastUsedAt: Math.max(0, Number(rawLease?.lastUsedAt) || 0), + }; + } + return leases; + } + + function getMailApiConfig(state = {}) { + return { + baseUrl: normalizeMailApiBaseUrl(state.mailApiBaseUrl), + apiKey: String(state.mailApiKey || '').trim(), + groupId: normalizeMailApiGroupId(state.mailApiGroupId), + leaseDays: normalizeMailApiLeaseDays(state.mailApiLeaseDays), + }; + } + + function ensureMailApiConfig(state = {}) { + const config = getMailApiConfig(state); + if (!config.baseUrl) { + throw new Error('Mail API 地址为空或格式无效。'); + } + if (!config.apiKey) { + throw new Error('Mail API Key 为空。'); + } + return config; + } + + async function requestMailApiJson(config, path, options = {}) { + if (!fetchImpl) { + throw new Error('Mail API 当前运行环境不支持 fetch。'); + } + const { searchParams = {}, timeoutMs = 20000 } = options; + const url = new URL(`${config.baseUrl}${String(path || '').startsWith('/') ? '' : '/'}${path}`); + Object.entries(searchParams || {}).forEach(([key, value]) => { + if (value !== undefined && value !== null && String(value) !== '') { + url.searchParams.set(key, String(value)); + } + }); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs); + let response; + try { + response = await fetchImpl(url.toString(), { + method: 'GET', + headers: { + Accept: 'application/json', + 'X-API-Key': config.apiKey, + }, + signal: controller.signal, + }); + } catch (error) { + const message = error?.name === 'AbortError' + ? `Mail API 请求超时(>${Math.round(timeoutMs / 1000)} 秒)` + : `Mail API 请求失败:${error?.message || error}`; + throw new Error(message); + } finally { + clearTimeout(timeoutId); + } + + const text = await response.text(); + let payload; + try { + payload = text ? JSON.parse(text) : {}; + } catch { + payload = text; + } + if (!response.ok) { + const payloadError = payload && typeof payload === 'object' + ? (payload.message || payload.error || payload.msg) + : ''; + throw new Error(`Mail API 请求失败:${payloadError || text || `HTTP ${response.status}`}`); + } + if (payload && typeof payload === 'object' && payload.success === false) { + throw new Error(`Mail API 业务错误:${payload.message || payload.error || 'success=false'}`); + } + return payload; + } + + function normalizeMailApiAccount(row = {}) { + if (!row || typeof row !== 'object') return null; + const email = normalizeMailApiAddress(row.email || row.mail || row.address); + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return null; + return { + id: String(row.id || email), + email, + aliases: Array.isArray(row.aliases) + ? row.aliases.map((alias) => normalizeMailApiAddress(alias)).filter(Boolean) + : [], + status: String(row.status || '').trim().toLowerCase(), + groupName: String(row.group_name || row.groupName || '').trim(), + }; + } + + function getMailApiAccountRows(payload) { + if (Array.isArray(payload)) return payload; + if (!payload || typeof payload !== 'object') return []; + if (Array.isArray(payload.accounts)) return payload.accounts; + if (Array.isArray(payload.data)) return payload.data; + if (Array.isArray(payload.items)) return payload.items; + return []; + } + + async function listMailApiAccounts(state = {}) { + const config = ensureMailApiConfig(state); + const payload = await requestMailApiJson(config, '/api/external/accounts', { + searchParams: { + limit: 10000, + offset: 0, + sort_by: 'created_at', + sort_order: 'asc', + group_id: config.groupId, + }, + }); + return getMailApiAccountRows(payload) + .map((row) => normalizeMailApiAccount(row)) + .filter(Boolean); + } + + function isMailApiLeaseActive(leases = {}, email = '', now = Date.now()) { + const normalizedEmail = normalizeMailApiAddress(email); + const lease = normalizeMailApiLeases(leases)[normalizedEmail]; + return Boolean(lease && Number(lease.expiresAt) > now); + } + + function isMailApiAccountAvailable(account = {}, leases = {}, now = Date.now()) { + if (!account?.email) return false; + if (['disabled', 'inactive', 'deleted'].includes(String(account.status || '').toLowerCase())) { + return false; + } + return !isMailApiLeaseActive(leases, account.email, now); + } + + async function persistResolvedEmailState(state = null, email, options = {}) { + if (typeof persistRegistrationEmailState === 'function') { + await persistRegistrationEmailState(state, email, options); + return; + } + await setEmailState(email, options); + } + + async function leaseMailApiEmail(state = {}, account = {}, options = {}) { + const email = normalizeMailApiAddress(account.email); + const leaseDays = normalizeMailApiLeaseDays(state.mailApiLeaseDays); + const now = Date.now(); + const leases = normalizeMailApiLeases(state.mailApiLeases); + const nextLease = { + email, + expiresAt: now + leaseDays * MS_PER_DAY, + lastUsedAt: now, + }; + const nextLeases = { + ...leases, + [email]: nextLease, + }; + const updates = { + mailApiLeases: nextLeases, + currentMailApiEmail: email, + }; + await setPersistentSettings(updates); + await setState(updates); + await persistResolvedEmailState(state, email, { + source: 'mail-api', + preserveAccountIdentity: Boolean(options?.preserveAccountIdentity), + }); + return nextLease; + } + + async function ensureMailApiAccountForFlow(options = {}) { + throwIfStopped(); + const state = await getState(); + ensureMailApiConfig(state); + const now = Date.now(); + const currentEmail = normalizeMailApiAddress(state.currentMailApiEmail || state.email); + if (options?.allowReuse !== false && currentEmail && isMailApiLeaseActive(state.mailApiLeases, currentEmail, now)) { + await persistResolvedEmailState(state, currentEmail, { source: 'mail-api:reuse' }); + return { email: currentEmail, reused: true }; + } + + const accounts = await listMailApiAccounts(state); + const leases = normalizeMailApiLeases(state.mailApiLeases); + const account = accounts.find((candidate) => isMailApiAccountAvailable(candidate, leases, now)); + if (!account) { + throw new Error('Mail API 没有可用邮箱账号:账号池为空或全部仍在有效期内。'); + } + const lease = await leaseMailApiEmail(state, account, options); + await addLog(`Mail API:已分配邮箱 ${account.email},有效期 ${normalizeMailApiLeaseDays(state.mailApiLeaseDays)} 天。`, 'ok'); + return { ...account, lease }; + } + + function normalizeMailApiMessage(row = {}) { + return { + ...row, + bodyPreview: row.bodyPreview || row.body_preview || row.preview || '', + receivedDateTime: row.receivedDateTime || row.received_at || row.receivedAt || row.date || row.created_at || '', + }; + } + + function normalizeMailApiMessages(payload) { + const rows = payload && typeof payload === 'object' && Array.isArray(payload.emails) + ? payload.emails + : getMailApiAccountRows(payload); + return normalizeHotmailMailApiMessages(rows.map((row) => normalizeMailApiMessage(row))); + } + + function summarizeMailApiMessagesForLog(messages = []) { + return messages + .slice() + .sort((left, right) => (Date.parse(right.receivedDateTime || '') || 0) - (Date.parse(left.receivedDateTime || '') || 0)) + .slice(0, 3) + .map((message) => { + const receivedAt = message?.receivedDateTime || '未知时间'; + const sender = message?.from?.emailAddress?.address || '未知发件人'; + const subject = message?.subject || '(无主题)'; + const preview = String(message?.bodyPreview || '').replace(/\s+/g, ' ').trim().slice(0, 80); + return `${receivedAt} | ${sender} | ${subject} | ${preview}`; + }) + .join(' || '); + } + + async function listMailApiMessages(state = {}, options = {}) { + const config = ensureMailApiConfig(state); + const targetEmail = normalizeMailApiAddress(options.email || state.email || state.currentMailApiEmail); + if (!targetEmail) { + throw new Error('Mail API 查信前缺少目标邮箱。'); + } + const payload = await requestMailApiJson(config, '/api/external/emails', { + searchParams: { + email: targetEmail, + folder: options.folder || 'all', + top: options.top || 10, + skip: options.skip || 0, + }, + }); + return { + config, + messages: normalizeMailApiMessages(payload), + }; + } + + async function pollMailApiVerificationCode(step, state, pollPayload = {}) { + const latestState = state || await getState(); + const targetEmail = normalizeMailApiAddress(pollPayload.targetEmail || latestState.email || latestState.currentMailApiEmail); + if (!targetEmail) { + throw new Error('Mail API 轮询前缺少目标邮箱地址。'); + } + await addLog(`步骤 ${step}:正在通过 Mail API 轮询邮件(${targetEmail})...`, 'info'); + const maxAttempts = Number(pollPayload.maxAttempts) || 5; + const intervalMs = Number(pollPayload.intervalMs) || 3000; + let lastError = null; + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + throwIfStopped(); + try { + const { messages } = await listMailApiMessages(latestState, { + email: targetEmail, + top: pollPayload.limit || 10, + }); + const matchResult = pickVerificationMessageWithTimeFallback(messages, { + afterTimestamp: pollPayload.filterAfterTimestamp || 0, + senderFilters: pollPayload.senderFilters || [], + subjectFilters: pollPayload.subjectFilters || [], + requiredKeywords: pollPayload.requiredKeywords || [], + codePatterns: pollPayload.codePatterns || [], + excludeCodes: pollPayload.excludeCodes || [], + }); + const match = matchResult.match; + if (match?.code) { + if (matchResult.usedRelaxedFilters) { + const fallbackLabel = matchResult.usedTimeFallback ? '宽松匹配 + 时间回退' : '宽松匹配'; + await addLog(`步骤 ${step}:严格规则未命中,已改用 ${fallbackLabel} 并命中 Mail API 验证码。`, 'warn'); + } + return { + ok: true, + code: match.code, + emailTimestamp: match.receivedAt || Date.now(), + mailId: match.message?.id || '', + }; + } + lastError = new Error(`步骤 ${step}:暂未在 Mail API 中找到匹配验证码(${attempt}/${maxAttempts})。`); + await addLog(lastError.message, attempt === maxAttempts ? 'warn' : 'info'); + const sample = summarizeMailApiMessagesForLog(messages); + if (sample) { + await addLog(`步骤 ${step}:最近邮件样本:${sample}`, 'info'); + } + } catch (error) { + lastError = error; + await addLog(`步骤 ${step}:Mail API 轮询失败:${error?.message || error}`, 'warn'); + } + if (attempt < maxAttempts) { + await sleepWithStop(intervalMs); + } + } + throw lastError || new Error(`步骤 ${step}:未在 Mail API 中找到新的匹配验证码。`); + } + + return { + DEFAULT_MAIL_API_LEASE_DAYS, + ensureMailApiAccountForFlow, + getMailApiConfig, + listMailApiAccounts, + listMailApiMessages, + normalizeMailApiBaseUrl, + normalizeMailApiGroupId, + normalizeMailApiLeaseDays, + normalizeMailApiLeases, + pollMailApiVerificationCode, + }; + } + + return { + DEFAULT_MAIL_API_LEASE_DAYS, + createMailApiProvider, + }; +}); diff --git a/background/signup-flow-helpers.js b/background/signup-flow-helpers.js index a73ec7b..62ba633 100644 --- a/background/signup-flow-helpers.js +++ b/background/signup-flow-helpers.js @@ -10,12 +10,14 @@ ensureHotmailAccountForFlow, ensureMail2925AccountForFlow, ensureLuckmailPurchaseForFlow, + ensureMailApiAccountForFlow, fetchGeneratedEmail, isGeneratedAliasProvider, isReusableGeneratedAliasEmail, isHotmailProvider, isRetryableContentScriptTransportError = () => false, isLuckmailProvider, + isMailApiProvider, isSignupEmailVerificationPageUrl, isSignupPasswordPageUrl, isSignupPhoneVerificationPageUrl = null, @@ -356,6 +358,9 @@ } else if (isLuckmailProvider(state)) { const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true }); resolvedEmail = purchase.email_address; + } else if (typeof isMailApiProvider === 'function' && isMailApiProvider(state)) { + const account = await ensureMailApiAccountForFlow({ allowReuse: false }); + resolvedEmail = account.email; } else if (isGeneratedAliasProvider(state)) { if (Boolean(state?.mail2925UseAccountPool) && String(state?.mailProvider || '').trim().toLowerCase() === '2925' diff --git a/background/steps/fetch-login-code.js b/background/steps/fetch-login-code.js index b507afb..b31e12b 100644 --- a/background/steps/fetch-login-code.js +++ b/background/steps/fetch-login-code.js @@ -9,6 +9,7 @@ chrome, CLOUDFLARE_TEMP_EMAIL_PROVIDER, CLOUD_MAIL_PROVIDER = 'cloudmail', + MAIL_API_PROVIDER = 'mail-api', FREEMAIL_PROVIDER = 'freemail', ICLOUD_API_PROVIDER = 'icloud-api', MOEMAIL_PROVIDER = 'moemail', @@ -617,6 +618,7 @@ || mail.provider === LUCKMAIL_PROVIDER || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER || mail.provider === CLOUD_MAIL_PROVIDER + || mail.provider === MAIL_API_PROVIDER || mail.provider === FREEMAIL_PROVIDER || mail.provider === MOEMAIL_PROVIDER || mail.provider === YYDSMAIL_PROVIDER @@ -667,7 +669,7 @@ pollAttemptPlan: mail.provider === '2925' ? [2, 3, 15] : undefined, resendIntervalMs: mail.provider === LUCKMAIL_PROVIDER ? 15000 - : ((mail.provider === HOTMAIL_PROVIDER || mail.provider === ICLOUD_API_PROVIDER || mail.provider === '2925') + : ((mail.provider === HOTMAIL_PROVIDER || mail.provider === ICLOUD_API_PROVIDER || mail.provider === MAIL_API_PROVIDER || mail.provider === '2925') ? 0 : STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS), }); diff --git a/background/steps/fetch-signup-code.js b/background/steps/fetch-signup-code.js index 7b5736f..75abac2 100644 --- a/background/steps/fetch-signup-code.js +++ b/background/steps/fetch-signup-code.js @@ -21,6 +21,7 @@ LUCKMAIL_PROVIDER, CLOUDFLARE_TEMP_EMAIL_PROVIDER, CLOUD_MAIL_PROVIDER = 'cloudmail', + MAIL_API_PROVIDER = 'mail-api', FREEMAIL_PROVIDER = 'freemail', MOEMAIL_PROVIDER = 'moemail', YYDSMAIL_PROVIDER = 'yydsmail', @@ -126,6 +127,7 @@ || mail.provider === LUCKMAIL_PROVIDER || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER || mail.provider === CLOUD_MAIL_PROVIDER + || mail.provider === MAIL_API_PROVIDER || mail.provider === FREEMAIL_PROVIDER || mail.provider === MOEMAIL_PROVIDER || mail.provider === YYDSMAIL_PROVIDER @@ -157,6 +159,7 @@ LUCKMAIL_PROVIDER, CLOUDFLARE_TEMP_EMAIL_PROVIDER, CLOUD_MAIL_PROVIDER, + MAIL_API_PROVIDER, FREEMAIL_PROVIDER, MOEMAIL_PROVIDER, YYDSMAIL_PROVIDER, @@ -172,7 +175,7 @@ signupProfile, resendIntervalMs: mail.provider === LUCKMAIL_PROVIDER ? 15000 - : ((mail.provider === HOTMAIL_PROVIDER || mail.provider === ICLOUD_API_PROVIDER || mail.provider === '2925') + : ((mail.provider === HOTMAIL_PROVIDER || mail.provider === ICLOUD_API_PROVIDER || mail.provider === MAIL_API_PROVIDER || mail.provider === '2925') ? 0 : STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS), }); diff --git a/background/verification-flow.js b/background/verification-flow.js index 435c36b..b30225c 100644 --- a/background/verification-flow.js +++ b/background/verification-flow.js @@ -1,4 +1,4 @@ -(function attachBackgroundVerificationFlow(root, factory) { +(function attachBackgroundVerificationFlow(root, factory) { root.MultiPageBackgroundVerificationFlow = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundVerificationFlowModule() { const ICLOUD_MAIL_POLL_MIN_ATTEMPTS = 5; @@ -12,6 +12,7 @@ closeConflictingTabsForSource, CLOUDFLARE_TEMP_EMAIL_PROVIDER, CLOUD_MAIL_PROVIDER = 'cloudmail', + MAIL_API_PROVIDER = 'mail-api', FREEMAIL_PROVIDER = 'freemail', ICLOUD_API_PROVIDER = 'icloud-api', MOEMAIL_PROVIDER = 'moemail', @@ -33,6 +34,7 @@ MAIL_2925_VERIFICATION_MAX_ATTEMPTS, pollCloudflareTempEmailVerificationCode, pollCloudMailVerificationCode, + pollMailApiVerificationCode, pollFreemailVerificationCode, pollIcloudApiVerificationCode, pollMoemailVerificationCode, @@ -995,6 +997,13 @@ }, cleanPollOverrides, `轮询${getVerificationCodeLabel(step)}验证码邮箱`); return pollCloudMailVerificationCode(step, state, timedPoll.payload); } + if (mail.provider === MAIL_API_PROVIDER) { + const timedPoll = await applyMailPollingTimeBudget(step, { + ...getVerificationPollPayload(step, state), + ...cleanPollOverrides, + }, cleanPollOverrides, `轮询${getVerificationCodeLabel(step)}验证码邮箱`); + return pollMailApiVerificationCode(step, state, timedPoll.payload); + } if (mail.provider === FREEMAIL_PROVIDER) { const timedPoll = await applyMailPollingTimeBudget(step, { ...getVerificationPollPayload(step, state), diff --git a/mail-provider-utils.js b/mail-provider-utils.js index 2eca8da..94c8c68 100644 --- a/mail-provider-utils.js +++ b/mail-provider-utils.js @@ -10,6 +10,7 @@ } })(typeof self !== 'undefined' ? self : globalThis, function createMailProviderUtils() { const HOTMAIL_PROVIDER = 'hotmail-api'; + const MAIL_API_PROVIDER = 'mail-api'; const GMAIL_PROVIDER = 'gmail'; const ICLOUD_PROVIDER = 'icloud'; const ICLOUD_API_PROVIDER = 'icloud-api'; @@ -32,6 +33,7 @@ const normalized = String(value || '').trim().toLowerCase(); switch (normalized) { case HOTMAIL_PROVIDER: + case MAIL_API_PROVIDER: case ICLOUD_PROVIDER: case ICLOUD_API_PROVIDER: case FREEMAIL_PROVIDER: @@ -135,6 +137,9 @@ if (provider === ICLOUD_API_PROVIDER) { return { provider: ICLOUD_API_PROVIDER, label: 'iCloud API(QQ 转发)' }; } + if (provider === MAIL_API_PROVIDER) { + return { provider: MAIL_API_PROVIDER, label: 'Mail API' }; + } if (provider === FREEMAIL_PROVIDER) { return { provider: FREEMAIL_PROVIDER, label: 'freemail' }; } @@ -191,6 +196,7 @@ return { GMAIL_PROVIDER, + MAIL_API_PROVIDER, HOTMAIL_PROVIDER, ICLOUD_API_PROVIDER, ICLOUD_PROVIDER, diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 5fbaf46..f0dfb80 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -1,4 +1,4 @@ - +
@@ -612,6 +612,7 @@ + @@ -969,6 +970,39 @@ +