2925邮箱增加账号池功能

This commit is contained in:
QLHazyCoder
2026-04-20 18:56:08 +08:00
parent 8dd6daccd1
commit 6783cf02b4
25 changed files with 2630 additions and 108 deletions
+322
View File
@@ -81,6 +81,16 @@ async function ensureSeenCodesSession(step, payload = {}) {
}
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') {
resetStopState();
handlePollEmail(message.step, message.payload).then((result) => {
@@ -150,11 +160,70 @@ const MAIL_SELECT_ALL_SELECTORS = [
'[class*="checkbox"]',
];
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) {
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) {
if (!node) return false;
if (node.hidden) return false;
@@ -208,6 +277,19 @@ function findActionBySelectors(selectors = []) {
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 = []) {
const directMatch = findActionBySelectors(selectors);
if (directMatch) {
@@ -231,6 +313,29 @@ function findToolbarActionButton(patterns = [], selectors = []) {
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() {
return findToolbarActionButton([
/刷新/i,
@@ -253,6 +358,117 @@ function findSelectAllControl() {
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) {
const checkbox = node?.matches?.('input[type="checkbox"], [role="checkbox"]')
? node
@@ -518,6 +734,9 @@ async function deleteAllMailboxEmails(step) {
}
async function refreshInbox() {
if (typeof throwIfMail2925LimitReached === 'function') {
throwIfMail2925LimitReached();
}
const refreshBtn = findRefreshButton();
if (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) {
await ensureSeenCodesSession(step, payload);
const {
@@ -543,6 +856,9 @@ async function handlePollEmail(step, payload) {
strictChatGPTCodeOnly = false,
} = payload || {};
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
if (typeof throwIfMail2925LimitReached === 'function') {
throwIfMail2925LimitReached();
}
log(`步骤 ${step}:开始轮询 2925 邮箱(最多 ${maxAttempts} 次)`);
@@ -562,6 +878,9 @@ async function handlePollEmail(step, payload) {
await returnToInbox();
await refreshInbox();
await sleep(2000);
if (typeof throwIfMail2925LimitReached === 'function') {
throwIfMail2925LimitReached();
}
initialItems = findMailItems();
}
@@ -572,6 +891,9 @@ async function handlePollEmail(step, payload) {
log(`步骤 ${step}:邮件列表已加载,共 ${initialItems.length} 封邮件`);
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
if (typeof throwIfMail2925LimitReached === 'function') {
throwIfMail2925LimitReached();
}
log(`步骤 ${step}:正在轮询 2925 邮箱,第 ${attempt}/${maxAttempts}`);
if (attempt > 1 || !initialLoadUsedRefresh) {