From b2c828047698c5c3e470b750c6b2b21e25fff4bd Mon Sep 17 00:00:00 2001 From: Q3CC Date: Sat, 16 May 2026 21:58:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20YYDS=20Mail=20?= =?UTF-8?q?=E9=82=AE=E7=AE=B1=E6=9C=8D=E5=8A=A1=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 120 +++++++ background/message-router.js | 10 + background/runtime-state.js | 1 + background/verification-flow.js | 9 + background/yyds-mail-provider.js | 330 ++++++++++++++++++ mail-provider-utils.js | 6 + sidepanel/sidepanel.html | 25 ++ sidepanel/sidepanel.js | 100 +++++- tests/background-luckmail.test.js | 14 + ...ackground-verification-flow-module.test.js | 35 ++ tests/mail-provider-utils.test.js | 12 + tests/yyds-mail-provider.test.js | 185 ++++++++++ tests/yyds-mail-utils.test.js | 91 +++++ yyds-mail-utils.js | 276 +++++++++++++++ 14 files changed, 1211 insertions(+), 3 deletions(-) create mode 100644 background/yyds-mail-provider.js create mode 100644 tests/yyds-mail-provider.test.js create mode 100644 tests/yyds-mail-utils.test.js create mode 100644 yyds-mail-utils.js diff --git a/background.js b/background.js index 09e1c0b..119e1f4 100644 --- a/background.js +++ b/background.js @@ -58,6 +58,8 @@ importScripts( 'cloudflare-temp-email-utils.js', 'cloudmail-utils.js', 'background/cloudmail-provider.js', + 'yyds-mail-utils.js', + 'background/yyds-mail-provider.js', 'icloud-utils.js', 'mail-provider-utils.js', 'content/activation-utils.js' @@ -261,6 +263,19 @@ const { normalizeCloudMailDomains, normalizeCloudMailMailApiMessages, } = self.CloudMailUtils; +const { + DEFAULT_YYDS_MAIL_BASE_URL, + YYDS_MAIL_PROVIDER, + buildYydsMailHeaders, + joinYydsMailUrl, + normalizeYydsMailAddress, + normalizeYydsMailApiKey, + normalizeYydsMailBaseUrl, + normalizeYydsMailCurrentInbox, + normalizeYydsMailInbox, + normalizeYydsMailMessageDetail, + normalizeYydsMailMessages, +} = self.YydsMailUtils; const { findIcloudAliasByEmail, getConfiguredIcloudHostPreference, @@ -405,6 +420,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 YYDS_MAIL_GENERATOR = YYDS_MAIL_PROVIDER; const CUSTOM_EMAIL_POOL_GENERATOR = 'custom-pool'; const HOTMAIL_MAILBOXES = ['INBOX', 'Junk']; const STOP_ERROR_MESSAGE = '流程已被用户停止。'; @@ -1003,6 +1019,8 @@ const PERSISTED_SETTING_DEFAULTS = { cloudMailReceiveMailbox: '', cloudMailDomain: '', cloudMailDomains: [], + yydsMailApiKey: '', + yydsMailBaseUrl: DEFAULT_YYDS_MAIL_BASE_URL, hotmailAccounts: [], mail2925Accounts: [], paypalAccounts: [], @@ -1139,6 +1157,7 @@ const DEFAULT_STATE = { luckmailPreserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME, currentLuckmailPurchase: null, currentLuckmailMailCursor: null, + currentYydsMailInbox: null, currentPhoneActivation: null, phoneNumber: '', currentPhoneVerificationCode: '', @@ -2114,6 +2133,9 @@ function normalizeEmailGenerator(value = '') { const gmailAliasGenerator = typeof GMAIL_ALIAS_GENERATOR === 'string' ? GMAIL_ALIAS_GENERATOR : 'gmail-alias'; + const yydsMailGenerator = typeof YYDS_MAIL_GENERATOR === 'string' + ? YYDS_MAIL_GENERATOR + : 'yyds-mail'; if (normalized === 'custom' || normalized === 'manual') { return 'custom'; } @@ -2129,6 +2151,7 @@ function normalizeEmailGenerator(value = '') { if (normalized === 'cloudflare') return 'cloudflare'; if (normalized === CLOUDFLARE_TEMP_EMAIL_GENERATOR) return CLOUDFLARE_TEMP_EMAIL_GENERATOR; if (normalized === 'cloudmail') return 'cloudmail'; + if (normalized === yydsMailGenerator) return yydsMailGenerator; return 'duck'; } @@ -2303,6 +2326,15 @@ async function markCurrentRegistrationAccountUsed(state = {}, options = {}) { } } + if (typeof isYydsMailProvider === 'function' && isYydsMailProvider(latestState)) { + const currentInbox = normalizeYydsMailCurrentInbox(latestState.currentYydsMailInbox); + if (currentInbox?.address) { + await clearYydsMailRuntimeState({ clearEmail: true }); + await addLog(`${reasonPrefix}:YYDS Mail 邮箱 ${currentInbox.address} 运行态已清空。`, options.level || 'warn'); + updated = true; + } + } + if (String(latestState.mailProvider || '').trim().toLowerCase() === '2925' && latestState.currentMail2925AccountId) { await patchMail2925Account(latestState.currentMail2925AccountId, { lastUsedAt: Date.now(), @@ -2355,6 +2387,9 @@ function normalizePanelMode(value = '') { function normalizeMailProvider(value = '') { const normalized = String(value || '').trim().toLowerCase(); + const yydsMailProvider = typeof YYDS_MAIL_PROVIDER === 'string' + ? YYDS_MAIL_PROVIDER + : 'yyds-mail'; switch (normalized) { case 'custom': case ICLOUD_PROVIDER: @@ -2363,6 +2398,7 @@ function normalizeMailProvider(value = '') { case LUCKMAIL_PROVIDER: case CLOUDFLARE_TEMP_EMAIL_PROVIDER: case CLOUD_MAIL_PROVIDER: + case yydsMailProvider: case '163': case '163-vip': case '126': @@ -2600,6 +2636,32 @@ const { pollCloudMailVerificationCode, resolveCloudMailPollTargetEmail, } = cloudMailProvider; +const yydsMailProvider = self.MultiPageBackgroundYydsMailProvider.createYydsMailProvider({ + addLog, + buildYydsMailHeaders, + DEFAULT_YYDS_MAIL_BASE_URL, + getState, + joinYydsMailUrl, + normalizeYydsMailAddress, + normalizeYydsMailApiKey, + normalizeYydsMailBaseUrl, + normalizeYydsMailCurrentInbox, + normalizeYydsMailInbox, + normalizeYydsMailMessageDetail, + normalizeYydsMailMessages, + persistRegistrationEmailState, + pickVerificationMessageWithTimeFallback, + setEmailState, + setState, + sleepWithStop, + throwIfStopped, + YYDS_MAIL_PROVIDER, +}); +const { + clearYydsMailRuntimeState, + fetchYydsMailAddress, + pollYydsMailVerificationCode, +} = yydsMailProvider; function normalizeSub2ApiGroupNames(value = '') { const source = Array.isArray(value) @@ -2964,6 +3026,10 @@ function normalizePersistentSettingValue(key, value) { return normalizeCloudMailDomain(value); case 'cloudMailDomains': return normalizeCloudMailDomains(value); + case 'yydsMailApiKey': + return normalizeYydsMailApiKey(value); + case 'yydsMailBaseUrl': + return normalizeYydsMailBaseUrl(value); case 'hotmailAccounts': return normalizeHotmailAccounts(value); case 'mail2925Accounts': @@ -3814,6 +3880,8 @@ async function resetState() { 'luckmailUsedPurchases', 'luckmailPreserveTagId', 'luckmailPreserveTagName', + 'yydsMailApiKey', + 'yydsMailBaseUrl', 'preferredIcloudHost', 'automationWindowId', ...CONTRIBUTION_RUNTIME_KEYS, @@ -3879,6 +3947,9 @@ async function resetState() { luckmailPreserveTagName: String(prev.luckmailPreserveTagName || '').trim() || DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME, currentLuckmailPurchase: null, currentLuckmailMailCursor: null, + yydsMailApiKey: normalizeYydsMailApiKey(prev.yydsMailApiKey ?? persistedSettings.yydsMailApiKey), + yydsMailBaseUrl: normalizeYydsMailBaseUrl(prev.yydsMailBaseUrl ?? persistedSettings.yydsMailBaseUrl), + currentYydsMailInbox: null, // Keep reusable phone activation across round resets so the same number can be reactivated up to maxUses. reusablePhoneActivation, // Keep free reuse phone activation until the user clears or the flow retires it. @@ -4014,6 +4085,16 @@ function isLuckmailProvider(stateOrProvider) { return provider === LUCKMAIL_PROVIDER; } +function isYydsMailProvider(stateOrProvider) { + const provider = typeof stateOrProvider === 'string' + ? stateOrProvider + : stateOrProvider?.mailProvider; + const yydsMailProvider = typeof YYDS_MAIL_PROVIDER === 'string' + ? YYDS_MAIL_PROVIDER + : 'yyds-mail'; + return provider === yydsMailProvider; +} + function isCustomMailProvider(stateOrProvider) { const provider = typeof stateOrProvider === 'string' ? stateOrProvider @@ -10545,6 +10626,9 @@ function getEmailGeneratorLabel(generator) { const gmailAliasGenerator = typeof GMAIL_ALIAS_GENERATOR === 'string' ? GMAIL_ALIAS_GENERATOR : 'gmail-alias'; + const yydsMailGenerator = typeof YYDS_MAIL_GENERATOR === 'string' + ? YYDS_MAIL_GENERATOR + : 'yyds-mail'; if (generator === 'custom') { return '自定义邮箱'; } @@ -10560,6 +10644,7 @@ function getEmailGeneratorLabel(generator) { if (generator === 'cloudflare') return 'Cloudflare 邮箱'; if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) return 'Cloudflare Temp Email'; if (generator === CLOUD_MAIL_GENERATOR) return 'Cloud Mail'; + if (generator === yydsMailGenerator) return 'YYDS Mail'; return 'Duck 邮箱'; } const mail2925SessionManager = self.MultiPageBackgroundMail2925Session?.createMail2925SessionManager({ @@ -10736,7 +10821,20 @@ async function fetchDuckEmail(options = {}) { async function fetchGeneratedEmail(state, options = {}) { const currentState = state || await getState(); + const yydsMailProvider = typeof YYDS_MAIL_PROVIDER === 'string' + ? YYDS_MAIL_PROVIDER + : 'yyds-mail'; + const yydsMailGenerator = typeof YYDS_MAIL_GENERATOR === 'string' + ? YYDS_MAIL_GENERATOR + : 'yyds-mail'; + const requestedMailProvider = normalizeMailProvider(options.mailProvider ?? currentState.mailProvider); + if (requestedMailProvider === yydsMailProvider) { + return fetchYydsMailAddress(currentState, options); + } const generator = normalizeEmailGenerator(options.generator ?? currentState.emailGenerator); + if (generator === yydsMailGenerator) { + return fetchYydsMailAddress(currentState, options); + } if (generator === CLOUD_MAIL_GENERATOR) { return fetchCloudMailAddress(currentState, options); } @@ -11294,6 +11392,12 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) { return purchase.email_address; } + if (isYydsMailProvider(currentState)) { + const email = await fetchYydsMailAddress(currentState, { generateNew: true }); + await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:YYDS Mail 邮箱已就绪:${email}(第 ${attemptRuns} 次尝试)===`, 'ok'); + return email; + } + if (isGeneratedAliasProvider(currentState)) { if (currentState.mailProvider === GMAIL_PROVIDER) { if (!currentState.emailPrefix) { @@ -11425,6 +11529,12 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) { return purchase.email_address; } + if (isYydsMailProvider(currentState)) { + const email = await fetchYydsMailAddress(currentState, { generateNew: true }); + await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:YYDS Mail 邮箱已就绪:${email}(第 ${attemptRuns} 次尝试)===`, 'ok'); + return email; + } + if (isGeneratedAliasProvider(currentState)) { if (isReusableGeneratedAliasEmail(currentState)) { await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:当前已复用 ${currentState.email},将直接继续执行(第 ${attemptRuns} 次尝试)===`, 'info'); @@ -12168,12 +12278,14 @@ const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.create isRetryableContentScriptTransportError, isStopError, LUCKMAIL_PROVIDER, + YYDS_MAIL_PROVIDER, MAIL_2925_VERIFICATION_INTERVAL_MS, MAIL_2925_VERIFICATION_MAX_ATTEMPTS, pollCloudflareTempEmailVerificationCode, pollCloudMailVerificationCode, pollHotmailVerificationCode, pollLuckmailVerificationCode, + pollYydsMailVerificationCode, sendToContentScript, sendToContentScriptResilient, sendToMailContentScriptResilient, @@ -12565,6 +12677,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter clearAutoRunTimerAlarm, clearFreeReusablePhoneActivation, clearLuckmailRuntimeState, + clearYydsMailRuntimeState, clearStopRequest, closeLocalhostCallbackTabs, closeTabsByUrlPrefix, @@ -12621,6 +12734,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter isHotmailProvider, isLocalhostOAuthCallbackUrl, isLuckmailProvider, + isYydsMailProvider, isStopError, isTabAlive, launchAutoRunTimerPlan, @@ -12833,6 +12947,9 @@ async function executeStep3(state) { function getMailConfig(state) { const provider = state.mailProvider || 'qq'; + const yydsMailProvider = typeof YYDS_MAIL_PROVIDER === 'string' + ? YYDS_MAIL_PROVIDER + : 'yyds-mail'; if (provider === 'custom') { return { provider: 'custom', label: '自定义邮箱' }; } @@ -12881,6 +12998,9 @@ function getMailConfig(state) { if (provider === 'cloudmail') { return { provider: 'cloudmail', label: 'Cloud Mail' }; } + if (provider === yydsMailProvider) { + return { provider: yydsMailProvider, label: 'YYDS Mail' }; + } if (provider === '163') { return { source: 'mail-163', url: 'https://mail.163.com/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', label: '163 邮箱' }; } diff --git a/background/message-router.js b/background/message-router.js index 87ff927..3e6caf9 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -18,6 +18,7 @@ clearAutoRunTimerAlarm, clearFreeReusablePhoneActivation, clearLuckmailRuntimeState, + clearYydsMailRuntimeState, clearStopRequest, closeLocalhostCallbackTabs, closeTabsByUrlPrefix, @@ -125,6 +126,7 @@ isHotmailProvider, isLocalhostOAuthCallbackUrl, isLuckmailProvider, + isYydsMailProvider = () => false, isStopError, isTabAlive, launchAutoRunTimerPlan, @@ -572,6 +574,14 @@ await clearLuckmailRuntimeState({ clearEmail: true }); await addLog('当前 LuckMail 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。', 'ok'); } + if ( + typeof markCurrentRegistrationAccountUsed !== 'function' + && isYydsMailProvider(latestState) + && typeof clearYydsMailRuntimeState === 'function' + ) { + await clearYydsMailRuntimeState({ clearEmail: true }); + await addLog('当前 YYDS Mail 邮箱运行态已清空,下轮将重新创建邮箱。', 'ok'); + } const localhostPrefix = buildLocalhostCleanupPrefix(payload.localhostUrl); if (localhostPrefix) { await closeTabsByUrlPrefix(localhostPrefix, { diff --git a/background/runtime-state.js b/background/runtime-state.js index 107a98b..431f239 100644 --- a/background/runtime-state.js +++ b/background/runtime-state.js @@ -105,6 +105,7 @@ luckmail: Object.freeze([ 'currentLuckmailPurchase', 'currentLuckmailMailCursor', + 'currentYydsMailInbox', ]), identity: Object.freeze([ 'resolvedSignupMethod', diff --git a/background/verification-flow.js b/background/verification-flow.js index 8b07c40..b684a52 100644 --- a/background/verification-flow.js +++ b/background/verification-flow.js @@ -24,12 +24,14 @@ isMail2925LimitReachedError, isStopError, LUCKMAIL_PROVIDER, + YYDS_MAIL_PROVIDER = 'yyds-mail', MAIL_2925_VERIFICATION_INTERVAL_MS, MAIL_2925_VERIFICATION_MAX_ATTEMPTS, pollCloudflareTempEmailVerificationCode, pollCloudMailVerificationCode, pollHotmailVerificationCode, pollLuckmailVerificationCode, + pollYydsMailVerificationCode, sendToContentScript, sendToContentScriptResilient, sendToMailContentScriptResilient, @@ -985,6 +987,13 @@ }, cleanPollOverrides, `轮询${getVerificationCodeLabel(step)}验证码邮箱`); return pollCloudMailVerificationCode(step, state, timedPoll.payload); } + if (mail.provider === YYDS_MAIL_PROVIDER) { + const timedPoll = await applyMailPollingTimeBudget(step, { + ...getVerificationPollPayload(step, state), + ...cleanPollOverrides, + }, cleanPollOverrides, `轮询${getVerificationCodeLabel(step)}验证码邮箱`); + return pollYydsMailVerificationCode(step, state, timedPoll.payload); + } if (Number(pollOverrides.resendIntervalMs) > 0) { return pollFreshVerificationCodeWithResendInterval(step, state, mail, pollOverrides); diff --git a/background/yyds-mail-provider.js b/background/yyds-mail-provider.js new file mode 100644 index 0000000..caed454 --- /dev/null +++ b/background/yyds-mail-provider.js @@ -0,0 +1,330 @@ +(function yydsMailProviderModule(root, factory) { + root.MultiPageBackgroundYydsMailProvider = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createYydsMailProviderModule() { + function createYydsMailProvider(deps = {}) { + const { + addLog = async () => {}, + buildYydsMailHeaders, + DEFAULT_YYDS_MAIL_BASE_URL = 'https://maliapi.215.im/v1', + fetchImpl = typeof fetch === 'function' ? fetch.bind(globalThis) : null, + getState = async () => ({}), + joinYydsMailUrl, + normalizeYydsMailAddress, + normalizeYydsMailApiKey, + normalizeYydsMailBaseUrl, + normalizeYydsMailCurrentInbox, + normalizeYydsMailInbox, + normalizeYydsMailMessageDetail, + normalizeYydsMailMessages, + persistRegistrationEmailState = null, + pickVerificationMessageWithTimeFallback, + setEmailState = async () => {}, + setState = async () => {}, + sleepWithStop = async () => {}, + throwIfStopped = () => {}, + YYDS_MAIL_PROVIDER = 'yyds-mail', + } = deps; + + async function persistResolvedEmailState(state = null, email, options = {}) { + if (typeof persistRegistrationEmailState === 'function') { + await persistRegistrationEmailState(state, email, options); + return; + } + await setEmailState(email, options); + } + + function getYydsMailConfig(state = {}) { + return { + apiKey: normalizeYydsMailApiKey(state.yydsMailApiKey), + baseUrl: normalizeYydsMailBaseUrl(state.yydsMailBaseUrl || DEFAULT_YYDS_MAIL_BASE_URL), + currentInbox: normalizeYydsMailCurrentInbox(state.currentYydsMailInbox), + }; + } + + function ensureYydsMailConfig(state = {}, options = {}) { + const { requireApiKey = false, requireInbox = false } = options; + const config = getYydsMailConfig(state); + if (!config.baseUrl) { + throw new Error('YYDS Mail API 地址为空或格式无效。'); + } + if (requireApiKey && !config.apiKey) { + throw new Error('YYDS Mail API Key 为空,请先在侧边栏填写。'); + } + if (requireInbox && (!config.currentInbox?.address || !config.currentInbox?.token)) { + throw new Error('YYDS Mail 当前没有可用邮箱,请先获取邮箱。'); + } + return config; + } + + async function requestYydsMailJson(config, path, options = {}) { + if (!fetchImpl) { + throw new Error('YYDS Mail 当前运行环境不支持 fetch。'); + } + const { + method = 'GET', + payload, + params, + timeoutMs = 20000, + auth = 'temp', + } = options; + const url = joinYydsMailUrl(config.baseUrl, path, params); + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs); + let response; + try { + response = await fetchImpl(url, { + method, + headers: buildYydsMailHeaders(config, { + apiKey: auth === 'apiKey' ? config.apiKey : '', + tempToken: auth === 'temp' ? config.currentInbox?.token : '', + includeConfigApiKey: auth === 'apiKey', + json: payload !== undefined, + }), + body: payload !== undefined ? JSON.stringify(payload || {}) : undefined, + signal: controller.signal, + }); + } catch (err) { + const errorMessage = err?.name === 'AbortError' + ? `YYDS Mail 请求超时(>${Math.round(timeoutMs / 1000)} 秒)` + : `YYDS Mail 请求失败:${err.message}`; + throw new Error(errorMessage); + } finally { + clearTimeout(timeoutId); + } + + const text = await response.text(); + let parsed = {}; + try { + parsed = text ? JSON.parse(text) : {}; + } catch { + parsed = text; + } + + if (!response.ok) { + const payloadError = parsed && typeof parsed === 'object' + ? (parsed.error || parsed.message || parsed.msg || parsed.errorCode) + : ''; + throw new Error(`YYDS Mail 请求失败:${payloadError || text || `HTTP ${response.status}`}`); + } + + if (parsed && typeof parsed === 'object' && parsed.success === false) { + throw new Error(`YYDS Mail 业务错误:${parsed.error || parsed.message || parsed.errorCode || 'unknown_error'}`); + } + + if (parsed && typeof parsed === 'object' && Object.prototype.hasOwnProperty.call(parsed, 'data')) { + return parsed.data; + } + return parsed; + } + + function generateYydsMailLocalPart() { + const letters = 'abcdefghijklmnopqrstuvwxyz'; + const digits = '0123456789'; + const chars = []; + for (let i = 0; i < 6; i += 1) chars.push(letters[Math.floor(Math.random() * letters.length)]); + for (let i = 0; i < 4; i += 1) chars.push(digits[Math.floor(Math.random() * digits.length)]); + for (let i = chars.length - 1; i > 0; i -= 1) { + const j = Math.floor(Math.random() * (i + 1)); + [chars[i], chars[j]] = [chars[j], chars[i]]; + } + return chars.join(''); + } + + async function fetchYydsMailAddress(state, options = {}) { + throwIfStopped(); + const latestState = state || await getState(); + const config = ensureYydsMailConfig(latestState, { requireApiKey: true }); + const localPart = String(options.localPart || options.name || '').trim().toLowerCase() + || generateYydsMailLocalPart(); + const data = await requestYydsMailJson(config, '/accounts', { + method: 'POST', + auth: 'apiKey', + payload: { localPart }, + }); + const inbox = normalizeYydsMailInbox(data); + if (!inbox.address || !inbox.token) { + throw new Error('YYDS Mail 创建邮箱成功,但未返回可用 address/token。'); + } + + await setState({ currentYydsMailInbox: inbox }); + await persistResolvedEmailState(latestState, inbox.address, { + source: `generated:${YYDS_MAIL_PROVIDER}`, + preserveAccountIdentity: Boolean(options?.preserveAccountIdentity), + }); + await addLog(`YYDS Mail:已创建邮箱 ${inbox.address}`, 'ok'); + return inbox.address; + } + + function resolveYydsMailInbox(state = {}) { + const config = getYydsMailConfig(state); + if (config.currentInbox?.address && config.currentInbox?.token) { + return config.currentInbox; + } + return null; + } + + function resolveYydsMailPollTargetEmail(state = {}, pollPayload = {}) { + return normalizeYydsMailAddress(pollPayload.targetEmail) + || resolveYydsMailInbox(state)?.address + || normalizeYydsMailAddress(state.email); + } + + async function listYydsMailMessages(state, options = {}) { + const latestState = state || await getState(); + const inbox = resolveYydsMailInbox(latestState); + const config = { + ...ensureYydsMailConfig(latestState, { requireInbox: true }), + currentInbox: inbox, + }; + const address = normalizeYydsMailAddress(options.address) || inbox.address; + const payload = await requestYydsMailJson(config, '/messages', { + method: 'GET', + auth: 'temp', + params: { + address, + limit: Number(options.limit) || 20, + }, + }); + return { + config, + messages: normalizeYydsMailMessages(payload), + }; + } + + async function getYydsMailMessageDetail(state, messageId, options = {}) { + const latestState = state || await getState(); + const inbox = resolveYydsMailInbox(latestState); + const config = { + ...ensureYydsMailConfig(latestState, { requireInbox: true }), + currentInbox: inbox, + }; + const address = normalizeYydsMailAddress(options.address) || inbox.address; + const payload = await requestYydsMailJson(config, `/messages/${encodeURIComponent(messageId)}`, { + method: 'GET', + auth: 'temp', + params: { address }, + }); + return normalizeYydsMailMessageDetail(payload); + } + + function summarizeYydsMailMessagesForLog(messages) { + return (messages || []) + .slice() + .sort((left, right) => { + const leftTime = Date.parse(left.receivedDateTime || '') || 0; + const rightTime = Date.parse(right.receivedDateTime || '') || 0; + return rightTime - leftTime; + }) + .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 hydrateYydsMailMessageDetails(state, messages, address) { + const details = []; + for (const message of (messages || []).slice(0, 8)) { + throwIfStopped(); + if (!message?.id) { + details.push(message); + continue; + } + try { + details.push(await getYydsMailMessageDetail(state, message.id, { address })); + } catch (err) { + await addLog(`YYDS Mail:读取邮件详情 ${message.id} 失败:${err.message}`, 'warn'); + details.push(message); + } + } + return details.filter(Boolean); + } + + async function pollYydsMailVerificationCode(step, state, pollPayload = {}) { + const latestState = state || await getState(); + const targetEmail = resolveYydsMailPollTargetEmail(latestState, pollPayload); + if (!targetEmail) { + throw new Error('YYDS Mail 轮询前缺少目标邮箱地址,请先获取邮箱。'); + } + + await addLog(`步骤 ${step}:正在轮询 YYDS Mail 邮件(${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 listYydsMailMessages(latestState, { + address: targetEmail, + limit: pollPayload.limit || 20, + }); + const detailedMessages = await hydrateYydsMailMessageDetails(latestState, messages, targetEmail); + const matchResult = pickVerificationMessageWithTimeFallback(detailedMessages, { + 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} 并命中 YYDS Mail 验证码。`, 'warn'); + } + return { + ok: true, + code: match.code, + emailTimestamp: match.receivedAt || Date.now(), + mailId: match.message?.id || '', + }; + } + + lastError = new Error(`步骤 ${step}:暂未在 YYDS Mail 中找到匹配验证码(${attempt}/${maxAttempts})。`); + await addLog(lastError.message, attempt === maxAttempts ? 'warn' : 'info'); + const sample = summarizeYydsMailMessagesForLog(detailedMessages.length ? detailedMessages : messages); + if (sample) { + await addLog(`步骤 ${step}:最近邮件样本:${sample}`, 'info'); + } + } catch (err) { + lastError = err; + await addLog(`步骤 ${step}:YYDS Mail 轮询失败:${err.message}`, 'warn'); + } + if (attempt < maxAttempts) { + await sleepWithStop(intervalMs); + } + } + + throw lastError || new Error(`步骤 ${step}:未在 YYDS Mail 中找到新的匹配验证码。`); + } + + async function clearYydsMailRuntimeState(options = {}) { + await setState({ + currentYydsMailInbox: null, + ...(options.clearEmail ? { email: null } : {}), + }); + } + + return { + clearYydsMailRuntimeState, + ensureYydsMailConfig, + fetchYydsMailAddress, + getYydsMailConfig, + getYydsMailMessageDetail, + listYydsMailMessages, + pollYydsMailVerificationCode, + requestYydsMailJson, + resolveYydsMailPollTargetEmail, + }; + } + + return { + createYydsMailProvider, + }; +}); diff --git a/mail-provider-utils.js b/mail-provider-utils.js index 0a61579..c17a37a 100644 --- a/mail-provider-utils.js +++ b/mail-provider-utils.js @@ -11,6 +11,7 @@ })(typeof self !== 'undefined' ? self : globalThis, function createMailProviderUtils() { const HOTMAIL_PROVIDER = 'hotmail-api'; const GMAIL_PROVIDER = 'gmail'; + const YYDS_MAIL_PROVIDER = 'yyds-mail'; 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'; @@ -26,6 +27,7 @@ const normalized = String(value || '').trim().toLowerCase(); switch (normalized) { case HOTMAIL_PROVIDER: + case YYDS_MAIL_PROVIDER: case '163': case '163-vip': case '126': @@ -76,6 +78,9 @@ if (provider === HOTMAIL_PROVIDER) { return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(微软 Graph)' }; } + if (provider === YYDS_MAIL_PROVIDER) { + return { provider: YYDS_MAIL_PROVIDER, label: 'YYDS Mail' }; + } if (provider === '163') { return { source: 'mail-163', @@ -121,6 +126,7 @@ return { GMAIL_PROVIDER, HOTMAIL_PROVIDER, + YYDS_MAIL_PROVIDER, getIcloudForwardMailConfig, getIcloudForwardMailProviderOptions, getMailProviderConfig, diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 9e6bb54..e3bf0c8 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -420,6 +420,7 @@ + @@ -778,6 +779,29 @@ +