feat: 新增 YYDS Mail 邮箱服务端点
This commit is contained in:
+120
@@ -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 邮箱' };
|
||||
}
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
luckmail: Object.freeze([
|
||||
'currentLuckmailPurchase',
|
||||
'currentLuckmailMailCursor',
|
||||
'currentYydsMailInbox',
|
||||
]),
|
||||
identity: Object.freeze([
|
||||
'resolvedSignupMethod',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -420,6 +420,7 @@
|
||||
<option value="custom">自定义邮箱</option>
|
||||
<option value="hotmail-api">Hotmail(账号池)</option>
|
||||
<option value="luckmail-api">LuckMail(API 购邮)</option>
|
||||
<option value="yyds-mail">YYDS Mail</option>
|
||||
<option value="icloud">iCloud 邮箱</option>
|
||||
<option value="163">163 邮箱 (mail.163.com) 版本老旧,请自行维护使用</option>
|
||||
<option value="163-vip">163 VIP 邮箱 (vip.163.com) 版本老旧,请自行维护使用</option>
|
||||
@@ -778,6 +779,29 @@
|
||||
<input type="text" id="input-cloud-mail-domain" class="data-input" placeholder="例如 mail.example.com" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="yyds-mail-section" class="data-card hotmail-card" style="display:none;">
|
||||
<div class="section-mini-header">
|
||||
<div class="section-mini-copy">
|
||||
<span class="section-label">YYDS Mail</span>
|
||||
<span class="data-value">自动创建临时邮箱并通过 API 收取验证码</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row">
|
||||
<span class="data-label">API Key</span>
|
||||
<div class="input-with-icon">
|
||||
<input type="password" id="input-yyds-mail-api-key" class="data-input data-input-with-icon mono"
|
||||
placeholder="请输入 YYDS Mail API Key(AC-...)" />
|
||||
<button id="btn-toggle-yyds-mail-api-key" class="input-icon-btn" type="button"
|
||||
data-password-toggle="input-yyds-mail-api-key" data-show-label="显示 YYDS Mail API Key"
|
||||
data-hide-label="隐藏 YYDS Mail API Key" aria-label="显示 YYDS Mail API Key" title="显示 YYDS Mail API Key"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row">
|
||||
<span class="data-label">Base URL</span>
|
||||
<input type="text" id="input-yyds-mail-base-url" class="data-input mono"
|
||||
placeholder="https://maliapi.215.im/v1" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="hotmail-section" class="data-card hotmail-card" style="display:none;">
|
||||
<div class="section-mini-header">
|
||||
<div class="section-mini-copy">
|
||||
@@ -1764,6 +1788,7 @@
|
||||
<script src="../mail-provider-utils.js"></script>
|
||||
<script src="../hotmail-utils.js"></script>
|
||||
<script src="../luckmail-utils.js"></script>
|
||||
<script src="../yyds-mail-utils.js"></script>
|
||||
<script src="../shared/flow-capabilities.js"></script>
|
||||
<script src="../data/step-definitions.js"></script>
|
||||
<script src="update-service.js"></script>
|
||||
|
||||
+97
-3
@@ -274,6 +274,9 @@ const inputCloudMailAdminEmail = document.getElementById('input-cloud-mail-admin
|
||||
const inputCloudMailAdminPassword = document.getElementById('input-cloud-mail-admin-password');
|
||||
const inputCloudMailReceiveMailbox = document.getElementById('input-cloud-mail-receive-mailbox');
|
||||
const inputCloudMailDomain = document.getElementById('input-cloud-mail-domain');
|
||||
const yydsMailSection = document.getElementById('yyds-mail-section');
|
||||
const inputYydsMailApiKey = document.getElementById('input-yyds-mail-api-key');
|
||||
const inputYydsMailBaseUrl = document.getElementById('input-yyds-mail-base-url');
|
||||
const hotmailSection = document.getElementById('hotmail-section');
|
||||
const mail2925Section = document.getElementById('mail2925-section');
|
||||
const luckmailSection = document.getElementById('luckmail-section');
|
||||
@@ -971,9 +974,11 @@ const ICLOUD_PROVIDER = 'icloud';
|
||||
const GMAIL_PROVIDER = 'gmail';
|
||||
const GMAIL_ALIAS_GENERATOR = 'gmail-alias';
|
||||
const LUCKMAIL_PROVIDER = 'luckmail-api';
|
||||
const YYDS_MAIL_PROVIDER = 'yyds-mail';
|
||||
const CUSTOM_EMAIL_POOL_GENERATOR = 'custom-pool';
|
||||
const DEFAULT_LUCKMAIL_BASE_URL = 'https://mails.luckyous.com';
|
||||
const DEFAULT_LUCKMAIL_EMAIL_TYPE = 'ms_graph';
|
||||
const DEFAULT_YYDS_MAIL_BASE_URL = window.YydsMailUtils?.DEFAULT_YYDS_MAIL_BASE_URL || 'https://maliapi.215.im/v1';
|
||||
const DISPLAY_TIMEZONE = 'Asia/Shanghai';
|
||||
const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = 'http://127.0.0.1:17373';
|
||||
const CONTRIBUTION_UPLOAD_URL = 'https://flowpilot.qlhazycoder.top/';
|
||||
@@ -1210,6 +1215,17 @@ function getCurrentRegistrationEmailUiCopy() {
|
||||
if (isCustomMailProvider()) {
|
||||
return getCustomMailProviderUiCopy();
|
||||
}
|
||||
const useYydsMail = typeof isYydsMailProvider === 'function'
|
||||
? isYydsMailProvider()
|
||||
: String(selectMailProvider.value || '').trim().toLowerCase() === 'yyds-mail';
|
||||
if (useYydsMail) {
|
||||
return {
|
||||
buttonLabel: '获取',
|
||||
placeholder: '点击获取 YYDS Mail 邮箱,或手动粘贴邮箱',
|
||||
successVerb: '获取',
|
||||
label: 'YYDS Mail',
|
||||
};
|
||||
}
|
||||
if (usesGeneratedAliasMailProvider()) {
|
||||
return getManagedAliasProviderUiCopy();
|
||||
}
|
||||
@@ -1554,6 +1570,11 @@ const MAIL_PROVIDER_LOGIN_CONFIGS = {
|
||||
url: 'https://github.com/QLHazyCoder/cloudflare_temp_email',
|
||||
buttonLabel: '部署',
|
||||
},
|
||||
[YYDS_MAIL_PROVIDER]: {
|
||||
label: 'YYDS Mail',
|
||||
url: 'https://vip.215.im/docs',
|
||||
buttonLabel: '文档',
|
||||
},
|
||||
'2925': {
|
||||
label: '2925 邮箱',
|
||||
url: 'https://2925.com/#/mailList',
|
||||
@@ -2860,8 +2881,12 @@ function restoreCustomEmailPoolEntriesFromState(state = {}) {
|
||||
}
|
||||
|
||||
function usesCustomEmailPoolGenerator(provider = selectMailProvider.value) {
|
||||
const providerUsesYydsMail = typeof isYydsMailProvider === 'function'
|
||||
? isYydsMailProvider(provider)
|
||||
: String(provider || '').trim().toLowerCase() === 'yyds-mail';
|
||||
return !isCustomMailProvider(provider)
|
||||
&& !isLuckmailProvider(provider)
|
||||
&& !providerUsesYydsMail
|
||||
&& getSelectedEmailGenerator() === CUSTOM_EMAIL_POOL_GENERATOR;
|
||||
}
|
||||
|
||||
@@ -3207,10 +3232,25 @@ function applyCloudMailSettingsState(state = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function applyYydsMailSettingsState(state = {}) {
|
||||
const normalizeYydsBaseUrlValue = typeof normalizeYydsMailBaseUrl === 'function'
|
||||
? normalizeYydsMailBaseUrl
|
||||
: ((value) => String(value || '').trim() || 'https://maliapi.215.im/v1');
|
||||
if (inputYydsMailApiKey) {
|
||||
inputYydsMailApiKey.value = state?.yydsMailApiKey || '';
|
||||
}
|
||||
if (inputYydsMailBaseUrl) {
|
||||
inputYydsMailBaseUrl.value = normalizeYydsBaseUrlValue(state?.yydsMailBaseUrl);
|
||||
}
|
||||
}
|
||||
|
||||
function collectSettingsPayload() {
|
||||
const defaultGpcHelperApiUrl = typeof DEFAULT_GPC_HELPER_API_URL !== 'undefined'
|
||||
? DEFAULT_GPC_HELPER_API_URL
|
||||
: 'https://gpc.qlhazycoder.top';
|
||||
const normalizeYydsBaseUrlValue = typeof normalizeYydsMailBaseUrl === 'function'
|
||||
? normalizeYydsMailBaseUrl
|
||||
: ((value) => String(value || '').trim() || 'https://maliapi.215.im/v1');
|
||||
const { domains, activeDomain } = getCloudflareDomainsFromState();
|
||||
const selectedCloudflareDomain = normalizeCloudflareDomainValue(
|
||||
!cloudflareDomainEditMode ? selectCfDomain.value : activeDomain
|
||||
@@ -3981,6 +4021,8 @@ function collectSettingsPayload() {
|
||||
cloudMailAdminPassword: (typeof inputCloudMailAdminPassword !== 'undefined' && inputCloudMailAdminPassword) ? inputCloudMailAdminPassword.value : '',
|
||||
cloudMailReceiveMailbox: normalizeCloudMailReceiveMailboxInput((typeof inputCloudMailReceiveMailbox !== 'undefined' && inputCloudMailReceiveMailbox) ? inputCloudMailReceiveMailbox.value : ''),
|
||||
cloudMailDomain: normalizeCloudMailDomainInput((typeof inputCloudMailDomain !== 'undefined' && inputCloudMailDomain) ? inputCloudMailDomain.value : ''),
|
||||
yydsMailApiKey: (typeof inputYydsMailApiKey !== 'undefined' && inputYydsMailApiKey) ? inputYydsMailApiKey.value.trim() : '',
|
||||
yydsMailBaseUrl: normalizeYydsBaseUrlValue((typeof inputYydsMailBaseUrl !== 'undefined' && inputYydsMailBaseUrl) ? inputYydsMailBaseUrl.value : ''),
|
||||
autoRunSkipFailures: inputAutoSkipFailures.checked,
|
||||
autoRunFallbackThreadIntervalMinutes: normalizeAutoRunThreadIntervalMinutes(inputAutoSkipFailuresThreadIntervalMinutes.value),
|
||||
step6CookieCleanupEnabled: typeof inputStep6CookieCleanupEnabled !== 'undefined' && inputStep6CookieCleanupEnabled
|
||||
@@ -9359,8 +9401,11 @@ function applySettingsState(state) {
|
||||
}
|
||||
inputCodex2ApiUrl.value = state?.codex2apiUrl || '';
|
||||
inputCodex2ApiAdminKey.value = state?.codex2apiAdminKey || '';
|
||||
const yydsMailProvider = typeof YYDS_MAIL_PROVIDER === 'string'
|
||||
? YYDS_MAIL_PROVIDER
|
||||
: 'yyds-mail';
|
||||
const restoredMailProvider = isCustomMailProvider(state?.mailProvider)
|
||||
|| [ICLOUD_PROVIDER, 'hotmail-api', GMAIL_PROVIDER, 'luckmail-api', '163', '163-vip', '126', 'qq', 'inbucket', '2925', 'cloudflare-temp-email', 'cloudmail'].includes(String(state?.mailProvider || '').trim())
|
||||
|| [ICLOUD_PROVIDER, 'hotmail-api', GMAIL_PROVIDER, 'luckmail-api', yydsMailProvider, '163', '163-vip', '126', 'qq', 'inbucket', '2925', 'cloudflare-temp-email', 'cloudmail'].includes(String(state?.mailProvider || '').trim())
|
||||
? String(state?.mailProvider || '163').trim()
|
||||
: (String(state?.emailGenerator || '').trim().toLowerCase() === 'custom'
|
||||
|| String(state?.emailGenerator || '').trim().toLowerCase() === 'manual'
|
||||
@@ -9442,6 +9487,9 @@ function applySettingsState(state) {
|
||||
if (typeof applyCloudMailSettingsState === 'function') {
|
||||
applyCloudMailSettingsState(state);
|
||||
}
|
||||
if (typeof applyYydsMailSettingsState === 'function') {
|
||||
applyYydsMailSettingsState(state);
|
||||
}
|
||||
renderCloudflareDomainOptions(state?.cloudflareDomain || '');
|
||||
setCloudflareDomainEditMode(false, { clearInput: true });
|
||||
inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
|
||||
@@ -10157,6 +10205,13 @@ function isLuckmailProvider(provider = selectMailProvider.value) {
|
||||
return String(provider || '').trim().toLowerCase() === LUCKMAIL_PROVIDER;
|
||||
}
|
||||
|
||||
function isYydsMailProvider(provider = selectMailProvider.value) {
|
||||
const yydsMailProvider = typeof YYDS_MAIL_PROVIDER === 'string'
|
||||
? YYDS_MAIL_PROVIDER
|
||||
: 'yyds-mail';
|
||||
return String(provider || '').trim().toLowerCase() === yydsMailProvider;
|
||||
}
|
||||
|
||||
function isIcloudMailProvider(provider = selectMailProvider.value) {
|
||||
return String(provider || '').trim().toLowerCase() === ICLOUD_PROVIDER;
|
||||
}
|
||||
@@ -10188,6 +10243,29 @@ function normalizeLuckmailEmailType(value = '') {
|
||||
: DEFAULT_LUCKMAIL_EMAIL_TYPE;
|
||||
}
|
||||
|
||||
function normalizeYydsMailBaseUrl(value = '') {
|
||||
if (window.YydsMailUtils?.normalizeYydsMailBaseUrl) {
|
||||
return window.YydsMailUtils.normalizeYydsMailBaseUrl(value);
|
||||
}
|
||||
const trimmed = String(value || '').trim();
|
||||
if (!trimmed) {
|
||||
return DEFAULT_YYDS_MAIL_BASE_URL;
|
||||
}
|
||||
const candidate = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
||||
try {
|
||||
const parsed = new URL(candidate);
|
||||
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
||||
return DEFAULT_YYDS_MAIL_BASE_URL;
|
||||
}
|
||||
parsed.hash = '';
|
||||
parsed.search = '';
|
||||
parsed.pathname = parsed.pathname === '/' ? '' : parsed.pathname.replace(/\/+$/, '');
|
||||
return `${parsed.origin}${parsed.pathname}` || DEFAULT_YYDS_MAIL_BASE_URL;
|
||||
} catch {
|
||||
return DEFAULT_YYDS_MAIL_BASE_URL;
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedEmailGenerator() {
|
||||
const generator = String(selectEmailGenerator.value || '').trim().toLowerCase();
|
||||
if (generator === 'custom' || generator === 'manual') {
|
||||
@@ -10678,10 +10756,13 @@ function updateMailProviderUI() {
|
||||
const useInbucket = selectMailProvider.value === 'inbucket';
|
||||
const useHotmail = selectMailProvider.value === 'hotmail-api';
|
||||
const useLuckmail = canShowLuckmail && isLuckmailProvider();
|
||||
const useYydsMail = typeof isYydsMailProvider === 'function'
|
||||
? isYydsMailProvider()
|
||||
: String(selectMailProvider.value || '').trim().toLowerCase() === 'yyds-mail';
|
||||
const useCustomEmail = isCustomMailProvider();
|
||||
const useCustomMailProviderPool = useCustomEmail && usesCustomMailProviderPool(selectMailProvider.value);
|
||||
const useIcloudProvider = isIcloudMailProvider();
|
||||
const useEmailGenerator = !useHotmail && !useLuckmail && !useCustomEmail && (!useGeneratedAlias || useGmail);
|
||||
const useEmailGenerator = !useHotmail && !useLuckmail && !useYydsMail && !useCustomEmail && (!useGeneratedAlias || useGmail);
|
||||
const useCloudflareTempEmailProvider = selectMailProvider.value === 'cloudflare-temp-email';
|
||||
const useCloudMailProvider = selectMailProvider.value === 'cloudmail';
|
||||
const aliasUiCopy = useGeneratedAlias
|
||||
@@ -10751,6 +10832,9 @@ function updateMailProviderUI() {
|
||||
if (typeof cloudMailSection !== 'undefined' && cloudMailSection) {
|
||||
cloudMailSection.style.display = showCloudMailSettings ? '' : 'none';
|
||||
}
|
||||
if (typeof yydsMailSection !== 'undefined' && yydsMailSection) {
|
||||
yydsMailSection.style.display = useYydsMail ? '' : 'none';
|
||||
}
|
||||
if (typeof rowCloudMailBaseUrl !== 'undefined' && rowCloudMailBaseUrl) rowCloudMailBaseUrl.style.display = showCloudMailSettings ? '' : 'none';
|
||||
if (typeof rowCloudMailAdminEmail !== 'undefined' && rowCloudMailAdminEmail) rowCloudMailAdminEmail.style.display = showCloudMailSettings ? '' : 'none';
|
||||
if (typeof rowCloudMailAdminPassword !== 'undefined' && rowCloudMailAdminPassword) rowCloudMailAdminPassword.style.display = showCloudMailSettings ? '' : 'none';
|
||||
@@ -10818,7 +10902,7 @@ function updateMailProviderUI() {
|
||||
}
|
||||
inputEmailPrefix.style.display = '';
|
||||
inputEmailPrefix.readOnly = false;
|
||||
selectEmailGenerator.disabled = useHotmail || useLuckmail || useCustomEmail || (useGeneratedAlias && !useGmail);
|
||||
selectEmailGenerator.disabled = useHotmail || useLuckmail || useYydsMail || useCustomEmail || (useGeneratedAlias && !useGmail);
|
||||
if (useGmail) {
|
||||
labelEmailPrefix.textContent = 'Gmail 原邮箱';
|
||||
inputEmailPrefix.placeholder = '例如 yourname@gmail.com';
|
||||
@@ -12945,6 +13029,16 @@ inputVpsPassword.addEventListener('blur', () => {
|
||||
});
|
||||
});
|
||||
|
||||
[inputYydsMailApiKey, inputYydsMailBaseUrl].forEach((input) => {
|
||||
input?.addEventListener('input', () => {
|
||||
markSettingsDirty(true);
|
||||
scheduleSettingsAutoSave();
|
||||
});
|
||||
input?.addEventListener('blur', () => {
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
});
|
||||
});
|
||||
|
||||
selectLuckmailEmailType?.addEventListener('change', () => {
|
||||
markSettingsDirty(true);
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
|
||||
@@ -688,12 +688,15 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
" luckmailBaseUrl: 'https://mails.luckyous.com',",
|
||||
" luckmailEmailType: 'ms_graph',",
|
||||
" luckmailDomain: '',",
|
||||
" yydsMailApiKey: '',",
|
||||
" yydsMailBaseUrl: 'https://maliapi.215.im/v1',",
|
||||
" panelMode: 'cpa',",
|
||||
' luckmailUsedPurchases: {},',
|
||||
' luckmailPreserveTagId: 0,',
|
||||
" luckmailPreserveTagName: '保留',",
|
||||
" currentLuckmailPurchase: { token: 'stale' },",
|
||||
" currentLuckmailMailCursor: { messageId: 'stale' },",
|
||||
" currentYydsMailInbox: { address: 'stale@example.com', token: 'stale-token' },",
|
||||
' currentPhoneActivation: null,',
|
||||
' reusablePhoneActivation: null,',
|
||||
' email: null,',
|
||||
@@ -725,6 +728,12 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
'function normalizeLuckmailUsedPurchases(value) {',
|
||||
' return value || {};',
|
||||
'}',
|
||||
'function normalizeYydsMailApiKey(value) {',
|
||||
" return String(value || '').trim();",
|
||||
'}',
|
||||
'function normalizeYydsMailBaseUrl(value) {',
|
||||
" return (String(value || '').trim() || 'https://maliapi.215.im/v1').replace(/\\/$/, '');",
|
||||
'}',
|
||||
'async function getPersistedSettings() {',
|
||||
" return { mailProvider: '163' };",
|
||||
'}',
|
||||
@@ -752,6 +761,8 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
" luckmailUsedPurchases: { 88: true },",
|
||||
' luckmailPreserveTagId: 9,',
|
||||
" luckmailPreserveTagName: '保留',",
|
||||
" yydsMailApiKey: 'AC-session',",
|
||||
" yydsMailBaseUrl: 'https://maliapi.215.im/v1/',",
|
||||
' };',
|
||||
' },',
|
||||
' async clear() {',
|
||||
@@ -786,6 +797,9 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
assert.equal(snapshot.storedPayload.luckmailPreserveTagName, '保留');
|
||||
assert.equal(snapshot.storedPayload.currentLuckmailPurchase, null);
|
||||
assert.equal(snapshot.storedPayload.currentLuckmailMailCursor, null);
|
||||
assert.equal(snapshot.storedPayload.yydsMailApiKey, 'AC-session');
|
||||
assert.equal(snapshot.storedPayload.yydsMailBaseUrl, 'https://maliapi.215.im/v1');
|
||||
assert.equal(snapshot.storedPayload.currentYydsMailInbox, null);
|
||||
assert.deepStrictEqual(snapshot.storedPayload.reusablePhoneActivation, {
|
||||
activationId: 'rx-001',
|
||||
phoneNumber: '66951112222',
|
||||
|
||||
@@ -15,3 +15,38 @@ test('verification flow module exposes a factory', () => {
|
||||
|
||||
assert.equal(typeof api?.createVerificationFlowHelpers, 'function');
|
||||
});
|
||||
|
||||
test('verification flow routes YYDS Mail provider to background poller', async () => {
|
||||
const source = fs.readFileSync('background/verification-flow.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundVerificationFlow;`)(globalScope);
|
||||
const pollCalls = [];
|
||||
const helpers = api.createVerificationFlowHelpers({
|
||||
addLog: async () => {},
|
||||
buildVerificationPollPayload: () => ({ maxAttempts: 1, intervalMs: 1 }),
|
||||
getState: async () => ({}),
|
||||
getTabId: async () => 1,
|
||||
isStopError: () => false,
|
||||
pollYydsMailVerificationCode: async (step, state, payload) => {
|
||||
pollCalls.push({ step, state, payload });
|
||||
return { ok: true, code: '123456', emailTimestamp: 1, mailId: 'msg-1' };
|
||||
},
|
||||
sendToContentScript: async () => ({}),
|
||||
setState: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
YYDS_MAIL_PROVIDER: 'yyds-mail',
|
||||
});
|
||||
|
||||
const result = await helpers.pollFreshVerificationCode(
|
||||
4,
|
||||
{ mailProvider: 'yyds-mail' },
|
||||
{ provider: 'yyds-mail', label: 'YYDS Mail' },
|
||||
{ disableTimeBudgetCap: true }
|
||||
);
|
||||
|
||||
assert.equal(result.code, '123456');
|
||||
assert.equal(pollCalls.length, 1);
|
||||
assert.equal(pollCalls[0].step, 4);
|
||||
assert.equal(pollCalls[0].payload.maxAttempts, 1);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ const assert = require('node:assert/strict');
|
||||
|
||||
const {
|
||||
HOTMAIL_PROVIDER,
|
||||
YYDS_MAIL_PROVIDER,
|
||||
getIcloudForwardMailConfig,
|
||||
getIcloudForwardMailProviderOptions,
|
||||
getMailProviderConfig,
|
||||
@@ -14,6 +15,7 @@ const {
|
||||
test('normalizeMailProvider accepts 126 and falls back to 163', () => {
|
||||
assert.equal(normalizeMailProvider('126'), '126');
|
||||
assert.equal(normalizeMailProvider('163-vip'), '163-vip');
|
||||
assert.equal(normalizeMailProvider(YYDS_MAIL_PROVIDER), YYDS_MAIL_PROVIDER);
|
||||
assert.equal(normalizeMailProvider('unknown-provider'), '163');
|
||||
});
|
||||
|
||||
@@ -38,6 +40,16 @@ test('getMailProviderConfig preserves the hotmail provider sentinel', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('getMailProviderConfig preserves the YYDS Mail provider sentinel', () => {
|
||||
assert.deepEqual(
|
||||
getMailProviderConfig({ mailProvider: YYDS_MAIL_PROVIDER }),
|
||||
{
|
||||
provider: YYDS_MAIL_PROVIDER,
|
||||
label: 'YYDS Mail',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('iCloud forward mailbox helpers normalize and expose supported providers', () => {
|
||||
assert.equal(normalizeIcloudTargetMailboxType('forward-mailbox'), 'forward-mailbox');
|
||||
assert.equal(normalizeIcloudTargetMailboxType('unknown'), 'icloud-inbox');
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const utils = require('../yyds-mail-utils.js');
|
||||
require('../background/yyds-mail-provider.js');
|
||||
|
||||
function createProviderApi(options = {}) {
|
||||
const {
|
||||
state = {
|
||||
yydsMailApiKey: 'AC-demo',
|
||||
yydsMailBaseUrl: 'https://maliapi.215.im/v1',
|
||||
currentYydsMailInbox: null,
|
||||
email: '',
|
||||
},
|
||||
fetchImpl,
|
||||
} = options;
|
||||
let currentState = { ...state };
|
||||
const logs = [];
|
||||
const persistCalls = [];
|
||||
const stateUpdates = [];
|
||||
const calls = [];
|
||||
|
||||
const api = globalThis.MultiPageBackgroundYydsMailProvider.createYydsMailProvider({
|
||||
addLog: async (message, level) => logs.push({ message, level }),
|
||||
buildYydsMailHeaders: utils.buildYydsMailHeaders,
|
||||
DEFAULT_YYDS_MAIL_BASE_URL: utils.DEFAULT_YYDS_MAIL_BASE_URL,
|
||||
fetchImpl: fetchImpl || (async (url, request = {}) => {
|
||||
calls.push({
|
||||
url: String(url),
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.body ? JSON.parse(request.body) : undefined,
|
||||
});
|
||||
if (String(url).endsWith('/accounts')) {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
id: 'inbox-1',
|
||||
address: 'fresh@example.com',
|
||||
token: 'temp-token',
|
||||
expiresAt: '2026-03-15T12:00:00Z',
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (String(url).includes('/messages/msg-1')) {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
id: 'msg-1',
|
||||
from: { address: 'noreply@tm.openai.com' },
|
||||
subject: 'OpenAI verification code',
|
||||
text: 'Your ChatGPT code is 987654.',
|
||||
createdAt: '2026-03-14T12:30:00Z',
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (String(url).includes('/messages')) {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({
|
||||
success: true,
|
||||
data: {
|
||||
messages: [{
|
||||
id: 'msg-1',
|
||||
from: { address: 'noreply@tm.openai.com' },
|
||||
subject: 'OpenAI verification code',
|
||||
createdAt: '2026-03-14T12:30:00Z',
|
||||
}],
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected URL ${url}`);
|
||||
}),
|
||||
getState: async () => currentState,
|
||||
joinYydsMailUrl: utils.joinYydsMailUrl,
|
||||
normalizeYydsMailAddress: utils.normalizeYydsMailAddress,
|
||||
normalizeYydsMailApiKey: utils.normalizeYydsMailApiKey,
|
||||
normalizeYydsMailBaseUrl: utils.normalizeYydsMailBaseUrl,
|
||||
normalizeYydsMailCurrentInbox: utils.normalizeYydsMailCurrentInbox,
|
||||
normalizeYydsMailInbox: utils.normalizeYydsMailInbox,
|
||||
normalizeYydsMailMessageDetail: utils.normalizeYydsMailMessageDetail,
|
||||
normalizeYydsMailMessages: utils.normalizeYydsMailMessages,
|
||||
persistRegistrationEmailState: async (callState, email, persistOptions) => {
|
||||
persistCalls.push({ state: callState, email, options: persistOptions });
|
||||
currentState = { ...currentState, email };
|
||||
},
|
||||
pickVerificationMessageWithTimeFallback: (messages) => {
|
||||
const match = messages.find((message) => message.verification_code);
|
||||
return match
|
||||
? {
|
||||
match: {
|
||||
code: match.verification_code,
|
||||
receivedAt: Date.parse(match.receivedDateTime),
|
||||
message: match,
|
||||
},
|
||||
usedRelaxedFilters: false,
|
||||
usedTimeFallback: false,
|
||||
}
|
||||
: { match: null, usedRelaxedFilters: false, usedTimeFallback: false };
|
||||
},
|
||||
setState: async (updates) => {
|
||||
stateUpdates.push(updates);
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
YYDS_MAIL_PROVIDER: utils.YYDS_MAIL_PROVIDER,
|
||||
});
|
||||
|
||||
return {
|
||||
...api,
|
||||
snapshot() {
|
||||
return { calls, currentState, logs, persistCalls, stateUpdates };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('fetchYydsMailAddress creates an inbox with localPart only and stores the returned address/token', async () => {
|
||||
const api = createProviderApi();
|
||||
const email = await api.fetchYydsMailAddress(null, { localPart: 'fresh' });
|
||||
const snapshot = api.snapshot();
|
||||
|
||||
assert.equal(email, 'fresh@example.com');
|
||||
assert.equal(snapshot.calls[0].url, 'https://maliapi.215.im/v1/accounts');
|
||||
assert.equal(snapshot.calls[0].method, 'POST');
|
||||
assert.deepEqual(snapshot.calls[0].body, { localPart: 'fresh' });
|
||||
assert.equal(snapshot.calls[0].headers['X-API-Key'], 'AC-demo');
|
||||
assert.equal(snapshot.calls[0].headers.Authorization, undefined);
|
||||
assert.equal(snapshot.currentState.currentYydsMailInbox.address, 'fresh@example.com');
|
||||
assert.equal(snapshot.currentState.currentYydsMailInbox.token, 'temp-token');
|
||||
assert.equal(snapshot.persistCalls[0].email, 'fresh@example.com');
|
||||
});
|
||||
|
||||
test('pollYydsMailVerificationCode lists messages with temp token and reads detail before matching code', async () => {
|
||||
const api = createProviderApi({
|
||||
state: {
|
||||
yydsMailApiKey: 'AC-demo',
|
||||
yydsMailBaseUrl: 'https://maliapi.215.im/v1',
|
||||
currentYydsMailInbox: {
|
||||
id: 'inbox-1',
|
||||
address: 'fresh@example.com',
|
||||
token: 'temp-token',
|
||||
},
|
||||
email: 'fresh@example.com',
|
||||
},
|
||||
});
|
||||
|
||||
const result = await api.pollYydsMailVerificationCode(4, null, {
|
||||
maxAttempts: 1,
|
||||
intervalMs: 1,
|
||||
senderFilters: ['openai'],
|
||||
subjectFilters: ['code'],
|
||||
});
|
||||
const snapshot = api.snapshot();
|
||||
const listCall = snapshot.calls.find((call) => call.url.includes('/messages?'));
|
||||
const detailCall = snapshot.calls.find((call) => call.url.includes('/messages/msg-1'));
|
||||
|
||||
assert.equal(result.code, '987654');
|
||||
assert.equal(listCall.headers.Authorization, 'Bearer temp-token');
|
||||
assert.equal(listCall.headers['X-API-Key'], undefined);
|
||||
assert.match(listCall.url, /address=fresh%40example\.com/);
|
||||
assert.equal(detailCall.headers.Authorization, 'Bearer temp-token');
|
||||
});
|
||||
|
||||
test('clearYydsMailRuntimeState clears current inbox and optionally current email', async () => {
|
||||
const api = createProviderApi({
|
||||
state: {
|
||||
currentYydsMailInbox: { address: 'fresh@example.com', token: 'temp-token' },
|
||||
email: 'fresh@example.com',
|
||||
},
|
||||
});
|
||||
|
||||
await api.clearYydsMailRuntimeState({ clearEmail: true });
|
||||
assert.deepEqual(api.snapshot().stateUpdates.at(-1), {
|
||||
currentYydsMailInbox: null,
|
||||
email: null,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const {
|
||||
DEFAULT_YYDS_MAIL_BASE_URL,
|
||||
buildYydsMailHeaders,
|
||||
joinYydsMailUrl,
|
||||
normalizeYydsMailBaseUrl,
|
||||
normalizeYydsMailInbox,
|
||||
normalizeYydsMailMessageDetail,
|
||||
normalizeYydsMailMessages,
|
||||
} = require('../yyds-mail-utils.js');
|
||||
|
||||
test('normalizeYydsMailBaseUrl defaults to official v1 endpoint and trims trailing slash', () => {
|
||||
assert.equal(normalizeYydsMailBaseUrl(''), DEFAULT_YYDS_MAIL_BASE_URL);
|
||||
assert.equal(normalizeYydsMailBaseUrl('maliapi.215.im/v1/'), DEFAULT_YYDS_MAIL_BASE_URL);
|
||||
assert.equal(normalizeYydsMailBaseUrl('http://127.0.0.1:8787/v1/'), 'http://127.0.0.1:8787/v1');
|
||||
});
|
||||
|
||||
test('buildYydsMailHeaders separates API key creation auth from temp-token read auth', () => {
|
||||
assert.deepEqual(buildYydsMailHeaders({ apiKey: 'AC-demo' }, { json: true }), {
|
||||
'X-API-Key': 'AC-demo',
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
buildYydsMailHeaders(
|
||||
{ apiKey: 'AC-demo', token: 'temp-token' },
|
||||
{ tempToken: 'temp-token', includeConfigApiKey: false }
|
||||
),
|
||||
{
|
||||
Authorization: 'Bearer temp-token',
|
||||
Accept: 'application/json',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('joinYydsMailUrl appends query parameters to the normalized base URL', () => {
|
||||
assert.equal(
|
||||
joinYydsMailUrl('https://maliapi.215.im/v1/', '/messages', { address: 'a+b@example.com', limit: 20 }),
|
||||
'https://maliapi.215.im/v1/messages?address=a%2Bb%40example.com&limit=20'
|
||||
);
|
||||
});
|
||||
|
||||
test('normalizeYydsMailInbox keeps the final returned address and temp token', () => {
|
||||
assert.deepEqual(normalizeYydsMailInbox({
|
||||
id: 'inbox-1',
|
||||
address: 'User@Example.com',
|
||||
token: 'jwt-token',
|
||||
expiresAt: '2026-03-15T12:00:00Z',
|
||||
}), {
|
||||
id: 'inbox-1',
|
||||
address: 'user@example.com',
|
||||
token: 'jwt-token',
|
||||
expiresAt: '2026-03-15T12:00:00Z',
|
||||
isActive: true,
|
||||
createdAt: null,
|
||||
raw: {
|
||||
id: 'inbox-1',
|
||||
address: 'User@Example.com',
|
||||
token: 'jwt-token',
|
||||
expiresAt: '2026-03-15T12:00:00Z',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('normalizeYydsMailMessages and detail expose Graph-like fields for verification matching', () => {
|
||||
const messages = normalizeYydsMailMessages({
|
||||
messages: [{
|
||||
id: 'msg-1',
|
||||
from: { address: 'noreply@tm.openai.com' },
|
||||
to: [{ address: 'User@Example.com' }],
|
||||
subject: 'OpenAI verification code',
|
||||
createdAt: '2026-03-14T12:30:00Z',
|
||||
}],
|
||||
});
|
||||
assert.equal(messages[0].id, 'msg-1');
|
||||
assert.equal(messages[0].address, 'user@example.com');
|
||||
assert.equal(messages[0].from.emailAddress.address, 'noreply@tm.openai.com');
|
||||
|
||||
const detail = normalizeYydsMailMessageDetail({
|
||||
id: 'msg-1',
|
||||
subject: 'OpenAI verification code',
|
||||
from: { address: 'noreply@tm.openai.com' },
|
||||
text: 'Your ChatGPT code is 654321.',
|
||||
createdAt: '2026-03-14T12:30:00Z',
|
||||
});
|
||||
assert.equal(detail.verification_code, '654321');
|
||||
assert.match(detail.bodyPreview, /654321/);
|
||||
});
|
||||
@@ -0,0 +1,276 @@
|
||||
(function yydsMailUtilsModule(root, factory) {
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = factory();
|
||||
return;
|
||||
}
|
||||
|
||||
root.YydsMailUtils = factory();
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createYydsMailUtils() {
|
||||
const DEFAULT_YYDS_MAIL_BASE_URL = 'https://maliapi.215.im/v1';
|
||||
const YYDS_MAIL_PROVIDER = 'yyds-mail';
|
||||
|
||||
function firstNonEmptyString(values) {
|
||||
for (const value of values) {
|
||||
if (value === undefined || value === null) continue;
|
||||
const normalized = String(value).trim();
|
||||
if (normalized) return normalized;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function normalizeYydsMailBaseUrl(rawValue = '') {
|
||||
const value = String(rawValue || '').trim();
|
||||
if (!value) return DEFAULT_YYDS_MAIL_BASE_URL;
|
||||
|
||||
const candidate = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(value)
|
||||
? value
|
||||
: `https://${value}`;
|
||||
try {
|
||||
const parsed = new URL(candidate);
|
||||
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
||||
return DEFAULT_YYDS_MAIL_BASE_URL;
|
||||
}
|
||||
parsed.hash = '';
|
||||
parsed.search = '';
|
||||
const pathname = parsed.pathname === '/' ? '' : parsed.pathname.replace(/\/+$/, '');
|
||||
return `${parsed.origin}${pathname}` || DEFAULT_YYDS_MAIL_BASE_URL;
|
||||
} catch {
|
||||
return DEFAULT_YYDS_MAIL_BASE_URL;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeYydsMailApiKey(value = '') {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function normalizeYydsMailAddress(value = '') {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function buildYydsMailHeaders(config = {}, options = {}) {
|
||||
const headers = {};
|
||||
const apiKey = firstNonEmptyString([
|
||||
options.apiKey,
|
||||
options.includeConfigApiKey === false ? '' : config.apiKey,
|
||||
options.includeConfigApiKey === false ? '' : config.yydsMailApiKey,
|
||||
]);
|
||||
const tempToken = firstNonEmptyString([
|
||||
options.tempToken,
|
||||
config.tempToken,
|
||||
config.token,
|
||||
]);
|
||||
|
||||
if (apiKey) {
|
||||
headers['X-API-Key'] = apiKey;
|
||||
}
|
||||
if (tempToken) {
|
||||
headers.Authorization = /^bearer\s+/i.test(tempToken)
|
||||
? tempToken
|
||||
: `Bearer ${tempToken}`;
|
||||
}
|
||||
if (options.json) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
if (options.acceptJson !== false) {
|
||||
headers.Accept = 'application/json';
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
function joinYydsMailUrl(baseUrl, path, params = {}) {
|
||||
const normalizedBase = normalizeYydsMailBaseUrl(baseUrl);
|
||||
const normalizedPath = String(path || '').trim();
|
||||
const url = new URL(`${normalizedBase}${normalizedPath.startsWith('/') ? '' : '/'}${normalizedPath}`);
|
||||
for (const [key, value] of Object.entries(params || {})) {
|
||||
if (value === undefined || value === null || value === '') continue;
|
||||
url.searchParams.set(key, String(value));
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function normalizeYydsMailInbox(payload = {}) {
|
||||
const safePayload = payload && typeof payload === 'object' ? payload : {};
|
||||
return {
|
||||
id: firstNonEmptyString([safePayload.id, safePayload.inbox_id, safePayload.inboxId]),
|
||||
address: normalizeYydsMailAddress(firstNonEmptyString([safePayload.address, safePayload.email])),
|
||||
token: firstNonEmptyString([safePayload.token, safePayload.tempToken, safePayload.accessToken]),
|
||||
expiresAt: firstNonEmptyString([safePayload.expiresAt, safePayload.expires_at]) || null,
|
||||
isActive: safePayload.isActive !== undefined ? Boolean(safePayload.isActive) : true,
|
||||
createdAt: firstNonEmptyString([safePayload.createdAt, safePayload.created_at]) || null,
|
||||
raw: safePayload,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeYydsMailCurrentInbox(value = null) {
|
||||
if (!value || typeof value !== 'object') return null;
|
||||
const inbox = normalizeYydsMailInbox(value);
|
||||
return inbox.address && inbox.token ? inbox : null;
|
||||
}
|
||||
|
||||
function getYydsMailRows(payload) {
|
||||
if (Array.isArray(payload)) return payload;
|
||||
if (!payload || typeof payload !== 'object') return [];
|
||||
const candidates = [
|
||||
payload.messages,
|
||||
payload.items,
|
||||
payload.list,
|
||||
payload.records,
|
||||
payload.data?.messages,
|
||||
payload.data?.items,
|
||||
payload.data?.list,
|
||||
payload.data?.records,
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
if (Array.isArray(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function stripHtmlTags(value = '') {
|
||||
return String(value || '')
|
||||
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
||||
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/ /gi, ' ')
|
||||
.replace(/&/gi, '&')
|
||||
.replace(/</gi, '<')
|
||||
.replace(/>/gi, '>')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function normalizeYydsMailSender(value = {}) {
|
||||
if (typeof value === 'string') {
|
||||
return { name: '', address: value };
|
||||
}
|
||||
const safeValue = value && typeof value === 'object' ? value : {};
|
||||
return {
|
||||
name: firstNonEmptyString([safeValue.name]),
|
||||
address: firstNonEmptyString([safeValue.address, safeValue.email]),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeYydsMailRecipients(value) {
|
||||
const list = Array.isArray(value) ? value : (value ? [value] : []);
|
||||
return list.map((item) => {
|
||||
if (typeof item === 'string') return { name: '', address: normalizeYydsMailAddress(item) };
|
||||
const safeItem = item && typeof item === 'object' ? item : {};
|
||||
return {
|
||||
name: firstNonEmptyString([safeItem.name]),
|
||||
address: normalizeYydsMailAddress(firstNonEmptyString([safeItem.address, safeItem.email])),
|
||||
};
|
||||
}).filter((item) => item.address);
|
||||
}
|
||||
|
||||
function normalizeYydsMailCreatedAt(value = '') {
|
||||
const source = firstNonEmptyString([value]);
|
||||
if (!source) return '';
|
||||
const parsed = Date.parse(source);
|
||||
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : source;
|
||||
}
|
||||
|
||||
function normalizeYydsMailMessage(row = {}) {
|
||||
if (!row || typeof row !== 'object') return null;
|
||||
const from = normalizeYydsMailSender(row.from || row.sender);
|
||||
const to = normalizeYydsMailRecipients(row.to || row.recipients || row.recipient);
|
||||
const htmlValue = Array.isArray(row.html)
|
||||
? row.html.join('\n')
|
||||
: firstNonEmptyString([row.html, row.bodyHtml, row.body_html]);
|
||||
const textValue = firstNonEmptyString([row.text, row.bodyText, row.body_text, row.bodyPreview, row.preview]);
|
||||
const raw = firstNonEmptyString([row.raw, row.source]);
|
||||
const bodyPreview = (textValue || stripHtmlTags(htmlValue) || stripHtmlTags(raw))
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
return {
|
||||
id: firstNonEmptyString([row.id, row.message_id, row.messageId]),
|
||||
address: normalizeYydsMailAddress(firstNonEmptyString([
|
||||
row.address,
|
||||
row.mailbox,
|
||||
to[0]?.address,
|
||||
])),
|
||||
inboxId: firstNonEmptyString([row.inboxId, row.inbox_id]),
|
||||
subject: firstNonEmptyString([row.subject, row.title]),
|
||||
from: {
|
||||
name: from.name,
|
||||
emailAddress: {
|
||||
address: from.address,
|
||||
},
|
||||
},
|
||||
to,
|
||||
seen: Boolean(row.seen),
|
||||
bodyPreview,
|
||||
raw: raw || htmlValue || textValue || '',
|
||||
text: textValue,
|
||||
html: htmlValue,
|
||||
receivedDateTime: normalizeYydsMailCreatedAt(firstNonEmptyString([
|
||||
row.createdAt,
|
||||
row.created_at,
|
||||
row.receivedDateTime,
|
||||
row.date,
|
||||
])),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeYydsMailMessages(payload) {
|
||||
return getYydsMailRows(payload)
|
||||
.map((row) => normalizeYydsMailMessage(row))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function extractYydsMailVerificationCode(text) {
|
||||
const source = String(text || '');
|
||||
const matchCn = source.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/i);
|
||||
if (matchCn) return matchCn[1];
|
||||
|
||||
const matchOpenAiLogin = source.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchOpenAiLogin) return matchOpenAiLogin[1];
|
||||
|
||||
const matchChatGPT = source.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
||||
if (matchChatGPT) return matchChatGPT[1];
|
||||
|
||||
const matchEn = source.match(/code(?:\s+is|[\s:])+(\d{6})/i);
|
||||
if (matchEn) return matchEn[1];
|
||||
|
||||
const matchStandalone = source.match(/\b(\d{6})\b/);
|
||||
return matchStandalone ? matchStandalone[1] : null;
|
||||
}
|
||||
|
||||
function normalizeYydsMailMessageDetail(payload = {}) {
|
||||
const message = normalizeYydsMailMessage(payload);
|
||||
if (!message) return null;
|
||||
const combined = [
|
||||
message.subject,
|
||||
message.from?.emailAddress?.address,
|
||||
message.bodyPreview,
|
||||
message.text,
|
||||
message.html,
|
||||
message.raw,
|
||||
].filter(Boolean).join(' ');
|
||||
return {
|
||||
...message,
|
||||
bodyPreview: message.bodyPreview || stripHtmlTags(combined),
|
||||
verification_code: extractYydsMailVerificationCode(combined) || '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
DEFAULT_YYDS_MAIL_BASE_URL,
|
||||
YYDS_MAIL_PROVIDER,
|
||||
buildYydsMailHeaders,
|
||||
extractYydsMailVerificationCode,
|
||||
firstNonEmptyString,
|
||||
joinYydsMailUrl,
|
||||
normalizeYydsMailAddress,
|
||||
normalizeYydsMailApiKey,
|
||||
normalizeYydsMailBaseUrl,
|
||||
normalizeYydsMailCurrentInbox,
|
||||
normalizeYydsMailInbox,
|
||||
normalizeYydsMailMessage,
|
||||
normalizeYydsMailMessageDetail,
|
||||
normalizeYydsMailMessages,
|
||||
stripHtmlTags,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user