重构修改2925获取邮箱
This commit is contained in:
@@ -51,9 +51,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeStep8VerificationTargetEmail(value) {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
async function runStep8Attempt(state) {
|
||||
const mail = getMailConfig(state);
|
||||
if (mail.error) throw new Error(mail.error);
|
||||
|
||||
const stepStartedAt = Date.now();
|
||||
const authTabId = await getTabId('signup-page');
|
||||
|
||||
@@ -67,10 +72,25 @@
|
||||
}
|
||||
|
||||
throwIfStopped();
|
||||
await ensureStep8VerificationPageReady({
|
||||
const pageState = await ensureStep8VerificationPageReady({
|
||||
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪'),
|
||||
});
|
||||
const shouldCompareVerificationEmail = mail.provider !== '2925';
|
||||
const displayedVerificationEmail = shouldCompareVerificationEmail
|
||||
? normalizeStep8VerificationTargetEmail(pageState?.displayedEmail)
|
||||
: '';
|
||||
const fixedTargetEmail = shouldCompareVerificationEmail
|
||||
? (displayedVerificationEmail || normalizeStep8VerificationTargetEmail(state?.email))
|
||||
: '';
|
||||
|
||||
await setState({
|
||||
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
||||
});
|
||||
|
||||
await addLog('步骤 8:登录验证码页面已就绪,开始获取验证码。', 'info');
|
||||
if (shouldCompareVerificationEmail && displayedVerificationEmail) {
|
||||
await addLog(`步骤 8:已固定当前验证码页显示邮箱 ${displayedVerificationEmail} 作为后续匹配目标。`, 'info');
|
||||
}
|
||||
|
||||
if (shouldUseCustomRegistrationEmail(state)) {
|
||||
await confirmCustomVerificationStepBypass(8);
|
||||
@@ -78,7 +98,11 @@
|
||||
}
|
||||
|
||||
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(`步骤 8:正在通过 ${mail.label} 轮询验证码...`);
|
||||
} else {
|
||||
await addLog(`步骤 8:正在打开${mail.label}...`);
|
||||
@@ -102,10 +126,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
await resolveVerificationStep(8, state, mail, {
|
||||
await resolveVerificationStep(8, {
|
||||
...state,
|
||||
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
||||
}, mail, {
|
||||
filterAfterTimestamp: stepStartedAt,
|
||||
getRemainingTimeMs: getStep8RemainingTimeResolver(),
|
||||
requestFreshCodeFirst: false,
|
||||
targetEmail: fixedTargetEmail,
|
||||
resendIntervalMs: (mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
|
||||
? 0
|
||||
: STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
const is2925Provider = state?.mailProvider === '2925';
|
||||
if (step === 4) {
|
||||
return {
|
||||
filterAfterTimestamp: getHotmailVerificationRequestTimestamp(4, state),
|
||||
filterAfterTimestamp: is2925Provider ? 0 : getHotmailVerificationRequestTimestamp(4, state),
|
||||
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'duckduckgo', 'forward'],
|
||||
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm'],
|
||||
targetEmail: state.email,
|
||||
@@ -124,10 +124,10 @@
|
||||
}
|
||||
|
||||
return {
|
||||
filterAfterTimestamp: getHotmailVerificationRequestTimestamp(8, state),
|
||||
filterAfterTimestamp: is2925Provider ? 0 : getHotmailVerificationRequestTimestamp(8, state),
|
||||
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'],
|
||||
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm', 'login'],
|
||||
targetEmail: state.email,
|
||||
targetEmail: String(state?.step8VerificationTargetEmail || '').trim() || state.email,
|
||||
maxAttempts: is2925Provider ? MAIL_2925_VERIFICATION_MAX_ATTEMPTS : 5,
|
||||
intervalMs: is2925Provider ? MAIL_2925_VERIFICATION_INTERVAL_MS : 3000,
|
||||
...overrides,
|
||||
@@ -236,6 +236,33 @@
|
||||
return requestedAt;
|
||||
}
|
||||
|
||||
function triggerPostSuccessMailboxCleanup(step, mail) {
|
||||
if (mail?.provider !== '2925') {
|
||||
return;
|
||||
}
|
||||
|
||||
Promise.resolve().then(async () => {
|
||||
try {
|
||||
await sendToMailContentScriptResilient(
|
||||
mail,
|
||||
{
|
||||
type: 'DELETE_ALL_EMAILS',
|
||||
step,
|
||||
source: 'background',
|
||||
payload: {},
|
||||
},
|
||||
{
|
||||
timeoutMs: 10000,
|
||||
responseTimeoutMs: 5000,
|
||||
maxRecoveryAttempts: 1,
|
||||
}
|
||||
);
|
||||
} catch (_) {
|
||||
// Best-effort cleanup only.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function pollFreshVerificationCodeWithResendInterval(step, state, mail, pollOverrides = {}) {
|
||||
const stateKey = getVerificationCodeStateKey(step);
|
||||
const rejectedCodes = new Set();
|
||||
@@ -656,6 +683,7 @@
|
||||
emailTimestamp: result.emailTimestamp,
|
||||
code: result.code,
|
||||
});
|
||||
triggerPostSuccessMailboxCleanup(step, mail);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+259
-148
@@ -1,4 +1,4 @@
|
||||
// content/mail-2925.js — Content script for 2925 Mail (steps 4, 7)
|
||||
// content/mail-2925.js - Content script for 2925 Mail (steps 4, 8)
|
||||
// Injected dynamically on: 2925.com
|
||||
|
||||
const MAIL2925_PREFIX = '[MultiPage:mail-2925]';
|
||||
@@ -37,19 +37,28 @@ async function persistSeenCodes() {
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.type === 'POLL_EMAIL') {
|
||||
resetStopState();
|
||||
handlePollEmail(message.step, message.payload).then(result => {
|
||||
handlePollEmail(message.step, message.payload).then((result) => {
|
||||
sendResponse(result);
|
||||
}).catch(err => {
|
||||
}).catch((err) => {
|
||||
if (isStopError(err)) {
|
||||
log(`步骤 ${message.step}:已被用户停止。`, 'warn');
|
||||
sendResponse({ stopped: true, error: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
log(`步骤 ${message.step}:邮箱轮询失败:${err.message}`, 'warn');
|
||||
sendResponse({ error: err.message });
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.type === 'DELETE_ALL_EMAILS') {
|
||||
Promise.resolve(deleteAllMailboxEmails(message.step)).catch(() => {});
|
||||
sendResponse({ ok: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const MAIL_ITEM_SELECTORS = [
|
||||
@@ -64,13 +73,67 @@ const MAIL_ITEM_SELECTORS = [
|
||||
'[class*="list-item"]',
|
||||
'li[class*="mail"]',
|
||||
];
|
||||
const MAIL_REFRESH_SELECTOR = '[class*="refresh"], [title*="鍒锋柊"], [aria-label*="鍒锋柊"], [class*="Refresh"]';
|
||||
const MAIL_ITEM_SELECTOR_GROUP = MAIL_ITEM_SELECTORS.join(', ');
|
||||
const MAIL_REFRESH_SELECTORS = [
|
||||
'[class*="refresh"]',
|
||||
'[title*="刷新"]',
|
||||
'[aria-label*="刷新"]',
|
||||
'[class*="Refresh"]',
|
||||
];
|
||||
const MAIL_INBOX_SELECTORS = [
|
||||
'a[href*="mailList"]',
|
||||
'[class*="inbox"]',
|
||||
'[class*="Inbox"]',
|
||||
'[title*="鏀朵欢绠?]',
|
||||
'[title*="收件箱"]',
|
||||
];
|
||||
const MAIL_DELETE_SELECTORS = [
|
||||
'[class*="delete"]',
|
||||
'[title*="删除"]',
|
||||
'[aria-label*="删除"]',
|
||||
'[class*="Delete"]',
|
||||
];
|
||||
const MAIL_SELECT_ALL_SELECTORS = [
|
||||
'input[type="checkbox"]',
|
||||
'[role="checkbox"]',
|
||||
'.el-checkbox__input',
|
||||
'.el-checkbox',
|
||||
'label[class*="checkbox"]',
|
||||
'[class*="checkbox"]',
|
||||
];
|
||||
const MAIL_ACTION_CANDIDATE_SELECTORS = 'button, [role="button"], a, label, span, div';
|
||||
|
||||
function normalizeNodeText(value) {
|
||||
return String(value || '').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function isVisibleNode(node) {
|
||||
if (!node) return false;
|
||||
if (node.hidden) return false;
|
||||
|
||||
const style = typeof window.getComputedStyle === 'function'
|
||||
? window.getComputedStyle(node)
|
||||
: null;
|
||||
if (style && (style.display === 'none' || style.visibility === 'hidden')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rect = typeof node.getBoundingClientRect === 'function'
|
||||
? node.getBoundingClientRect()
|
||||
: null;
|
||||
if (rect && rect.width <= 0 && rect.height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isMailItemNode(node) {
|
||||
return Boolean(node?.closest?.(MAIL_ITEM_SELECTOR_GROUP));
|
||||
}
|
||||
|
||||
function resolveActionTarget(node) {
|
||||
return node?.closest?.('button, [role="button"], a, label, .el-checkbox, .el-checkbox__input') || node || null;
|
||||
}
|
||||
|
||||
function findMailItems() {
|
||||
for (const selector of MAIL_ITEM_SELECTORS) {
|
||||
@@ -82,20 +145,81 @@ function findMailItems() {
|
||||
return [];
|
||||
}
|
||||
|
||||
function findRefreshButton() {
|
||||
return document.querySelector(MAIL_REFRESH_SELECTOR);
|
||||
}
|
||||
|
||||
function findInboxLink() {
|
||||
for (const selector of MAIL_INBOX_SELECTORS) {
|
||||
const node = document.querySelector(selector);
|
||||
if (node) {
|
||||
return node;
|
||||
function findActionBySelectors(selectors = []) {
|
||||
for (const selector of selectors) {
|
||||
const candidates = document.querySelectorAll(selector);
|
||||
for (const candidate of candidates) {
|
||||
const target = resolveActionTarget(candidate);
|
||||
if (!isVisibleNode(target) || isMailItemNode(target)) {
|
||||
continue;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findToolbarActionButton(patterns = [], selectors = []) {
|
||||
const directMatch = findActionBySelectors(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 (patterns.some((pattern) => pattern.test(text) || pattern.test(label))) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function findRefreshButton() {
|
||||
return findToolbarActionButton([
|
||||
/刷新/i,
|
||||
/refresh/i,
|
||||
], MAIL_REFRESH_SELECTORS);
|
||||
}
|
||||
|
||||
function findInboxLink() {
|
||||
return findActionBySelectors(MAIL_INBOX_SELECTORS);
|
||||
}
|
||||
|
||||
function findDeleteButton() {
|
||||
return findToolbarActionButton([
|
||||
/删除/i,
|
||||
/delete/i,
|
||||
], MAIL_DELETE_SELECTORS);
|
||||
}
|
||||
|
||||
function findSelectAllControl() {
|
||||
return findActionBySelectors(MAIL_SELECT_ALL_SELECTORS);
|
||||
}
|
||||
|
||||
function isCheckboxChecked(node) {
|
||||
const checkbox = node?.matches?.('input[type="checkbox"], [role="checkbox"]')
|
||||
? node
|
||||
: node?.querySelector?.('input[type="checkbox"], [role="checkbox"]');
|
||||
if (checkbox?.checked === true) {
|
||||
return true;
|
||||
}
|
||||
if (String(checkbox?.getAttribute?.('aria-checked') || '').toLowerCase() === 'true') {
|
||||
return true;
|
||||
}
|
||||
return Boolean(
|
||||
node?.classList?.contains('is-checked')
|
||||
|| node?.classList?.contains('checked')
|
||||
);
|
||||
}
|
||||
|
||||
function getMailItemText(item) {
|
||||
if (!item) return '';
|
||||
const contentCell = item.querySelector('td.content, .content, .mail-content');
|
||||
@@ -112,11 +236,11 @@ function getMailItemText(item) {
|
||||
|
||||
function getMailItemTimeText(item) {
|
||||
const timeEl = item?.querySelector('.date-time-text, [class*="date-time"], [class*="time"], td.time');
|
||||
return (timeEl?.textContent || '').replace(/\s+/g, ' ').trim();
|
||||
return normalizeNodeText(timeEl?.textContent || '');
|
||||
}
|
||||
|
||||
function normalizeMailIdentityPart(value) {
|
||||
return (value || '').replace(/\s+/g, ' ').trim().toLowerCase();
|
||||
return normalizeNodeText(value).toLowerCase();
|
||||
}
|
||||
|
||||
function getMailItemId(item, index = 0) {
|
||||
@@ -148,82 +272,36 @@ function getCurrentMailIds(items = []) {
|
||||
return ids;
|
||||
}
|
||||
|
||||
function normalizeMinuteTimestamp(timestamp) {
|
||||
if (!Number.isFinite(timestamp) || timestamp <= 0) return 0;
|
||||
const date = new Date(timestamp);
|
||||
date.setSeconds(0, 0);
|
||||
return date.getTime();
|
||||
}
|
||||
|
||||
function matchesMailFilters(text, senderFilters, subjectFilters) {
|
||||
const lower = (text || '').toLowerCase();
|
||||
const senderMatch = senderFilters.some(filter => lower.includes(filter.toLowerCase()));
|
||||
const subjectMatch = subjectFilters.some(filter => lower.includes(filter.toLowerCase()));
|
||||
const lower = String(text || '').toLowerCase();
|
||||
const senderMatch = senderFilters.some((filter) => lower.includes(String(filter || '').toLowerCase()));
|
||||
const subjectMatch = subjectFilters.some((filter) => lower.includes(String(filter || '').toLowerCase()));
|
||||
return senderMatch || subjectMatch;
|
||||
}
|
||||
|
||||
function extractVerificationCode(text, strictChatGPTCodeOnly = false) {
|
||||
if (strictChatGPTCodeOnly) {
|
||||
const strictMatch = text.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
||||
const strictMatch = String(text || '').match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
||||
return strictMatch ? strictMatch[1] : null;
|
||||
}
|
||||
|
||||
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||
const normalized = String(text || '');
|
||||
|
||||
const matchCn = normalized.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||
if (matchCn) return matchCn[1];
|
||||
|
||||
const matchChatGPT = text.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
||||
const matchChatGPT = normalized.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
||||
if (matchChatGPT) return matchChatGPT[1];
|
||||
|
||||
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||
const matchEn = normalized.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||
if (matchEn) return matchEn[1] || matchEn[2];
|
||||
|
||||
const match6 = text.match(/\b(\d{6})\b/);
|
||||
const match6 = normalized.match(/\b(\d{6})\b/);
|
||||
if (match6) return match6[1];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractEmails(text) {
|
||||
const matches = text.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig) || [];
|
||||
return [...new Set(matches.map(item => item.toLowerCase()))];
|
||||
}
|
||||
|
||||
function emailMatchesTarget(candidate, targetEmail) {
|
||||
const normalizedCandidate = String(candidate || '').trim().toLowerCase();
|
||||
const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
|
||||
return Boolean(normalizedCandidate && normalizedTarget && normalizedCandidate === normalizedTarget);
|
||||
}
|
||||
|
||||
function getTargetEmailMatchState(text, targetEmail) {
|
||||
const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
|
||||
if (!normalizedTarget) {
|
||||
return { matches: true, hasExplicitEmail: false };
|
||||
}
|
||||
|
||||
const normalizedText = String(text || '').toLowerCase();
|
||||
if (normalizedText.includes(normalizedTarget)) {
|
||||
return { matches: true, hasExplicitEmail: true };
|
||||
}
|
||||
|
||||
const atIndex = normalizedTarget.indexOf('@');
|
||||
if (atIndex > 0) {
|
||||
const encodedTarget = `${normalizedTarget.slice(0, atIndex)}=${normalizedTarget.slice(atIndex + 1)}`;
|
||||
if (normalizedText.includes(encodedTarget)) {
|
||||
return { matches: true, hasExplicitEmail: true };
|
||||
}
|
||||
}
|
||||
|
||||
const emails = extractEmails(text);
|
||||
if (!emails.length) {
|
||||
return { matches: false, hasExplicitEmail: false };
|
||||
}
|
||||
|
||||
return {
|
||||
matches: emails.some(email => emailMatchesTarget(email, normalizedTarget)),
|
||||
hasExplicitEmail: true,
|
||||
};
|
||||
}
|
||||
|
||||
function parseMailItemTimestamp(item) {
|
||||
const timeText = getMailItemTimeText(item);
|
||||
if (!timeText) return null;
|
||||
@@ -236,7 +314,7 @@ function parseMailItemTimestamp(item) {
|
||||
return now.getTime();
|
||||
}
|
||||
|
||||
match = timeText.match(/(\d+)\s*分(?:钟)?前/);
|
||||
match = timeText.match(/(\d+)\s*分钟前/);
|
||||
if (match) {
|
||||
return now.getTime() - Number(match[1]) * 60 * 1000;
|
||||
}
|
||||
@@ -324,19 +402,70 @@ async function openMailAndGetMessageText(item) {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCurrentMailboxEmail(step) {
|
||||
try {
|
||||
const deleteButton = findDeleteButton();
|
||||
if (!deleteButton) {
|
||||
return false;
|
||||
}
|
||||
|
||||
simulateClick(deleteButton);
|
||||
await sleepRandom(200, 500);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn(MAIL2925_PREFIX, `Step ${step}: delete-current cleanup failed:`, err?.message || err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function openMailAndDeleteAfterRead(item, step) {
|
||||
simulateClick(item);
|
||||
try {
|
||||
await sleepRandom(1200, 2200);
|
||||
return document.body?.textContent || '';
|
||||
} finally {
|
||||
await deleteCurrentMailboxEmail(step);
|
||||
await returnToInbox();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAllMailboxEmails(step) {
|
||||
try {
|
||||
await returnToInbox();
|
||||
|
||||
const selectAllControl = findSelectAllControl();
|
||||
if (!selectAllControl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isCheckboxChecked(selectAllControl)) {
|
||||
simulateClick(selectAllControl);
|
||||
await sleepRandom(200, 500);
|
||||
}
|
||||
|
||||
const deleteButton = findDeleteButton();
|
||||
if (!deleteButton) {
|
||||
return false;
|
||||
}
|
||||
|
||||
simulateClick(deleteButton);
|
||||
await sleepRandom(200, 500);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn(MAIL2925_PREFIX, `Step ${step}: delete-all cleanup failed:`, err?.message || err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshInbox() {
|
||||
const refreshBtn = document.querySelector(
|
||||
'[class*="refresh"], [title*="刷新"], [aria-label*="刷新"], [class*="Refresh"]'
|
||||
);
|
||||
const refreshBtn = findRefreshButton();
|
||||
if (refreshBtn) {
|
||||
simulateClick(refreshBtn);
|
||||
await sleepRandom(700, 1200);
|
||||
return;
|
||||
}
|
||||
|
||||
const inboxLink = document.querySelector(
|
||||
'a[href*="mailList"], [class*="inbox"], [class*="Inbox"], [title*="收件箱"]'
|
||||
);
|
||||
const inboxLink = findInboxLink();
|
||||
if (inboxLink) {
|
||||
simulateClick(inboxLink);
|
||||
await sleepRandom(700, 1200);
|
||||
@@ -349,27 +478,26 @@ async function handlePollEmail(step, payload) {
|
||||
subjectFilters,
|
||||
maxAttempts,
|
||||
intervalMs,
|
||||
filterAfterTimestamp = 0,
|
||||
excludeCodes = [],
|
||||
strictChatGPTCodeOnly = false,
|
||||
targetEmail = '',
|
||||
} = payload;
|
||||
} = payload || {};
|
||||
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
||||
const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0);
|
||||
|
||||
log(`步骤 ${step}:开始轮询 2925 邮箱(最多 ${maxAttempts} 次)`);
|
||||
if (filterAfterMinute) {
|
||||
log(`步骤 ${step}:仅尝试 ${new Date(filterAfterMinute).toLocaleString('zh-CN', { hour12: false })} 及之后时间的邮件。`);
|
||||
}
|
||||
|
||||
let initialItems = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
let initialLoadUsedRefresh = false;
|
||||
|
||||
for (let i = 0; i < 20; i += 1) {
|
||||
initialItems = findMailItems();
|
||||
if (initialItems.length > 0) break;
|
||||
if (initialItems.length > 0) {
|
||||
break;
|
||||
}
|
||||
await sleep(500);
|
||||
}
|
||||
|
||||
if (initialItems.length === 0) {
|
||||
initialLoadUsedRefresh = true;
|
||||
await returnToInbox();
|
||||
await refreshInbox();
|
||||
await sleep(2000);
|
||||
@@ -380,16 +508,16 @@ async function handlePollEmail(step, payload) {
|
||||
throw new Error('2925 邮箱列表未加载完成,请确认当前已打开收件箱。');
|
||||
}
|
||||
|
||||
const existingMailIds = getCurrentMailIds(initialItems);
|
||||
const knownMailIds = getCurrentMailIds(initialItems);
|
||||
log(`步骤 ${step}:邮件列表已加载,共 ${initialItems.length} 封邮件`);
|
||||
log(`步骤 ${step}:已记录当前 ${existingMailIds.size} 封旧邮件快照`);
|
||||
log(`步骤 ${step}:已记录当前 ${knownMailIds.size} 封旧邮件快照`);
|
||||
|
||||
const FALLBACK_AFTER = 3;
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||
log(`步骤 ${step}:正在轮询 2925 邮箱,第 ${attempt}/${maxAttempts} 次`);
|
||||
|
||||
if (attempt > 1) {
|
||||
if (attempt > 1 || !initialLoadUsedRefresh) {
|
||||
await returnToInbox();
|
||||
await refreshInbox();
|
||||
await sleepRandom(900, 1500);
|
||||
@@ -398,78 +526,61 @@ async function handlePollEmail(step, payload) {
|
||||
const items = findMailItems();
|
||||
if (items.length > 0) {
|
||||
const useFallback = attempt > FALLBACK_AFTER;
|
||||
const newMailIds = new Set();
|
||||
|
||||
for (let index = 0; index < items.length; index++) {
|
||||
items.forEach((item, index) => {
|
||||
const itemId = getMailItemId(item, index);
|
||||
if (!knownMailIds.has(itemId)) {
|
||||
newMailIds.add(itemId);
|
||||
}
|
||||
});
|
||||
|
||||
for (let index = 0; index < items.length; index += 1) {
|
||||
const item = items[index];
|
||||
const itemId = getMailItemId(item, index);
|
||||
const isNewMail = newMailIds.has(itemId);
|
||||
const itemTimestamp = parseMailItemTimestamp(item);
|
||||
const itemMinute = normalizeMinuteTimestamp(itemTimestamp || 0);
|
||||
const passesTimeFilter = !filterAfterMinute || (itemMinute && itemMinute >= filterAfterMinute);
|
||||
const shouldBypassOldSnapshot = Boolean(filterAfterMinute && passesTimeFilter && itemMinute > 0);
|
||||
|
||||
if (!passesTimeFilter) {
|
||||
if (!useFallback && !isNewMail) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!useFallback && !shouldBypassOldSnapshot && existingMailIds.has(itemId)) {
|
||||
const previewText = getMailItemText(item);
|
||||
if (!matchesMailFilters(previewText, senderFilters, subjectFilters)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const text = getMailItemText(item);
|
||||
if (!matchesMailFilters(text, senderFilters, subjectFilters)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const previewEmails = extractEmails(text);
|
||||
const previewTargetState = getTargetEmailMatchState(text, targetEmail);
|
||||
const previewMatchesTarget = previewTargetState.matches;
|
||||
if (targetEmail && previewEmails.length > 0 && !previewMatchesTarget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const code = extractVerificationCode(text, strictChatGPTCodeOnly);
|
||||
if (code && previewMatchesTarget) {
|
||||
if (excludedCodeSet.has(code)) {
|
||||
log(`步骤 ${step}:跳过排除的验证码:${code}`, 'info');
|
||||
continue;
|
||||
}
|
||||
if (seenCodes.has(code)) {
|
||||
log(`步骤 ${step}:跳过已处理过的验证码:${code}`, 'info');
|
||||
continue;
|
||||
}
|
||||
seenCodes.add(code);
|
||||
persistSeenCodes();
|
||||
const source = useFallback && existingMailIds.has(itemId) ? '回退匹配邮件' : '新邮件';
|
||||
const timeLabel = itemTimestamp ? `,时间:${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : '';
|
||||
log(`步骤 ${step}:已找到验证码:${code}(来源:${source}${timeLabel})`, 'ok');
|
||||
await sleep(1000);
|
||||
return { ok: true, code, emailTimestamp: Date.now() };
|
||||
}
|
||||
|
||||
const openedText = await openMailAndGetMessageText(item);
|
||||
const previewCode = extractVerificationCode(previewText, strictChatGPTCodeOnly);
|
||||
const openedText = await openMailAndDeleteAfterRead(item, step);
|
||||
const bodyCode = extractVerificationCode(openedText, strictChatGPTCodeOnly);
|
||||
const openedTargetState = getTargetEmailMatchState(openedText, targetEmail);
|
||||
if (targetEmail && openedTargetState.hasExplicitEmail && !openedTargetState.matches) {
|
||||
const candidateCode = bodyCode || previewCode;
|
||||
|
||||
if (!candidateCode) {
|
||||
continue;
|
||||
}
|
||||
if (bodyCode) {
|
||||
if (excludedCodeSet.has(bodyCode)) {
|
||||
log(`步骤 ${step}:跳过排除的验证码:${bodyCode}`, 'info');
|
||||
continue;
|
||||
}
|
||||
if (seenCodes.has(bodyCode)) {
|
||||
log(`步骤 ${step}:跳过已处理过的验证码:${bodyCode}`, 'info');
|
||||
continue;
|
||||
}
|
||||
seenCodes.add(bodyCode);
|
||||
persistSeenCodes();
|
||||
const source = useFallback && existingMailIds.has(itemId) ? '回退匹配邮件正文' : '新邮件正文';
|
||||
const timeLabel = itemTimestamp ? `,时间:${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : '';
|
||||
log(`步骤 ${step}:已在邮件正文中找到验证码:${bodyCode}(来源:${source}${timeLabel})`, 'ok');
|
||||
await sleep(1000);
|
||||
return { ok: true, code: bodyCode, emailTimestamp: Date.now() };
|
||||
|
||||
if (excludedCodeSet.has(candidateCode)) {
|
||||
log(`步骤 ${step}:跳过排除的验证码:${candidateCode}`, 'info');
|
||||
continue;
|
||||
}
|
||||
if (seenCodes.has(candidateCode)) {
|
||||
log(`步骤 ${step}:跳过已处理过的验证码:${candidateCode}`, 'info');
|
||||
continue;
|
||||
}
|
||||
|
||||
seenCodes.add(candidateCode);
|
||||
persistSeenCodes();
|
||||
const source = useFallback && !isNewMail
|
||||
? (bodyCode ? '回退匹配邮件正文' : '回退匹配邮件')
|
||||
: (bodyCode ? '新邮件正文' : '新邮件');
|
||||
const timeLabel = itemTimestamp ? `,时间:${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : '';
|
||||
log(`步骤 ${step}:已找到验证码:${candidateCode}(来源:${source}${timeLabel})`, 'ok');
|
||||
return { ok: true, code: candidateCode, emailTimestamp: Date.now() };
|
||||
}
|
||||
|
||||
items.forEach((item, index) => {
|
||||
knownMailIds.add(getMailItemId(item, index));
|
||||
});
|
||||
}
|
||||
|
||||
if (attempt === FALLBACK_AFTER + 1) {
|
||||
|
||||
@@ -695,6 +695,12 @@ function getPageTextSnapshot() {
|
||||
.trim();
|
||||
}
|
||||
|
||||
function getLoginVerificationDisplayedEmail() {
|
||||
const pageText = getPageTextSnapshot();
|
||||
const matches = pageText.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig) || [];
|
||||
return matches[0] ? String(matches[0]).trim().toLowerCase() : '';
|
||||
}
|
||||
|
||||
function getOAuthConsentForm() {
|
||||
return document.querySelector(OAUTH_CONSENT_FORM_SELECTOR);
|
||||
}
|
||||
@@ -1076,6 +1082,7 @@ function inspectLoginAuthState() {
|
||||
state: 'unknown',
|
||||
url: location.href,
|
||||
path: location.pathname || '',
|
||||
displayedEmail: getLoginVerificationDisplayedEmail(),
|
||||
retryButton: retryState?.retryButton || null,
|
||||
retryEnabled: Boolean(retryState?.retryEnabled),
|
||||
titleMatched: Boolean(retryState?.titleMatched),
|
||||
@@ -1141,6 +1148,7 @@ function serializeLoginAuthState(snapshot) {
|
||||
state: snapshot?.state || 'unknown',
|
||||
url: snapshot?.url || location.href,
|
||||
path: snapshot?.path || location.pathname || '',
|
||||
displayedEmail: snapshot?.displayedEmail || '',
|
||||
retryEnabled: Boolean(snapshot?.retryEnabled),
|
||||
titleMatched: Boolean(snapshot?.titleMatched),
|
||||
detailMatched: Boolean(snapshot?.detailMatched),
|
||||
|
||||
@@ -13,6 +13,7 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
executeStep7: 0,
|
||||
sleep: [],
|
||||
resolveOptions: null,
|
||||
setStates: [],
|
||||
};
|
||||
const realDateNow = Date.now;
|
||||
Date.now = () => 123456;
|
||||
@@ -29,7 +30,7 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
ensureStep8VerificationPageReady: async (options) => {
|
||||
calls.ensureReady += 1;
|
||||
calls.ensureReadyOptions.push(options || null);
|
||||
return { state: 'verification_page' };
|
||||
return { state: 'verification_page', displayedEmail: 'display.user@example.com' };
|
||||
},
|
||||
executeStep7: async () => {
|
||||
calls.executeStep7 += 1;
|
||||
@@ -53,7 +54,9 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
calls.resolveOptions = options;
|
||||
},
|
||||
reuseOrCreateTab: async () => {},
|
||||
setState: async () => {},
|
||||
setState: async (payload) => {
|
||||
calls.setStates.push(payload);
|
||||
},
|
||||
setStepStatus: async () => {},
|
||||
shouldUseCustomRegistrationEmail: () => false,
|
||||
sleepWithStop: async (ms) => {
|
||||
@@ -82,6 +85,10 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
assert.equal(typeof calls.resolveOptions.getRemainingTimeMs, 'function');
|
||||
assert.equal(await calls.resolveOptions.getRemainingTimeMs({ actionLabel: '登录验证码流程' }), 5000);
|
||||
assert.equal(calls.resolveOptions.resendIntervalMs, 25000);
|
||||
assert.equal(calls.resolveOptions.targetEmail, 'display.user@example.com');
|
||||
assert.deepStrictEqual(calls.setStates, [
|
||||
{ step8VerificationTargetEmail: 'display.user@example.com' },
|
||||
]);
|
||||
assert.deepStrictEqual(calls.ensureReadyOptions, [
|
||||
{ timeoutMs: 5000 },
|
||||
]);
|
||||
@@ -136,10 +143,62 @@ test('step 8 disables resend interval for 2925 mailbox polling', async () => {
|
||||
});
|
||||
|
||||
assert.equal(capturedOptions.resendIntervalMs, 0);
|
||||
assert.equal(capturedOptions.targetEmail, '');
|
||||
assert.equal(capturedOptions.beforeSubmit, undefined);
|
||||
assert.equal(typeof capturedOptions.getRemainingTimeMs, 'function');
|
||||
});
|
||||
|
||||
test('step 8 falls back to the run email when the verification page does not expose a displayed email', async () => {
|
||||
let capturedOptions = null;
|
||||
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep8VerificationPageReady: async () => ({ state: 'verification_page', displayedEmail: '' }),
|
||||
executeStep7: async () => {},
|
||||
getOAuthFlowRemainingMs: async () => 8000,
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => Math.min(defaultTimeoutMs, 8000),
|
||||
getMailConfig: () => ({
|
||||
provider: 'qq',
|
||||
label: 'QQ 邮箱',
|
||||
source: 'mail-qq',
|
||||
url: 'https://mail.qq.com',
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
resolveVerificationStep: async (_step, _state, _mail, options) => {
|
||||
capturedOptions = options;
|
||||
},
|
||||
reuseOrCreateTab: async () => {},
|
||||
setState: async () => {},
|
||||
setStepStatus: async () => {},
|
||||
shouldUseCustomRegistrationEmail: () => false,
|
||||
sleepWithStop: async () => {},
|
||||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 8,
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep8({
|
||||
email: 'user@example.com',
|
||||
password: 'secret',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
|
||||
assert.equal(capturedOptions.targetEmail, 'user@example.com');
|
||||
});
|
||||
|
||||
test('step 8 does not rerun step 7 when verification submit lands on add-phone', async () => {
|
||||
const calls = {
|
||||
executeStep7: 0,
|
||||
|
||||
+245
-49
@@ -51,37 +51,38 @@ function extractFunction(name) {
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
test('handlePollEmail returns to inbox before initial refresh when 2925 opens on a detail page', async () => {
|
||||
test('handlePollEmail establishes a baseline after opening from detail view and only picks mail from a later refresh', async () => {
|
||||
const bundle = extractFunction('handlePollEmail');
|
||||
|
||||
const api = new Function(`
|
||||
let detailMode = true;
|
||||
let state = 'detail';
|
||||
let refreshCalls = 0;
|
||||
const clickOrder = [];
|
||||
const readAndDeleteCalls = [];
|
||||
const seenCodes = new Set();
|
||||
const mailItem = { text: 'OpenAI verification code 654321' };
|
||||
const baselineMail = { id: 'baseline', text: 'OpenAI newsletter without code' };
|
||||
const newMail = { id: 'new', text: 'OpenAI verification code 654321' };
|
||||
|
||||
function findMailItems() {
|
||||
return detailMode ? [] : [mailItem];
|
||||
if (state === 'detail') return [];
|
||||
if (state === 'baseline') return [baselineMail];
|
||||
return [baselineMail, newMail];
|
||||
}
|
||||
|
||||
function getMailItemId() {
|
||||
return 'mail-1';
|
||||
function getMailItemId(item) {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
function getCurrentMailIds(items = []) {
|
||||
return new Set(items.map(() => 'mail-1'));
|
||||
}
|
||||
|
||||
function normalizeMinuteTimestamp(value) {
|
||||
return Number(value) || 0;
|
||||
return new Set(items.map((item) => item.id));
|
||||
}
|
||||
|
||||
function parseMailItemTimestamp() {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
function matchesMailFilters() {
|
||||
return true;
|
||||
function matchesMailFilters(text) {
|
||||
return /openai|verification/i.test(String(text || ''));
|
||||
}
|
||||
|
||||
function getMailItemText(item) {
|
||||
@@ -93,29 +94,26 @@ function extractVerificationCode(text) {
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function extractEmails() {
|
||||
return [];
|
||||
}
|
||||
|
||||
function emailMatchesTarget() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getTargetEmailMatchState() {
|
||||
return { matches: true, hasExplicitEmail: false };
|
||||
}
|
||||
|
||||
async function sleep() {}
|
||||
async function sleepRandom() {}
|
||||
|
||||
async function returnToInbox() {
|
||||
clickOrder.push('inbox');
|
||||
detailMode = false;
|
||||
state = 'baseline';
|
||||
return true;
|
||||
}
|
||||
|
||||
async function refreshInbox() {
|
||||
clickOrder.push('refresh');
|
||||
refreshCalls += 1;
|
||||
if (refreshCalls >= 2) {
|
||||
state = 'with-new';
|
||||
}
|
||||
}
|
||||
|
||||
async function openMailAndDeleteAfterRead(item) {
|
||||
readAndDeleteCalls.push(item.id);
|
||||
return item.id === 'new' ? 'Your ChatGPT code is 654321' : 'No code here';
|
||||
}
|
||||
|
||||
function persistSeenCodes() {}
|
||||
@@ -128,38 +126,113 @@ return {
|
||||
getClickOrder() {
|
||||
return clickOrder.slice();
|
||||
},
|
||||
getReadAndDeleteCalls() {
|
||||
return readAndDeleteCalls.slice();
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.handlePollEmail(4, {
|
||||
senderFilters: ['openai'],
|
||||
subjectFilters: ['verification'],
|
||||
maxAttempts: 1,
|
||||
maxAttempts: 2,
|
||||
intervalMs: 1,
|
||||
filterAfterTimestamp: Date.now(),
|
||||
});
|
||||
|
||||
assert.equal(result.code, '654321');
|
||||
assert.deepEqual(api.getClickOrder(), ['inbox', 'refresh']);
|
||||
assert.deepEqual(api.getClickOrder(), ['inbox', 'refresh', 'inbox', 'refresh']);
|
||||
assert.deepEqual(api.getReadAndDeleteCalls(), ['new']);
|
||||
});
|
||||
|
||||
test('handlePollEmail ignores targetEmail and still tests any matching ChatGPT mail', async () => {
|
||||
const bundle = extractFunction('handlePollEmail');
|
||||
|
||||
const api = new Function(`
|
||||
let state = 'empty';
|
||||
const seenCodes = new Set();
|
||||
const readAndDeleteCalls = [];
|
||||
const matchingMail = {
|
||||
id: 'mail-1',
|
||||
text: 'ChatGPT verification code 112233 for another.user@example.com',
|
||||
};
|
||||
|
||||
function findMailItems() {
|
||||
return state === 'ready' ? [matchingMail] : [];
|
||||
}
|
||||
|
||||
function getMailItemId(item) {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
function getCurrentMailIds(items = []) {
|
||||
return new Set(items.map((item) => item.id));
|
||||
}
|
||||
|
||||
function parseMailItemTimestamp() {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
function matchesMailFilters(text) {
|
||||
return /chatgpt|openai|verification/i.test(String(text || ''));
|
||||
}
|
||||
|
||||
function getMailItemText(item) {
|
||||
return item.text;
|
||||
}
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
const match = String(text || '').match(/(\\d{6})/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
async function sleep() {}
|
||||
async function sleepRandom() {}
|
||||
async function returnToInbox() {
|
||||
return true;
|
||||
}
|
||||
async function refreshInbox() {
|
||||
state = 'ready';
|
||||
}
|
||||
|
||||
async function openMailAndDeleteAfterRead(item) {
|
||||
readAndDeleteCalls.push(item.id);
|
||||
return item.text;
|
||||
}
|
||||
|
||||
function persistSeenCodes() {}
|
||||
function log() {}
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
handlePollEmail,
|
||||
getReadAndDeleteCalls() {
|
||||
return readAndDeleteCalls.slice();
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.handlePollEmail(8, {
|
||||
senderFilters: ['chatgpt'],
|
||||
subjectFilters: ['verification'],
|
||||
maxAttempts: 4,
|
||||
intervalMs: 1,
|
||||
targetEmail: 'expected@example.com',
|
||||
});
|
||||
|
||||
assert.equal(result.code, '112233');
|
||||
assert.deepEqual(api.getReadAndDeleteCalls(), ['mail-1']);
|
||||
});
|
||||
|
||||
test('openMailAndGetMessageText always returns to inbox after opening a 2925 message', async () => {
|
||||
const bundle = [
|
||||
extractFunction('findInboxLink'),
|
||||
extractFunction('returnToInbox'),
|
||||
extractFunction('openMailAndGetMessageText'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
const MAIL_INBOX_SELECTORS = [
|
||||
'a[href*="mailList"]',
|
||||
'[class*="inbox"]',
|
||||
'[class*="Inbox"]',
|
||||
'[title*="鏀朵欢绠?]',
|
||||
];
|
||||
const clickOrder = [];
|
||||
const mailItem = { kind: 'mail' };
|
||||
const inboxLink = { kind: 'inbox' };
|
||||
let listVisible = true;
|
||||
let bodyText = '';
|
||||
|
||||
@@ -169,18 +242,16 @@ const document = {
|
||||
return bodyText;
|
||||
},
|
||||
},
|
||||
querySelector(selector) {
|
||||
if (selector.includes('mailList') || selector.includes('inbox') || selector.includes('Inbox')) {
|
||||
return inboxLink;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
function findMailItems() {
|
||||
return listVisible ? [mailItem] : [];
|
||||
}
|
||||
|
||||
function findInboxLink() {
|
||||
return { kind: 'inbox' };
|
||||
}
|
||||
|
||||
function simulateClick(node) {
|
||||
if (node === mailItem) {
|
||||
clickOrder.push('mail');
|
||||
@@ -188,12 +259,8 @@ function simulateClick(node) {
|
||||
bodyText = 'Your ChatGPT code is 731091';
|
||||
return;
|
||||
}
|
||||
if (node === inboxLink) {
|
||||
clickOrder.push('inbox');
|
||||
listVisible = true;
|
||||
return;
|
||||
}
|
||||
throw new Error('unexpected node');
|
||||
clickOrder.push('inbox');
|
||||
listVisible = true;
|
||||
}
|
||||
|
||||
async function sleep() {}
|
||||
@@ -219,3 +286,132 @@ return {
|
||||
assert.deepEqual(api.getClickOrder(), ['mail', 'inbox']);
|
||||
assert.equal(api.isListVisible(), true);
|
||||
});
|
||||
|
||||
test('openMailAndDeleteAfterRead deletes the opened message before returning to inbox', async () => {
|
||||
const bundle = [
|
||||
extractFunction('deleteCurrentMailboxEmail'),
|
||||
extractFunction('returnToInbox'),
|
||||
extractFunction('openMailAndDeleteAfterRead'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
const calls = [];
|
||||
const mailItem = { kind: 'mail' };
|
||||
const deleteButton = { kind: 'delete' };
|
||||
let listVisible = true;
|
||||
let bodyText = '';
|
||||
|
||||
const document = {
|
||||
body: {
|
||||
get textContent() {
|
||||
return bodyText;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function findMailItems() {
|
||||
return listVisible ? [mailItem] : [];
|
||||
}
|
||||
|
||||
function findDeleteButton() {
|
||||
return deleteButton;
|
||||
}
|
||||
|
||||
function findInboxLink() {
|
||||
return { kind: 'inbox' };
|
||||
}
|
||||
|
||||
function simulateClick(node) {
|
||||
if (node === mailItem) {
|
||||
calls.push('mail');
|
||||
listVisible = false;
|
||||
bodyText = 'Your ChatGPT code is 778899';
|
||||
return;
|
||||
}
|
||||
if (node === deleteButton) {
|
||||
calls.push('delete');
|
||||
return;
|
||||
}
|
||||
calls.push('inbox');
|
||||
listVisible = true;
|
||||
}
|
||||
|
||||
async function sleep() {}
|
||||
async function sleepRandom() {}
|
||||
const console = { warn() {} };
|
||||
const MAIL2925_PREFIX = '[MultiPage:mail-2925]';
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
mailItem,
|
||||
openMailAndDeleteAfterRead,
|
||||
getCalls() {
|
||||
return calls.slice();
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const text = await api.openMailAndDeleteAfterRead(api.mailItem, 8);
|
||||
|
||||
assert.match(text, /778899/);
|
||||
assert.deepEqual(api.getCalls(), ['mail', 'delete', 'inbox']);
|
||||
});
|
||||
|
||||
test('deleteAllMailboxEmails selects all messages and clicks delete', async () => {
|
||||
const bundle = extractFunction('deleteAllMailboxEmails');
|
||||
|
||||
const api = new Function(`
|
||||
const calls = [];
|
||||
const selectAllControl = { kind: 'select-all' };
|
||||
const deleteButton = { kind: 'delete' };
|
||||
|
||||
async function returnToInbox() {
|
||||
calls.push('inbox');
|
||||
return true;
|
||||
}
|
||||
|
||||
function findSelectAllControl() {
|
||||
return selectAllControl;
|
||||
}
|
||||
|
||||
function isCheckboxChecked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function findDeleteButton() {
|
||||
return deleteButton;
|
||||
}
|
||||
|
||||
function simulateClick(node) {
|
||||
if (node === selectAllControl) {
|
||||
calls.push('select-all');
|
||||
return;
|
||||
}
|
||||
if (node === deleteButton) {
|
||||
calls.push('delete');
|
||||
return;
|
||||
}
|
||||
throw new Error('unexpected node');
|
||||
}
|
||||
|
||||
async function sleepRandom() {}
|
||||
|
||||
const console = { warn() {} };
|
||||
const MAIL2925_PREFIX = '[MultiPage:mail-2925]';
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
deleteAllMailboxEmails,
|
||||
getCalls() {
|
||||
return calls.slice();
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.deleteAllMailboxEmails(4);
|
||||
|
||||
assert.equal(result, true);
|
||||
assert.deepEqual(api.getCalls(), ['inbox', 'select-all', 'delete']);
|
||||
});
|
||||
|
||||
@@ -51,6 +51,8 @@ function extractFunction(name) {
|
||||
}
|
||||
|
||||
const bundle = [
|
||||
extractFunction('getPageTextSnapshot'),
|
||||
extractFunction('getLoginVerificationDisplayedEmail'),
|
||||
extractFunction('inspectLoginAuthState'),
|
||||
extractFunction('normalizeStep6Snapshot'),
|
||||
].join('\n');
|
||||
@@ -62,6 +64,13 @@ const location = {
|
||||
pathname: ${JSON.stringify(overrides.pathname || '/log-in')},
|
||||
};
|
||||
|
||||
const document = {
|
||||
body: {
|
||||
innerText: ${JSON.stringify(overrides.pageText || '')},
|
||||
textContent: ${JSON.stringify(overrides.pageText || '')},
|
||||
},
|
||||
};
|
||||
|
||||
function getLoginTimeoutErrorPageState() {
|
||||
return ${JSON.stringify(overrides.retryState || null)};
|
||||
}
|
||||
@@ -127,6 +136,16 @@ return {
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const api = createApi({
|
||||
verificationTarget: { id: 'otp' },
|
||||
pageText: 'We emailed a code to display.user@example.com. Enter it below.',
|
||||
});
|
||||
|
||||
const snapshot = api.inspectLoginAuthState();
|
||||
assert.strictEqual(snapshot.displayedEmail, 'display.user@example.com');
|
||||
}
|
||||
|
||||
{
|
||||
const api = createApi({
|
||||
oauthConsentPage: true,
|
||||
|
||||
@@ -37,8 +37,10 @@ test('verification flow extends 2925 polling window', () => {
|
||||
const step4Payload = helpers.getVerificationPollPayload(4, { email: 'user@example.com', mailProvider: '2925' });
|
||||
const step8Payload = helpers.getVerificationPollPayload(8, { email: 'user@example.com', mailProvider: '2925' });
|
||||
|
||||
assert.equal(step4Payload.filterAfterTimestamp, 0);
|
||||
assert.equal(step4Payload.maxAttempts, 15);
|
||||
assert.equal(step4Payload.intervalMs, 15000);
|
||||
assert.equal(step8Payload.filterAfterTimestamp, 0);
|
||||
assert.equal(step8Payload.maxAttempts, 15);
|
||||
assert.equal(step8Payload.intervalMs, 15000);
|
||||
});
|
||||
@@ -109,6 +111,67 @@ test('verification flow runs beforeSubmit hook before filling the code', async (
|
||||
]);
|
||||
});
|
||||
|
||||
test('verification flow triggers 2925 mailbox cleanup only after code submission succeeds', async () => {
|
||||
const mailMessages = [];
|
||||
|
||||
const helpers = api.createVerificationFlowHelpers({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
getState: async () => ({}),
|
||||
getTabId: async () => 1,
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isStopError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||
pollHotmailVerificationCode: async () => ({}),
|
||||
pollLuckmailVerificationCode: async () => ({}),
|
||||
sendToContentScript: async (_source, message) => {
|
||||
if (message.type === 'FILL_CODE') {
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
sendToMailContentScriptResilient: async (_mail, message) => {
|
||||
mailMessages.push(message.type);
|
||||
if (message.type === 'POLL_EMAIL') {
|
||||
return { code: '654321', emailTimestamp: 123 };
|
||||
}
|
||||
return { ok: true };
|
||||
},
|
||||
setState: async () => {},
|
||||
setStepStatus: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||
});
|
||||
|
||||
await helpers.resolveVerificationStep(
|
||||
8,
|
||||
{
|
||||
email: 'user@example.com',
|
||||
mailProvider: '2925',
|
||||
lastLoginCode: null,
|
||||
},
|
||||
{ provider: '2925', label: '2925 邮箱' },
|
||||
{}
|
||||
);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
assert.deepStrictEqual(mailMessages, ['POLL_EMAIL', 'DELETE_ALL_EMAILS']);
|
||||
});
|
||||
|
||||
test('verification flow treats add-phone after login code submit as fatal instead of completing step 8', async () => {
|
||||
const events = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user