2925邮箱增加账号池功能
This commit is contained in:
@@ -53,6 +53,7 @@
|
|||||||
- 自动显示当前使用中的密码,便于后续保存
|
- 自动显示当前使用中的密码,便于后续保存
|
||||||
- 自动获取注册验证码与登录验证码
|
- 自动获取注册验证码与登录验证码
|
||||||
- 支持 `Hotmail`:继续使用 `邮箱 + 客户端 ID + 刷新令牌(refresh token)`,并可在远程服务与本地助手两种模式间切换
|
- 支持 `Hotmail`:继续使用 `邮箱 + 客户端 ID + 刷新令牌(refresh token)`,并可在远程服务与本地助手两种模式间切换
|
||||||
|
- 支持 `2925`:新增多账号池、自动登录登出、Step 4 / Step 8 命中“子邮箱已达上限邮箱”后的 24 小时禁用与自动切号
|
||||||
- 支持 `QQ Mail`、`163 Mail`、`Inbucket mailbox`
|
- 支持 `QQ Mail`、`163 Mail`、`Inbucket mailbox`
|
||||||
- 支持从 DuckDuckGo Email Protection 自动生成新的 `@duck.com` 地址
|
- 支持从 DuckDuckGo Email Protection 自动生成新的 `@duck.com` 地址
|
||||||
- 支持基于 Cloudflare 自定义域名自动生成随机邮箱前缀
|
- 支持基于 Cloudflare 自定义域名自动生成随机邮箱前缀
|
||||||
@@ -138,6 +139,17 @@
|
|||||||
4. 通过后再执行步骤或 `Auto`
|
4. 通过后再执行步骤或 `Auto`
|
||||||
5. 当前项目中,`Mail = Hotmail` 时会直接使用账号池里的邮箱作为注册邮箱,不再走 `Duck / Cloudflare` 自动生成
|
5. 当前项目中,`Mail = Hotmail` 时会直接使用账号池里的邮箱作为注册邮箱,不再走 `Duck / Cloudflare` 自动生成
|
||||||
|
|
||||||
|
### 方案 D:`2925 账号池`
|
||||||
|
|
||||||
|
1. `Mail` 选择 `2925`
|
||||||
|
2. 在 `2925 账号池` 中添加 `邮箱 / 密码`
|
||||||
|
3. 在“2925 基邮箱”右侧打开 `号池` 开关后,基邮箱输入框会切成下拉框,只能从 2925 账号池中选择邮箱;关闭开关则回退到原来的手填基邮箱路线
|
||||||
|
4. 可先点 `使用此账号` 让当前别名基邮箱切到这条 2925 账号,再点 `登录` 手动验证网页邮箱登录态
|
||||||
|
5. 只有在 `号池` 开关开启时,自动流程执行到 Step 4 / Step 8 前才会自动检查 2925 登录态;如果未登录,会直接使用当前账号自动登录
|
||||||
|
6. 当 Step 4 / Step 8 轮询邮箱时遇到“子邮箱已达上限邮箱”,扩展会记录当前时间、把当前 2925 账号禁用 24 小时、自动切换到下一个可用账号并完成登录,然后直接结束当前尝试
|
||||||
|
7. 如果你同时开启了 `Auto` 的自动重试,当前尝试结束后会按现有逻辑自动进入下一次尝试,不需要再手动介入
|
||||||
|
8. `Mail = 2925` 仍然走 Gmail / 2925 共用的别名邮箱链路;实际注册邮箱会基于当前 2925 账号邮箱生成,例如 `name@2925.com -> name123456@2925.com`
|
||||||
|
|
||||||
## 侧边栏配置说明
|
## 侧边栏配置说明
|
||||||
|
|
||||||
### `CPA`
|
### `CPA`
|
||||||
@@ -166,6 +178,7 @@ Step 1 和 Step 10 都依赖这个地址。
|
|||||||
支持五种验证码来源:
|
支持五种验证码来源:
|
||||||
|
|
||||||
- `Hotmail`
|
- `Hotmail`
|
||||||
|
- `2925`
|
||||||
- `163 Mail`
|
- `163 Mail`
|
||||||
- `163 VIP Mail`
|
- `163 VIP Mail`
|
||||||
- `QQ Mail`
|
- `QQ Mail`
|
||||||
@@ -174,6 +187,7 @@ Step 1 和 Step 10 都依赖这个地址。
|
|||||||
说明:
|
说明:
|
||||||
|
|
||||||
- `Hotmail` 通过侧边栏里的 Hotmail 账号池选择账号,可切换为远程服务模式或本地助手模式
|
- `Hotmail` 通过侧边栏里的 Hotmail 账号池选择账号,可切换为远程服务模式或本地助手模式
|
||||||
|
- `2925` 通过侧边栏里的 2925 账号池选择账号,并在 Step 4 / Step 8 前自动校验网页邮箱登录态
|
||||||
- `QQ`、`163`、`163 VIP` 用于直接轮询网页邮箱
|
- `QQ`、`163`、`163 VIP` 用于直接轮询网页邮箱
|
||||||
- `Inbucket` 通过你在侧边栏里配置的 host 访问 `mailbox` 页面:`https://<your-inbucket-host>/m/<mailbox>/`
|
- `Inbucket` 通过你在侧边栏里配置的 host 访问 `mailbox` 页面:`https://<your-inbucket-host>/m/<mailbox>/`
|
||||||
|
|
||||||
@@ -207,6 +221,24 @@ Step 1 和 Step 10 都依赖这个地址。
|
|||||||
- 校验通过后,可点击 `测试收信`
|
- 校验通过后,可点击 `测试收信`
|
||||||
- Auto 模式每轮会自动选用一个可用账号
|
- Auto 模式每轮会自动选用一个可用账号
|
||||||
|
|
||||||
|
### `2925 账号池`
|
||||||
|
|
||||||
|
仅当 `Mail = 2925` 时使用。
|
||||||
|
|
||||||
|
每条账号支持保存:
|
||||||
|
|
||||||
|
- `email`
|
||||||
|
- `password`
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
|
||||||
|
- 添加一个或多个 `2925.com` 账号
|
||||||
|
- 点击 `使用此账号` 可以切换当前别名基邮箱来源
|
||||||
|
- 点击 `登录` 可以让扩展直接打开 / 复用 2925 邮箱标签页,并使用当前账号自动登录
|
||||||
|
- 如果账号因为“子邮箱已达上限邮箱”被临时禁用,列表里会展示上限记录时间和恢复时间
|
||||||
|
- 命中冷却的账号可手动 `清冷却`,也可以等待 24 小时后自动恢复可用
|
||||||
|
- Auto 模式在生成 2925 别名邮箱前,会自动分配当前可用账号;若 Step 4 / Step 8 命中上限,会自动切到下一个账号
|
||||||
|
|
||||||
#### 本地 helper 启动命令
|
#### 本地 helper 启动命令
|
||||||
|
|
||||||
Windows:
|
Windows:
|
||||||
|
|||||||
+135
-2
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
importScripts(
|
importScripts(
|
||||||
'managed-alias-utils.js',
|
'managed-alias-utils.js',
|
||||||
|
'mail2925-utils.js',
|
||||||
'background/account-run-history.js',
|
'background/account-run-history.js',
|
||||||
'background/contribution-oauth.js',
|
'background/contribution-oauth.js',
|
||||||
|
'background/mail-2925-session.js',
|
||||||
'background/panel-bridge.js',
|
'background/panel-bridge.js',
|
||||||
'background/generated-email-helpers.js',
|
'background/generated-email-helpers.js',
|
||||||
'background/signup-flow-helpers.js',
|
'background/signup-flow-helpers.js',
|
||||||
@@ -57,6 +59,16 @@ const {
|
|||||||
pickVerificationMessageWithTimeFallback,
|
pickVerificationMessageWithTimeFallback,
|
||||||
shouldClearHotmailCurrentSelection,
|
shouldClearHotmailCurrentSelection,
|
||||||
} = self.HotmailUtils;
|
} = self.HotmailUtils;
|
||||||
|
const {
|
||||||
|
MAIL2925_LIMIT_COOLDOWN_MS,
|
||||||
|
findMail2925Account,
|
||||||
|
getMail2925AccountStatus,
|
||||||
|
normalizeMail2925Account,
|
||||||
|
normalizeMail2925Accounts,
|
||||||
|
parseMail2925ImportText,
|
||||||
|
pickMail2925AccountForRun,
|
||||||
|
upsertMail2925AccountInList,
|
||||||
|
} = self.Mail2925Utils;
|
||||||
const {
|
const {
|
||||||
fetchMicrosoftMailboxMessages,
|
fetchMicrosoftMailboxMessages,
|
||||||
} = self.MultiPageMicrosoftEmail;
|
} = self.MultiPageMicrosoftEmail;
|
||||||
@@ -248,6 +260,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT,
|
verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT,
|
||||||
mailProvider: '163',
|
mailProvider: '163',
|
||||||
mail2925Mode: DEFAULT_MAIL_2925_MODE,
|
mail2925Mode: DEFAULT_MAIL_2925_MODE,
|
||||||
|
mail2925UseAccountPool: false,
|
||||||
emailGenerator: 'duck',
|
emailGenerator: 'duck',
|
||||||
autoDeleteUsedIcloudAlias: false,
|
autoDeleteUsedIcloudAlias: false,
|
||||||
icloudHostPreference: 'auto',
|
icloudHostPreference: 'auto',
|
||||||
@@ -270,6 +283,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
cloudflareTempEmailDomain: '',
|
cloudflareTempEmailDomain: '',
|
||||||
cloudflareTempEmailDomains: [],
|
cloudflareTempEmailDomains: [],
|
||||||
hotmailAccounts: [],
|
hotmailAccounts: [],
|
||||||
|
mail2925Accounts: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
||||||
@@ -344,6 +358,7 @@ const DEFAULT_STATE = {
|
|||||||
oauthFlowDeadlineAt: null,
|
oauthFlowDeadlineAt: null,
|
||||||
oauthFlowDeadlineSourceUrl: null,
|
oauthFlowDeadlineSourceUrl: null,
|
||||||
currentHotmailAccountId: null,
|
currentHotmailAccountId: null,
|
||||||
|
currentMail2925AccountId: null,
|
||||||
preferredIcloudHost: '',
|
preferredIcloudHost: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -874,6 +889,8 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return normalizeMailProvider(value);
|
return normalizeMailProvider(value);
|
||||||
case 'mail2925Mode':
|
case 'mail2925Mode':
|
||||||
return normalizeMail2925Mode(value);
|
return normalizeMail2925Mode(value);
|
||||||
|
case 'mail2925UseAccountPool':
|
||||||
|
return Boolean(value);
|
||||||
case 'emailGenerator':
|
case 'emailGenerator':
|
||||||
return normalizeEmailGenerator(value);
|
return normalizeEmailGenerator(value);
|
||||||
case 'autoDeleteUsedIcloudAlias':
|
case 'autoDeleteUsedIcloudAlias':
|
||||||
@@ -914,6 +931,8 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return normalizeCloudflareTempEmailDomains(value);
|
return normalizeCloudflareTempEmailDomains(value);
|
||||||
case 'hotmailAccounts':
|
case 'hotmailAccounts':
|
||||||
return normalizeHotmailAccounts(value);
|
return normalizeHotmailAccounts(value);
|
||||||
|
case 'mail2925Accounts':
|
||||||
|
return normalizeMail2925Accounts(value);
|
||||||
default:
|
default:
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -2240,6 +2259,12 @@ function getManagedAliasBaseEmail(state = {}, provider = state?.mailProvider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedProvider === '2925') {
|
if (normalizedProvider === '2925') {
|
||||||
|
const currentAccount = Boolean(state?.mail2925UseAccountPool)
|
||||||
|
? getCurrentMail2925Account(state)
|
||||||
|
: null;
|
||||||
|
if (currentAccount?.email) {
|
||||||
|
return currentAccount.email;
|
||||||
|
}
|
||||||
const mail2925BaseEmail = String(state?.mail2925BaseEmail || '').trim();
|
const mail2925BaseEmail = String(state?.mail2925BaseEmail || '').trim();
|
||||||
if (mail2925BaseEmail) {
|
if (mail2925BaseEmail) {
|
||||||
return mail2925BaseEmail;
|
return mail2925BaseEmail;
|
||||||
@@ -5355,6 +5380,79 @@ function getEmailGeneratorLabel(generator) {
|
|||||||
if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) return 'Cloudflare Temp Email';
|
if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) return 'Cloudflare Temp Email';
|
||||||
return 'Duck 邮箱';
|
return 'Duck 邮箱';
|
||||||
}
|
}
|
||||||
|
const mail2925SessionManager = self.MultiPageBackgroundMail2925Session?.createMail2925SessionManager({
|
||||||
|
addLog,
|
||||||
|
broadcastDataUpdate,
|
||||||
|
chrome,
|
||||||
|
findMail2925Account,
|
||||||
|
getMail2925AccountStatus,
|
||||||
|
getState,
|
||||||
|
isMail2925AccountAvailable: self.Mail2925Utils?.isMail2925AccountAvailable,
|
||||||
|
MAIL2925_LIMIT_COOLDOWN_MS,
|
||||||
|
normalizeMail2925Account,
|
||||||
|
normalizeMail2925Accounts,
|
||||||
|
pickMail2925AccountForRun,
|
||||||
|
reuseOrCreateTab,
|
||||||
|
sendToMailContentScriptResilient,
|
||||||
|
setPersistentSettings,
|
||||||
|
setState,
|
||||||
|
throwIfStopped,
|
||||||
|
upsertMail2925AccountInList,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function upsertMail2925Account(input = {}) {
|
||||||
|
return mail2925SessionManager.upsertMail2925Account(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteMail2925Account(accountId) {
|
||||||
|
return mail2925SessionManager.deleteMail2925Account(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteMail2925Accounts(mode = 'all') {
|
||||||
|
return mail2925SessionManager.deleteMail2925Accounts(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function patchMail2925Account(accountId, updates = {}) {
|
||||||
|
return mail2925SessionManager.patchMail2925Account(accountId, updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setCurrentMail2925Account(accountId, options = {}) {
|
||||||
|
return mail2925SessionManager.setCurrentMail2925Account(accountId, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentMail2925Account(state = null) {
|
||||||
|
return mail2925SessionManager.getCurrentMail2925Account(state || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureMail2925AccountForFlow(options = {}) {
|
||||||
|
return mail2925SessionManager.ensureMail2925AccountForFlow(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureMail2925MailboxSession(options = {}) {
|
||||||
|
return mail2925SessionManager.ensureMail2925MailboxSession(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMail2925LimitReachedError(step, error) {
|
||||||
|
return mail2925SessionManager.handleMail2925LimitReachedError(step, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMail2925LimitReachedError(error) {
|
||||||
|
if (typeof mail2925SessionManager !== 'undefined' && mail2925SessionManager?.isMail2925LimitReachedError) {
|
||||||
|
return mail2925SessionManager.isMail2925LimitReachedError(error);
|
||||||
|
}
|
||||||
|
const message = String(typeof error === 'string' ? error : error?.message || '');
|
||||||
|
return /^MAIL2925_LIMIT_REACHED::/.test(message)
|
||||||
|
|| /子邮箱.{0,12}已达上限|已达上限邮箱|子邮箱上限|邮箱已达上限/i.test(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMail2925ThreadTerminatedError(error) {
|
||||||
|
if (typeof mail2925SessionManager !== 'undefined' && mail2925SessionManager?.isMail2925ThreadTerminatedError) {
|
||||||
|
return mail2925SessionManager.isMail2925ThreadTerminatedError(error);
|
||||||
|
}
|
||||||
|
const message = String(typeof error === 'string' ? error : error?.message || '');
|
||||||
|
return /^MAIL2925_THREAD_TERMINATED::/.test(message);
|
||||||
|
}
|
||||||
|
|
||||||
const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGeneratedEmailHelpers({
|
const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGeneratedEmailHelpers({
|
||||||
addLog,
|
addLog,
|
||||||
buildGeneratedAliasEmail,
|
buildGeneratedAliasEmail,
|
||||||
@@ -5366,6 +5464,7 @@ const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGenerat
|
|||||||
getCloudflareTempEmailAddressFromResponse,
|
getCloudflareTempEmailAddressFromResponse,
|
||||||
getCloudflareTempEmailConfig,
|
getCloudflareTempEmailConfig,
|
||||||
getState,
|
getState,
|
||||||
|
ensureMail2925AccountForFlow,
|
||||||
joinCloudflareTempEmailUrl,
|
joinCloudflareTempEmailUrl,
|
||||||
normalizeCloudflareDomain,
|
normalizeCloudflareDomain,
|
||||||
normalizeCloudflareTempEmailAddress,
|
normalizeCloudflareTempEmailAddress,
|
||||||
@@ -5690,8 +5789,25 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
|
|||||||
return currentState.email;
|
return currentState.email;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseEmail = getManagedAliasBaseEmail(currentState);
|
let managedAliasState = currentState;
|
||||||
if (!baseEmail && !currentState.email) {
|
if (
|
||||||
|
String(currentState.mailProvider || '').trim().toLowerCase() === '2925'
|
||||||
|
&& Boolean(currentState.mail2925UseAccountPool)
|
||||||
|
) {
|
||||||
|
const account = await ensureMail2925AccountForFlow({
|
||||||
|
allowAllocate: true,
|
||||||
|
preferredAccountId: currentState.currentMail2925AccountId || null,
|
||||||
|
markUsed: true,
|
||||||
|
});
|
||||||
|
managedAliasState = {
|
||||||
|
...(await getState()),
|
||||||
|
currentMail2925AccountId: account.id,
|
||||||
|
};
|
||||||
|
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:已分配 2925 账号 ${account.email}(第 ${attemptRuns} 次尝试)===`, 'ok');
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseEmail = getManagedAliasBaseEmail(managedAliasState);
|
||||||
|
if (!baseEmail && !managedAliasState.email) {
|
||||||
const baseLabel = currentState.mailProvider === GMAIL_PROVIDER ? 'Gmail 原邮箱' : '2925 基邮箱';
|
const baseLabel = currentState.mailProvider === GMAIL_PROVIDER ? 'Gmail 原邮箱' : '2925 基邮箱';
|
||||||
throw new Error(`${baseLabel}未设置,请先填写,或直接在“注册邮箱”中手动填写完整邮箱。`);
|
throw new Error(`${baseLabel}未设置,请先填写,或直接在“注册邮箱”中手动填写完整邮箱。`);
|
||||||
}
|
}
|
||||||
@@ -5830,6 +5946,10 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
|
|||||||
if (isSignupUserAlreadyExistsFailure(err)) {
|
if (isSignupUserAlreadyExistsFailure(err)) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
if (isMail2925ThreadTerminatedError(err)) {
|
||||||
|
await addLog(`步骤 4:2925 已切换账号并要求结束当前尝试:${getErrorMessage(err)}`, 'warn');
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
step4RestartCount += 1;
|
step4RestartCount += 1;
|
||||||
const preservedState = await getState();
|
const preservedState = await getState();
|
||||||
const preservedEmail = String(preservedState.email || '').trim();
|
const preservedEmail = String(preservedState.email || '').trim();
|
||||||
@@ -6019,6 +6139,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe
|
|||||||
chrome,
|
chrome,
|
||||||
ensureContentScriptReadyOnTab,
|
ensureContentScriptReadyOnTab,
|
||||||
ensureHotmailAccountForFlow,
|
ensureHotmailAccountForFlow,
|
||||||
|
ensureMail2925AccountForFlow,
|
||||||
ensureLuckmailPurchaseForFlow,
|
ensureLuckmailPurchaseForFlow,
|
||||||
getTabId,
|
getTabId,
|
||||||
isGeneratedAliasProvider,
|
isGeneratedAliasProvider,
|
||||||
@@ -6046,9 +6167,11 @@ const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.create
|
|||||||
}),
|
}),
|
||||||
getHotmailVerificationPollConfig,
|
getHotmailVerificationPollConfig,
|
||||||
getHotmailVerificationRequestTimestamp,
|
getHotmailVerificationRequestTimestamp,
|
||||||
|
handleMail2925LimitReachedError,
|
||||||
getState,
|
getState,
|
||||||
getTabId,
|
getTabId,
|
||||||
HOTMAIL_PROVIDER,
|
HOTMAIL_PROVIDER,
|
||||||
|
isMail2925LimitReachedError,
|
||||||
isStopError,
|
isStopError,
|
||||||
LUCKMAIL_PROVIDER,
|
LUCKMAIL_PROVIDER,
|
||||||
MAIL_2925_VERIFICATION_INTERVAL_MS,
|
MAIL_2925_VERIFICATION_INTERVAL_MS,
|
||||||
@@ -6099,6 +6222,7 @@ const step4Executor = self.MultiPageBackgroundStep4?.createStep4Executor({
|
|||||||
chrome,
|
chrome,
|
||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
|
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
|
||||||
|
ensureMail2925MailboxSession,
|
||||||
getMailConfig,
|
getMailConfig,
|
||||||
getTabId,
|
getTabId,
|
||||||
HOTMAIL_PROVIDER,
|
HOTMAIL_PROVIDER,
|
||||||
@@ -6144,6 +6268,7 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({
|
|||||||
chrome,
|
chrome,
|
||||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
||||||
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
|
confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass,
|
||||||
|
ensureMail2925MailboxSession,
|
||||||
ensureStep8VerificationPageReady,
|
ensureStep8VerificationPageReady,
|
||||||
getOAuthFlowRemainingMs,
|
getOAuthFlowRemainingMs,
|
||||||
getOAuthFlowStepTimeoutMs,
|
getOAuthFlowStepTimeoutMs,
|
||||||
@@ -6219,6 +6344,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
|||||||
deleteUsedIcloudAliases,
|
deleteUsedIcloudAliases,
|
||||||
disableUsedLuckmailPurchases,
|
disableUsedLuckmailPurchases,
|
||||||
doesStepUseCompletionSignal,
|
doesStepUseCompletionSignal,
|
||||||
|
ensureMail2925MailboxSession,
|
||||||
ensureManualInteractionAllowed,
|
ensureManualInteractionAllowed,
|
||||||
executeStep,
|
executeStep,
|
||||||
executeStepViaCompletionSignal,
|
executeStepViaCompletionSignal,
|
||||||
@@ -6254,12 +6380,15 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
|||||||
launchAutoRunTimerPlan,
|
launchAutoRunTimerPlan,
|
||||||
listIcloudAliases,
|
listIcloudAliases,
|
||||||
listLuckmailPurchasesForManagement,
|
listLuckmailPurchasesForManagement,
|
||||||
|
getCurrentMail2925Account,
|
||||||
normalizeHotmailAccounts,
|
normalizeHotmailAccounts,
|
||||||
|
normalizeMail2925Accounts,
|
||||||
normalizeRunCount,
|
normalizeRunCount,
|
||||||
AUTO_RUN_TIMER_KIND_SCHEDULED_START,
|
AUTO_RUN_TIMER_KIND_SCHEDULED_START,
|
||||||
notifyStepComplete,
|
notifyStepComplete,
|
||||||
notifyStepError,
|
notifyStepError,
|
||||||
patchHotmailAccount,
|
patchHotmailAccount,
|
||||||
|
patchMail2925Account,
|
||||||
registerTab,
|
registerTab,
|
||||||
requestStop,
|
requestStop,
|
||||||
resetState,
|
resetState,
|
||||||
@@ -6267,6 +6396,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
|||||||
scheduleAutoRun,
|
scheduleAutoRun,
|
||||||
selectLuckmailPurchase,
|
selectLuckmailPurchase,
|
||||||
setCurrentHotmailAccount,
|
setCurrentHotmailAccount,
|
||||||
|
setCurrentMail2925Account,
|
||||||
setContributionMode,
|
setContributionMode,
|
||||||
setEmailState,
|
setEmailState,
|
||||||
setEmailStateSilently,
|
setEmailStateSilently,
|
||||||
@@ -6284,7 +6414,10 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
|||||||
startAutoRunLoop,
|
startAutoRunLoop,
|
||||||
pollContributionStatus: (...args) => contributionOAuthManager?.pollContributionStatus?.(...args),
|
pollContributionStatus: (...args) => contributionOAuthManager?.pollContributionStatus?.(...args),
|
||||||
syncHotmailAccounts,
|
syncHotmailAccounts,
|
||||||
|
deleteMail2925Account,
|
||||||
|
deleteMail2925Accounts,
|
||||||
testHotmailAccountMailAccess,
|
testHotmailAccountMailAccess,
|
||||||
|
upsertMail2925Account,
|
||||||
upsertHotmailAccount,
|
upsertHotmailAccount,
|
||||||
verifyHotmailAccount,
|
verifyHotmailAccount,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -374,6 +374,7 @@
|
|||||||
emailGenerator: prevState.emailGenerator,
|
emailGenerator: prevState.emailGenerator,
|
||||||
gmailBaseEmail: prevState.gmailBaseEmail,
|
gmailBaseEmail: prevState.gmailBaseEmail,
|
||||||
mail2925BaseEmail: prevState.mail2925BaseEmail,
|
mail2925BaseEmail: prevState.mail2925BaseEmail,
|
||||||
|
currentMail2925AccountId: prevState.currentMail2925AccountId,
|
||||||
emailPrefix: prevState.emailPrefix,
|
emailPrefix: prevState.emailPrefix,
|
||||||
inbucketHost: prevState.inbucketHost,
|
inbucketHost: prevState.inbucketHost,
|
||||||
inbucketMailbox: prevState.inbucketMailbox,
|
inbucketMailbox: prevState.inbucketMailbox,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
getCloudflareTempEmailAddressFromResponse,
|
getCloudflareTempEmailAddressFromResponse,
|
||||||
getCloudflareTempEmailConfig,
|
getCloudflareTempEmailConfig,
|
||||||
getState,
|
getState,
|
||||||
|
ensureMail2925AccountForFlow,
|
||||||
joinCloudflareTempEmailUrl,
|
joinCloudflareTempEmailUrl,
|
||||||
normalizeCloudflareDomain,
|
normalizeCloudflareDomain,
|
||||||
normalizeCloudflareTempEmailAddress,
|
normalizeCloudflareTempEmailAddress,
|
||||||
@@ -190,7 +191,7 @@
|
|||||||
async function fetchManagedAliasEmail(state, options = {}) {
|
async function fetchManagedAliasEmail(state, options = {}) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
const provider = String(options.mailProvider || state?.mailProvider || '').trim().toLowerCase();
|
const provider = String(options.mailProvider || state?.mailProvider || '').trim().toLowerCase();
|
||||||
const mergedState = {
|
let mergedState = {
|
||||||
...(state || {}),
|
...(state || {}),
|
||||||
mailProvider: provider,
|
mailProvider: provider,
|
||||||
};
|
};
|
||||||
@@ -200,6 +201,22 @@
|
|||||||
if (options.mail2925BaseEmail !== undefined) {
|
if (options.mail2925BaseEmail !== undefined) {
|
||||||
mergedState.mail2925BaseEmail = String(options.mail2925BaseEmail || '').trim();
|
mergedState.mail2925BaseEmail = String(options.mail2925BaseEmail || '').trim();
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
provider === '2925'
|
||||||
|
&& Boolean(mergedState.mail2925UseAccountPool)
|
||||||
|
&& typeof ensureMail2925AccountForFlow === 'function'
|
||||||
|
) {
|
||||||
|
const account = await ensureMail2925AccountForFlow({
|
||||||
|
allowAllocate: true,
|
||||||
|
preferredAccountId: mergedState.currentMail2925AccountId || null,
|
||||||
|
});
|
||||||
|
const latestState = await getState();
|
||||||
|
mergedState = {
|
||||||
|
...latestState,
|
||||||
|
...mergedState,
|
||||||
|
currentMail2925AccountId: account.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const email = buildGeneratedAliasEmail(mergedState);
|
const email = buildGeneratedAliasEmail(mergedState);
|
||||||
await setEmailState(email);
|
await setEmailState(email);
|
||||||
|
|||||||
@@ -0,0 +1,469 @@
|
|||||||
|
(function attachBackgroundMail2925Session(root, factory) {
|
||||||
|
root.MultiPageBackgroundMail2925Session = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundMail2925SessionModule() {
|
||||||
|
function createMail2925SessionManager(deps = {}) {
|
||||||
|
const {
|
||||||
|
addLog,
|
||||||
|
broadcastDataUpdate,
|
||||||
|
chrome,
|
||||||
|
findMail2925Account,
|
||||||
|
getMail2925AccountStatus,
|
||||||
|
isMail2925AccountAvailable,
|
||||||
|
MAIL2925_LIMIT_COOLDOWN_MS,
|
||||||
|
normalizeMail2925Account,
|
||||||
|
normalizeMail2925Accounts,
|
||||||
|
pickMail2925AccountForRun,
|
||||||
|
getState,
|
||||||
|
reuseOrCreateTab,
|
||||||
|
sendToMailContentScriptResilient,
|
||||||
|
setPersistentSettings,
|
||||||
|
setState,
|
||||||
|
throwIfStopped,
|
||||||
|
upsertMail2925AccountInList,
|
||||||
|
} = deps;
|
||||||
|
|
||||||
|
const MAIL2925_SOURCE = 'mail-2925';
|
||||||
|
const MAIL2925_URL = 'https://2925.com/#/mailList';
|
||||||
|
const MAIL2925_LOGIN_URL = 'https://2925.com/';
|
||||||
|
const MAIL2925_INJECT = ['content/utils.js', 'content/mail-2925.js'];
|
||||||
|
const MAIL2925_INJECT_SOURCE = 'mail-2925';
|
||||||
|
const MAIL2925_COOKIE_DOMAINS = [
|
||||||
|
'2925.com',
|
||||||
|
'www.2925.com',
|
||||||
|
'mail2.xiyouji.com',
|
||||||
|
];
|
||||||
|
const MAIL2925_COOKIE_ORIGINS = [
|
||||||
|
'https://2925.com',
|
||||||
|
'https://www.2925.com',
|
||||||
|
'https://mail2.xiyouji.com',
|
||||||
|
];
|
||||||
|
const MAIL2925_LIMIT_ERROR_PREFIX = 'MAIL2925_LIMIT_REACHED::';
|
||||||
|
const MAIL2925_THREAD_TERMINATED_ERROR_PREFIX = 'MAIL2925_THREAD_TERMINATED::';
|
||||||
|
|
||||||
|
function getMail2925MailConfig() {
|
||||||
|
return {
|
||||||
|
provider: '2925',
|
||||||
|
source: MAIL2925_SOURCE,
|
||||||
|
url: MAIL2925_URL,
|
||||||
|
label: '2925 邮箱',
|
||||||
|
inject: MAIL2925_INJECT,
|
||||||
|
injectSource: MAIL2925_INJECT_SOURCE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage(error) {
|
||||||
|
return String(typeof error === 'string' ? error : error?.message || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMail2925ThreadTerminatedError(message) {
|
||||||
|
return new Error(`${MAIL2925_THREAD_TERMINATED_ERROR_PREFIX}${String(message || '').trim()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMail2925LimitReachedError(error) {
|
||||||
|
const message = getErrorMessage(error);
|
||||||
|
return message.startsWith(MAIL2925_LIMIT_ERROR_PREFIX)
|
||||||
|
|| /子邮箱.{0,12}已达上限|已达上限邮箱|子邮箱上限|邮箱已达上限/i.test(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMail2925ThreadTerminatedError(error) {
|
||||||
|
return getErrorMessage(error).startsWith(MAIL2925_THREAD_TERMINATED_ERROR_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncMail2925Accounts(accounts) {
|
||||||
|
const normalized = normalizeMail2925Accounts(accounts);
|
||||||
|
await setPersistentSettings({ mail2925Accounts: normalized });
|
||||||
|
await setState({ mail2925Accounts: normalized });
|
||||||
|
broadcastDataUpdate({ mail2925Accounts: normalized });
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function upsertMail2925Account(input = {}) {
|
||||||
|
const state = await getState();
|
||||||
|
const accounts = normalizeMail2925Accounts(state.mail2925Accounts);
|
||||||
|
const normalizedEmail = String(input?.email || '').trim().toLowerCase();
|
||||||
|
const existing = input?.id
|
||||||
|
? findMail2925Account(accounts, input.id)
|
||||||
|
: accounts.find((account) => account.email === normalizedEmail) || null;
|
||||||
|
const credentialsChanged = !existing
|
||||||
|
|| (input?.email !== undefined && normalizedEmail !== existing.email)
|
||||||
|
|| (input?.password !== undefined && String(input.password || '') !== existing.password);
|
||||||
|
const normalized = normalizeMail2925Account({
|
||||||
|
...(existing || {}),
|
||||||
|
...(credentialsChanged ? { lastError: '' } : {}),
|
||||||
|
...input,
|
||||||
|
id: input?.id || existing?.id || crypto.randomUUID(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextAccounts = existing
|
||||||
|
? accounts.map((account) => (account.id === normalized.id ? normalized : account))
|
||||||
|
: [...accounts, normalized];
|
||||||
|
|
||||||
|
await syncMail2925Accounts(nextAccounts);
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentMail2925Account(state = {}) {
|
||||||
|
return findMail2925Account(state.mail2925Accounts, state.currentMail2925AccountId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setCurrentMail2925Account(accountId, options = {}) {
|
||||||
|
const { logMessage = '', updateLastUsedAt = false } = options;
|
||||||
|
const state = await getState();
|
||||||
|
const accounts = normalizeMail2925Accounts(state.mail2925Accounts);
|
||||||
|
const account = findMail2925Account(accounts, accountId);
|
||||||
|
if (!account) {
|
||||||
|
throw new Error('未找到对应的 2925 账号。');
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextAccount = account;
|
||||||
|
if (updateLastUsedAt) {
|
||||||
|
nextAccount = normalizeMail2925Account({
|
||||||
|
...account,
|
||||||
|
lastUsedAt: Date.now(),
|
||||||
|
});
|
||||||
|
await syncMail2925Accounts(accounts.map((item) => (item.id === account.id ? nextAccount : item)));
|
||||||
|
}
|
||||||
|
|
||||||
|
await setState({ currentMail2925AccountId: nextAccount.id });
|
||||||
|
broadcastDataUpdate({ currentMail2925AccountId: nextAccount.id });
|
||||||
|
if (logMessage) {
|
||||||
|
await addLog(logMessage, 'ok');
|
||||||
|
}
|
||||||
|
return nextAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function patchMail2925Account(accountId, updates = {}) {
|
||||||
|
const state = await getState();
|
||||||
|
const accounts = normalizeMail2925Accounts(state.mail2925Accounts);
|
||||||
|
const account = findMail2925Account(accounts, accountId);
|
||||||
|
if (!account) {
|
||||||
|
throw new Error('未找到对应的 2925 账号。');
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextAccount = normalizeMail2925Account({
|
||||||
|
...account,
|
||||||
|
...updates,
|
||||||
|
id: account.id,
|
||||||
|
});
|
||||||
|
await syncMail2925Accounts(accounts.map((item) => (item.id === account.id ? nextAccount : item)));
|
||||||
|
|
||||||
|
if (state.currentMail2925AccountId === account.id && nextAccount.enabled === false) {
|
||||||
|
await setState({ currentMail2925AccountId: null });
|
||||||
|
broadcastDataUpdate({ currentMail2925AccountId: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteMail2925Account(accountId) {
|
||||||
|
const state = await getState();
|
||||||
|
const accounts = normalizeMail2925Accounts(state.mail2925Accounts);
|
||||||
|
const nextAccounts = accounts.filter((account) => account.id !== accountId);
|
||||||
|
await syncMail2925Accounts(nextAccounts);
|
||||||
|
|
||||||
|
if (state.currentMail2925AccountId === accountId) {
|
||||||
|
await setState({ currentMail2925AccountId: null });
|
||||||
|
broadcastDataUpdate({ currentMail2925AccountId: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteMail2925Accounts(mode = 'all') {
|
||||||
|
const state = await getState();
|
||||||
|
const accounts = normalizeMail2925Accounts(state.mail2925Accounts);
|
||||||
|
const nextAccounts = mode === 'all'
|
||||||
|
? []
|
||||||
|
: accounts.filter((account) => getMail2925AccountStatus(account) !== String(mode || '').trim());
|
||||||
|
const deletedCount = Math.max(0, accounts.length - nextAccounts.length);
|
||||||
|
await syncMail2925Accounts(nextAccounts);
|
||||||
|
|
||||||
|
if (state.currentMail2925AccountId && !findMail2925Account(nextAccounts, state.currentMail2925AccountId)) {
|
||||||
|
await setState({ currentMail2925AccountId: null });
|
||||||
|
broadcastDataUpdate({ currentMail2925AccountId: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletedCount,
|
||||||
|
remainingCount: nextAccounts.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureMail2925AccountForFlow(options = {}) {
|
||||||
|
const {
|
||||||
|
allowAllocate = true,
|
||||||
|
preferredAccountId = null,
|
||||||
|
excludeIds = [],
|
||||||
|
markUsed = false,
|
||||||
|
} = options;
|
||||||
|
const state = await getState();
|
||||||
|
const accounts = normalizeMail2925Accounts(state.mail2925Accounts);
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
let account = null;
|
||||||
|
if (preferredAccountId) {
|
||||||
|
account = findMail2925Account(accounts, preferredAccountId);
|
||||||
|
}
|
||||||
|
if (!account && state.currentMail2925AccountId) {
|
||||||
|
account = findMail2925Account(accounts, state.currentMail2925AccountId);
|
||||||
|
}
|
||||||
|
if ((!account || !isMail2925AccountAvailable(account, now)) && allowAllocate) {
|
||||||
|
account = pickMail2925AccountForRun(accounts, {
|
||||||
|
excludeIds,
|
||||||
|
now,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
throw new Error('没有可用的 2925 账号。请先在侧边栏添加至少一个带密码的 2925 账号。');
|
||||||
|
}
|
||||||
|
if (!account.password) {
|
||||||
|
throw new Error(`2925 账号 ${account.email || account.id} 缺少密码,无法自动登录。`);
|
||||||
|
}
|
||||||
|
if (!isMail2925AccountAvailable(account, now)) {
|
||||||
|
const disabledUntil = Number(account.disabledUntil || 0);
|
||||||
|
if (disabledUntil > now) {
|
||||||
|
throw new Error(`2925 账号 ${account.email || account.id} 当前处于冷却期,将在 ${new Date(disabledUntil).toLocaleString('zh-CN', { hour12: false })} 后恢复。`);
|
||||||
|
}
|
||||||
|
throw new Error(`2925 账号 ${account.email || account.id} 当前不可用。`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return setCurrentMail2925Account(account.id, { updateLastUsedAt: markUsed });
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCookieDomainForMatch(domain) {
|
||||||
|
return String(domain || '').trim().replace(/^\.+/, '').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldClearMail2925Cookie(cookie) {
|
||||||
|
const domain = normalizeCookieDomainForMatch(cookie?.domain);
|
||||||
|
if (!domain) return false;
|
||||||
|
return MAIL2925_COOKIE_DOMAINS.some((target) => (
|
||||||
|
domain === target || domain.endsWith(`.${target}`)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCookieRemovalUrl(cookie) {
|
||||||
|
const host = normalizeCookieDomainForMatch(cookie?.domain);
|
||||||
|
const path = String(cookie?.path || '/').startsWith('/')
|
||||||
|
? String(cookie?.path || '/')
|
||||||
|
: `/${String(cookie?.path || '')}`;
|
||||||
|
return `https://${host}${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectMail2925Cookies() {
|
||||||
|
if (!chrome.cookies?.getAll) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const stores = chrome.cookies.getAllCookieStores
|
||||||
|
? await chrome.cookies.getAllCookieStores()
|
||||||
|
: [{ id: undefined }];
|
||||||
|
const cookies = [];
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
for (const store of stores) {
|
||||||
|
const storeId = store?.id;
|
||||||
|
const batch = await chrome.cookies.getAll(storeId ? { storeId } : {});
|
||||||
|
for (const cookie of batch || []) {
|
||||||
|
if (!shouldClearMail2925Cookie(cookie)) continue;
|
||||||
|
const key = [
|
||||||
|
cookie.storeId || storeId || '',
|
||||||
|
cookie.domain || '',
|
||||||
|
cookie.path || '',
|
||||||
|
cookie.name || '',
|
||||||
|
cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : '',
|
||||||
|
].join('|');
|
||||||
|
if (seen.has(key)) continue;
|
||||||
|
seen.add(key);
|
||||||
|
cookies.push(cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeMail2925Cookie(cookie) {
|
||||||
|
const details = {
|
||||||
|
url: buildCookieRemovalUrl(cookie),
|
||||||
|
name: cookie.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cookie.storeId) {
|
||||||
|
details.storeId = cookie.storeId;
|
||||||
|
}
|
||||||
|
if (cookie.partitionKey) {
|
||||||
|
details.partitionKey = cookie.partitionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Boolean(await chrome.cookies.remove(details));
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearMail2925SessionCookies() {
|
||||||
|
if (!chrome.cookies?.getAll || !chrome.cookies?.remove) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookies = await collectMail2925Cookies();
|
||||||
|
let removedCount = 0;
|
||||||
|
for (const cookie of cookies) {
|
||||||
|
throwIfStopped();
|
||||||
|
if (await removeMail2925Cookie(cookie)) {
|
||||||
|
removedCount += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chrome.browsingData?.removeCookies) {
|
||||||
|
try {
|
||||||
|
await chrome.browsingData.removeCookies({
|
||||||
|
since: 0,
|
||||||
|
origins: MAIL2925_COOKIE_ORIGINS,
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
// Best-effort cleanup only.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureMail2925MailboxSession(options = {}) {
|
||||||
|
const {
|
||||||
|
accountId = null,
|
||||||
|
forceRelogin = false,
|
||||||
|
actionLabel = '确保 2925 邮箱登录态',
|
||||||
|
} = options;
|
||||||
|
const account = await ensureMail2925AccountForFlow({
|
||||||
|
allowAllocate: true,
|
||||||
|
preferredAccountId: accountId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (forceRelogin) {
|
||||||
|
const removedCount = await clearMail2925SessionCookies();
|
||||||
|
await addLog(`2925:已清理 ${removedCount} 个登录相关 cookie,准备使用 ${account.email} 重新登录。`, 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
throwIfStopped();
|
||||||
|
await reuseOrCreateTab(MAIL2925_SOURCE, forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL, {
|
||||||
|
inject: MAIL2925_INJECT,
|
||||||
|
injectSource: MAIL2925_INJECT_SOURCE,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await sendToMailContentScriptResilient(
|
||||||
|
getMail2925MailConfig(),
|
||||||
|
{
|
||||||
|
type: 'ENSURE_MAIL2925_SESSION',
|
||||||
|
step: 0,
|
||||||
|
source: 'background',
|
||||||
|
payload: {
|
||||||
|
email: account.email,
|
||||||
|
password: account.password,
|
||||||
|
forceLogin: forceRelogin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeoutMs: forceRelogin ? 90000 : 45000,
|
||||||
|
responseTimeoutMs: forceRelogin ? 90000 : 45000,
|
||||||
|
maxRecoveryAttempts: 2,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result?.error) {
|
||||||
|
throw new Error(result.error);
|
||||||
|
}
|
||||||
|
if (result?.limitReached) {
|
||||||
|
throw new Error(`${MAIL2925_LIMIT_ERROR_PREFIX}${result.limitMessage || '2925 子邮箱已达上限邮箱'}`);
|
||||||
|
}
|
||||||
|
if (!result?.loggedIn) {
|
||||||
|
throw new Error(`2925:${actionLabel}失败,当前页面仍未进入收件箱。`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await patchMail2925Account(account.id, {
|
||||||
|
lastLoginAt: Date.now(),
|
||||||
|
lastError: '',
|
||||||
|
});
|
||||||
|
await setState({ currentMail2925AccountId: account.id });
|
||||||
|
broadcastDataUpdate({ currentMail2925AccountId: account.id });
|
||||||
|
|
||||||
|
return {
|
||||||
|
account: await ensureMail2925AccountForFlow({
|
||||||
|
allowAllocate: false,
|
||||||
|
preferredAccountId: account.id,
|
||||||
|
}),
|
||||||
|
mail: getMail2925MailConfig(),
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMail2925LimitReachedError(step, error) {
|
||||||
|
const reason = getErrorMessage(error).replace(MAIL2925_LIMIT_ERROR_PREFIX, '').trim()
|
||||||
|
|| '子邮箱已达上限邮箱';
|
||||||
|
const state = await getState();
|
||||||
|
const currentAccount = getCurrentMail2925Account(state);
|
||||||
|
if (!currentAccount) {
|
||||||
|
return buildMail2925ThreadTerminatedError(`步骤 ${step}:2925 检测到“${reason}”,但当前没有可识别的账号,已结束本次尝试。`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const disabledUntil = Date.now() + Math.max(1, Number(MAIL2925_LIMIT_COOLDOWN_MS) || (24 * 60 * 60 * 1000));
|
||||||
|
await patchMail2925Account(currentAccount.id, {
|
||||||
|
lastLimitAt: Date.now(),
|
||||||
|
disabledUntil,
|
||||||
|
lastError: reason,
|
||||||
|
});
|
||||||
|
await addLog(
|
||||||
|
`步骤 ${step}:2925 账号 ${currentAccount.email} 命中“${reason}”,已禁用 24 小时,恢复时间 ${new Date(disabledUntil).toLocaleString('zh-CN', { hour12: false })}。`,
|
||||||
|
'warn'
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextState = await getState();
|
||||||
|
const nextAccounts = normalizeMail2925Accounts(nextState.mail2925Accounts);
|
||||||
|
const nextAccount = pickMail2925AccountForRun(nextAccounts, {
|
||||||
|
excludeIds: [currentAccount.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!nextAccount) {
|
||||||
|
await setState({ currentMail2925AccountId: null });
|
||||||
|
broadcastDataUpdate({ currentMail2925AccountId: null });
|
||||||
|
return buildMail2925ThreadTerminatedError(
|
||||||
|
`步骤 ${step}:2925 账号 ${currentAccount.email} 已因“${reason}”禁用 24 小时,且当前没有可切换的下一个账号,本次尝试结束。`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await setCurrentMail2925Account(nextAccount.id);
|
||||||
|
await ensureMail2925MailboxSession({
|
||||||
|
accountId: nextAccount.id,
|
||||||
|
forceRelogin: true,
|
||||||
|
actionLabel: `步骤 ${step}:切换 2925 账号`,
|
||||||
|
});
|
||||||
|
await addLog(`步骤 ${step}:2925 已自动切换到下一个账号 ${nextAccount.email} 并完成登录,当前尝试将直接结束。`, 'warn');
|
||||||
|
return buildMail2925ThreadTerminatedError(
|
||||||
|
`步骤 ${step}:2925 账号 ${currentAccount.email} 命中“${reason}”并已禁用 24 小时,已切换到 ${nextAccount.email},当前尝试结束,等待自动重试进入下一次尝试。`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
MAIL2925_LIMIT_ERROR_PREFIX,
|
||||||
|
MAIL2925_THREAD_TERMINATED_ERROR_PREFIX,
|
||||||
|
clearMail2925SessionCookies,
|
||||||
|
deleteMail2925Account,
|
||||||
|
deleteMail2925Accounts,
|
||||||
|
ensureMail2925AccountForFlow,
|
||||||
|
ensureMail2925MailboxSession,
|
||||||
|
getCurrentMail2925Account,
|
||||||
|
getMail2925MailConfig,
|
||||||
|
handleMail2925LimitReachedError,
|
||||||
|
isMail2925LimitReachedError,
|
||||||
|
isMail2925ThreadTerminatedError,
|
||||||
|
patchMail2925Account,
|
||||||
|
setCurrentMail2925Account,
|
||||||
|
syncMail2925Accounts,
|
||||||
|
upsertMail2925Account,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createMail2925SessionManager,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
deleteUsedIcloudAliases,
|
deleteUsedIcloudAliases,
|
||||||
disableUsedLuckmailPurchases,
|
disableUsedLuckmailPurchases,
|
||||||
doesStepUseCompletionSignal,
|
doesStepUseCompletionSignal,
|
||||||
|
ensureMail2925MailboxSession,
|
||||||
ensureManualInteractionAllowed,
|
ensureManualInteractionAllowed,
|
||||||
executeStep,
|
executeStep,
|
||||||
executeStepViaCompletionSignal,
|
executeStepViaCompletionSignal,
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
findHotmailAccount,
|
findHotmailAccount,
|
||||||
flushCommand,
|
flushCommand,
|
||||||
getCurrentLuckmailPurchase,
|
getCurrentLuckmailPurchase,
|
||||||
|
getCurrentMail2925Account,
|
||||||
getPendingAutoRunTimerPlan,
|
getPendingAutoRunTimerPlan,
|
||||||
getSourceLabel,
|
getSourceLabel,
|
||||||
getState,
|
getState,
|
||||||
@@ -52,10 +54,12 @@
|
|||||||
listIcloudAliases,
|
listIcloudAliases,
|
||||||
listLuckmailPurchasesForManagement,
|
listLuckmailPurchasesForManagement,
|
||||||
normalizeHotmailAccounts,
|
normalizeHotmailAccounts,
|
||||||
|
normalizeMail2925Accounts,
|
||||||
normalizeRunCount,
|
normalizeRunCount,
|
||||||
AUTO_RUN_TIMER_KIND_SCHEDULED_START,
|
AUTO_RUN_TIMER_KIND_SCHEDULED_START,
|
||||||
notifyStepComplete,
|
notifyStepComplete,
|
||||||
notifyStepError,
|
notifyStepError,
|
||||||
|
patchMail2925Account,
|
||||||
patchHotmailAccount,
|
patchHotmailAccount,
|
||||||
pollContributionStatus,
|
pollContributionStatus,
|
||||||
registerTab,
|
registerTab,
|
||||||
@@ -65,6 +69,7 @@
|
|||||||
resumeAutoRun,
|
resumeAutoRun,
|
||||||
scheduleAutoRun,
|
scheduleAutoRun,
|
||||||
selectLuckmailPurchase,
|
selectLuckmailPurchase,
|
||||||
|
setCurrentMail2925Account,
|
||||||
setCurrentHotmailAccount,
|
setCurrentHotmailAccount,
|
||||||
setContributionMode,
|
setContributionMode,
|
||||||
setEmailState,
|
setEmailState,
|
||||||
@@ -81,8 +86,11 @@
|
|||||||
skipStep,
|
skipStep,
|
||||||
startContributionFlow,
|
startContributionFlow,
|
||||||
startAutoRunLoop,
|
startAutoRunLoop,
|
||||||
|
deleteMail2925Account,
|
||||||
|
deleteMail2925Accounts,
|
||||||
syncHotmailAccounts,
|
syncHotmailAccounts,
|
||||||
testHotmailAccountMailAccess,
|
testHotmailAccountMailAccess,
|
||||||
|
upsertMail2925Account,
|
||||||
upsertHotmailAccount,
|
upsertHotmailAccount,
|
||||||
verifyHotmailAccount,
|
verifyHotmailAccount,
|
||||||
} = deps;
|
} = deps;
|
||||||
@@ -178,6 +186,13 @@
|
|||||||
});
|
});
|
||||||
await addLog('当前 Hotmail 账号已自动标记为已用。', 'ok');
|
await addLog('当前 Hotmail 账号已自动标记为已用。', 'ok');
|
||||||
}
|
}
|
||||||
|
if (String(latestState.mailProvider || '').trim().toLowerCase() === '2925' && latestState.currentMail2925AccountId) {
|
||||||
|
await patchMail2925Account(latestState.currentMail2925AccountId, {
|
||||||
|
lastUsedAt: Date.now(),
|
||||||
|
lastError: '',
|
||||||
|
});
|
||||||
|
await addLog('当前 2925 账号已记录最近使用时间。', 'ok');
|
||||||
|
}
|
||||||
if (isLuckmailProvider(latestState)) {
|
if (isLuckmailProvider(latestState)) {
|
||||||
const currentPurchase = getCurrentLuckmailPurchase(latestState);
|
const currentPurchase = getCurrentLuckmailPurchase(latestState);
|
||||||
if (currentPurchase?.id) {
|
if (currentPurchase?.id) {
|
||||||
@@ -578,6 +593,52 @@
|
|||||||
return { ok: true, ...result };
|
return { ok: true, ...result };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'UPSERT_MAIL2925_ACCOUNT': {
|
||||||
|
const account = await upsertMail2925Account(message.payload || {});
|
||||||
|
return { ok: true, account };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'DELETE_MAIL2925_ACCOUNT': {
|
||||||
|
await deleteMail2925Account(String(message.payload?.accountId || ''));
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'DELETE_MAIL2925_ACCOUNTS': {
|
||||||
|
const result = await deleteMail2925Accounts(String(message.payload?.mode || 'all'));
|
||||||
|
return { ok: true, ...result };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SELECT_MAIL2925_ACCOUNT': {
|
||||||
|
const account = await setCurrentMail2925Account(String(message.payload?.accountId || ''), {
|
||||||
|
updateLastUsedAt: false,
|
||||||
|
});
|
||||||
|
return { ok: true, account };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'PATCH_MAIL2925_ACCOUNT': {
|
||||||
|
const account = await patchMail2925Account(
|
||||||
|
String(message.payload?.accountId || ''),
|
||||||
|
message.payload?.updates || {}
|
||||||
|
);
|
||||||
|
return { ok: true, account };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'LOGIN_MAIL2925_ACCOUNT': {
|
||||||
|
const accountId = String(message.payload?.accountId || '');
|
||||||
|
const account = await setCurrentMail2925Account(accountId, {
|
||||||
|
updateLastUsedAt: false,
|
||||||
|
});
|
||||||
|
if (typeof deps.ensureMail2925MailboxSession !== 'function') {
|
||||||
|
throw new Error('2925 登录能力尚未接入。');
|
||||||
|
}
|
||||||
|
await deps.ensureMail2925MailboxSession({
|
||||||
|
accountId: account.id,
|
||||||
|
forceRelogin: Boolean(message.payload?.forceRelogin),
|
||||||
|
actionLabel: '侧边栏手动登录 2925 账号',
|
||||||
|
});
|
||||||
|
return { ok: true, account };
|
||||||
|
}
|
||||||
|
|
||||||
case 'LIST_LUCKMAIL_PURCHASES': {
|
case 'LIST_LUCKMAIL_PURCHASES': {
|
||||||
const purchases = await listLuckmailPurchasesForManagement();
|
const purchases = await listLuckmailPurchasesForManagement();
|
||||||
return { ok: true, purchases };
|
return { ok: true, purchases };
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
chrome,
|
chrome,
|
||||||
ensureContentScriptReadyOnTab,
|
ensureContentScriptReadyOnTab,
|
||||||
ensureHotmailAccountForFlow,
|
ensureHotmailAccountForFlow,
|
||||||
|
ensureMail2925AccountForFlow,
|
||||||
ensureLuckmailPurchaseForFlow,
|
ensureLuckmailPurchaseForFlow,
|
||||||
isGeneratedAliasProvider,
|
isGeneratedAliasProvider,
|
||||||
isReusableGeneratedAliasEmail,
|
isReusableGeneratedAliasEmail,
|
||||||
@@ -198,6 +199,15 @@
|
|||||||
const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true });
|
const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true });
|
||||||
resolvedEmail = purchase.email_address;
|
resolvedEmail = purchase.email_address;
|
||||||
} else if (isGeneratedAliasProvider(state)) {
|
} else if (isGeneratedAliasProvider(state)) {
|
||||||
|
if (Boolean(state?.mail2925UseAccountPool)
|
||||||
|
&& String(state?.mailProvider || '').trim().toLowerCase() === '2925'
|
||||||
|
&& typeof ensureMail2925AccountForFlow === 'function') {
|
||||||
|
await ensureMail2925AccountForFlow({
|
||||||
|
allowAllocate: true,
|
||||||
|
preferredAccountId: state.currentMail2925AccountId || null,
|
||||||
|
markUsed: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (!isReusableGeneratedAliasEmail?.(state, resolvedEmail)) {
|
if (!isReusableGeneratedAliasEmail?.(state, resolvedEmail)) {
|
||||||
resolvedEmail = buildGeneratedAliasEmail(state);
|
resolvedEmail = buildGeneratedAliasEmail(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
chrome,
|
chrome,
|
||||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
||||||
confirmCustomVerificationStepBypass,
|
confirmCustomVerificationStepBypass,
|
||||||
|
ensureMail2925MailboxSession,
|
||||||
ensureStep8VerificationPageReady,
|
ensureStep8VerificationPageReady,
|
||||||
getOAuthFlowRemainingMs,
|
getOAuthFlowRemainingMs,
|
||||||
getOAuthFlowStepTimeoutMs,
|
getOAuthFlowStepTimeoutMs,
|
||||||
@@ -105,6 +106,14 @@
|
|||||||
|| mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER
|
|| mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER
|
||||||
) {
|
) {
|
||||||
await addLog(`步骤 8:正在通过 ${mail.label} 轮询验证码...`);
|
await addLog(`步骤 8:正在通过 ${mail.label} 轮询验证码...`);
|
||||||
|
} else if (mail.provider === '2925') {
|
||||||
|
if (state?.mail2925UseAccountPool && typeof ensureMail2925MailboxSession === 'function') {
|
||||||
|
await ensureMail2925MailboxSession({
|
||||||
|
accountId: state.currentMail2925AccountId || null,
|
||||||
|
actionLabel: '步骤 8:确认 2925 邮箱登录态',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await addLog(`步骤 8:正在通过 ${mail.label} 轮询验证码...`);
|
||||||
} else {
|
} else {
|
||||||
await addLog(`步骤 8:正在打开${mail.label}...`);
|
await addLog(`步骤 8:正在打开${mail.label}...`);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
chrome,
|
chrome,
|
||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
confirmCustomVerificationStepBypass,
|
confirmCustomVerificationStepBypass,
|
||||||
|
ensureMail2925MailboxSession,
|
||||||
getMailConfig,
|
getMailConfig,
|
||||||
getTabId,
|
getTabId,
|
||||||
HOTMAIL_PROVIDER,
|
HOTMAIL_PROVIDER,
|
||||||
@@ -69,6 +70,14 @@
|
|||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
if (mail.provider === HOTMAIL_PROVIDER || mail.provider === LUCKMAIL_PROVIDER || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
|
if (mail.provider === HOTMAIL_PROVIDER || mail.provider === LUCKMAIL_PROVIDER || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
|
||||||
await addLog(`步骤 4:正在通过 ${mail.label} 轮询验证码...`);
|
await addLog(`步骤 4:正在通过 ${mail.label} 轮询验证码...`);
|
||||||
|
} else if (mail.provider === '2925') {
|
||||||
|
if (state?.mail2925UseAccountPool && typeof ensureMail2925MailboxSession === 'function') {
|
||||||
|
await ensureMail2925MailboxSession({
|
||||||
|
accountId: state.currentMail2925AccountId || null,
|
||||||
|
actionLabel: '步骤 4:确认 2925 邮箱登录态',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await addLog(`步骤 4:正在通过 ${mail.label} 轮询验证码...`);
|
||||||
} else {
|
} else {
|
||||||
await addLog(`步骤 4:正在打开${mail.label}...`);
|
await addLog(`步骤 4:正在打开${mail.label}...`);
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,11 @@
|
|||||||
confirmCustomVerificationStepBypassRequest,
|
confirmCustomVerificationStepBypassRequest,
|
||||||
getHotmailVerificationPollConfig,
|
getHotmailVerificationPollConfig,
|
||||||
getHotmailVerificationRequestTimestamp,
|
getHotmailVerificationRequestTimestamp,
|
||||||
|
handleMail2925LimitReachedError,
|
||||||
getState,
|
getState,
|
||||||
getTabId,
|
getTabId,
|
||||||
HOTMAIL_PROVIDER,
|
HOTMAIL_PROVIDER,
|
||||||
|
isMail2925LimitReachedError,
|
||||||
isStopError,
|
isStopError,
|
||||||
LUCKMAIL_PROVIDER,
|
LUCKMAIL_PROVIDER,
|
||||||
MAIL_2925_VERIFICATION_INTERVAL_MS,
|
MAIL_2925_VERIFICATION_INTERVAL_MS,
|
||||||
@@ -417,6 +419,13 @@
|
|||||||
if (isStopError(err)) {
|
if (isStopError(err)) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
if (mail?.provider === '2925' && typeof isMail2925LimitReachedError === 'function' && isMail2925LimitReachedError(err)) {
|
||||||
|
const latestState = await getState();
|
||||||
|
if (latestState?.mail2925UseAccountPool && typeof handleMail2925LimitReachedError === 'function') {
|
||||||
|
throw await handleMail2925LimitReachedError(step, err);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
lastError = err;
|
lastError = err;
|
||||||
await addLog(`步骤 ${step}:${err.message}`, 'warn');
|
await addLog(`步骤 ${step}:${err.message}`, 'warn');
|
||||||
}
|
}
|
||||||
@@ -554,6 +563,13 @@
|
|||||||
if (isStopError(err)) {
|
if (isStopError(err)) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
if (mail?.provider === '2925' && typeof isMail2925LimitReachedError === 'function' && isMail2925LimitReachedError(err)) {
|
||||||
|
const latestState = await getState();
|
||||||
|
if (latestState?.mail2925UseAccountPool && typeof handleMail2925LimitReachedError === 'function') {
|
||||||
|
throw await handleMail2925LimitReachedError(step, err);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
lastError = err;
|
lastError = err;
|
||||||
await addLog(`步骤 ${step}:${err.message}`, 'warn');
|
await addLog(`步骤 ${step}:${err.message}`, 'warn');
|
||||||
if (round < maxRounds) {
|
if (round < maxRounds) {
|
||||||
|
|||||||
@@ -81,6 +81,16 @@ async function ensureSeenCodesSession(step, payload = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
if (message.type === 'ENSURE_MAIL2925_SESSION') {
|
||||||
|
resetStopState();
|
||||||
|
ensureMail2925Session(message.payload).then((result) => {
|
||||||
|
sendResponse(result);
|
||||||
|
}).catch((err) => {
|
||||||
|
sendResponse({ error: err?.message || String(err || '2925 登录失败') });
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (message.type === 'POLL_EMAIL') {
|
if (message.type === 'POLL_EMAIL') {
|
||||||
resetStopState();
|
resetStopState();
|
||||||
handlePollEmail(message.step, message.payload).then((result) => {
|
handlePollEmail(message.step, message.payload).then((result) => {
|
||||||
@@ -150,11 +160,70 @@ const MAIL_SELECT_ALL_SELECTORS = [
|
|||||||
'[class*="checkbox"]',
|
'[class*="checkbox"]',
|
||||||
];
|
];
|
||||||
const MAIL_ACTION_CANDIDATE_SELECTORS = 'button, [role="button"], a, label, span, div';
|
const MAIL_ACTION_CANDIDATE_SELECTORS = 'button, [role="button"], a, label, span, div';
|
||||||
|
const MAIL2925_LIMIT_ERROR_PREFIX = 'MAIL2925_LIMIT_REACHED::';
|
||||||
|
const MAIL2925_LOGIN_INPUT_SELECTORS = [
|
||||||
|
'input[type="email"]',
|
||||||
|
'input[name*="mail"]',
|
||||||
|
'input[id*="mail"]',
|
||||||
|
'input[name*="user"]',
|
||||||
|
'input[id*="user"]',
|
||||||
|
'input[placeholder*="邮箱"]',
|
||||||
|
'input[placeholder*="账号"]',
|
||||||
|
'input[placeholder*="用户名"]',
|
||||||
|
'input[type="text"]',
|
||||||
|
];
|
||||||
|
const MAIL2925_PASSWORD_INPUT_SELECTORS = [
|
||||||
|
'input[type="password"]',
|
||||||
|
];
|
||||||
|
const MAIL2925_LOGIN_BUTTON_SELECTORS = [
|
||||||
|
'button[type="submit"]',
|
||||||
|
'.ivu-btn-primary',
|
||||||
|
'.el-button--primary',
|
||||||
|
'[class*="login"]',
|
||||||
|
'[class*="Login"]',
|
||||||
|
];
|
||||||
|
const MAIL2925_LOGIN_BUTTON_PATTERNS = [
|
||||||
|
/登录/i,
|
||||||
|
/login/i,
|
||||||
|
/立即登录/i,
|
||||||
|
/submit/i,
|
||||||
|
];
|
||||||
|
const MAIL2925_AGREEMENT_PATTERNS = [
|
||||||
|
/我已阅读并同意/,
|
||||||
|
/服务协议/,
|
||||||
|
/隐私政策/,
|
||||||
|
];
|
||||||
|
|
||||||
function normalizeNodeText(value) {
|
function normalizeNodeText(value) {
|
||||||
return String(value || '').replace(/\s+/g, ' ').trim();
|
return String(value || '').replace(/\s+/g, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildMail2925LimitError(message = '') {
|
||||||
|
const normalized = normalizeNodeText(message || '子邮箱已达上限邮箱') || '子邮箱已达上限邮箱';
|
||||||
|
return new Error(`${MAIL2925_LIMIT_ERROR_PREFIX}${normalized}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPageTextSample(limit = 4000) {
|
||||||
|
return normalizeNodeText(document.body?.innerText || document.body?.textContent || '').slice(0, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectMail2925LimitMessage() {
|
||||||
|
const text = getPageTextSample();
|
||||||
|
if (!text) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = text.match(/子邮箱.{0,12}已达上限|已达上限邮箱|子邮箱上限|邮箱已达上限/);
|
||||||
|
return match ? match[0] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwIfMail2925LimitReached() {
|
||||||
|
const limitMessage = detectMail2925LimitMessage();
|
||||||
|
if (limitMessage) {
|
||||||
|
throw buildMail2925LimitError(limitMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isVisibleNode(node) {
|
function isVisibleNode(node) {
|
||||||
if (!node) return false;
|
if (!node) return false;
|
||||||
if (node.hidden) return false;
|
if (node.hidden) return false;
|
||||||
@@ -208,6 +277,19 @@ function findActionBySelectors(selectors = []) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findVisibleInputBySelectors(selectors = []) {
|
||||||
|
for (const selector of selectors) {
|
||||||
|
const candidates = document.querySelectorAll(selector);
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (!isVisibleNode(candidate) || candidate.disabled || candidate.readOnly) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function findToolbarActionButton(patterns = [], selectors = []) {
|
function findToolbarActionButton(patterns = [], selectors = []) {
|
||||||
const directMatch = findActionBySelectors(selectors);
|
const directMatch = findActionBySelectors(selectors);
|
||||||
if (directMatch) {
|
if (directMatch) {
|
||||||
@@ -231,6 +313,29 @@ function findToolbarActionButton(patterns = [], selectors = []) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findLoginButton() {
|
||||||
|
const directMatch = findActionBySelectors(MAIL2925_LOGIN_BUTTON_SELECTORS);
|
||||||
|
if (directMatch) {
|
||||||
|
return directMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = document.querySelectorAll(MAIL_ACTION_CANDIDATE_SELECTORS);
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const target = resolveActionTarget(candidate);
|
||||||
|
if (!isVisibleNode(target) || isMailItemNode(target)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = normalizeNodeText(target.innerText || target.textContent || '');
|
||||||
|
const label = normalizeNodeText(target.getAttribute?.('aria-label') || target.getAttribute?.('title') || '');
|
||||||
|
if (MAIL2925_LOGIN_BUTTON_PATTERNS.some((pattern) => pattern.test(text) || pattern.test(label))) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function findRefreshButton() {
|
function findRefreshButton() {
|
||||||
return findToolbarActionButton([
|
return findToolbarActionButton([
|
||||||
/刷新/i,
|
/刷新/i,
|
||||||
@@ -253,6 +358,117 @@ function findSelectAllControl() {
|
|||||||
return findActionBySelectors(MAIL_SELECT_ALL_SELECTORS);
|
return findActionBySelectors(MAIL_SELECT_ALL_SELECTORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findMail2925LoginEmailInput() {
|
||||||
|
const candidates = Array.from(document.querySelectorAll(MAIL2925_LOGIN_INPUT_SELECTORS.join(', ')));
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (!isVisibleNode(candidate) || candidate.disabled || candidate.readOnly) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (candidate.type === 'password') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const identifier = normalizeNodeText([
|
||||||
|
candidate.name,
|
||||||
|
candidate.id,
|
||||||
|
candidate.placeholder,
|
||||||
|
candidate.getAttribute?.('aria-label'),
|
||||||
|
candidate.getAttribute?.('autocomplete'),
|
||||||
|
].join(' ')).toLowerCase();
|
||||||
|
if (/邮箱|账号|用户|mail|user|login/.test(identifier) || candidate.type === 'email') {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMail2925LoginPasswordInput() {
|
||||||
|
return findVisibleInputBySelectors(MAIL2925_PASSWORD_INPUT_SELECTORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAgreementContainer() {
|
||||||
|
const candidates = document.querySelectorAll('label, div, span, p, form');
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (!isVisibleNode(candidate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const text = normalizeNodeText(candidate.innerText || candidate.textContent || '');
|
||||||
|
if (!text) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (MAIL2925_AGREEMENT_PATTERNS.every((pattern) => pattern.test(text) || /我已阅读并同意/.test(text))) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
if (/我已阅读并同意/.test(text) && (/服务协议/.test(text) || /隐私政策/.test(text))) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAgreementCheckbox() {
|
||||||
|
const agreementContainer = findAgreementContainer();
|
||||||
|
if (agreementContainer) {
|
||||||
|
const checkbox = agreementContainer.querySelector('input[type="checkbox"], [role="checkbox"], .ivu-checkbox, .el-checkbox');
|
||||||
|
if (checkbox) {
|
||||||
|
return resolveActionTarget(checkbox);
|
||||||
|
}
|
||||||
|
const nearbyCheckbox = agreementContainer.parentElement?.querySelector?.('input[type="checkbox"], [role="checkbox"], .ivu-checkbox, .el-checkbox');
|
||||||
|
if (nearbyCheckbox) {
|
||||||
|
return resolveActionTarget(nearbyCheckbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const genericCheckboxes = document.querySelectorAll('input[type="checkbox"], [role="checkbox"], .ivu-checkbox, .el-checkbox');
|
||||||
|
for (const checkbox of genericCheckboxes) {
|
||||||
|
const target = resolveActionTarget(checkbox);
|
||||||
|
if (!isVisibleNode(target)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const wrapper = target.closest('label, div, span') || target.parentElement || target;
|
||||||
|
const text = normalizeNodeText(wrapper?.innerText || wrapper?.textContent || '');
|
||||||
|
if (/我已阅读并同意/.test(text) || /服务协议/.test(text) || /隐私政策/.test(text)) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureAgreementChecked() {
|
||||||
|
const agreementCheckbox = findAgreementCheckbox();
|
||||||
|
if (!agreementCheckbox) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isCheckboxChecked(agreementCheckbox)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
simulateClick(agreementCheckbox);
|
||||||
|
await sleep(150);
|
||||||
|
return isCheckboxChecked(agreementCheckbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectMail2925ViewState() {
|
||||||
|
const limitMessage = detectMail2925LimitMessage();
|
||||||
|
if (limitMessage) {
|
||||||
|
return { view: 'limit', limitMessage };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (findMailItems().length > 0) {
|
||||||
|
return { view: 'mailbox', limitMessage: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (findMail2925LoginPasswordInput() && findMail2925LoginEmailInput()) {
|
||||||
|
return { view: 'login', limitMessage: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageText = getPageTextSample();
|
||||||
|
if (/欢迎使用邮箱|登录|login/i.test(pageText) && /密码|password/i.test(pageText)) {
|
||||||
|
return { view: 'login', limitMessage: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { view: 'unknown', limitMessage: '' };
|
||||||
|
}
|
||||||
|
|
||||||
function isCheckboxChecked(node) {
|
function isCheckboxChecked(node) {
|
||||||
const checkbox = node?.matches?.('input[type="checkbox"], [role="checkbox"]')
|
const checkbox = node?.matches?.('input[type="checkbox"], [role="checkbox"]')
|
||||||
? node
|
? node
|
||||||
@@ -518,6 +734,9 @@ async function deleteAllMailboxEmails(step) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function refreshInbox() {
|
async function refreshInbox() {
|
||||||
|
if (typeof throwIfMail2925LimitReached === 'function') {
|
||||||
|
throwIfMail2925LimitReached();
|
||||||
|
}
|
||||||
const refreshBtn = findRefreshButton();
|
const refreshBtn = findRefreshButton();
|
||||||
if (refreshBtn) {
|
if (refreshBtn) {
|
||||||
simulateClick(refreshBtn);
|
simulateClick(refreshBtn);
|
||||||
@@ -532,6 +751,100 @@ async function refreshInbox() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForMail2925View(targetView, timeoutMs = 45000) {
|
||||||
|
const startedAt = Date.now();
|
||||||
|
while (Date.now() - startedAt <= timeoutMs) {
|
||||||
|
throwIfStopped();
|
||||||
|
const currentState = detectMail2925ViewState();
|
||||||
|
if (currentState.view === 'limit') {
|
||||||
|
throw buildMail2925LimitError(currentState.limitMessage);
|
||||||
|
}
|
||||||
|
if (currentState.view === targetView) {
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
await sleep(500);
|
||||||
|
}
|
||||||
|
return detectMail2925ViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureMail2925Session(payload = {}) {
|
||||||
|
const email = String(payload?.email || '').trim();
|
||||||
|
const password = String(payload?.password || '');
|
||||||
|
const forceLogin = Boolean(payload?.forceLogin);
|
||||||
|
|
||||||
|
for (let attempt = 0; attempt < 10; attempt += 1) {
|
||||||
|
throwIfStopped();
|
||||||
|
const currentState = detectMail2925ViewState();
|
||||||
|
if (currentState.view === 'limit') {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
loggedIn: false,
|
||||||
|
currentView: 'limit',
|
||||||
|
limitReached: true,
|
||||||
|
limitMessage: currentState.limitMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (currentState.view === 'mailbox' && !forceLogin) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
loggedIn: true,
|
||||||
|
currentView: 'mailbox',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (currentState.view === 'login') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginState = detectMail2925ViewState();
|
||||||
|
if (loginState.view === 'mailbox') {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
loggedIn: true,
|
||||||
|
currentView: 'mailbox',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (loginState.view === 'limit') {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
loggedIn: false,
|
||||||
|
currentView: 'limit',
|
||||||
|
limitReached: true,
|
||||||
|
limitMessage: loginState.limitMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailInput = findMail2925LoginEmailInput();
|
||||||
|
const passwordInput = findMail2925LoginPasswordInput();
|
||||||
|
const loginButton = findLoginButton();
|
||||||
|
if (!emailInput || !passwordInput || !loginButton) {
|
||||||
|
throw new Error('2925:未识别到可用的登录表单,请确认当前页面处于 2925 登录页。');
|
||||||
|
}
|
||||||
|
if (!email || !password) {
|
||||||
|
throw new Error('2925:当前账号缺少邮箱或密码,无法自动登录。');
|
||||||
|
}
|
||||||
|
|
||||||
|
await ensureAgreementChecked();
|
||||||
|
fillInput(emailInput, email);
|
||||||
|
await sleep(150);
|
||||||
|
fillInput(passwordInput, password);
|
||||||
|
await sleep(200);
|
||||||
|
simulateClick(loginButton);
|
||||||
|
|
||||||
|
const finalState = await waitForMail2925View('mailbox', 60000);
|
||||||
|
if (finalState.view !== 'mailbox') {
|
||||||
|
throw new Error('2925:提交账号密码后未进入收件箱。');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
loggedIn: true,
|
||||||
|
currentView: 'mailbox',
|
||||||
|
usedCredentials: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function handlePollEmail(step, payload) {
|
async function handlePollEmail(step, payload) {
|
||||||
await ensureSeenCodesSession(step, payload);
|
await ensureSeenCodesSession(step, payload);
|
||||||
const {
|
const {
|
||||||
@@ -543,6 +856,9 @@ async function handlePollEmail(step, payload) {
|
|||||||
strictChatGPTCodeOnly = false,
|
strictChatGPTCodeOnly = false,
|
||||||
} = payload || {};
|
} = payload || {};
|
||||||
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
||||||
|
if (typeof throwIfMail2925LimitReached === 'function') {
|
||||||
|
throwIfMail2925LimitReached();
|
||||||
|
}
|
||||||
|
|
||||||
log(`步骤 ${step}:开始轮询 2925 邮箱(最多 ${maxAttempts} 次)`);
|
log(`步骤 ${step}:开始轮询 2925 邮箱(最多 ${maxAttempts} 次)`);
|
||||||
|
|
||||||
@@ -562,6 +878,9 @@ async function handlePollEmail(step, payload) {
|
|||||||
await returnToInbox();
|
await returnToInbox();
|
||||||
await refreshInbox();
|
await refreshInbox();
|
||||||
await sleep(2000);
|
await sleep(2000);
|
||||||
|
if (typeof throwIfMail2925LimitReached === 'function') {
|
||||||
|
throwIfMail2925LimitReached();
|
||||||
|
}
|
||||||
initialItems = findMailItems();
|
initialItems = findMailItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,6 +891,9 @@ async function handlePollEmail(step, payload) {
|
|||||||
log(`步骤 ${step}:邮件列表已加载,共 ${initialItems.length} 封邮件`);
|
log(`步骤 ${step}:邮件列表已加载,共 ${initialItems.length} 封邮件`);
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
|
if (typeof throwIfMail2925LimitReached === 'function') {
|
||||||
|
throwIfMail2925LimitReached();
|
||||||
|
}
|
||||||
log(`步骤 ${step}:正在轮询 2925 邮箱,第 ${attempt}/${maxAttempts} 次`);
|
log(`步骤 ${step}:正在轮询 2925 邮箱,第 ${attempt}/${maxAttempts} 次`);
|
||||||
|
|
||||||
if (attempt > 1 || !initialLoadUsedRefresh) {
|
if (attempt > 1 || !initialLoadUsedRefresh) {
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
(function mail2925UtilsModule(root, factory) {
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = factory();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.Mail2925Utils = factory();
|
||||||
|
})(typeof self !== 'undefined' ? self : globalThis, function createMail2925Utils() {
|
||||||
|
const MAIL2925_LIMIT_COOLDOWN_MS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
function normalizeTimestamp(value) {
|
||||||
|
const numeric = Number(value);
|
||||||
|
return Number.isFinite(numeric) && numeric > 0 ? numeric : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMail2925Account(account = {}) {
|
||||||
|
return {
|
||||||
|
id: String(account.id || crypto.randomUUID()),
|
||||||
|
email: String(account.email || '').trim().toLowerCase(),
|
||||||
|
password: String(account.password || ''),
|
||||||
|
enabled: account.enabled !== undefined ? Boolean(account.enabled) : true,
|
||||||
|
lastUsedAt: normalizeTimestamp(account.lastUsedAt),
|
||||||
|
lastLoginAt: normalizeTimestamp(account.lastLoginAt),
|
||||||
|
lastLimitAt: normalizeTimestamp(account.lastLimitAt),
|
||||||
|
disabledUntil: normalizeTimestamp(account.disabledUntil),
|
||||||
|
lastError: String(account.lastError || '').trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMail2925Accounts(accounts) {
|
||||||
|
if (!Array.isArray(accounts)) return [];
|
||||||
|
|
||||||
|
const deduped = new Map();
|
||||||
|
for (const account of accounts) {
|
||||||
|
const normalized = normalizeMail2925Account(account);
|
||||||
|
if (!normalized.email) continue;
|
||||||
|
deduped.set(normalized.id, normalized);
|
||||||
|
}
|
||||||
|
return [...deduped.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMail2925Account(accounts, accountId) {
|
||||||
|
return normalizeMail2925Accounts(accounts).find((account) => account.id === accountId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMail2925AccountCoolingDown(account, now = Date.now()) {
|
||||||
|
return normalizeTimestamp(account?.disabledUntil) > normalizeTimestamp(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMail2925AccountAvailable(account, now = Date.now()) {
|
||||||
|
return Boolean(account)
|
||||||
|
&& Boolean(account.email)
|
||||||
|
&& Boolean(account.password)
|
||||||
|
&& account.enabled !== false
|
||||||
|
&& !isMail2925AccountCoolingDown(account, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMail2925AccountStatus(account, now = Date.now()) {
|
||||||
|
if (!account) return 'missing';
|
||||||
|
if (account.enabled === false) return 'disabled';
|
||||||
|
if (isMail2925AccountCoolingDown(account, now)) return 'cooldown';
|
||||||
|
if (!account.password) return 'pending';
|
||||||
|
if (account.lastError) return 'error';
|
||||||
|
return 'ready';
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterMail2925AccountsByStatus(accounts, mode = 'all', now = Date.now()) {
|
||||||
|
const list = normalizeMail2925Accounts(accounts);
|
||||||
|
switch (String(mode || 'all').trim()) {
|
||||||
|
case 'available':
|
||||||
|
return list.filter((account) => isMail2925AccountAvailable(account, now));
|
||||||
|
case 'cooldown':
|
||||||
|
return list.filter((account) => isMail2925AccountCoolingDown(account, now));
|
||||||
|
case 'disabled':
|
||||||
|
return list.filter((account) => account.enabled === false);
|
||||||
|
default:
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickMail2925AccountForRun(accounts, options = {}) {
|
||||||
|
const now = normalizeTimestamp(options.now) || Date.now();
|
||||||
|
const excludeIds = new Set((options.excludeIds || []).filter(Boolean));
|
||||||
|
const candidates = normalizeMail2925Accounts(accounts)
|
||||||
|
.filter((account) => isMail2925AccountAvailable(account, now));
|
||||||
|
if (!candidates.length) return null;
|
||||||
|
|
||||||
|
const filtered = candidates.filter((account) => !excludeIds.has(account.id));
|
||||||
|
const pool = filtered.length ? filtered : candidates;
|
||||||
|
|
||||||
|
return pool
|
||||||
|
.slice()
|
||||||
|
.sort((left, right) => {
|
||||||
|
const leftUsedAt = normalizeTimestamp(left.lastUsedAt);
|
||||||
|
const rightUsedAt = normalizeTimestamp(right.lastUsedAt);
|
||||||
|
if (leftUsedAt !== rightUsedAt) {
|
||||||
|
return leftUsedAt - rightUsedAt;
|
||||||
|
}
|
||||||
|
return String(left.email || '').localeCompare(String(right.email || ''));
|
||||||
|
})[0] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMail2925BulkActionLabel(mode = 'all', count = 0) {
|
||||||
|
const normalizedCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0;
|
||||||
|
const prefix = mode === 'cooldown' ? '清空冷却' : '全部删除';
|
||||||
|
const suffix = normalizedCount > 0 ? `(${normalizedCount})` : '';
|
||||||
|
return `${prefix}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMail2925ListToggleLabel(expanded, count = 0) {
|
||||||
|
const normalizedCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0;
|
||||||
|
const suffix = normalizedCount > 0 ? `(${normalizedCount})` : '';
|
||||||
|
return `${expanded ? '收起列表' : '展开列表'}${suffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function upsertMail2925AccountInList(accounts, nextAccount) {
|
||||||
|
const list = Array.isArray(accounts) ? accounts.slice() : [];
|
||||||
|
if (!nextAccount?.id) return list;
|
||||||
|
|
||||||
|
const existingIndex = list.findIndex((account) => account?.id === nextAccount.id);
|
||||||
|
if (existingIndex === -1) {
|
||||||
|
list.push(nextAccount);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
list[existingIndex] = nextAccount;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMail2925ImportText(rawText) {
|
||||||
|
const lines = String(rawText || '')
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
return lines
|
||||||
|
.filter((line, index) => !(index === 0 && /^邮箱----密码$/i.test(line)))
|
||||||
|
.map((line) => line.split('----').map((part) => part.trim()))
|
||||||
|
.filter((parts) => parts.length >= 2 && parts[0] && parts[1])
|
||||||
|
.map(([email, password]) => ({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
MAIL2925_LIMIT_COOLDOWN_MS,
|
||||||
|
filterMail2925AccountsByStatus,
|
||||||
|
findMail2925Account,
|
||||||
|
getMail2925AccountStatus,
|
||||||
|
getMail2925BulkActionLabel,
|
||||||
|
getMail2925ListToggleLabel,
|
||||||
|
isMail2925AccountAvailable,
|
||||||
|
isMail2925AccountCoolingDown,
|
||||||
|
normalizeMail2925Account,
|
||||||
|
normalizeMail2925Accounts,
|
||||||
|
normalizeTimestamp,
|
||||||
|
parseMail2925ImportText,
|
||||||
|
pickMail2925AccountForRun,
|
||||||
|
upsertMail2925AccountInList,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
dom.rowSub2ApiGroup,
|
dom.rowSub2ApiGroup,
|
||||||
dom.rowSub2ApiDefaultProxy,
|
dom.rowSub2ApiDefaultProxy,
|
||||||
dom.rowCustomPassword,
|
dom.rowCustomPassword,
|
||||||
dom.rowAccountRunHistoryTextEnabled,
|
|
||||||
dom.rowAccountRunHistoryHelperBaseUrl,
|
dom.rowAccountRunHistoryHelperBaseUrl,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,463 @@
|
|||||||
|
(function attachSidepanelMail2925Manager(globalScope) {
|
||||||
|
function createMail2925Manager(context = {}) {
|
||||||
|
const {
|
||||||
|
state,
|
||||||
|
dom,
|
||||||
|
helpers,
|
||||||
|
runtime,
|
||||||
|
constants = {},
|
||||||
|
mail2925Utils = {},
|
||||||
|
} = context;
|
||||||
|
|
||||||
|
const expandedStorageKey = constants.expandedStorageKey || 'multipage-mail2925-list-expanded';
|
||||||
|
const displayTimeZone = constants.displayTimeZone || 'Asia/Shanghai';
|
||||||
|
const copyIcon = constants.copyIcon || '';
|
||||||
|
|
||||||
|
let actionInFlight = false;
|
||||||
|
let listExpanded = false;
|
||||||
|
|
||||||
|
function getMail2925Accounts(currentState = state.getLatestState()) {
|
||||||
|
return helpers.getMail2925Accounts(currentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentMail2925AccountId(currentState = state.getLatestState()) {
|
||||||
|
return String(currentState?.currentMail2925AccountId || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMail2925ListViewport() {
|
||||||
|
const count = getMail2925Accounts().length;
|
||||||
|
if (dom.btnDeleteAllMail2925Accounts) {
|
||||||
|
dom.btnDeleteAllMail2925Accounts.textContent = `全部删除${count > 0 ? `(${count})` : ''}`;
|
||||||
|
dom.btnDeleteAllMail2925Accounts.disabled = count === 0;
|
||||||
|
}
|
||||||
|
if (dom.btnToggleMail2925List) {
|
||||||
|
const label = typeof mail2925Utils.getMail2925ListToggleLabel === 'function'
|
||||||
|
? mail2925Utils.getMail2925ListToggleLabel(listExpanded, count)
|
||||||
|
: `${listExpanded ? '收起列表' : '展开列表'}${count > 0 ? `(${count})` : ''}`;
|
||||||
|
dom.btnToggleMail2925List.textContent = label;
|
||||||
|
dom.btnToggleMail2925List.setAttribute('aria-expanded', String(listExpanded));
|
||||||
|
dom.btnToggleMail2925List.disabled = count === 0;
|
||||||
|
}
|
||||||
|
if (dom.mail2925ListShell) {
|
||||||
|
dom.mail2925ListShell.classList.toggle('is-expanded', listExpanded);
|
||||||
|
dom.mail2925ListShell.classList.toggle('is-collapsed', !listExpanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMail2925ListExpanded(expanded, options = {}) {
|
||||||
|
const { persist = true } = options;
|
||||||
|
listExpanded = Boolean(expanded);
|
||||||
|
updateMail2925ListViewport();
|
||||||
|
if (persist) {
|
||||||
|
localStorage.setItem(expandedStorageKey, listExpanded ? '1' : '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initMail2925ListExpandedState() {
|
||||||
|
const saved = localStorage.getItem(expandedStorageKey);
|
||||||
|
setMail2925ListExpanded(saved === '1', { persist: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateTime(timestamp) {
|
||||||
|
const value = Number(timestamp);
|
||||||
|
if (!Number.isFinite(value) || value <= 0) {
|
||||||
|
return '未记录';
|
||||||
|
}
|
||||||
|
return new Date(value).toLocaleString('zh-CN', {
|
||||||
|
hour12: false,
|
||||||
|
timeZone: displayTimeZone,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusSnapshot(account) {
|
||||||
|
const status = typeof mail2925Utils.getMail2925AccountStatus === 'function'
|
||||||
|
? mail2925Utils.getMail2925AccountStatus(account, Date.now())
|
||||||
|
: 'ready';
|
||||||
|
switch (status) {
|
||||||
|
case 'cooldown':
|
||||||
|
return { label: '冷却中', className: 'status-used' };
|
||||||
|
case 'disabled':
|
||||||
|
return { label: '已禁用', className: 'status-disabled' };
|
||||||
|
case 'error':
|
||||||
|
return { label: '异常', className: 'status-error' };
|
||||||
|
case 'pending':
|
||||||
|
return { label: '待完善', className: 'status-pending' };
|
||||||
|
default:
|
||||||
|
return { label: '可用', className: 'status-authorized' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshManagedAliasBaseEmail() {
|
||||||
|
if (typeof helpers.refreshManagedAliasBaseEmail === 'function') {
|
||||||
|
helpers.refreshManagedAliasBaseEmail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMail2925AccountMutation(account, options = {}) {
|
||||||
|
if (!account?.id) return;
|
||||||
|
const latestState = state.getLatestState();
|
||||||
|
const currentId = getCurrentMail2925AccountId(latestState);
|
||||||
|
const nextAccounts = typeof mail2925Utils.upsertMail2925AccountInList === 'function'
|
||||||
|
? mail2925Utils.upsertMail2925AccountInList(getMail2925Accounts(latestState), account)
|
||||||
|
: getMail2925Accounts(latestState).map((item) => (item.id === account.id ? account : item));
|
||||||
|
|
||||||
|
const nextState = {
|
||||||
|
mail2925Accounts: nextAccounts,
|
||||||
|
};
|
||||||
|
if (currentId === account.id && account.enabled === false) {
|
||||||
|
nextState.currentMail2925AccountId = null;
|
||||||
|
}
|
||||||
|
state.syncLatestState(nextState);
|
||||||
|
refreshManagedAliasBaseEmail();
|
||||||
|
renderMail2925Accounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearMail2925Form() {
|
||||||
|
if (dom.inputMail2925Email) dom.inputMail2925Email.value = '';
|
||||||
|
if (dom.inputMail2925Password) dom.inputMail2925Password.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMail2925Accounts() {
|
||||||
|
if (!dom.mail2925AccountsList) return;
|
||||||
|
|
||||||
|
const latestState = state.getLatestState();
|
||||||
|
const accounts = getMail2925Accounts(latestState);
|
||||||
|
const currentId = getCurrentMail2925AccountId(latestState);
|
||||||
|
|
||||||
|
if (!accounts.length) {
|
||||||
|
dom.mail2925AccountsList.innerHTML = '<div class="hotmail-empty">还没有 2925 账号,先添加一条再使用。</div>';
|
||||||
|
updateMail2925ListViewport();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dom.mail2925AccountsList.innerHTML = accounts.map((account) => {
|
||||||
|
const status = getStatusSnapshot(account);
|
||||||
|
const coolingDown = status.label === '冷却中';
|
||||||
|
return `
|
||||||
|
<div class="hotmail-account-item${account.id === currentId ? ' is-current' : ''}">
|
||||||
|
<div class="hotmail-account-top">
|
||||||
|
<div class="hotmail-account-title-row">
|
||||||
|
<div class="hotmail-account-email">${helpers.escapeHtml(account.email || '(未命名账号)')}</div>
|
||||||
|
<button
|
||||||
|
class="hotmail-copy-btn"
|
||||||
|
type="button"
|
||||||
|
data-account-action="copy-email"
|
||||||
|
data-account-id="${helpers.escapeHtml(account.id)}"
|
||||||
|
title="复制邮箱"
|
||||||
|
aria-label="复制邮箱 ${helpers.escapeHtml(account.email || '')}"
|
||||||
|
>${copyIcon}</button>
|
||||||
|
</div>
|
||||||
|
<span class="hotmail-status-chip ${helpers.escapeHtml(status.className)}">${helpers.escapeHtml(status.label)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="hotmail-account-meta">
|
||||||
|
<span>密码:${account.password ? '已保存' : '未保存'}</span>
|
||||||
|
<span>上次登录:${helpers.escapeHtml(formatDateTime(account.lastLoginAt))}</span>
|
||||||
|
<span>上次使用:${helpers.escapeHtml(formatDateTime(account.lastUsedAt))}</span>
|
||||||
|
<span>上限记录:${helpers.escapeHtml(formatDateTime(account.lastLimitAt))}</span>
|
||||||
|
<span>恢复时间:${helpers.escapeHtml(formatDateTime(account.disabledUntil))}</span>
|
||||||
|
</div>
|
||||||
|
${account.lastError ? `<div class="hotmail-account-error">${helpers.escapeHtml(account.lastError)}</div>` : ''}
|
||||||
|
<div class="hotmail-account-actions">
|
||||||
|
<button class="btn btn-outline btn-sm" type="button" data-account-action="select" data-account-id="${helpers.escapeHtml(account.id)}">使用此账号</button>
|
||||||
|
<button class="btn btn-primary btn-sm" type="button" data-account-action="login" data-account-id="${helpers.escapeHtml(account.id)}">登录</button>
|
||||||
|
<button class="btn btn-outline btn-sm" type="button" data-account-action="toggle-enabled" data-account-id="${helpers.escapeHtml(account.id)}">${account.enabled === false ? '启用' : '禁用'}</button>
|
||||||
|
${coolingDown ? `<button class="btn btn-outline btn-sm" type="button" data-account-action="clear-cooldown" data-account-id="${helpers.escapeHtml(account.id)}">清冷却</button>` : ''}
|
||||||
|
<button class="btn btn-ghost btn-sm" type="button" data-account-action="delete" data-account-id="${helpers.escapeHtml(account.id)}">删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
updateMail2925ListViewport();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAddMail2925Account() {
|
||||||
|
if (actionInFlight) return;
|
||||||
|
|
||||||
|
const email = String(dom.inputMail2925Email?.value || '').trim();
|
||||||
|
const password = String(dom.inputMail2925Password?.value || '');
|
||||||
|
if (!email) {
|
||||||
|
helpers.showToast('请先填写 2925 邮箱。', 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!password) {
|
||||||
|
helpers.showToast('请先填写 2925 密码。', 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionInFlight = true;
|
||||||
|
if (dom.btnAddMail2925Account) {
|
||||||
|
dom.btnAddMail2925Account.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await runtime.sendMessage({
|
||||||
|
type: 'UPSERT_MAIL2925_ACCOUNT',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (response?.error) {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyMail2925AccountMutation(response.account);
|
||||||
|
clearMail2925Form();
|
||||||
|
helpers.showToast(`已保存 2925 账号 ${email}`, 'success', 1800);
|
||||||
|
} catch (err) {
|
||||||
|
helpers.showToast(`保存 2925 账号失败:${err.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
actionInFlight = false;
|
||||||
|
if (dom.btnAddMail2925Account) {
|
||||||
|
dom.btnAddMail2925Account.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleImportMail2925Accounts() {
|
||||||
|
if (actionInFlight) return;
|
||||||
|
if (typeof mail2925Utils.parseMail2925ImportText !== 'function') {
|
||||||
|
helpers.showToast('2925 导入解析器未加载,请刷新扩展后重试。', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawText = String(dom.inputMail2925Import?.value || '').trim();
|
||||||
|
if (!rawText) {
|
||||||
|
helpers.showToast('请先粘贴 2925 账号导入内容。', 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedAccounts = mail2925Utils.parseMail2925ImportText(rawText);
|
||||||
|
if (!parsedAccounts.length) {
|
||||||
|
helpers.showToast('没有解析到有效账号,请检查格式是否为 邮箱----密码。', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionInFlight = true;
|
||||||
|
if (dom.btnImportMail2925Accounts) {
|
||||||
|
dom.btnImportMail2925Accounts.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const account of parsedAccounts) {
|
||||||
|
const response = await runtime.sendMessage({
|
||||||
|
type: 'UPSERT_MAIL2925_ACCOUNT',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: account,
|
||||||
|
});
|
||||||
|
if (response?.error) {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dom.inputMail2925Import) {
|
||||||
|
dom.inputMail2925Import.value = '';
|
||||||
|
}
|
||||||
|
helpers.showToast(`已导入 ${parsedAccounts.length} 条 2925 账号`, 'success', 2200);
|
||||||
|
} catch (err) {
|
||||||
|
helpers.showToast(`批量导入 2925 账号失败:${err.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
actionInFlight = false;
|
||||||
|
if (dom.btnImportMail2925Accounts) {
|
||||||
|
dom.btnImportMail2925Accounts.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAllMail2925Accounts() {
|
||||||
|
const accounts = getMail2925Accounts();
|
||||||
|
if (!accounts.length) {
|
||||||
|
helpers.showToast('没有可删除的 2925 账号。', 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = await helpers.openConfirmModal({
|
||||||
|
title: '全部删除 2925 账号',
|
||||||
|
message: `确认删除当前全部 ${accounts.length} 个 2925 账号吗?`,
|
||||||
|
confirmLabel: '确认全部删除',
|
||||||
|
confirmVariant: 'btn-danger',
|
||||||
|
});
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await runtime.sendMessage({
|
||||||
|
type: 'DELETE_MAIL2925_ACCOUNTS',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: { mode: 'all' },
|
||||||
|
});
|
||||||
|
if (response?.error) {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.syncLatestState({
|
||||||
|
mail2925Accounts: [],
|
||||||
|
currentMail2925AccountId: null,
|
||||||
|
});
|
||||||
|
refreshManagedAliasBaseEmail();
|
||||||
|
renderMail2925Accounts();
|
||||||
|
helpers.showToast(`已删除全部 ${response.deletedCount || 0} 个 2925 账号`, 'success', 2200);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAccountListClick(event) {
|
||||||
|
const actionButton = event.target.closest('[data-account-action]');
|
||||||
|
if (!actionButton || actionInFlight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountId = String(actionButton.dataset.accountId || '');
|
||||||
|
const action = String(actionButton.dataset.accountAction || '');
|
||||||
|
if (!accountId || !action) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetAccount = getMail2925Accounts().find((account) => account.id === accountId) || null;
|
||||||
|
actionInFlight = true;
|
||||||
|
actionButton.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (action === 'copy-email') {
|
||||||
|
if (!targetAccount?.email) throw new Error('未找到可复制的 2925 邮箱。');
|
||||||
|
await helpers.copyTextToClipboard(targetAccount.email);
|
||||||
|
helpers.showToast(`已复制 ${targetAccount.email}`, 'success', 1800);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'select') {
|
||||||
|
const response = await runtime.sendMessage({
|
||||||
|
type: 'SELECT_MAIL2925_ACCOUNT',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: { accountId },
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
state.syncLatestState({ currentMail2925AccountId: response.account.id });
|
||||||
|
refreshManagedAliasBaseEmail();
|
||||||
|
renderMail2925Accounts();
|
||||||
|
helpers.showToast(`已切换当前 2925 账号为 ${response.account.email}`, 'success', 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'login') {
|
||||||
|
const response = await runtime.sendMessage({
|
||||||
|
type: 'LOGIN_MAIL2925_ACCOUNT',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: {
|
||||||
|
accountId,
|
||||||
|
forceRelogin: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
state.syncLatestState({ currentMail2925AccountId: response.account.id });
|
||||||
|
refreshManagedAliasBaseEmail();
|
||||||
|
renderMail2925Accounts();
|
||||||
|
helpers.showToast(`已使用 ${response.account.email} 登录 2925 邮箱`, 'success', 2200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'toggle-enabled') {
|
||||||
|
if (!targetAccount) throw new Error('未找到目标 2925 账号。');
|
||||||
|
const response = await runtime.sendMessage({
|
||||||
|
type: 'PATCH_MAIL2925_ACCOUNT',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: {
|
||||||
|
accountId,
|
||||||
|
updates: {
|
||||||
|
enabled: targetAccount.enabled === false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
applyMail2925AccountMutation(response.account);
|
||||||
|
helpers.showToast(`2925 账号 ${response.account.email} 已${response.account.enabled === false ? '禁用' : '启用'}`, 'success', 2200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'clear-cooldown') {
|
||||||
|
const response = await runtime.sendMessage({
|
||||||
|
type: 'PATCH_MAIL2925_ACCOUNT',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: {
|
||||||
|
accountId,
|
||||||
|
updates: {
|
||||||
|
disabledUntil: 0,
|
||||||
|
lastError: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
applyMail2925AccountMutation(response.account);
|
||||||
|
helpers.showToast(`2925 账号 ${response.account.email} 已清除冷却`, 'success', 2200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'delete') {
|
||||||
|
const confirmed = await helpers.openConfirmModal({
|
||||||
|
title: '删除 2925 账号',
|
||||||
|
message: '确认删除这个 2925 账号吗?',
|
||||||
|
confirmLabel: '确认删除',
|
||||||
|
confirmVariant: 'btn-danger',
|
||||||
|
});
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await runtime.sendMessage({
|
||||||
|
type: 'DELETE_MAIL2925_ACCOUNT',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: { accountId },
|
||||||
|
});
|
||||||
|
if (response?.error) throw new Error(response.error);
|
||||||
|
|
||||||
|
const nextAccounts = getMail2925Accounts().filter((account) => account.id !== accountId);
|
||||||
|
const nextState = { mail2925Accounts: nextAccounts };
|
||||||
|
if (getCurrentMail2925AccountId() === accountId) {
|
||||||
|
nextState.currentMail2925AccountId = null;
|
||||||
|
}
|
||||||
|
state.syncLatestState(nextState);
|
||||||
|
refreshManagedAliasBaseEmail();
|
||||||
|
renderMail2925Accounts();
|
||||||
|
helpers.showToast('2925 账号已删除', 'success', 1800);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
helpers.showToast(err.message, 'error');
|
||||||
|
} finally {
|
||||||
|
actionInFlight = false;
|
||||||
|
actionButton.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindMail2925Events() {
|
||||||
|
dom.btnToggleMail2925List?.addEventListener('click', () => {
|
||||||
|
setMail2925ListExpanded(!listExpanded);
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.btnDeleteAllMail2925Accounts?.addEventListener('click', async () => {
|
||||||
|
if (actionInFlight) return;
|
||||||
|
actionInFlight = true;
|
||||||
|
try {
|
||||||
|
await deleteAllMail2925Accounts();
|
||||||
|
} catch (err) {
|
||||||
|
helpers.showToast(err.message, 'error');
|
||||||
|
} finally {
|
||||||
|
actionInFlight = false;
|
||||||
|
updateMail2925ListViewport();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.btnAddMail2925Account?.addEventListener('click', handleAddMail2925Account);
|
||||||
|
dom.btnImportMail2925Accounts?.addEventListener('click', handleImportMail2925Accounts);
|
||||||
|
dom.mail2925AccountsList?.addEventListener('click', handleAccountListClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
bindMail2925Events,
|
||||||
|
initMail2925ListExpandedState,
|
||||||
|
renderMail2925Accounts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScope.SidepanelMail2925Manager = {
|
||||||
|
createMail2925Manager,
|
||||||
|
};
|
||||||
|
})(window);
|
||||||
+44
-59
@@ -697,6 +697,21 @@ header {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mail2925-base-inline {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail2925-base-input,
|
||||||
|
.mail2925-pool-select {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail2925-pool-toggle {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.data-value-actions {
|
.data-value-actions {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
@@ -1395,36 +1410,51 @@ header {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fallback-inline {
|
.setting-pair {
|
||||||
justify-content: space-between;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fallback-thread-interval {
|
.setting-group {
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timing-field {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timing-field-labeled {
|
.setting-group-primary {
|
||||||
min-width: 196px;
|
flex: 1;
|
||||||
justify-content: flex-end;
|
min-width: 112px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timing-field-right {
|
.setting-group-secondary {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
|
||||||
|
|
||||||
.setting-inline-right {
|
|
||||||
min-width: 196px;
|
min-width: 196px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setting-caption {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 56px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.toggle-switch {
|
.toggle-switch {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -1482,51 +1512,6 @@ header {
|
|||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auto-delay-inline {
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auto-delay-side {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auto-delay-side-right {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auto-delay-check {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auto-delay-check span {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auto-delay-caption {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-muted);
|
|
||||||
flex: 0 0 auto;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-width: 56px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auto-delay-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auto-delay-input {
|
.auto-delay-input {
|
||||||
width: 72px;
|
width: 72px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|||||||
+79
-32
@@ -261,7 +261,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-email-prefix" style="display:none;">
|
<div class="data-row" id="row-email-prefix" style="display:none;">
|
||||||
<span class="data-label" id="label-email-prefix">别名基邮箱</span>
|
<span class="data-label" id="label-email-prefix">别名基邮箱</span>
|
||||||
<input type="text" id="input-email-prefix" class="data-input" placeholder="例如 yourname@example.com" />
|
<div class="data-inline mail2925-base-inline">
|
||||||
|
<input type="text" id="input-email-prefix" class="data-input mail2925-base-input" placeholder="例如 yourname@example.com" />
|
||||||
|
<select id="select-mail2925-pool-account" class="data-select mail2925-pool-select" style="display:none;">
|
||||||
|
<option value="">请选择号池邮箱</option>
|
||||||
|
</select>
|
||||||
|
<label class="toggle-switch mail2925-pool-toggle" id="label-mail2925-use-account-pool" for="input-mail2925-use-account-pool" style="display:none;" title="开启后只能从号池中选择 2925 基邮箱">
|
||||||
|
<input type="checkbox" id="input-mail2925-use-account-pool" />
|
||||||
|
<span class="toggle-switch-track" aria-hidden="true">
|
||||||
|
<span class="toggle-switch-thumb"></span>
|
||||||
|
</span>
|
||||||
|
<span>号池</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-inbucket-host" style="display:none;">
|
<div class="data-row" id="row-inbucket-host" style="display:none;">
|
||||||
<span class="data-label">Inbucket</span>
|
<span class="data-label">Inbucket</span>
|
||||||
@@ -280,23 +292,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<span class="data-label">延迟</span>
|
<span class="data-label">延迟</span>
|
||||||
<div class="data-inline auto-delay-inline">
|
<div class="data-inline setting-pair">
|
||||||
<div class="auto-delay-side auto-delay-side-left">
|
<div class="setting-group setting-group-primary">
|
||||||
<label class="toggle-switch" for="input-auto-delay-enabled">
|
<label class="toggle-switch" for="input-auto-delay-enabled">
|
||||||
<input type="checkbox" id="input-auto-delay-enabled" />
|
<input type="checkbox" id="input-auto-delay-enabled" />
|
||||||
<span class="toggle-switch-track" aria-hidden="true">
|
<span class="toggle-switch-track" aria-hidden="true">
|
||||||
<span class="toggle-switch-thumb"></span>
|
<span class="toggle-switch-thumb"></span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="auto-delay-controls">
|
<div class="setting-controls">
|
||||||
<input type="number" id="input-auto-delay-minutes" class="data-input auto-delay-input" value="30" min="1"
|
<input type="number" id="input-auto-delay-minutes" class="data-input auto-delay-input" value="30" min="1"
|
||||||
max="1440" step="1" title="启动前倒计时分钟数" />
|
max="1440" step="1" title="启动前倒计时分钟数" />
|
||||||
<span class="data-unit">分钟</span>
|
<span class="data-unit">分钟</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="timing-field timing-field-labeled timing-field-right auto-delay-side auto-delay-side-right">
|
<div class="setting-group setting-group-secondary">
|
||||||
<span class="auto-delay-caption">步间间隔</span>
|
<span class="setting-caption">步间间隔</span>
|
||||||
<div class="auto-delay-controls">
|
<div class="setting-controls">
|
||||||
<input type="number" id="input-auto-step-delay-seconds" class="data-input auto-delay-input" value=""
|
<input type="number" id="input-auto-step-delay-seconds" class="data-input auto-delay-input" value=""
|
||||||
min="0" max="600" step="1" title="步间延迟秒数,0 或留空则不延迟" />
|
min="0" max="600" step="1" title="步间延迟秒数,0 或留空则不延迟" />
|
||||||
<span class="data-unit">秒</span>
|
<span class="data-unit">秒</span>
|
||||||
@@ -306,16 +318,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<span class="data-label">自动重试</span>
|
<span class="data-label">自动重试</span>
|
||||||
<div class="data-inline fallback-inline">
|
<div class="data-inline setting-pair">
|
||||||
<label class="toggle-switch" for="input-auto-skip-failures">
|
<div class="setting-group setting-group-primary">
|
||||||
<input type="checkbox" id="input-auto-skip-failures" />
|
<label class="toggle-switch" for="input-auto-skip-failures">
|
||||||
<span class="toggle-switch-track" aria-hidden="true">
|
<input type="checkbox" id="input-auto-skip-failures" />
|
||||||
<span class="toggle-switch-thumb"></span>
|
<span class="toggle-switch-track" aria-hidden="true">
|
||||||
</span>
|
<span class="toggle-switch-thumb"></span>
|
||||||
</label>
|
</span>
|
||||||
<div class="timing-field timing-field-labeled timing-field-right fallback-thread-interval">
|
</label>
|
||||||
<span class="auto-delay-caption">线程间隔</span>
|
</div>
|
||||||
<div class="auto-delay-controls">
|
<div class="setting-group setting-group-secondary">
|
||||||
|
<span class="setting-caption">线程间隔</span>
|
||||||
|
<div class="setting-controls">
|
||||||
<input type="number" id="input-auto-skip-failures-thread-interval-minutes" class="data-input auto-delay-input" value="0"
|
<input type="number" id="input-auto-skip-failures-thread-interval-minutes" class="data-input auto-delay-input" value="0"
|
||||||
min="0" max="1440" step="1" title="兜底模式下,两轮线程之间的等待分钟数" />
|
min="0" max="1440" step="1" title="兜底模式下,两轮线程之间的等待分钟数" />
|
||||||
<span class="data-unit">分钟</span>
|
<span class="data-unit">分钟</span>
|
||||||
@@ -325,21 +339,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-account-run-history-text-enabled">
|
<div class="data-row" id="row-account-run-history-text-enabled">
|
||||||
<span class="data-label">本地同步</span>
|
<span class="data-label">本地同步</span>
|
||||||
<div class="data-inline">
|
<div class="data-inline setting-pair">
|
||||||
<label class="toggle-switch" for="input-account-run-history-text-enabled">
|
<div class="setting-group setting-group-primary">
|
||||||
<input type="checkbox" id="input-account-run-history-text-enabled" />
|
<label class="toggle-switch" for="input-account-run-history-text-enabled">
|
||||||
<span class="toggle-switch-track" aria-hidden="true">
|
<input type="checkbox" id="input-account-run-history-text-enabled" />
|
||||||
<span class="toggle-switch-thumb"></span>
|
<span class="toggle-switch-track" aria-hidden="true">
|
||||||
</span>
|
<span class="toggle-switch-thumb"></span>
|
||||||
</label>
|
</span>
|
||||||
</div>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-verification-resend-count">
|
<div class="setting-group setting-group-secondary">
|
||||||
<span class="data-label">验证码</span>
|
<span class="setting-caption">验证码重发</span>
|
||||||
<div class="data-inline">
|
<div class="setting-controls">
|
||||||
<div class="timing-field timing-field-labeled timing-field-right setting-inline-right">
|
|
||||||
<span class="auto-delay-caption">验证码重发</span>
|
|
||||||
<div class="auto-delay-controls">
|
|
||||||
<input type="number" id="input-verification-resend-count" class="data-input auto-delay-input" value="4"
|
<input type="number" id="input-verification-resend-count" class="data-input auto-delay-input" value="4"
|
||||||
min="0" max="20" step="1" title="自动点击重新发送验证码的次数" />
|
min="0" max="20" step="1" title="自动点击重新发送验证码的次数" />
|
||||||
<span class="data-unit">次</span>
|
<span class="data-unit">次</span>
|
||||||
@@ -424,6 +435,40 @@
|
|||||||
<div id="hotmail-accounts-list" class="hotmail-accounts-list"></div>
|
<div id="hotmail-accounts-list" class="hotmail-accounts-list"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="mail2925-section" class="data-card hotmail-card" style="display:none;">
|
||||||
|
<div class="section-mini-header">
|
||||||
|
<div class="section-mini-copy">
|
||||||
|
<span class="section-label">2925 账号池</span>
|
||||||
|
</div>
|
||||||
|
<div class="section-mini-actions">
|
||||||
|
<button id="btn-delete-all-mail2925-accounts" class="btn btn-ghost btn-xs" type="button">全部删除</button>
|
||||||
|
<button id="btn-toggle-mail2925-list" class="btn btn-ghost btn-xs" type="button" aria-expanded="false">展开列表</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">邮箱</span>
|
||||||
|
<input type="text" id="input-mail2925-email" class="data-input" placeholder="name@2925.com" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">密码</span>
|
||||||
|
<input type="password" id="input-mail2925-password" class="data-input" placeholder="2925 登录密码" />
|
||||||
|
</div>
|
||||||
|
<div class="data-row hotmail-actions-row">
|
||||||
|
<span class="data-label"></span>
|
||||||
|
<button id="btn-add-mail2925-account" class="btn btn-primary btn-sm" type="button">添加账号</button>
|
||||||
|
</div>
|
||||||
|
<div class="data-row hotmail-import-row">
|
||||||
|
<span class="data-label">批量导入</span>
|
||||||
|
<div class="hotmail-import-box">
|
||||||
|
<textarea id="input-mail2925-import" class="data-textarea mono"
|
||||||
|
placeholder="邮箱----密码 name@2925.com----password"></textarea>
|
||||||
|
<button id="btn-import-mail2925-accounts" class="btn btn-outline btn-sm" type="button">导入</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="mail2925-list-shell" class="hotmail-list-shell is-collapsed">
|
||||||
|
<div id="mail2925-accounts-list" class="hotmail-accounts-list"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="luckmail-section" class="data-card luckmail-card" style="display:none;">
|
<div id="luckmail-section" class="data-card luckmail-card" style="display:none;">
|
||||||
<div class="section-mini-header">
|
<div class="section-mini-header">
|
||||||
<div class="section-mini-copy">
|
<div class="section-mini-copy">
|
||||||
@@ -656,12 +701,14 @@
|
|||||||
<div id="toast-container"></div>
|
<div id="toast-container"></div>
|
||||||
<input id="input-import-settings-file" type="file" accept=".json,application/json" hidden />
|
<input id="input-import-settings-file" type="file" accept=".json,application/json" hidden />
|
||||||
<script src="../managed-alias-utils.js"></script>
|
<script src="../managed-alias-utils.js"></script>
|
||||||
|
<script src="../mail2925-utils.js"></script>
|
||||||
<script src="../icloud-utils.js"></script>
|
<script src="../icloud-utils.js"></script>
|
||||||
<script src="../hotmail-utils.js"></script>
|
<script src="../hotmail-utils.js"></script>
|
||||||
<script src="../luckmail-utils.js"></script>
|
<script src="../luckmail-utils.js"></script>
|
||||||
<script src="../data/step-definitions.js"></script>
|
<script src="../data/step-definitions.js"></script>
|
||||||
<script src="update-service.js"></script>
|
<script src="update-service.js"></script>
|
||||||
<script src="hotmail-manager.js"></script>
|
<script src="hotmail-manager.js"></script>
|
||||||
|
<script src="mail-2925-manager.js"></script>
|
||||||
<script src="icloud-manager.js"></script>
|
<script src="icloud-manager.js"></script>
|
||||||
<script src="luckmail-manager.js"></script>
|
<script src="luckmail-manager.js"></script>
|
||||||
<script src="contribution-mode.js"></script>
|
<script src="contribution-mode.js"></script>
|
||||||
|
|||||||
+218
-1
@@ -110,6 +110,7 @@ const selectTempEmailDomain = document.getElementById('select-temp-email-domain'
|
|||||||
const inputTempEmailDomain = document.getElementById('input-temp-email-domain');
|
const inputTempEmailDomain = document.getElementById('input-temp-email-domain');
|
||||||
const btnTempEmailDomainMode = document.getElementById('btn-temp-email-domain-mode');
|
const btnTempEmailDomainMode = document.getElementById('btn-temp-email-domain-mode');
|
||||||
const hotmailSection = document.getElementById('hotmail-section');
|
const hotmailSection = document.getElementById('hotmail-section');
|
||||||
|
const mail2925Section = document.getElementById('mail2925-section');
|
||||||
const luckmailSection = document.getElementById('luckmail-section');
|
const luckmailSection = document.getElementById('luckmail-section');
|
||||||
const icloudSection = document.getElementById('icloud-section');
|
const icloudSection = document.getElementById('icloud-section');
|
||||||
const icloudSummary = document.getElementById('icloud-summary');
|
const icloudSummary = document.getElementById('icloud-summary');
|
||||||
@@ -150,6 +151,15 @@ const btnDeleteAllHotmailAccounts = document.getElementById('btn-delete-all-hotm
|
|||||||
const btnToggleHotmailList = document.getElementById('btn-toggle-hotmail-list');
|
const btnToggleHotmailList = document.getElementById('btn-toggle-hotmail-list');
|
||||||
const hotmailListShell = document.getElementById('hotmail-list-shell');
|
const hotmailListShell = document.getElementById('hotmail-list-shell');
|
||||||
const hotmailAccountsList = document.getElementById('hotmail-accounts-list');
|
const hotmailAccountsList = document.getElementById('hotmail-accounts-list');
|
||||||
|
const inputMail2925Email = document.getElementById('input-mail2925-email');
|
||||||
|
const inputMail2925Password = document.getElementById('input-mail2925-password');
|
||||||
|
const inputMail2925Import = document.getElementById('input-mail2925-import');
|
||||||
|
const btnAddMail2925Account = document.getElementById('btn-add-mail2925-account');
|
||||||
|
const btnImportMail2925Accounts = document.getElementById('btn-import-mail2925-accounts');
|
||||||
|
const btnDeleteAllMail2925Accounts = document.getElementById('btn-delete-all-mail2925-accounts');
|
||||||
|
const btnToggleMail2925List = document.getElementById('btn-toggle-mail2925-list');
|
||||||
|
const mail2925ListShell = document.getElementById('mail2925-list-shell');
|
||||||
|
const mail2925AccountsList = document.getElementById('mail2925-accounts-list');
|
||||||
const inputLuckmailApiKey = document.getElementById('input-luckmail-api-key');
|
const inputLuckmailApiKey = document.getElementById('input-luckmail-api-key');
|
||||||
const inputLuckmailBaseUrl = document.getElementById('input-luckmail-base-url');
|
const inputLuckmailBaseUrl = document.getElementById('input-luckmail-base-url');
|
||||||
const selectLuckmailEmailType = document.getElementById('select-luckmail-email-type');
|
const selectLuckmailEmailType = document.getElementById('select-luckmail-email-type');
|
||||||
@@ -171,6 +181,9 @@ const luckmailList = document.getElementById('luckmail-list');
|
|||||||
const rowEmailPrefix = document.getElementById('row-email-prefix');
|
const rowEmailPrefix = document.getElementById('row-email-prefix');
|
||||||
const labelEmailPrefix = document.getElementById('label-email-prefix');
|
const labelEmailPrefix = document.getElementById('label-email-prefix');
|
||||||
const inputEmailPrefix = document.getElementById('input-email-prefix');
|
const inputEmailPrefix = document.getElementById('input-email-prefix');
|
||||||
|
const selectMail2925PoolAccount = document.getElementById('select-mail2925-pool-account');
|
||||||
|
const inputMail2925UseAccountPool = document.getElementById('input-mail2925-use-account-pool');
|
||||||
|
const labelMail2925UseAccountPool = document.getElementById('label-mail2925-use-account-pool');
|
||||||
const rowInbucketHost = document.getElementById('row-inbucket-host');
|
const rowInbucketHost = document.getElementById('row-inbucket-host');
|
||||||
const inputInbucketHost = document.getElementById('input-inbucket-host');
|
const inputInbucketHost = document.getElementById('input-inbucket-host');
|
||||||
const rowInbucketMailbox = document.getElementById('row-inbucket-mailbox');
|
const rowInbucketMailbox = document.getElementById('row-inbucket-mailbox');
|
||||||
@@ -307,7 +320,70 @@ function getManagedAliasBaseEmailKey(provider = selectMailProvider.value) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isMail2925AccountPoolEnabled(state = latestState) {
|
||||||
|
return Boolean(state?.mail2925UseAccountPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreferredMail2925PoolAccountId(state = latestState) {
|
||||||
|
const currentId = String(state?.currentMail2925AccountId || '').trim();
|
||||||
|
if (currentId && getMail2925Accounts(state).some((account) => account.id === currentId)) {
|
||||||
|
return currentId;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncMail2925PoolAccountOptions(state = latestState) {
|
||||||
|
if (!selectMail2925PoolAccount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accounts = getMail2925Accounts(state);
|
||||||
|
const selectedId = getPreferredMail2925PoolAccountId(state);
|
||||||
|
const options = ['<option value="">请选择号池邮箱</option>'].concat(
|
||||||
|
accounts.map((account) => `<option value="${escapeHtml(account.id)}">${escapeHtml(account.email || '(未命名账号)')}</option>`)
|
||||||
|
);
|
||||||
|
selectMail2925PoolAccount.innerHTML = options.join('');
|
||||||
|
selectMail2925PoolAccount.value = selectedId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncSelectedMail2925PoolAccount(options = {}) {
|
||||||
|
const { silent = false } = options;
|
||||||
|
if (!selectMail2925PoolAccount || !isMail2925AccountPoolEnabled(latestState)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountId = String(selectMail2925PoolAccount.value || '').trim();
|
||||||
|
if (!accountId) {
|
||||||
|
syncLatestState({ currentMail2925AccountId: null });
|
||||||
|
setManagedAliasBaseEmailInputForProvider('2925', latestState);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
type: 'SELECT_MAIL2925_ACCOUNT',
|
||||||
|
source: 'sidepanel',
|
||||||
|
payload: { accountId },
|
||||||
|
});
|
||||||
|
if (response?.error) {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncLatestState({ currentMail2925AccountId: response.account?.id || accountId });
|
||||||
|
setManagedAliasBaseEmailInputForProvider('2925', latestState);
|
||||||
|
if (!silent) {
|
||||||
|
showToast(`已切换当前 2925 号池邮箱为 ${response.account?.email || accountId}`, 'success', 1800);
|
||||||
|
}
|
||||||
|
return response.account || null;
|
||||||
|
}
|
||||||
|
|
||||||
function getManagedAliasBaseEmailForProvider(provider = selectMailProvider.value, state = latestState) {
|
function getManagedAliasBaseEmailForProvider(provider = selectMailProvider.value, state = latestState) {
|
||||||
|
if (String(provider || '').trim().toLowerCase() === '2925' && isMail2925AccountPoolEnabled(state)) {
|
||||||
|
const currentMail2925Email = getCurrentMail2925Email(state);
|
||||||
|
if (currentMail2925Email) {
|
||||||
|
return currentMail2925Email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const key = getManagedAliasBaseEmailKey(provider);
|
const key = getManagedAliasBaseEmailKey(provider);
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return '';
|
return '';
|
||||||
@@ -326,11 +402,16 @@ function buildManagedAliasBaseEmailPayload(state = latestState) {
|
|||||||
const payload = {
|
const payload = {
|
||||||
gmailBaseEmail: String(state?.gmailBaseEmail || '').trim(),
|
gmailBaseEmail: String(state?.gmailBaseEmail || '').trim(),
|
||||||
mail2925BaseEmail: String(state?.mail2925BaseEmail || '').trim(),
|
mail2925BaseEmail: String(state?.mail2925BaseEmail || '').trim(),
|
||||||
|
mail2925UseAccountPool: Boolean(state?.mail2925UseAccountPool),
|
||||||
emailPrefix: '',
|
emailPrefix: '',
|
||||||
};
|
};
|
||||||
const key = getManagedAliasBaseEmailKey();
|
const key = getManagedAliasBaseEmailKey();
|
||||||
if (key) {
|
if (key) {
|
||||||
payload[key] = inputEmailPrefix.value.trim();
|
if (key === 'mail2925BaseEmail' && isMail2925AccountPoolEnabled(state)) {
|
||||||
|
payload[key] = String(state?.mail2925BaseEmail || '').trim();
|
||||||
|
} else {
|
||||||
|
payload[key] = inputEmailPrefix.value.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
@@ -340,10 +421,14 @@ function syncManagedAliasBaseEmailDraftFromInput(provider = selectMailProvider.v
|
|||||||
if (!key) {
|
if (!key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (key === 'mail2925BaseEmail' && isMail2925AccountPoolEnabled(latestState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
syncLatestState({ [key]: inputEmailPrefix.value.trim() });
|
syncLatestState({ [key]: inputEmailPrefix.value.trim() });
|
||||||
}
|
}
|
||||||
|
|
||||||
function setManagedAliasBaseEmailInputForProvider(provider = selectMailProvider.value, state = latestState) {
|
function setManagedAliasBaseEmailInputForProvider(provider = selectMailProvider.value, state = latestState) {
|
||||||
|
syncMail2925PoolAccountOptions(state);
|
||||||
inputEmailPrefix.value = getManagedAliasBaseEmailForProvider(provider, state);
|
inputEmailPrefix.value = getManagedAliasBaseEmailForProvider(provider, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1342,6 +1427,9 @@ function collectSettingsPayload() {
|
|||||||
!cloudflareTempEmailDomainEditMode ? selectTempEmailDomain.value : tempEmailActiveDomain
|
!cloudflareTempEmailDomainEditMode ? selectTempEmailDomain.value : tempEmailActiveDomain
|
||||||
) || tempEmailActiveDomain;
|
) || tempEmailActiveDomain;
|
||||||
const contributionModeEnabled = Boolean(latestState?.contributionMode);
|
const contributionModeEnabled = Boolean(latestState?.contributionMode);
|
||||||
|
const mail2925UseAccountPool = typeof inputMail2925UseAccountPool !== 'undefined'
|
||||||
|
? Boolean(inputMail2925UseAccountPool?.checked)
|
||||||
|
: Boolean(latestState?.mail2925UseAccountPool);
|
||||||
return {
|
return {
|
||||||
panelMode: selectPanelMode.value,
|
panelMode: selectPanelMode.value,
|
||||||
vpsUrl: inputVpsUrl.value.trim(),
|
vpsUrl: inputVpsUrl.value.trim(),
|
||||||
@@ -1357,6 +1445,7 @@ function collectSettingsPayload() {
|
|||||||
}),
|
}),
|
||||||
mailProvider: selectMailProvider.value,
|
mailProvider: selectMailProvider.value,
|
||||||
mail2925Mode: getSelectedMail2925Mode(),
|
mail2925Mode: getSelectedMail2925Mode(),
|
||||||
|
mail2925UseAccountPool,
|
||||||
emailGenerator: selectEmailGenerator.value,
|
emailGenerator: selectEmailGenerator.value,
|
||||||
autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked,
|
autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked,
|
||||||
icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
|
icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
|
||||||
@@ -1771,6 +1860,9 @@ function applySettingsState(state) {
|
|||||||
if (inputContributionQq) {
|
if (inputContributionQq) {
|
||||||
inputContributionQq.value = state?.contributionQq || '';
|
inputContributionQq.value = state?.contributionQq || '';
|
||||||
}
|
}
|
||||||
|
if (inputMail2925UseAccountPool) {
|
||||||
|
inputMail2925UseAccountPool.checked = Boolean(state?.mail2925UseAccountPool);
|
||||||
|
}
|
||||||
setManagedAliasBaseEmailInputForProvider(restoredMailProvider, state);
|
setManagedAliasBaseEmailInputForProvider(restoredMailProvider, state);
|
||||||
inputInbucketHost.value = state?.inbucketHost || '';
|
inputInbucketHost.value = state?.inbucketHost || '';
|
||||||
inputInbucketMailbox.value = state?.inbucketMailbox || '';
|
inputInbucketMailbox.value = state?.inbucketMailbox || '';
|
||||||
@@ -2226,6 +2318,19 @@ function getCurrentHotmailEmail(state = latestState) {
|
|||||||
return String(getCurrentHotmailAccount(state)?.email || '').trim();
|
return String(getCurrentHotmailAccount(state)?.email || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMail2925Accounts(state = latestState) {
|
||||||
|
return Array.isArray(state?.mail2925Accounts) ? state.mail2925Accounts : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentMail2925Account(state = latestState) {
|
||||||
|
const currentId = state?.currentMail2925AccountId;
|
||||||
|
return getMail2925Accounts(state).find((account) => account.id === currentId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentMail2925Email(state = latestState) {
|
||||||
|
return String(getCurrentMail2925Account(state)?.email || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
function getCurrentLuckmailPurchase(state = latestState) {
|
function getCurrentLuckmailPurchase(state = latestState) {
|
||||||
return state?.currentLuckmailPurchase || null;
|
return state?.currentLuckmailPurchase || null;
|
||||||
}
|
}
|
||||||
@@ -2365,6 +2470,8 @@ function updateMailLoginButtonState() {
|
|||||||
function updateMailProviderUI() {
|
function updateMailProviderUI() {
|
||||||
const use2925 = selectMailProvider.value === '2925';
|
const use2925 = selectMailProvider.value === '2925';
|
||||||
const useGmail = selectMailProvider.value === GMAIL_PROVIDER;
|
const useGmail = selectMailProvider.value === GMAIL_PROVIDER;
|
||||||
|
const useMail2925 = selectMailProvider.value === '2925';
|
||||||
|
const useMail2925AccountPool = useMail2925 && Boolean(inputMail2925UseAccountPool?.checked);
|
||||||
const mail2925Mode = getSelectedMail2925Mode();
|
const mail2925Mode = getSelectedMail2925Mode();
|
||||||
const useGeneratedAlias = usesGeneratedAliasMailProvider(selectMailProvider.value, mail2925Mode);
|
const useGeneratedAlias = usesGeneratedAliasMailProvider(selectMailProvider.value, mail2925Mode);
|
||||||
const useInbucket = selectMailProvider.value === 'inbucket';
|
const useInbucket = selectMailProvider.value === 'inbucket';
|
||||||
@@ -2427,11 +2534,24 @@ function updateMailProviderUI() {
|
|||||||
if (hotmailSection) {
|
if (hotmailSection) {
|
||||||
hotmailSection.style.display = useHotmail ? '' : 'none';
|
hotmailSection.style.display = useHotmail ? '' : 'none';
|
||||||
}
|
}
|
||||||
|
if (mail2925Section) {
|
||||||
|
mail2925Section.style.display = useMail2925AccountPool ? '' : 'none';
|
||||||
|
}
|
||||||
if (luckmailSection) {
|
if (luckmailSection) {
|
||||||
luckmailSection.style.display = useLuckmail ? '' : 'none';
|
luckmailSection.style.display = useLuckmail ? '' : 'none';
|
||||||
}
|
}
|
||||||
labelEmailPrefix.textContent = '邮箱前缀';
|
labelEmailPrefix.textContent = '邮箱前缀';
|
||||||
inputEmailPrefix.placeholder = '例如 abc';
|
inputEmailPrefix.placeholder = '例如 abc';
|
||||||
|
if (labelMail2925UseAccountPool) {
|
||||||
|
labelMail2925UseAccountPool.style.display = useMail2925 ? '' : 'none';
|
||||||
|
}
|
||||||
|
syncMail2925PoolAccountOptions(latestState);
|
||||||
|
if (selectMail2925PoolAccount) {
|
||||||
|
selectMail2925PoolAccount.style.display = useMail2925AccountPool ? '' : 'none';
|
||||||
|
selectMail2925PoolAccount.disabled = !useMail2925AccountPool || getMail2925Accounts().length === 0;
|
||||||
|
}
|
||||||
|
inputEmailPrefix.style.display = useMail2925AccountPool ? 'none' : '';
|
||||||
|
inputEmailPrefix.readOnly = useMail2925AccountPool;
|
||||||
selectEmailGenerator.disabled = useHotmail || useLuckmail || useGeneratedAlias || useCustomEmail;
|
selectEmailGenerator.disabled = useHotmail || useLuckmail || useGeneratedAlias || useCustomEmail;
|
||||||
if (useGmail) {
|
if (useGmail) {
|
||||||
labelEmailPrefix.textContent = 'Gmail 原邮箱';
|
labelEmailPrefix.textContent = 'Gmail 原邮箱';
|
||||||
@@ -2480,6 +2600,11 @@ function updateMailProviderUI() {
|
|||||||
if (autoHintText && useGeneratedAlias && aliasUiCopy?.hint) {
|
if (autoHintText && useGeneratedAlias && aliasUiCopy?.hint) {
|
||||||
autoHintText.textContent = aliasUiCopy.hint;
|
autoHintText.textContent = aliasUiCopy.hint;
|
||||||
}
|
}
|
||||||
|
if (autoHintText && useMail2925AccountPool) {
|
||||||
|
autoHintText.textContent = getMail2925Accounts().length
|
||||||
|
? '当前已启用 2925 号池模式,步骤 3 会基于下拉框选中的号池邮箱生成别名地址'
|
||||||
|
: '当前已启用 2925 号池模式,请先在下方 2925 账号池中添加账号并选择邮箱';
|
||||||
|
}
|
||||||
if (autoHintText && showCloudflareTempEmailReceiveMailbox) {
|
if (autoHintText && showCloudflareTempEmailReceiveMailbox) {
|
||||||
autoHintText.textContent = '若注册邮箱会转发到 Cloudflare Temp Email,请在“邮件接收”中填写实际接收转发邮件的邮箱。';
|
autoHintText.textContent = '若注册邮箱会转发到 Cloudflare Temp Email,请在“邮件接收”中填写实际接收转发邮件的邮箱。';
|
||||||
}
|
}
|
||||||
@@ -2489,6 +2614,9 @@ function updateMailProviderUI() {
|
|||||||
inputEmail.value = getCurrentLuckmailEmail();
|
inputEmail.value = getCurrentLuckmailEmail();
|
||||||
}
|
}
|
||||||
renderHotmailAccounts();
|
renderHotmailAccounts();
|
||||||
|
if (useMail2925) {
|
||||||
|
renderMail2925Accounts();
|
||||||
|
}
|
||||||
if (useLuckmail) {
|
if (useLuckmail) {
|
||||||
renderLuckmailPurchases();
|
renderLuckmailPurchases();
|
||||||
}
|
}
|
||||||
@@ -2905,6 +3033,50 @@ const bindHotmailEvents = hotmailManager?.bindHotmailEvents
|
|||||||
|| (() => { });
|
|| (() => { });
|
||||||
bindHotmailEvents();
|
bindHotmailEvents();
|
||||||
|
|
||||||
|
const mail2925Manager = window.SidepanelMail2925Manager?.createMail2925Manager({
|
||||||
|
state: {
|
||||||
|
getLatestState: () => latestState,
|
||||||
|
syncLatestState,
|
||||||
|
},
|
||||||
|
dom: {
|
||||||
|
btnAddMail2925Account,
|
||||||
|
btnDeleteAllMail2925Accounts,
|
||||||
|
btnImportMail2925Accounts,
|
||||||
|
btnToggleMail2925List,
|
||||||
|
inputMail2925Email,
|
||||||
|
inputMail2925Import,
|
||||||
|
inputMail2925Password,
|
||||||
|
mail2925AccountsList,
|
||||||
|
mail2925ListShell,
|
||||||
|
},
|
||||||
|
helpers: {
|
||||||
|
copyTextToClipboard,
|
||||||
|
escapeHtml,
|
||||||
|
getMail2925Accounts,
|
||||||
|
openConfirmModal,
|
||||||
|
refreshManagedAliasBaseEmail: () => {
|
||||||
|
setManagedAliasBaseEmailInputForProvider('2925', latestState);
|
||||||
|
},
|
||||||
|
showToast,
|
||||||
|
},
|
||||||
|
runtime: {
|
||||||
|
sendMessage: (message) => chrome.runtime.sendMessage(message),
|
||||||
|
},
|
||||||
|
constants: {
|
||||||
|
copyIcon: COPY_ICON,
|
||||||
|
displayTimeZone: DISPLAY_TIMEZONE,
|
||||||
|
expandedStorageKey: 'multipage-mail2925-list-expanded',
|
||||||
|
},
|
||||||
|
mail2925Utils: window.Mail2925Utils || {},
|
||||||
|
});
|
||||||
|
const initMail2925ListExpandedState = mail2925Manager?.initMail2925ListExpandedState
|
||||||
|
|| (() => { });
|
||||||
|
const renderMail2925Accounts = mail2925Manager?.renderMail2925Accounts
|
||||||
|
|| (() => { });
|
||||||
|
const bindMail2925Events = mail2925Manager?.bindMail2925Events
|
||||||
|
|| (() => { });
|
||||||
|
bindMail2925Events();
|
||||||
|
|
||||||
const icloudManager = window.SidepanelIcloudManager?.createIcloudManager({
|
const icloudManager = window.SidepanelIcloudManager?.createIcloudManager({
|
||||||
dom: {
|
dom: {
|
||||||
btnIcloudBulkDelete,
|
btnIcloudBulkDelete,
|
||||||
@@ -3775,6 +3947,13 @@ selectMailProvider.addEventListener('change', async () => {
|
|||||||
if (leavingHotmail || leavingLuckmail || leavingGeneratedAlias) {
|
if (leavingHotmail || leavingLuckmail || leavingGeneratedAlias) {
|
||||||
await clearRegistrationEmail({ silent: true }).catch(() => { });
|
await clearRegistrationEmail({ silent: true }).catch(() => { });
|
||||||
}
|
}
|
||||||
|
if (nextProvider === '2925' && Boolean(inputMail2925UseAccountPool?.checked)) {
|
||||||
|
syncMail2925PoolAccountOptions(latestState);
|
||||||
|
if (!selectMail2925PoolAccount.value && getMail2925Accounts().length > 0) {
|
||||||
|
selectMail2925PoolAccount.value = String(getMail2925Accounts()[0]?.id || '');
|
||||||
|
}
|
||||||
|
await syncSelectedMail2925PoolAccount({ silent: true }).catch(() => { });
|
||||||
|
}
|
||||||
if (nextProvider === LUCKMAIL_PROVIDER) {
|
if (nextProvider === LUCKMAIL_PROVIDER) {
|
||||||
queueLuckmailPurchaseRefresh();
|
queueLuckmailPurchaseRefresh();
|
||||||
}
|
}
|
||||||
@@ -3956,6 +4135,36 @@ inputEmailPrefix.addEventListener('blur', () => {
|
|||||||
saveSettings({ silent: true }).catch(() => {});
|
saveSettings({ silent: true }).catch(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
selectMail2925PoolAccount?.addEventListener('change', async () => {
|
||||||
|
try {
|
||||||
|
await syncSelectedMail2925PoolAccount();
|
||||||
|
markSettingsDirty(true);
|
||||||
|
saveSettings({ silent: true }).catch(() => {});
|
||||||
|
} catch (err) {
|
||||||
|
showToast(err.message, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
inputMail2925UseAccountPool?.addEventListener('change', async () => {
|
||||||
|
const enabled = Boolean(inputMail2925UseAccountPool.checked);
|
||||||
|
syncLatestState({ mail2925UseAccountPool: enabled });
|
||||||
|
if (enabled) {
|
||||||
|
syncMail2925PoolAccountOptions(latestState);
|
||||||
|
if (!selectMail2925PoolAccount.value && getMail2925Accounts().length > 0) {
|
||||||
|
selectMail2925PoolAccount.value = String(getMail2925Accounts()[0]?.id || '');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await syncSelectedMail2925PoolAccount({ silent: true });
|
||||||
|
} catch (err) {
|
||||||
|
showToast(err.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setManagedAliasBaseEmailInputForProvider('2925', latestState);
|
||||||
|
updateMailProviderUI();
|
||||||
|
markSettingsDirty(true);
|
||||||
|
saveSettings({ silent: true }).catch(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
inputInbucketMailbox.addEventListener('input', () => {
|
inputInbucketMailbox.addEventListener('input', () => {
|
||||||
markSettingsDirty(true);
|
markSettingsDirty(true);
|
||||||
scheduleSettingsAutoSave();
|
scheduleSettingsAutoSave();
|
||||||
@@ -4207,6 +4416,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
updateProgressCounter();
|
updateProgressCounter();
|
||||||
updateButtonStates();
|
updateButtonStates();
|
||||||
renderHotmailAccounts();
|
renderHotmailAccounts();
|
||||||
|
renderMail2925Accounts();
|
||||||
if (isLuckmailProvider()) {
|
if (isLuckmailProvider()) {
|
||||||
queueLuckmailPurchaseRefresh();
|
queueLuckmailPurchaseRefresh();
|
||||||
}
|
}
|
||||||
@@ -4261,6 +4471,12 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
inputEmail.value = getCurrentHotmailEmail();
|
inputEmail.value = getCurrentHotmailEmail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (message.payload.currentMail2925AccountId !== undefined || message.payload.mail2925Accounts !== undefined) {
|
||||||
|
renderMail2925Accounts();
|
||||||
|
if (selectMailProvider.value === '2925') {
|
||||||
|
setManagedAliasBaseEmailInputForProvider('2925', latestState);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (message.payload.luckmailApiKey !== undefined) {
|
if (message.payload.luckmailApiKey !== undefined) {
|
||||||
inputLuckmailApiKey.value = message.payload.luckmailApiKey || '';
|
inputLuckmailApiKey.value = message.payload.luckmailApiKey || '';
|
||||||
}
|
}
|
||||||
@@ -4421,6 +4637,7 @@ document.addEventListener('keydown', (event) => {
|
|||||||
initializeManualStepActions();
|
initializeManualStepActions();
|
||||||
initTheme();
|
initTheme();
|
||||||
initHotmailListExpandedState();
|
initHotmailListExpandedState();
|
||||||
|
initMail2925ListExpandedState();
|
||||||
updateSaveButtonState();
|
updateSaveButtonState();
|
||||||
updateConfigMenuControls();
|
updateConfigMenuControls();
|
||||||
setLocalCpaStep9Mode(DEFAULT_LOCAL_CPA_STEP9_MODE);
|
setLocalCpaStep9Mode(DEFAULT_LOCAL_CPA_STEP9_MODE);
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
|
|
||||||
|
function extractFunction(name) {
|
||||||
|
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||||
|
const start = markers
|
||||||
|
.map((marker) => source.indexOf(marker))
|
||||||
|
.find((index) => index >= 0);
|
||||||
|
if (start < 0) {
|
||||||
|
throw new Error(`missing function ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parenDepth = 0;
|
||||||
|
let signatureEnded = false;
|
||||||
|
let braceStart = -1;
|
||||||
|
for (let i = start; i < source.length; i += 1) {
|
||||||
|
const ch = source[i];
|
||||||
|
if (ch === '(') {
|
||||||
|
parenDepth += 1;
|
||||||
|
} else if (ch === ')') {
|
||||||
|
parenDepth -= 1;
|
||||||
|
if (parenDepth === 0) {
|
||||||
|
signatureEnded = true;
|
||||||
|
}
|
||||||
|
} else if (ch === '{' && signatureEnded) {
|
||||||
|
braceStart = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (braceStart < 0) {
|
||||||
|
throw new Error(`missing body for function ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let depth = 0;
|
||||||
|
let end = braceStart;
|
||||||
|
for (; end < source.length; end += 1) {
|
||||||
|
const ch = source[end];
|
||||||
|
if (ch === '{') depth += 1;
|
||||||
|
if (ch === '}') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
end += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('isAddPhoneAuthUrl'),
|
||||||
|
extractFunction('isAddPhoneAuthState'),
|
||||||
|
extractFunction('isMail2925ThreadTerminatedError'),
|
||||||
|
extractFunction('isSignupUserAlreadyExistsFailure'),
|
||||||
|
extractFunction('getPostStep6AutoRestartDecision'),
|
||||||
|
extractFunction('runAutoSequenceFromStep'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
test('auto-run stops step4 restart path when mail2925 ends the current thread', async () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const AUTO_STEP_DELAYS = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0 };
|
||||||
|
const LAST_STEP_ID = 10;
|
||||||
|
const FINAL_OAUTH_CHAIN_START_STEP = 7;
|
||||||
|
const chrome = {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
runtime: {
|
||||||
|
sendMessage: async () => {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentState = {
|
||||||
|
email: 'demo123456@2925.com',
|
||||||
|
password: 'Secret123!',
|
||||||
|
mailProvider: '2925',
|
||||||
|
stepStatuses: {
|
||||||
|
1: 'pending',
|
||||||
|
2: 'pending',
|
||||||
|
3: 'pending',
|
||||||
|
4: 'pending',
|
||||||
|
5: 'pending',
|
||||||
|
6: 'pending',
|
||||||
|
7: 'pending',
|
||||||
|
8: 'pending',
|
||||||
|
9: 'pending',
|
||||||
|
10: 'pending',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const events = {
|
||||||
|
steps: [],
|
||||||
|
invalidations: [],
|
||||||
|
logs: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function addLog(message, level = 'info') {
|
||||||
|
events.logs.push({ message, level });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureAutoEmailReady() {
|
||||||
|
return currentState.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function broadcastAutoRunStatus() {}
|
||||||
|
|
||||||
|
async function getState() {
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setState(updates) {
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
...updates,
|
||||||
|
stepStatuses: updates.stepStatuses ? { ...updates.stepStatuses } : currentState.stepStatuses,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStopError(error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStepDoneStatus(status) {
|
||||||
|
return status === 'completed' || status === 'manual_completed' || status === 'skipped';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeStepAndWait(step) {
|
||||||
|
events.steps.push(step);
|
||||||
|
if (step === 4) {
|
||||||
|
throw new Error('MAIL2925_THREAD_TERMINATED::步骤 4:2925 已切换账号并要求结束当前尝试。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTabId() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function invalidateDownstreamAfterStepRestart(step, options = {}) {
|
||||||
|
events.invalidations.push({ step, options });
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoginAuthStateLabel(state) {
|
||||||
|
return state || 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage(error) {
|
||||||
|
return error?.message || String(error || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLoginAuthStateFromContent() {
|
||||||
|
return { state: 'password_page', url: 'https://auth.openai.com/log-in' };
|
||||||
|
}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
try {
|
||||||
|
await runAutoSequenceFromStep(1, {
|
||||||
|
targetRun: 1,
|
||||||
|
totalRuns: 1,
|
||||||
|
attemptRuns: 1,
|
||||||
|
continued: false,
|
||||||
|
});
|
||||||
|
return { events, currentState, error: null };
|
||||||
|
} catch (error) {
|
||||||
|
return { events, currentState, error: error.message };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const result = await api.run();
|
||||||
|
|
||||||
|
assert.match(result.error, /^MAIL2925_THREAD_TERMINATED::/);
|
||||||
|
assert.deepStrictEqual(result.events.invalidations, []);
|
||||||
|
assert.deepStrictEqual(result.events.steps, [1, 2, 3, 4]);
|
||||||
|
assert.equal(result.events.logs.some(({ message }) => /回到步骤 1/.test(message)), false);
|
||||||
|
});
|
||||||
@@ -55,6 +55,7 @@ function extractFunction(name) {
|
|||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('isAddPhoneAuthUrl'),
|
extractFunction('isAddPhoneAuthUrl'),
|
||||||
extractFunction('isAddPhoneAuthState'),
|
extractFunction('isAddPhoneAuthState'),
|
||||||
|
extractFunction('isMail2925ThreadTerminatedError'),
|
||||||
extractFunction('isSignupUserAlreadyExistsFailure'),
|
extractFunction('isSignupUserAlreadyExistsFailure'),
|
||||||
extractFunction('getPostStep6AutoRestartDecision'),
|
extractFunction('getPostStep6AutoRestartDecision'),
|
||||||
extractFunction('runAutoSequenceFromStep'),
|
extractFunction('runAutoSequenceFromStep'),
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const mail2925Utils = require('../mail2925-utils.js');
|
||||||
|
|
||||||
|
test('background mail2925 session module exposes a factory', () => {
|
||||||
|
const source = fs.readFileSync('background/mail-2925-session.js', 'utf8');
|
||||||
|
const globalScope = {};
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundMail2925Session;`)(globalScope);
|
||||||
|
|
||||||
|
assert.equal(typeof api?.createMail2925SessionManager, 'function');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleMail2925LimitReachedError disables current account and switches to the next one', async () => {
|
||||||
|
const source = fs.readFileSync('background/mail-2925-session.js', 'utf8');
|
||||||
|
const globalScope = {};
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundMail2925Session;`)(globalScope);
|
||||||
|
|
||||||
|
let currentState = {
|
||||||
|
mail2925Accounts: mail2925Utils.normalizeMail2925Accounts([
|
||||||
|
{ id: 'current', email: 'current@2925.com', password: 'p1', enabled: true, lastUsedAt: 10 },
|
||||||
|
{ id: 'next', email: 'next@2925.com', password: 'p2', enabled: true, lastUsedAt: 20 },
|
||||||
|
]),
|
||||||
|
currentMail2925AccountId: 'current',
|
||||||
|
};
|
||||||
|
const events = {
|
||||||
|
logs: [],
|
||||||
|
dataUpdates: [],
|
||||||
|
tabOpens: 0,
|
||||||
|
sessionChecks: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const manager = api.createMail2925SessionManager({
|
||||||
|
addLog: async (message, level = 'info') => {
|
||||||
|
events.logs.push({ message, level });
|
||||||
|
},
|
||||||
|
broadcastDataUpdate: (payload) => {
|
||||||
|
events.dataUpdates.push(payload);
|
||||||
|
},
|
||||||
|
chrome: {
|
||||||
|
cookies: {
|
||||||
|
getAll: async () => [],
|
||||||
|
remove: async () => ({ ok: true }),
|
||||||
|
},
|
||||||
|
browsingData: {
|
||||||
|
removeCookies: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findMail2925Account: mail2925Utils.findMail2925Account,
|
||||||
|
getMail2925AccountStatus: mail2925Utils.getMail2925AccountStatus,
|
||||||
|
getState: async () => currentState,
|
||||||
|
isMail2925AccountAvailable: mail2925Utils.isMail2925AccountAvailable,
|
||||||
|
MAIL2925_LIMIT_COOLDOWN_MS: mail2925Utils.MAIL2925_LIMIT_COOLDOWN_MS,
|
||||||
|
normalizeMail2925Account: mail2925Utils.normalizeMail2925Account,
|
||||||
|
normalizeMail2925Accounts: mail2925Utils.normalizeMail2925Accounts,
|
||||||
|
pickMail2925AccountForRun: mail2925Utils.pickMail2925AccountForRun,
|
||||||
|
reuseOrCreateTab: async () => {
|
||||||
|
events.tabOpens += 1;
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
sendToMailContentScriptResilient: async () => {
|
||||||
|
events.sessionChecks += 1;
|
||||||
|
return { loggedIn: true };
|
||||||
|
},
|
||||||
|
setPersistentSettings: async (payload) => {
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
...payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setState: async (updates) => {
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
...updates,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
upsertMail2925AccountInList: mail2925Utils.upsertMail2925AccountInList,
|
||||||
|
});
|
||||||
|
|
||||||
|
const error = await manager.handleMail2925LimitReachedError(
|
||||||
|
4,
|
||||||
|
new Error('MAIL2925_LIMIT_REACHED::子邮箱已达上限邮箱')
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.match(error.message, /^MAIL2925_THREAD_TERMINATED::/);
|
||||||
|
assert.equal(currentState.currentMail2925AccountId, 'next');
|
||||||
|
assert.ok(events.tabOpens >= 1);
|
||||||
|
assert.ok(events.sessionChecks >= 1);
|
||||||
|
|
||||||
|
const currentAccount = currentState.mail2925Accounts.find((account) => account.id === 'current');
|
||||||
|
assert.equal(currentAccount.lastError, '子邮箱已达上限邮箱');
|
||||||
|
assert.ok(currentAccount.disabledUntil > Date.now());
|
||||||
|
});
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const signupFlowSource = fs.readFileSync('background/signup-flow-helpers.js', 'utf8');
|
||||||
|
const signupFlowGlobalScope = {};
|
||||||
|
const signupFlowApi = new Function('self', `${signupFlowSource}; return self.MultiPageSignupFlowHelpers;`)(signupFlowGlobalScope);
|
||||||
|
|
||||||
|
test('signup flow helper allocates mail2925 account before generating alias email', async () => {
|
||||||
|
const calls = {
|
||||||
|
ensureMail2925: [],
|
||||||
|
buildAlias: 0,
|
||||||
|
setEmail: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const helpers = signupFlowApi.createSignupFlowHelpers({
|
||||||
|
buildGeneratedAliasEmail: (state) => {
|
||||||
|
calls.buildAlias += 1;
|
||||||
|
assert.equal(state.currentMail2925AccountId, 'acc-2');
|
||||||
|
return 'demo123456@2925.com';
|
||||||
|
},
|
||||||
|
chrome: { tabs: { get: async () => ({ id: 1, url: 'https://auth.openai.com/create-account/password' }) } },
|
||||||
|
ensureContentScriptReadyOnTab: async () => {},
|
||||||
|
ensureHotmailAccountForFlow: async () => ({}),
|
||||||
|
ensureMail2925AccountForFlow: async (options) => {
|
||||||
|
calls.ensureMail2925.push(options);
|
||||||
|
return { id: 'acc-2', email: 'demo@2925.com' };
|
||||||
|
},
|
||||||
|
ensureLuckmailPurchaseForFlow: async () => ({}),
|
||||||
|
isGeneratedAliasProvider: () => true,
|
||||||
|
isReusableGeneratedAliasEmail: () => false,
|
||||||
|
isHotmailProvider: () => false,
|
||||||
|
isLuckmailProvider: () => false,
|
||||||
|
isSignupEmailVerificationPageUrl: () => false,
|
||||||
|
isSignupPasswordPageUrl: () => true,
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async () => ({}),
|
||||||
|
setEmailState: async (email) => {
|
||||||
|
calls.setEmail.push(email);
|
||||||
|
},
|
||||||
|
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||||
|
SIGNUP_PAGE_INJECT_FILES: [],
|
||||||
|
waitForTabUrlMatch: async () => null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = await helpers.resolveSignupEmailForFlow({
|
||||||
|
mailProvider: '2925',
|
||||||
|
mail2925UseAccountPool: true,
|
||||||
|
currentMail2925AccountId: 'acc-2',
|
||||||
|
email: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(email, 'demo123456@2925.com');
|
||||||
|
assert.deepStrictEqual(calls.ensureMail2925, [
|
||||||
|
{
|
||||||
|
allowAllocate: true,
|
||||||
|
preferredAccountId: 'acc-2',
|
||||||
|
markUsed: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert.equal(calls.buildAlias, 1);
|
||||||
|
assert.deepStrictEqual(calls.setEmail, ['demo123456@2925.com']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('signup flow helper skips mail2925 account allocation when account pool switch is off', async () => {
|
||||||
|
const calls = {
|
||||||
|
ensureMail2925: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const helpers = signupFlowApi.createSignupFlowHelpers({
|
||||||
|
buildGeneratedAliasEmail: () => 'manual123456@2925.com',
|
||||||
|
chrome: { tabs: { get: async () => ({ id: 1, url: 'https://auth.openai.com/create-account/password' }) } },
|
||||||
|
ensureContentScriptReadyOnTab: async () => {},
|
||||||
|
ensureHotmailAccountForFlow: async () => ({}),
|
||||||
|
ensureMail2925AccountForFlow: async () => {
|
||||||
|
calls.ensureMail2925 += 1;
|
||||||
|
return { id: 'acc-2', email: 'demo@2925.com' };
|
||||||
|
},
|
||||||
|
ensureLuckmailPurchaseForFlow: async () => ({}),
|
||||||
|
isGeneratedAliasProvider: () => true,
|
||||||
|
isReusableGeneratedAliasEmail: () => false,
|
||||||
|
isHotmailProvider: () => false,
|
||||||
|
isLuckmailProvider: () => false,
|
||||||
|
isSignupEmailVerificationPageUrl: () => false,
|
||||||
|
isSignupPasswordPageUrl: () => true,
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async () => ({}),
|
||||||
|
setEmailState: async () => {},
|
||||||
|
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
|
||||||
|
SIGNUP_PAGE_INJECT_FILES: [],
|
||||||
|
waitForTabUrlMatch: async () => null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = await helpers.resolveSignupEmailForFlow({
|
||||||
|
mailProvider: '2925',
|
||||||
|
mail2925UseAccountPool: false,
|
||||||
|
currentMail2925AccountId: 'acc-2',
|
||||||
|
email: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(email, 'manual123456@2925.com');
|
||||||
|
assert.equal(calls.ensureMail2925, 0);
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const utils = require('../mail2925-utils.js');
|
||||||
|
|
||||||
|
test('normalizeMail2925Account normalizes key fields', () => {
|
||||||
|
const account = utils.normalizeMail2925Account({
|
||||||
|
id: 'a-1',
|
||||||
|
email: ' Demo@2925.com ',
|
||||||
|
password: 'secret',
|
||||||
|
enabled: 0,
|
||||||
|
lastUsedAt: '123',
|
||||||
|
disabledUntil: '456',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(account, {
|
||||||
|
id: 'a-1',
|
||||||
|
email: 'demo@2925.com',
|
||||||
|
password: 'secret',
|
||||||
|
enabled: false,
|
||||||
|
lastUsedAt: 123,
|
||||||
|
lastLoginAt: 0,
|
||||||
|
lastLimitAt: 0,
|
||||||
|
disabledUntil: 456,
|
||||||
|
lastError: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pickMail2925AccountForRun skips cooldown and picks the least recently used account', () => {
|
||||||
|
const now = 1000;
|
||||||
|
const picked = utils.pickMail2925AccountForRun([
|
||||||
|
{ id: 'cooldown', email: 'cool@2925.com', password: 'x', disabledUntil: now + 10, lastUsedAt: 1 },
|
||||||
|
{ id: 'b', email: 'b@2925.com', password: 'x', lastUsedAt: 50 },
|
||||||
|
{ id: 'a', email: 'a@2925.com', password: 'x', lastUsedAt: 10 },
|
||||||
|
], { now });
|
||||||
|
|
||||||
|
assert.equal(picked.id, 'a');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getMail2925AccountStatus reports cooldown before error', () => {
|
||||||
|
const status = utils.getMail2925AccountStatus({
|
||||||
|
email: 'demo@2925.com',
|
||||||
|
password: 'secret',
|
||||||
|
enabled: true,
|
||||||
|
disabledUntil: Date.now() + 60_000,
|
||||||
|
lastError: '子邮箱已达上限邮箱',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(status, 'cooldown');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parseMail2925ImportText parses 邮箱----密码 rows', () => {
|
||||||
|
const parsed = utils.parseMail2925ImportText(`
|
||||||
|
邮箱----密码
|
||||||
|
demo1@2925.com----pass1
|
||||||
|
demo2@2925.com----pass2
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(parsed, [
|
||||||
|
{ email: 'demo1@2925.com', password: 'pass1' },
|
||||||
|
{ email: 'demo2@2925.com', password: 'pass2' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
test('sidepanel loads mail2925 manager before sidepanel bootstrap', () => {
|
||||||
|
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||||
|
const managerIndex = html.indexOf('<script src="mail-2925-manager.js"></script>');
|
||||||
|
const sidepanelIndex = html.indexOf('<script src="sidepanel.js"></script>');
|
||||||
|
|
||||||
|
assert.notEqual(managerIndex, -1);
|
||||||
|
assert.notEqual(sidepanelIndex, -1);
|
||||||
|
assert.ok(managerIndex < sidepanelIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sidepanel html contains mail2925 pool toggle and selector controls', () => {
|
||||||
|
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||||
|
|
||||||
|
assert.match(html, /id="input-mail2925-use-account-pool"/);
|
||||||
|
assert.match(html, /id="select-mail2925-pool-account"/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mail2925 manager exposes a factory and renders empty state', () => {
|
||||||
|
const source = fs.readFileSync('sidepanel/mail-2925-manager.js', 'utf8');
|
||||||
|
const windowObject = {};
|
||||||
|
const localStorageMock = {
|
||||||
|
getItem() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
setItem() {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const api = new Function('window', 'localStorage', `${source}; return window.SidepanelMail2925Manager;`)(
|
||||||
|
windowObject,
|
||||||
|
localStorageMock
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(typeof api?.createMail2925Manager, 'function');
|
||||||
|
|
||||||
|
const mail2925AccountsList = { innerHTML: '', addEventListener() {} };
|
||||||
|
const toggleButton = {
|
||||||
|
textContent: '',
|
||||||
|
disabled: false,
|
||||||
|
setAttribute() {},
|
||||||
|
addEventListener() {},
|
||||||
|
};
|
||||||
|
const noopClassList = { toggle() {} };
|
||||||
|
|
||||||
|
const manager = api.createMail2925Manager({
|
||||||
|
state: {
|
||||||
|
getLatestState: () => ({ currentMail2925AccountId: null, mail2925Accounts: [] }),
|
||||||
|
syncLatestState() {},
|
||||||
|
},
|
||||||
|
dom: {
|
||||||
|
btnAddMail2925Account: { disabled: false, addEventListener() {} },
|
||||||
|
btnDeleteAllMail2925Accounts: { textContent: '', disabled: false, addEventListener() {} },
|
||||||
|
btnImportMail2925Accounts: { disabled: false, addEventListener() {} },
|
||||||
|
btnToggleMail2925List: toggleButton,
|
||||||
|
inputMail2925Email: { value: '' },
|
||||||
|
inputMail2925Import: { value: '' },
|
||||||
|
inputMail2925Password: { value: '' },
|
||||||
|
mail2925AccountsList,
|
||||||
|
mail2925ListShell: { classList: noopClassList },
|
||||||
|
},
|
||||||
|
helpers: {
|
||||||
|
getMail2925Accounts: () => [],
|
||||||
|
escapeHtml: (value) => String(value || ''),
|
||||||
|
showToast() {},
|
||||||
|
openConfirmModal: async () => true,
|
||||||
|
copyTextToClipboard: async () => {},
|
||||||
|
refreshManagedAliasBaseEmail() {},
|
||||||
|
},
|
||||||
|
runtime: {
|
||||||
|
sendMessage: async () => ({}),
|
||||||
|
},
|
||||||
|
constants: {
|
||||||
|
copyIcon: '',
|
||||||
|
displayTimeZone: 'Asia/Shanghai',
|
||||||
|
expandedStorageKey: 'multipage-mail2925-list-expanded',
|
||||||
|
},
|
||||||
|
mail2925Utils: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(typeof manager.renderMail2925Accounts, 'function');
|
||||||
|
assert.equal(typeof manager.bindMail2925Events, 'function');
|
||||||
|
assert.equal(typeof manager.initMail2925ListExpandedState, 'function');
|
||||||
|
|
||||||
|
manager.renderMail2925Accounts();
|
||||||
|
assert.match(mail2925AccountsList.innerHTML, /还没有 2925 账号/);
|
||||||
|
});
|
||||||
+32
-2
@@ -138,6 +138,7 @@
|
|||||||
- 标签注册表
|
- 标签注册表
|
||||||
- 最近打开的来源地址
|
- 最近打开的来源地址
|
||||||
- LuckMail 当前运行时选择
|
- LuckMail 当前运行时选择
|
||||||
|
- 2925 当前运行时账号选择 `currentMail2925AccountId`
|
||||||
|
|
||||||
补充:
|
补充:
|
||||||
|
|
||||||
@@ -150,6 +151,8 @@
|
|||||||
- CPA / SUB2API 配置
|
- CPA / SUB2API 配置
|
||||||
- 邮箱 provider 配置
|
- 邮箱 provider 配置
|
||||||
- Hotmail 账号池
|
- Hotmail 账号池
|
||||||
|
- 2925 账号池
|
||||||
|
- 2925 是否启用号池模式 `mail2925UseAccountPool`
|
||||||
- Cloudflare / Temp Email 设置
|
- Cloudflare / Temp Email 设置
|
||||||
- iCloud 相关偏好
|
- iCloud 相关偏好
|
||||||
- LuckMail API 配置
|
- LuckMail API 配置
|
||||||
@@ -305,14 +308,16 @@
|
|||||||
补充行为:
|
补充行为:
|
||||||
|
|
||||||
- `2925` provider 会关闭 Step 4 / 8 的自动重发间隔 25 秒节流;每次“重新发送验证码”之间,会在邮箱页内部执行一轮固定 15 次的刷新轮询,不再因 OAuth 剩余时间预算而缩短。
|
- `2925` provider 会关闭 Step 4 / 8 的自动重发间隔 25 秒节流;每次“重新发送验证码”之间,会在邮箱页内部执行一轮固定 15 次的刷新轮询,不再因 OAuth 剩余时间预算而缩短。
|
||||||
|
- 当 provider 为 `2925` 时,后台会在进入 Step 4 / Step 8 之前先确保当前 2925 账号已登录网页邮箱;如果当前标签页仍未登录,会自动补一次“清理 2925 cookie -> 使用当前账号密码重新登录”。
|
||||||
- 普通邮箱仍会携带 `filterAfterTimestamp` 做时间窗筛选;`2925` 当前既不依赖时间窗,也不再做“新旧邮件快照差集”比较,而是每次刷新后直接遍历当前列表中的匹配邮件。
|
- 普通邮箱仍会携带 `filterAfterTimestamp` 做时间窗筛选;`2925` 当前既不依赖时间窗,也不再做“新旧邮件快照差集”比较,而是每次刷新后直接遍历当前列表中的匹配邮件。
|
||||||
- 自动重新发送验证码次数现在使用 sidepanel 里的单一“验证码重发”配置;普通邮箱仍按 25 秒间隔节流,Hotmail / 2925 不走这个 25 秒间隔。Step 4 若启用先请求新验证码,会先消耗一次当前步骤的自动重发次数。
|
- 自动重新发送验证码次数现在使用 sidepanel 里的单一“验证码重发”配置;普通邮箱仍按 25 秒间隔节流,Hotmail / 2925 不走这个 25 秒间隔。Step 4 若启用先请求新验证码,会先消耗一次当前步骤的自动重发次数。
|
||||||
- 验证码提交重试上限当前为 15 次;页面明确拒绝验证码时,会在上限内继续拉取新验证码并重提。
|
- 验证码提交重试上限当前为 15 次;页面明确拒绝验证码时,会在上限内继续拉取新验证码并重提。
|
||||||
- `2925` 内容脚本会把每一封实际打开检测的邮件立即删除;同一验证码步骤启动后,试过的验证码会按“步骤 ID + 启动时间”隔离缓存,不会在本次步骤里重复提交;如果再次遇到相同验证码,对应邮件也会在读取后立即删除,避免后续反复打开。
|
- `2925` 内容脚本会把每一封实际打开检测的邮件立即删除;同一验证码步骤启动后,试过的验证码会按“步骤 ID + 启动时间”隔离缓存,不会在本次步骤里重复提交;如果再次遇到相同验证码,对应邮件也会在读取后立即删除,避免后续反复打开。
|
||||||
- `2925` 当前不再对邮件里的收件邮箱做比对,只要邮件内容命中 ChatGPT / OpenAI 验证码过滤条件,就会尝试该邮件。
|
- `2925` 当前不再对邮件里的收件邮箱做比对,只要邮件内容命中 ChatGPT / OpenAI 验证码过滤条件,就会尝试该邮件。
|
||||||
- 当验证码最终提交成功后,后台会异步向 2925 邮箱页发送 `DELETE_ALL_EMAILS`,执行“全选 + 删除”清理剩余邮件,不阻塞主流程。
|
- 当验证码最终提交成功后,后台会异步向 2925 邮箱页发送 `DELETE_ALL_EMAILS`,执行“全选 + 删除”清理剩余邮件,不阻塞主流程。
|
||||||
|
- 如果 `2925` 邮箱页在轮询期间出现“子邮箱已达上限邮箱”,后台会记录当前时间,把当前 2925 账号禁用 24 小时,自动切到下一个可用账号并完成登录,然后直接报错结束当前尝试;如果 Auto 开启了自动重试,现有控制器会按原逻辑进入下一次尝试。
|
||||||
|
|
||||||
- Auto 模式下,如果 Step 4 当前轮失败,后台会沿用当前邮箱回到 Step 1 重新开始当前轮,而不是立刻换邮箱开新尝试。
|
- Auto 模式下,普通 Step 4 失败仍会沿用当前邮箱回到 Step 1 重新开始当前轮;但若失败原因是 2925 主动要求“结束当前尝试”,则不会再回到 Step 1 重开,而是直接把错误抛给自动重试控制器。
|
||||||
|
|
||||||
### Step 5
|
### Step 5
|
||||||
|
|
||||||
@@ -500,7 +505,32 @@
|
|||||||
- [luckmail-utils.js](c:/Users/projectf/Downloads/codex注册扩展/luckmail-utils.js)
|
- [luckmail-utils.js](c:/Users/projectf/Downloads/codex注册扩展/luckmail-utils.js)
|
||||||
- LuckMail 相关后台领域逻辑仍在 [background.js](c:/Users/projectf/Downloads/codex注册扩展/background.js)
|
- LuckMail 相关后台领域逻辑仍在 [background.js](c:/Users/projectf/Downloads/codex注册扩展/background.js)
|
||||||
|
|
||||||
### 7.4 iCloud
|
### 7.4 2925 账号池
|
||||||
|
|
||||||
|
组成:
|
||||||
|
|
||||||
|
- [mail2925-utils.js](c:/Users/projectf/Downloads/codex注册扩展/mail2925-utils.js)
|
||||||
|
- [background/mail-2925-session.js](c:/Users/projectf/Downloads/codex注册扩展/background/mail-2925-session.js)
|
||||||
|
- [sidepanel/mail-2925-manager.js](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/mail-2925-manager.js)
|
||||||
|
|
||||||
|
职责:
|
||||||
|
|
||||||
|
- `mail2925-utils.js`
|
||||||
|
统一承接 2925 账号池的归一化、冷却期判断、可用账号挑选、导入格式解析与列表更新。
|
||||||
|
- `background/mail-2925-session.js`
|
||||||
|
统一承接 2925 账号池的持久化、当前账号切换、cookie 清理登出、网页登录态确认、自动登录,以及命中“子邮箱已达上限邮箱”后的 24 小时禁用与自动切号。
|
||||||
|
- `sidepanel/mail-2925-manager.js`
|
||||||
|
负责 2925 账号池的新增、导入、切换、手动登录、禁用、清冷却与删除。
|
||||||
|
|
||||||
|
链路:
|
||||||
|
|
||||||
|
1. 用户在 sidepanel 的 2925 账号池中保存 `email / password`
|
||||||
|
2. 只有当 sidepanel 中的 `mail2925UseAccountPool` 开关开启时,别名基邮箱才会优先取当前账号池选中的 2925 账号邮箱;关闭时会回退到原来的手填 `mail2925BaseEmail`
|
||||||
|
3. 手动点击 `登录` 或自动流程进入 Step 4 / Step 8 前,只有在号池模式开启时,后台才会确保当前 2925 账号已登录网页邮箱
|
||||||
|
4. 一旦轮询期间出现“子邮箱已达上限邮箱”,且当前处于号池模式,后台会记录当前时间、把当前账号禁用 24 小时、自动切到下一个账号并重新登录
|
||||||
|
5. 切号完成后,后台直接抛出“结束当前尝试”错误;如果 Auto 开启了自动重试,现有控制器会自动进入下一次尝试
|
||||||
|
|
||||||
|
### 7.5 iCloud
|
||||||
|
|
||||||
组成:
|
组成:
|
||||||
|
|
||||||
|
|||||||
+18
-10
@@ -26,6 +26,7 @@
|
|||||||
- `hotmail-utils.js`:Hotmail 账号与验证码提取相关的纯工具函数,负责账号筛选、验证码匹配、第三方接口数据归一化。
|
- `hotmail-utils.js`:Hotmail 账号与验证码提取相关的纯工具函数,负责账号筛选、验证码匹配、第三方接口数据归一化。
|
||||||
- `icloud-utils.js`:iCloud 隐私邮箱相关的纯工具函数,负责 host、别名列表、保留状态、已用状态等归一化。
|
- `icloud-utils.js`:iCloud 隐私邮箱相关的纯工具函数,负责 host、别名列表、保留状态、已用状态等归一化。
|
||||||
- `luckmail-utils.js`:LuckMail 相关的纯工具函数,负责邮箱购买记录、标签、邮件 cursor、验证码匹配等归一化。
|
- `luckmail-utils.js`:LuckMail 相关的纯工具函数,负责邮箱购买记录、标签、邮件 cursor、验证码匹配等归一化。
|
||||||
|
- `mail2925-utils.js`:2925 账号池相关的纯工具函数,负责账号归一化、冷却期判断、可用账号挑选、批量导入解析与列表更新。
|
||||||
- `managed-alias-utils.js`:共享 Gmail / 2925 的别名邮箱规则,负责解析基邮箱、校验“当前完整注册邮箱”是否仍与基邮箱兼容、生成 Gmail `+tag` 与 2925 随机后缀邮箱,并输出 sidepanel 可复用的 UI 文案。
|
- `managed-alias-utils.js`:共享 Gmail / 2925 的别名邮箱规则,负责解析基邮箱、校验“当前完整注册邮箱”是否仍与基邮箱兼容、生成 Gmail `+tag` 与 2925 随机后缀邮箱,并输出 sidepanel 可复用的 UI 文案。
|
||||||
- `manifest.json`:Chrome 扩展清单,声明权限、背景脚本、侧边栏、内容脚本与规则集。
|
- `manifest.json`:Chrome 扩展清单,声明权限、背景脚本、侧边栏、内容脚本与规则集。
|
||||||
- `microsoft-email.js`:Microsoft Graph / Outlook 邮件读取辅助模块,负责刷新令牌换 token、邮箱夹轮询和验证码提取。
|
- `microsoft-email.js`:Microsoft Graph / Outlook 邮件读取辅助模块,负责刷新令牌换 token、邮箱夹轮询和验证码提取。
|
||||||
@@ -41,23 +42,24 @@
|
|||||||
## `background/`
|
## `background/`
|
||||||
|
|
||||||
- `background/account-run-history.js`:邮箱记录模块,负责以邮箱为主键维护最新记录(同邮箱后续状态覆盖),在步骤 2 设定邮箱后支持先写入“停止/未完成”占位状态,统一归一化成功/失败/停止三态并落地到 `chrome.storage.local`,支持清理记录,并在启用独立本地同步配置后把完整快照同步到本地 helper。
|
- `background/account-run-history.js`:邮箱记录模块,负责以邮箱为主键维护最新记录(同邮箱后续状态覆盖),在步骤 2 设定邮箱后支持先写入“停止/未完成”占位状态,统一归一化成功/失败/停止三态并落地到 `chrome.storage.local`,支持清理记录,并在启用独立本地同步配置后把完整快照同步到本地 helper。
|
||||||
- `background/auto-run-controller.js`:自动运行主控制器,封装多轮执行、重试、轮次摘要、线程间隔与倒计时恢复逻辑;当前自动流程会绑定 `autoRunSessionId`,手动停止后旧的倒计时计划、旧重试链路和旧恢复入口不会再复活已失效的自动运行;fresh-attempt reset 时会额外保留 `gmailBaseEmail` 与 `mail2925BaseEmail`,避免自动流程重置后丢失别名基邮箱配置。
|
- `background/auto-run-controller.js`:自动运行主控制器,封装多轮执行、重试、轮次摘要、线程间隔与倒计时恢复逻辑;当前自动流程会绑定 `autoRunSessionId`,手动停止后旧的倒计时计划、旧重试链路和旧恢复入口不会再复活已失效的自动运行;fresh-attempt reset 时会额外保留 `gmailBaseEmail`、`mail2925BaseEmail` 与当前 2925 账号选择,避免自动流程重置后丢失别名基邮箱与 2925 切号上下文。
|
||||||
- `background/contribution-oauth.js`:贡献模式的公开 OAuth 流程模块,负责调用 `apikey.qzz.io` 的公开贡献接口、保存贡献会话运行态、在主 10 步流程里为步骤 7 提供贡献登录地址、在步骤 9/10 衔接 callback 捕获与兼容提交 `/oauth/api/submit-callback`,并把真实服务端状态映射回 sidepanel 运行态。
|
- `background/contribution-oauth.js`:贡献模式的公开 OAuth 流程模块,负责调用 `apikey.qzz.io` 的公开贡献接口、保存贡献会话运行态、在主 10 步流程里为步骤 7 提供贡献登录地址、在步骤 9/10 衔接 callback 捕获与兼容提交 `/oauth/api/submit-callback`,并把真实服务端状态映射回 sidepanel 运行态。
|
||||||
- `background/generated-email-helpers.js`:生成邮箱辅助层,除了 Duck / Cloudflare / iCloud / Cloudflare Temp Email,也统一承接 Gmail / 2925 的别名邮箱生成入口。
|
- `background/generated-email-helpers.js`:生成邮箱辅助层,除了 Duck / Cloudflare / iCloud / Cloudflare Temp Email,也统一承接 Gmail / 2925 的别名邮箱生成入口;当 provider 为 2925 时,会先确保当前账号池里已有可用账号,再基于该账号生成别名邮箱。
|
||||||
- `background/logging-status.js`:后台日志、步骤状态、错误信息和若干状态判断的公共工具层;当前额外承接 `add-phone / 手机号页` 这类认证 fatal 错误的共享判定。
|
- `background/logging-status.js`:后台日志、步骤状态、错误信息和若干状态判断的公共工具层;当前额外承接 `add-phone / 手机号页` 这类认证 fatal 错误的共享判定。
|
||||||
- `background/message-router.js`:后台消息路由层,负责处理 `chrome.runtime.onMessage` 进入的所有业务消息。
|
- `background/mail-2925-session.js`:2925 会话模块,负责 2925 账号池持久化、当前账号切换、cookie 清理登出、自动登录、命中“子邮箱已达上限邮箱”后的 24 小时禁用与自动切号。
|
||||||
|
- `background/message-router.js`:后台消息路由层,负责处理 `chrome.runtime.onMessage` 进入的所有业务消息;当前额外接入 2925 账号池的新增、导入、切换、登录、禁用与删除消息。
|
||||||
- `background/navigation-utils.js`:导航与 URL 判断工具层,负责 callback、入口页、CPA / SUB2API 地址、步骤跳转相关判断。
|
- `background/navigation-utils.js`:导航与 URL 判断工具层,负责 callback、入口页、CPA / SUB2API 地址、步骤跳转相关判断。
|
||||||
- `background/panel-bridge.js`:CPA / SUB2API 面板桥接层,封装 OAuth 地址获取所需的页面打开、脚本注入和通信。
|
- `background/panel-bridge.js`:CPA / SUB2API 面板桥接层,封装 OAuth 地址获取所需的页面打开、脚本注入和通信。
|
||||||
- `background/signup-flow-helpers.js`:注册页辅助层,负责打开注册入口、等待密码页以及解析当前流程所用邮箱;在 Gmail / 2925 模式下会优先复用已经存在且仍兼容的完整注册邮箱,只有不兼容或为空时才重新生成。
|
- `background/signup-flow-helpers.js`:注册页辅助层,负责打开注册入口、等待密码页以及解析当前流程所用邮箱;在 Gmail / 2925 模式下会优先复用已经存在且仍兼容的完整注册邮箱,只有不兼容或为空时才重新生成;当 provider 为 2925 时,会先确保账号池中已选中可用账号。
|
||||||
- `background/tab-runtime.js`:标签页与内容脚本运行时基础设施,封装标签注册、冲突清理、消息超时、注入重试与队列;当前等待标签完成、注入后的短暂延迟和内容脚本重试等待都已做 Stop 感知,避免用户停止后后台还继续等待并恢复执行。
|
- `background/tab-runtime.js`:标签页与内容脚本运行时基础设施,封装标签注册、冲突清理、消息超时、注入重试与队列;当前等待标签完成、注入后的短暂延迟和内容脚本重试等待都已做 Stop 感知,避免用户停止后后台还继续等待并恢复执行。
|
||||||
- `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过、共享验证码自动重发次数配置以及 2925 长轮询参数;当前验证码提交重试上限为 15 次,2925 每次重发验证码之间都会固定跑完一轮 15 次邮箱刷新轮询,步骤 8 提交登录验证码后若页面进入 `add-phone / 手机号页`,会直接抛出 fatal 错误而不是把当前步骤视为成功,并在 2925 提交成功后异步触发“删除全部邮件”的清理消息。
|
- `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过、共享验证码自动重发次数配置以及 2925 长轮询参数;当前验证码提交重试上限为 15 次,2925 每次重发验证码之间都会固定跑完一轮 15 次邮箱刷新轮询;若 2925 轮询命中“子邮箱已达上限邮箱”,会转入 2925 会话模块执行“记录时间、禁用 24 小时、切下一个账号并重新登录”,然后直接结束当前尝试。
|
||||||
|
|
||||||
## `background/steps/`
|
## `background/steps/`
|
||||||
|
|
||||||
- `background/steps/clear-login-cookies.js`:步骤 6 实现,负责登录前 Cookie 清理。
|
- `background/steps/clear-login-cookies.js`:步骤 6 实现,负责登录前 Cookie 清理。
|
||||||
- `background/steps/confirm-oauth.js`:步骤 9 实现,负责 OAuth 同意页按钮定位、点击、localhost 回调监听与回调完成。
|
- `background/steps/confirm-oauth.js`:步骤 9 实现,负责 OAuth 同意页按钮定位、点击、localhost 回调监听与回调完成。
|
||||||
- `background/steps/fetch-login-code.js`:步骤 8 实现,负责登录验证码阶段的邮箱轮询、验证码回填与回退控制;验证码获取后直接提交,不再在提交前回放步骤 7 刷新 OAuth;对非 2925 provider 会固定当前验证码页显示邮箱作为本次 Step 8 的目标邮箱。
|
- `background/steps/fetch-login-code.js`:步骤 8 实现,负责登录验证码阶段的邮箱轮询、验证码回填与回退控制;验证码获取后直接提交,不再在提交前回放步骤 7 刷新 OAuth;对非 2925 provider 会固定当前验证码页显示邮箱作为本次 Step 8 的目标邮箱;当 provider 为 2925 时,会在轮询前先确保当前 2925 账号已自动登录。
|
||||||
- `background/steps/fetch-signup-code.js`:步骤 4 实现,负责注册验证码阶段的页面准备与验证码流程入口。
|
- `background/steps/fetch-signup-code.js`:步骤 4 实现,负责注册验证码阶段的页面准备与验证码流程入口;当 provider 为 2925 时,会在轮询前先确保当前 2925 账号已自动登录。
|
||||||
- `background/steps/fill-password.js`:步骤 3 实现,负责密码生成、保存、回填与提交。
|
- `background/steps/fill-password.js`:步骤 3 实现,负责密码生成、保存、回填与提交。
|
||||||
- `background/steps/fill-profile.js`:步骤 5 实现,负责姓名、生日填写并把资料提交给注册页内容脚本。
|
- `background/steps/fill-profile.js`:步骤 5 实现,负责姓名、生日填写并把资料提交给注册页内容脚本。
|
||||||
- `background/steps/oauth-login.js`:步骤 7 实现,负责刷新 OAuth 链接、登录和确保进入验证码页;普通可恢复登录态失败会按上限重试,但一旦识别到认证流程进入 `add-phone / 手机号页`,会立即退出步骤 7 内部重试。
|
- `background/steps/oauth-login.js`:步骤 7 实现,负责刷新 OAuth 链接、登录和确保进入验证码页;普通可恢复登录态失败会按上限重试,但一旦识别到认证流程进入 `add-phone / 手机号页`,会立即退出步骤 7 内部重试。
|
||||||
@@ -75,7 +77,7 @@
|
|||||||
- `content/icloud-mail.js`:iCloud 邮箱页面脚本,负责在 iCloud Mail 页面中读取邮件详情和验证码。
|
- `content/icloud-mail.js`:iCloud 邮箱页面脚本,负责在 iCloud Mail 页面中读取邮件详情和验证码。
|
||||||
- `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。
|
- `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。
|
||||||
- `content/mail-163.js`:163 / 163 VIP 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。
|
- `content/mail-163.js`:163 / 163 VIP 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。
|
||||||
- `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理。
|
- `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路。
|
||||||
- `content/qq-mail.js`:QQ 邮箱轮询脚本,负责网页邮箱验证码读取。
|
- `content/qq-mail.js`:QQ 邮箱轮询脚本,负责网页邮箱验证码读取。
|
||||||
- `content/signup-page.js`:注册、登录、授权主内容脚本,负责 OpenAI / ChatGPT 页面上的步骤执行;其中 Step 2 / 3 的延迟点击与延迟提交在真正触发前会先检查 Stop 状态,避免停止后页面继续自动点击。
|
- `content/signup-page.js`:注册、登录、授权主内容脚本,负责 OpenAI / ChatGPT 页面上的步骤执行;其中 Step 2 / 3 的延迟点击与延迟提交在真正触发前会先检查 Stop 状态,避免停止后页面继续自动点击。
|
||||||
- `content/sub2api-panel.js`:SUB2API 后台内容脚本,负责获取 OAuth 地址和提交 localhost 回调;当前承接步骤 10。
|
- `content/sub2api-panel.js`:SUB2API 后台内容脚本,负责获取 OAuth 地址和提交 localhost 回调;当前承接步骤 10。
|
||||||
@@ -113,11 +115,12 @@
|
|||||||
- `sidepanel/hotmail-manager.js`:侧边栏 Hotmail 账号池管理器,负责列表、导入、验证、测试收信和批量操作。
|
- `sidepanel/hotmail-manager.js`:侧边栏 Hotmail 账号池管理器,负责列表、导入、验证、测试收信和批量操作。
|
||||||
- `sidepanel/icloud-manager.js`:侧边栏 iCloud 隐私邮箱管理器,负责列表、筛选、保留、删除和批量操作。
|
- `sidepanel/icloud-manager.js`:侧边栏 iCloud 隐私邮箱管理器,负责列表、筛选、保留、删除和批量操作。
|
||||||
- `sidepanel/luckmail-manager.js`:侧边栏 LuckMail 管理器,负责邮箱列表、筛选、启停、保留与批量操作。
|
- `sidepanel/luckmail-manager.js`:侧边栏 LuckMail 管理器,负责邮箱列表、筛选、启停、保留与批量操作。
|
||||||
|
- `sidepanel/mail-2925-manager.js`:侧边栏 2925 账号池管理器,负责 2925 账号的新增、导入、切换、手动登录、启停、清冷却与删除。
|
||||||
- `sidepanel/account-records-manager.js`:侧边栏邮箱记录面板管理器,负责“记录”按钮、覆盖层开关、分页列表、成功/失败/停止统计摘要和清理确认。
|
- `sidepanel/account-records-manager.js`:侧边栏邮箱记录面板管理器,负责“记录”按钮、覆盖层开关、分页列表、成功/失败/停止统计摘要和清理确认。
|
||||||
- `sidepanel/contribution-mode.js`:侧边栏贡献模式管理器,负责顶部“贡献”按钮、确认弹窗、贡献模式显隐、复用主自动流程启动、侧栏内贡献状态轮询、上传页跳转,以及贡献模式下对来源选择、配置入口、记录入口和敏感配置行的禁用与隐藏。
|
- `sidepanel/contribution-mode.js`:侧边栏贡献模式管理器,负责顶部“贡献”按钮、确认弹窗、贡献模式显隐、复用主自动流程启动、侧栏内贡献状态轮询、上传页跳转,以及贡献模式下对来源选择、配置入口、记录入口和敏感配置行的禁用与隐藏。
|
||||||
- `sidepanel/sidepanel.css`:侧边栏样式文件。
|
- `sidepanel/sidepanel.css`:侧边栏样式文件。
|
||||||
- `sidepanel/sidepanel.html`:侧边栏页面结构;当前步骤列表已改为动态容器,日志区提供“记录”按钮并挂接邮箱记录覆盖层,顶部新增“贡献”按钮并在设置卡片中新增贡献模式主面板;贡献面板内展示 `OAUTH / 回调 / 总状态` 三块真实运行态信息,同时把“本地同步”与“验证码重发”拆成独立行以避免特殊模式隐藏时互相影响;页面继续加载 `managed-alias-utils.js`,并把旧的“邮箱前缀”字段语义改为“别名基邮箱”。
|
- `sidepanel/sidepanel.html`:侧边栏页面结构;当前步骤列表已改为动态容器,日志区提供“记录”按钮并挂接邮箱记录覆盖层,顶部新增“贡献”按钮并在设置卡片中新增贡献模式主面板;贡献面板内展示 `OAUTH / 回调 / 总状态` 三块真实运行态信息,同时把“本地同步”与“验证码重发”拆成独立行以避免特殊模式隐藏时互相影响;页面继续加载 `managed-alias-utils.js`,并把旧的“邮箱前缀”字段语义改为“别名基邮箱”;当 provider 为 2925 时,别名基邮箱行右侧会显示“号池”开关,开启后输入框切换为号池邮箱下拉选择,并显示 2925 账号池管理区。
|
||||||
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、独立本地同步配置、共享验证码自动重发次数配置与广播接收,并装配 Hotmail / iCloud / LuckMail / 贡献模式 / 邮箱记录面板 manager;当前贡献模式的“开始贡献”会直接复用主自动流程启动逻辑,而独立 manager 负责贡献运行态展示与轮询,同时把 Gmail / 2925 的基邮箱输入、完整注册邮箱输入、自动生成按钮与兼容性校验统一接到共享别名逻辑上。
|
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、独立本地同步配置、共享验证码自动重发次数配置与广播接收,并装配 Hotmail / 2925 / iCloud / LuckMail / 贡献模式 / 邮箱记录面板 manager;当前贡献模式的“开始贡献”会直接复用主自动流程启动逻辑,而独立 manager 负责贡献运行态展示与轮询,同时把 Gmail / 2925 的基邮箱输入、完整注册邮箱输入、自动生成按钮与兼容性校验统一接到共享别名逻辑上;当 2925 号池开关开启时,当前账号变化会同步映射到 2925 基邮箱下拉选择,关闭时则回退到原来的手填基邮箱路线。
|
||||||
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询、`Pro` / `v` 双版本族排序、缓存读取与版本展示。
|
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询、`Pro` / `v` 双版本族排序、缓存读取与版本展示。
|
||||||
|
|
||||||
## `tests/`
|
## `tests/`
|
||||||
@@ -125,6 +128,7 @@
|
|||||||
- `tests/activation-utils.test.js`:测试内容脚本激活策略与平台回调可恢复错误判断。
|
- `tests/activation-utils.test.js`:测试内容脚本激活策略与平台回调可恢复错误判断。
|
||||||
- `tests/auth-page-recovery.test.js`:测试认证页共享恢复层的重试页识别与恢复行为。
|
- `tests/auth-page-recovery.test.js`:测试认证页共享恢复层的重试页识别与恢复行为。
|
||||||
- `tests/auto-run-fresh-attempt-reset.test.js`:测试自动运行在新一轮开始前会重置旧运行时上下文,并补充 `gmailBaseEmail` / `mail2925BaseEmail` 在 fresh reset 后仍被保留的回归验证。
|
- `tests/auto-run-fresh-attempt-reset.test.js`:测试自动运行在新一轮开始前会重置旧运行时上下文,并补充 `gmailBaseEmail` / `mail2925BaseEmail` 在 fresh reset 后仍被保留的回归验证。
|
||||||
|
- `tests/auto-run-step4-mail2925-thread-terminate.test.js`:测试步骤 4 命中 2925“结束当前尝试”错误时,不会再沿用当前邮箱回到步骤 1 重开,而是直接把错误抛给自动重试控制器。
|
||||||
- `tests/auto-run-timer-session-guard.test.js`:测试自动运行的旧计时计划在 Stop 使 session 失效后,不能再把已停止的自动流程重新拉起。
|
- `tests/auto-run-timer-session-guard.test.js`:测试自动运行的旧计时计划在 Stop 使 session 失效后,不能再把已停止的自动流程重新拉起。
|
||||||
- `tests/auto-run-add-phone-stop.test.js`:测试自动运行控制器在开启自动重试时,遇到 `add-phone / 手机号页` 会直接结束当前轮并继续下一轮,而不是把整条自动流程暂停。
|
- `tests/auto-run-add-phone-stop.test.js`:测试自动运行控制器在开启自动重试时,遇到 `add-phone / 手机号页` 会直接结束当前轮并继续下一轮,而不是把整条自动流程暂停。
|
||||||
- `tests/auto-run-step6-restart.test.js`:测试自动运行在后半段授权链路遇错时会回到步骤 7 重开,并在命中 add-phone 或泛化手机号页 fatal 错误时停止重开。
|
- `tests/auto-run-step6-restart.test.js`:测试自动运行在后半段授权链路遇错时会回到步骤 7 重开,并在命中 add-phone 或泛化手机号页 fatal 错误时停止重开。
|
||||||
@@ -138,6 +142,8 @@
|
|||||||
- `tests/background-icloud-mail-provider.test.js`:测试 iCloud 邮箱 provider 配置解析。
|
- `tests/background-icloud-mail-provider.test.js`:测试 iCloud 邮箱 provider 配置解析。
|
||||||
- `tests/background-logging-status-module.test.js`:测试日志 / 状态模块已接入且导出工厂。
|
- `tests/background-logging-status-module.test.js`:测试日志 / 状态模块已接入且导出工厂。
|
||||||
- `tests/background-luckmail.test.js`:测试 LuckMail 相关后台逻辑,如购买、复用、标记已用与重置。
|
- `tests/background-luckmail.test.js`:测试 LuckMail 相关后台逻辑,如购买、复用、标记已用与重置。
|
||||||
|
- `tests/background-mail2925-session-module.test.js`:测试 2925 会话模块已接入且导出工厂,并覆盖命中上限后“禁用 24 小时 + 切下一个号 + 自动登录”的核心链路。
|
||||||
|
- `tests/background-mail2925-signup-flow.test.js`:测试注册页辅助层在 2925 provider 下,会先确保账号池里已分配可用账号,再继续生成别名邮箱。
|
||||||
- `tests/background-message-router-module.test.js`:测试消息路由模块已接入且导出工厂。
|
- `tests/background-message-router-module.test.js`:测试消息路由模块已接入且导出工厂。
|
||||||
- `tests/background-message-router-step2-skip.test.js`:测试步骤 2 直接落到验证码页时的跳步与状态保护逻辑。
|
- `tests/background-message-router-step2-skip.test.js`:测试步骤 2 直接落到验证码页时的跳步与状态保护逻辑。
|
||||||
- `tests/background-navigation-utils-module.test.js`:测试导航工具模块已接入且导出工厂。
|
- `tests/background-navigation-utils-module.test.js`:测试导航工具模块已接入且导出工厂。
|
||||||
@@ -161,6 +167,7 @@
|
|||||||
- `tests/icloud-utils.test.js`:测试 iCloud 工具层的 host、别名列表、保留状态与筛选逻辑。
|
- `tests/icloud-utils.test.js`:测试 iCloud 工具层的 host、别名列表、保留状态与筛选逻辑。
|
||||||
- `tests/luckmail-utils.test.js`:测试 LuckMail 工具层的购买记录、邮件、游标和验证码匹配逻辑。
|
- `tests/luckmail-utils.test.js`:测试 LuckMail 工具层的购买记录、邮件、游标和验证码匹配逻辑。
|
||||||
- `tests/mail-2925-content.test.js`:测试 2925 邮箱内容脚本的轮询快照、忽略邮箱对比、按步骤会话隔离已试验证码、单封邮件删除与整箱删除逻辑。
|
- `tests/mail-2925-content.test.js`:测试 2925 邮箱内容脚本的轮询快照、忽略邮箱对比、按步骤会话隔离已试验证码、单封邮件删除与整箱删除逻辑。
|
||||||
|
- `tests/mail2925-utils.test.js`:测试 2925 账号池工具层的账号归一化、冷却期判断、可用账号挑选与批量导入解析。
|
||||||
- `tests/managed-alias-utils.test.js`:覆盖 Gmail / 2925 共享别名工具的解析、生成、兼容性判断。
|
- `tests/managed-alias-utils.test.js`:覆盖 Gmail / 2925 共享别名工具的解析、生成、兼容性判断。
|
||||||
- `tests/microsoft-email.test.js`:测试 Microsoft 邮件拉取与验证码提取逻辑。
|
- `tests/microsoft-email.test.js`:测试 Microsoft 邮件拉取与验证码提取逻辑。
|
||||||
- `tests/sidepanel-hotmail-manager.test.js`:测试侧边栏 Hotmail 管理器模块接线与空态渲染。
|
- `tests/sidepanel-hotmail-manager.test.js`:测试侧边栏 Hotmail 管理器模块接线与空态渲染。
|
||||||
@@ -170,6 +177,7 @@
|
|||||||
- `tests/sidepanel-icloud-manager.test.js`:测试侧边栏 iCloud 管理器模块接线与空态渲染。
|
- `tests/sidepanel-icloud-manager.test.js`:测试侧边栏 iCloud 管理器模块接线与空态渲染。
|
||||||
- `tests/sidepanel-icloud-provider.test.js`:测试侧边栏 iCloud 登录地址解析逻辑。
|
- `tests/sidepanel-icloud-provider.test.js`:测试侧边栏 iCloud 登录地址解析逻辑。
|
||||||
- `tests/sidepanel-luckmail-manager.test.js`:测试侧边栏 LuckMail 管理器模块接线与空态渲染。
|
- `tests/sidepanel-luckmail-manager.test.js`:测试侧边栏 LuckMail 管理器模块接线与空态渲染。
|
||||||
|
- `tests/sidepanel-mail2925-manager.test.js`:测试侧边栏 2925 管理器模块接线、脚本加载顺序与空态渲染。
|
||||||
- `tests/signup-entry-diagnostics.test.js`:测试注册入口诊断快照输出。
|
- `tests/signup-entry-diagnostics.test.js`:测试注册入口诊断快照输出。
|
||||||
- `tests/signup-page-tab-cleanup.test.js`:测试注册页来源标签的冲突清理逻辑。
|
- `tests/signup-page-tab-cleanup.test.js`:测试注册页来源标签的冲突清理逻辑。
|
||||||
- `tests/step-definitions-module.test.js`:测试共享步骤定义模块及侧边栏脚本加载顺序。
|
- `tests/step-definitions-module.test.js`:测试共享步骤定义模块及侧边栏脚本加载顺序。
|
||||||
|
|||||||
Reference in New Issue
Block a user