Merge pull request #253 from QLHazyCoder/codex/yyds-mail-provider
新增 YYDS Mail 邮箱服务端点
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
一个用于批量跑通 ChatGPT OAuth 注册/登录流程的 Chrome 扩展。
|
||||
|
||||
当前版本基于侧边栏控制,支持单步执行、整套自动执行、停止当前流程、保存常用配置,以及通过 DuckDuckGo / QQ / 163 / 163 VIP / 126 / Inbucket / Hotmail / Cloud Mail 协助获取验证码。
|
||||
当前版本基于侧边栏控制,支持单步执行、整套自动执行、停止当前流程、保存常用配置,以及通过 DuckDuckGo / QQ / 163 / 163 VIP / 126 / Inbucket / Hotmail / Cloud Mail / YYDS Mail 协助获取验证码。
|
||||
|
||||
## 插件效果
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
- 支持 `Hotmail`:继续使用 `邮箱 + 客户端 ID + 刷新令牌(refresh token)`,并可在远程服务与本地助手两种模式间切换
|
||||
- 支持 `2925`:新增多账号池、自动登录登出、Step 4 / Step 8 命中“子邮箱已达上限邮箱”后的 24 小时禁用与自动切号
|
||||
- 支持 `Cloud Mail`:可通过 skymail.ink API 生成自定义域邮箱,也可作为转发收件通道轮询验证码
|
||||
- 支持 `YYDS Mail`:通过 API Key 自动创建临时邮箱,并用返回的临时 Bearer token 读取验证码邮件
|
||||
- 支持 `QQ Mail`、`163 Mail`、`163 VIP Mail`、`126 Mail`、`Inbucket mailbox`
|
||||
- 支持从 DuckDuckGo Email Protection 自动生成新的 `@duck.com` 地址
|
||||
- 支持基于 Cloudflare 自定义域名自动生成随机邮箱前缀
|
||||
@@ -207,11 +208,12 @@ Step 1 和 Step 10 都依赖这个地址。
|
||||
|
||||
### `Mail`
|
||||
|
||||
支持八种验证码来源:
|
||||
支持九种验证码来源:
|
||||
|
||||
- `Hotmail`
|
||||
- `2925`
|
||||
- `Cloud Mail`
|
||||
- `YYDS Mail`
|
||||
- `163 Mail`
|
||||
- `163 VIP Mail`
|
||||
- `126 Mail`
|
||||
@@ -223,9 +225,26 @@ Step 1 和 Step 10 都依赖这个地址。
|
||||
- `Hotmail` 通过侧边栏里的 Hotmail 账号池选择账号,可切换为远程服务模式或本地助手模式
|
||||
- `2925` 通过侧边栏里的 2925 账号池选择账号,并在 Step 4 / Step 8 前自动校验网页邮箱登录态
|
||||
- `Cloud Mail` 通过侧边栏配置 API 地址、管理员账号、接收邮箱或生成域名,可直接生成邮箱或轮询转发收件箱
|
||||
- `YYDS Mail` 通过侧边栏配置 API Key 与 Base URL;点击“获取”或自动运行时会创建新的临时邮箱,Step 4 / Step 8 使用返回的临时 token 轮询验证码
|
||||
- `QQ`、`163`、`163 VIP`、`126` 用于直接轮询网页邮箱
|
||||
- `Inbucket` 通过你在侧边栏里配置的 host 访问 `mailbox` 页面:`https://<your-inbucket-host>/m/<mailbox>/`
|
||||
|
||||
### `YYDS Mail`
|
||||
|
||||
仅当 `Mail = YYDS Mail` 时使用。
|
||||
|
||||
可配置项:
|
||||
|
||||
- `API Key`:用于创建临时邮箱,请求头为 `X-API-Key`
|
||||
- `Base URL`:默认 `https://maliapi.215.im/v1`,可按自建或兼容服务地址修改
|
||||
|
||||
行为说明:
|
||||
|
||||
1. 点击注册邮箱区域的 `获取`,或 Auto 每轮准备邮箱时,扩展会调用 `POST /accounts` 创建临时邮箱。
|
||||
2. 创建请求只发送 `localPart`;最终注册邮箱以后端返回的 `address` 为准。
|
||||
3. 后台保存本轮返回的临时 `token`,Step 4 / Step 8 通过 `Bearer token` 读取 `/messages` 与 `/messages/{id}`。
|
||||
4. 平台验证完成或重置当前流程后,会清空本轮 YYDS Mail 邮箱运行态;API Key 与 Base URL 配置会保留,下一轮重新创建邮箱。
|
||||
|
||||
### `Hotmail 账号池`
|
||||
|
||||
仅当 `Mail = Hotmail` 时使用。
|
||||
|
||||
+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,
|
||||
};
|
||||
});
|
||||
+23
@@ -172,6 +172,7 @@
|
||||
- 标签注册表
|
||||
- 最近打开的来源地址
|
||||
- LuckMail 当前运行时选择
|
||||
- YYDS Mail 当前运行时邮箱 `currentYydsMailInbox`,保存本轮创建得到的最终邮箱地址和临时 Bearer token
|
||||
- 2925 当前选中的账号 ID `currentMail2925AccountId`(运行时会同步到持久配置,用于重开浏览器后恢复同一个号池账号)
|
||||
|
||||
补充:
|
||||
@@ -190,6 +191,7 @@
|
||||
- PayPal 账号池配置 `paypalAccounts / currentPayPalAccountId`,以及供后台步骤兼容读取的 `paypalEmail / paypalPassword`
|
||||
- 邮箱 provider 配置
|
||||
- Cloud Mail / SkyMail API 配置:`cloudMailBaseUrl / cloudMailAdminEmail / cloudMailAdminPassword / cloudMailToken / cloudMailReceiveMailbox / cloudMailDomain / cloudMailDomains`
|
||||
- YYDS Mail API 配置:`yydsMailApiKey / yydsMailBaseUrl`;reset 会保留这两个持久配置,但清空 `currentYydsMailInbox`,确保下一轮重新创建临时邮箱
|
||||
- Hotmail 账号池
|
||||
- 2925 账号池
|
||||
- 2925 是否启用号池模式 `mail2925UseAccountPool`
|
||||
@@ -681,6 +683,7 @@ Plus 模式可见步骤:
|
||||
- Cloudflare
|
||||
- Cloudflare Temp Email
|
||||
- Cloud Mail
|
||||
- YYDS Mail
|
||||
- 自定义邮箱池
|
||||
- iCloud 隐私邮箱
|
||||
|
||||
@@ -707,6 +710,26 @@ Plus 模式可见步骤:
|
||||
4. 邮件读取统一走 `/api/public/emailList`,返回数据先经 `cloudmail-utils.js` 归一化,再复用现有验证码匹配与时间窗过滤逻辑。
|
||||
5. 如果 Token 失效,后台会尝试重新登录刷新 Token 后重试当前 API 请求。
|
||||
|
||||
### 7.1.2 YYDS Mail
|
||||
|
||||
组成:
|
||||
|
||||
- [yyds-mail-utils.js](c:/Users/projectf/Downloads/codex注册扩展/yyds-mail-utils.js)
|
||||
- [background/yyds-mail-provider.js](c:/Users/projectf/Downloads/codex注册扩展/background/yyds-mail-provider.js)
|
||||
- [background/verification-flow.js](c:/Users/projectf/Downloads/codex注册扩展/background/verification-flow.js)
|
||||
- [sidepanel/sidepanel.html](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/sidepanel.html)
|
||||
- [sidepanel/sidepanel.js](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/sidepanel.js)
|
||||
|
||||
链路:
|
||||
|
||||
1. 侧栏在 `Mail = YYDS Mail` 时只展示 `API Key` 与 `Base URL`,不展示普通邮箱生成、转发邮箱或账号池配置。
|
||||
2. 持久配置只保存 `yydsMailApiKey / yydsMailBaseUrl`;本轮真实邮箱和临时 token 只保存在运行态 `currentYydsMailInbox`。
|
||||
3. 手动点击“获取”、Step 3 需要生成邮箱、或 Auto 每轮准备邮箱时,后台通过 `POST /accounts` 创建邮箱,请求头使用 `X-API-Key`,请求体只发送自动生成或用户传入的 `localPart`。
|
||||
4. 创建成功后以后端返回的最终 `address` 为准回写 `email / registrationEmailState`,并把返回的 `token` 写入 `currentYydsMailInbox`,Step 8 `add-email` 的手机号身份链路仍通过共享注册邮箱状态持久化保留 `signupPhone* / accountIdentifier*`。
|
||||
5. Step 4 / Step 8 轮询验证码时,`background/verification-flow.js` 会把 `provider = yyds-mail` 路由到后台 provider;后台用临时 token 发送 `Authorization: Bearer <token>` 调用 `/messages` 与 `/messages/{id}`,再复用现有验证码匹配、时间窗、排除旧验证码等规则。
|
||||
6. 平台验证完成后的成功收尾会清空 `currentYydsMailInbox` 与当前邮箱运行态,下一轮重新创建新的 YYDS Mail 邮箱。
|
||||
7. `reset` 保留 YYDS Mail 的 API Key / Base URL 配置,但清空当前邮箱运行态,避免旧临时 token 被下一轮误用。
|
||||
|
||||
### 7.2 Hotmail
|
||||
|
||||
组成:
|
||||
|
||||
+8
-4
@@ -28,7 +28,8 @@
|
||||
- `hotmail-utils.js`:Hotmail 账号与验证码提取相关的纯工具函数,负责账号筛选、验证码匹配、第三方接口数据归一化。
|
||||
- `icloud-utils.js`:iCloud 隐私邮箱相关的纯工具函数,负责 host、别名列表、保留状态、已用状态等归一化。
|
||||
- `luckmail-utils.js`:LuckMail 相关的纯工具函数,负责邮箱购买记录、标签、邮件 cursor、验证码匹配等归一化。
|
||||
- `mail-provider-utils.js`:网页邮箱 provider 配置纯工具,负责 `163 / 163 VIP / 126 / QQ / Inbucket / Hotmail` 的基础归一化与页面入口配置,并统一承接 iCloud 转发收码目标邮箱 provider 的归一化、选项列表和收码入口配置。
|
||||
- `yyds-mail-utils.js`:YYDS Mail 相关的纯工具函数,负责 API 地址、API Key、临时邮箱、Bearer token、邮件列表/详情、验证码字段与 Graph-like 邮件结构归一化。
|
||||
- `mail-provider-utils.js`:网页邮箱 provider 配置纯工具,负责 `163 / 163 VIP / 126 / QQ / Inbucket / Hotmail / YYDS Mail` 的基础归一化与页面入口配置,并统一承接 iCloud 转发收码目标邮箱 provider 的归一化、选项列表和收码入口配置。
|
||||
- `mail2925-utils.js`:2925 账号池相关的纯工具函数,负责账号归一化、冷却期判断、可用账号挑选、批量导入解析与列表更新。
|
||||
- `managed-alias-utils.js`:共享 Gmail / 2925 的别名邮箱规则,负责解析基邮箱、校验“当前完整注册邮箱”是否仍与基邮箱兼容、生成 Gmail `+tag` 与 2925 随机后缀邮箱,并输出 sidepanel 可复用的 UI 文案;当前还统一承接 `mail2925Mode` 的归一化与“2925 仅在 provide 模式下参与别名生成”的共享判定。
|
||||
- `paypal-utils.js`:PayPal 账号池相关的纯工具函数,负责账号归一化、按 ID 查找和列表 upsert。
|
||||
@@ -49,6 +50,7 @@
|
||||
- `background/auto-run-controller.js`:自动运行主控制器,封装多轮执行、重试、轮次摘要、线程间隔与倒计时恢复逻辑;当前自动流程会绑定 `autoRunSessionId`,手动停止后旧的倒计时计划、旧重试链路和旧恢复入口不会再复活已失效的自动运行;fresh-attempt reset 时会额外保留 Plus 模式与 PayPal 配置、`gmailBaseEmail`、`mail2925BaseEmail` 与当前 2925 账号选择,避免自动流程重置后丢失关键持久配置。
|
||||
- `background/contribution-oauth.js`:贡献模式的公开 OAuth 流程模块,负责调用 `flowpilot.qlhazycoder.top` 的公开贡献接口、保存贡献会话运行态、在主 10 步流程里为步骤 7 提供贡献登录地址、在步骤 9/10 衔接 callback 捕获与兼容提交 `/oauth/api/submit-callback`,并把真实服务端状态映射回 sidepanel 运行态。
|
||||
- `background/cloudmail-provider.js`:Cloud Mail / SkyMail 后台 provider 模块,负责 Token 获取、邮箱创建、邮件列表读取、验证码轮询和生成/转发收件目标邮箱选择;新生成的 Cloud Mail 地址会统一走共享注册邮箱状态持久化,既回写带来源标记的注册邮箱运行态,也能在 Step 8 `add-email` 的手机号身份链路中保留 `signupPhone* / accountIdentifier*`。
|
||||
- `background/yyds-mail-provider.js`:YYDS Mail 后台 provider 模块,负责通过 `X-API-Key` 创建临时邮箱、保存返回的地址与临时 Bearer token、按 `/messages` 与 `/messages/{id}` 读取验证码邮件,并在成功收尾或 reset 时清空当前运行态邮箱。
|
||||
- `background/generated-email-helpers.js`:生成邮箱辅助层,除了 Duck / Cloudflare / iCloud / Cloudflare Temp Email,也统一承接 Gmail / 2925 的别名邮箱生成入口与自定义邮箱池读取;当 provider 为 2925 且 `mail2925Mode = provide` 时,会先确保当前账号池里已有可用账号,再基于该账号生成别名邮箱;若 `mail2925Mode = receive`,则会回退到普通邮箱生成链路;当生成方式为 iCloud 且 `icloudFetchMode = always_new` 时,会强制跳过未用别名复用并新建别名;当生成方式为 `custom-pool` 时,会按当前目标轮次读取邮箱池中的对应邮箱;Duck 生成前会优先结合 `registrationEmailState` 与侧栏当前可见邮箱解析对比基线,避免重复拿到旧地址;所有生成结果都会统一走共享注册邮箱状态持久化,普通邮箱流仍切回邮箱身份,Step 8 `add-email` 的手机号身份流则保留当前手机号身份。
|
||||
- `background/ip-proxy-core.js`:IP 代理核心模块,负责代理条目解析、账号/接口代理池运行态、PAC 应用与清除、代理鉴权回填、出口探测、失败时的目标站点 fail-close 防漏规则,以及自动运行成功阈值后的代理切换支撑。
|
||||
- `background/ip-proxy-provider-711proxy.js`:711Proxy provider 规则模块,负责从账号串中识别和写回 `region / session / sessTime` 等参数,并在固定账号模式下把侧栏配置转换为最终生效的代理账号。
|
||||
@@ -63,7 +65,7 @@
|
||||
- `background/sub2api-api.js`:SUB2API 管理接口直连模块,负责后台登录 SUB2API、解析 openai 分组、按配置选择代理、生成 OpenAI OAuth 地址、交换 localhost callback 授权码并创建 SUB2API 账号;主链路不再依赖 SUB2API 后台页面 DOM 自动化。
|
||||
- `background/signup-flow-helpers.js`:注册页辅助层,负责打开注册入口、Step 2 打开入口页后的加载完成与额外 3 秒稳定等待、等待密码页以及解析当前流程所用邮箱;在 Gmail 与 `2925 + provide` 模式下会优先复用已经存在且仍兼容的完整注册邮箱,只有不兼容或为空时才重新生成;当 provider 为 2925 且启用了 provide 模式下的号池时,会先确保账号池中已选中可用账号;若邮箱生成器在生成阶段已经写回运行态,这里不会再重复覆盖 `registrationEmailState`,但仍会在 Step 8 `add-email` 的手机号身份场景下通过共享持久化按需保留手机号身份。
|
||||
- `background/tab-runtime.js`:标签页与内容脚本运行时基础设施,封装标签注册、冲突清理、消息超时、注入重试与队列;内容脚本恢复等待日志支持 `logStep / logStepKey`,用于保持 Plus 复用步骤的日志标签正确;当前等待标签完成、等待标签完成并短暂稳定、注入后的短暂延迟和内容脚本重试等待都已做 Stop 感知,避免用户停止后后台还继续等待并恢复执行。
|
||||
- `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过、共享验证码自动重发次数配置、Cloud Mail API 轮询以及 2925 长轮询参数;日志步骤号使用 `completionStep`,因此 Plus 登录验证码会显示可见第 11 步而不是内部复用的 Step 8;当前验证码提交重试上限为 15 次,2925 每次重发验证码之间都会固定跑完一轮 15 次邮箱刷新轮询;若 `mail2925Mode = receive`,会额外把目标注册邮箱传给 2925 内容脚本做弱匹配;若登录验证码提交后页面转入 `add-phone / 手机号页`,则会把“后续需要手机号验证”的结果继续传给 OAuth 确认步骤,而不是误判为普通失败;若登录验证码提交后通信中断但认证页已进入 OAuth 同意页或手机号验证页,会按真实页面状态继续后续流程;若 2925 轮询命中“子邮箱已达上限邮箱”,会转入 2925 会话模块执行“记录时间、禁用 24 小时、切下一个账号并重新登录”,然后直接结束当前尝试。
|
||||
- `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过、共享验证码自动重发次数配置、Cloud Mail / YYDS Mail API 轮询以及 2925 长轮询参数;日志步骤号使用 `completionStep`,因此 Plus 登录验证码会显示可见第 11 步而不是内部复用的 Step 8;当前验证码提交重试上限为 15 次,2925 每次重发验证码之间都会固定跑完一轮 15 次邮箱刷新轮询;若 `mail2925Mode = receive`,会额外把目标注册邮箱传给 2925 内容脚本做弱匹配;若登录验证码提交后页面转入 `add-phone / 手机号页`,则会把“后续需要手机号验证”的结果继续传给 OAuth 确认步骤,而不是误判为普通失败;若登录验证码提交后通信中断但认证页已进入 OAuth 同意页或手机号验证页,会按真实页面状态继续后续流程;若 2925 轮询命中“子邮箱已达上限邮箱”,会转入 2925 会话模块执行“记录时间、禁用 24 小时、切下一个账号并重新登录”,然后直接结束当前尝试。
|
||||
|
||||
## `background/steps/`
|
||||
|
||||
@@ -160,7 +162,7 @@
|
||||
名基邮箱”;当 provider 为 2925 时,会额外显示 `提供邮箱 / 接收邮箱` 模式切换,并把“2925 号池”从别名基邮箱行拆成独立配置行,避免
|
||||
receive 模式把账号池开关一起隐藏;当前在 `邮箱生成` 区域新增 `自定义邮箱池` 选项和多行邮箱池输入框,并在 `邮箱服务 = 自定义邮箱` 时
|
||||
额外显示 `自定义号池` 文本框;当邮箱服务为 iCloud 时,额外提供目标邮箱类型与转发邮箱 provider 配置,用于选择直接从 iCloud 收件箱收
|
||||
码或从 QQ / 网易 / Gmail 转发目标邮箱收码;当邮箱服务或邮箱生成器为 Cloud Mail 时,额外显示 API 地址、管理员账号、接收邮箱和生成域名配置行;来源下拉框当前支持 `CPA / SUB2API / Codex2API`,其中 Codex2API 额外提供后台地址和管理密
|
||||
码或从 QQ / 网易 / Gmail 转发目标邮箱收码;当邮箱服务或邮箱生成器为 Cloud Mail 时,额外显示 API 地址、管理员账号、接收邮箱和生成域名配置行;当邮箱服务为 YYDS Mail 时,额外显示 API Key 与 Base URL 配置行,并隐藏普通邮箱生成/转发邮箱相关配置;来源下拉框当前支持 `CPA / SUB2API / Codex2API`,其中 Codex2API 额外提供后台地址和管理密
|
||||
钥配置行;设置卡片新增 `IP代理` 开关与代理配置折叠区,支持 711Proxy 账号密码模式、代理状态卡与出口检测按钮;接码卡内把注册方式、接码配置、接码订单运行态与“注册手机号”身份运行态分层展示,“注册手机号”位于订单运行状态下方,不嵌入当前分配/验证码网格;设置卡片新增 `Plus 模式` 开关与 PayPal 账号下拉框,右侧使用公共表单弹窗添加账号;GPC Plus 配置额外提供只读 API 地址、API Key、专用手机号、OTP 渠道、本地 OTP helper 开关、helper URL 与 PIN,并提供 `购买卡密` 与 `转换 API Key` 入口;Hotmail / 2925 两个账号池当前都使用统一的头部“添加账号/取消添加”按钮和共享表单容器;设置卡片还新增“授权总超时”开关,用于控制 Step 7 后链 5 分钟总预算。
|
||||
- `sidepanel/paypal-manager.js`:侧边栏 PayPal 账号管理器,负责 Plus 模式下的账号下拉框渲染、添加账号弹窗、保存账号与切换当前账号。
|
||||
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、共享验证码自动重发次数配置与广播接收,并装
|
||||
@@ -168,7 +170,7 @@
|
||||
动逻辑,而独立 manager 负责贡献运行态展示与轮询,同时把 Gmail / 2925 的基邮箱输入、完整注册邮箱输入、自动生成按钮与兼容性校验统一接
|
||||
到共享别名逻辑上;当 provider 为 2925 时,会根据 `mail2925Mode` 决定是否启用别名基邮箱链路,而 2925 号池开关与当前账号选择则独立显
|
||||
示并同时服务于 provide / receive 两种模式;当 provider 为 iCloud 时,会保存并回显目标邮箱类型与转发邮箱 provider,相关 provider 选
|
||||
项和 label 复用 `mail-provider-utils.js`;Cloud Mail 配置保存、回显和显隐由这里接入,可分别服务于邮箱生成器和转发收件 provider;手动点击“获取/生成邮箱”时,这里会把输入框当前可见邮箱一并传给后台,作为 Duck 新地址检测的优先对比基线;`自定义邮箱池` 模式会按邮箱池长度锁定自动轮数,并把当前输入的邮箱池配置参与自动启动前保
|
||||
项和 label 复用 `mail-provider-utils.js`;Cloud Mail 配置保存、回显和显隐由这里接入,可分别服务于邮箱生成器和转发收件 provider;YYDS Mail 配置保存、回显和显隐由这里接入,只暴露 API Key / Base URL,并在点击“获取”或自动运行时委托后台创建临时邮箱;手动点击“获取/生成邮箱”时,这里会把输入框当前可见邮箱一并传给后台,作为 Duck 新地址检测的优先对比基线;`自定义邮箱池` 模式会按邮箱池长度锁定自动轮数,并把当前输入的邮箱池配置参与自动启动前保
|
||||
存;点击“自动”时会先冻结当时的目标轮数,避免启动前的异步刷新、保存或后台旧状态回灌把输入框重置为 1;`邮箱服务 = 自定义邮箱` 时,如果配置了 `customMailProviderPool`,也会按号池长度锁定自动轮数并在 Auto 中按轮次分配注册邮箱,同时
|
||||
在普通失败时继续复用当前邮箱,只有成功或出现手机号验证时才切换下一个邮箱;Plus 模式开启后,步骤列表会切换为 13 步,并显示 PayPal 账
|
||||
号下拉框与添加入口;当前选中的 PayPal 账号会同步回兼容字段 `paypalEmail / paypalPassword` 供后台步骤复用;Step 8 的自定义邮箱确认弹
|
||||
@@ -233,6 +235,8 @@
|
||||
- `tests/cloudflare-temp-email-utils.test.js`:测试 Cloudflare Temp Email 工具层的 URL、域名、邮件解析逻辑。
|
||||
- `tests/cloudmail-provider.test.js`:测试 Cloud Mail provider 的验证码轮询和生成/转发收件目标邮箱选择逻辑,并覆盖生成邮箱时对共享注册邮箱状态持久化的接入。
|
||||
- `tests/cloudmail-utils.test.js`:测试 Cloud Mail 工具层的 URL、域名、鉴权头、Token 与邮件响应归一化逻辑。
|
||||
- `tests/yyds-mail-provider.test.js`:测试 YYDS Mail provider 创建邮箱、临时 token 收信、邮件详情读取、验证码轮询与运行态清理逻辑。
|
||||
- `tests/yyds-mail-utils.test.js`:测试 YYDS Mail 工具层的 Base URL、请求头、邮箱响应、邮件列表/详情与验证码字段归一化逻辑。
|
||||
- `tests/content-utils.test.js`:测试内容脚本公共工具层对 `mail.126.com` 等网页邮箱来源的识别逻辑。
|
||||
- `tests/duck-mail-content.test.js`:测试 Duck 页面脚本会等待页面现有地址或显式基线出现后再比较新地址,并要求新地址稳定渲染后才判定生成成功。
|
||||
- `tests/hotmail-api-mode.test.js`:测试 Hotmail API 模式相关文案和集成方式。
|
||||
|
||||
Reference in New Issue
Block a user