2925邮箱增加账号池功能
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user