Refactor multi-flow mail capability boundaries
This commit is contained in:
+47
-8
@@ -98,6 +98,39 @@ function getTargetEmailMatchState(text, targetEmail) {
|
||||
return { matches: false, hasExplicitEmail: false };
|
||||
}
|
||||
|
||||
function normalizeRulePatternList(patterns = []) {
|
||||
return Array.isArray(patterns) ? patterns : [];
|
||||
}
|
||||
|
||||
function extractCodeByRulePatterns(text, patterns = []) {
|
||||
const normalizedText = String(text || '');
|
||||
for (const pattern of normalizeRulePatternList(patterns)) {
|
||||
try {
|
||||
const source = String(pattern?.source || '').trim();
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
const flags = String(pattern?.flags || '').replace(/[^dgimsuvy]/g, '');
|
||||
const match = normalizedText.match(new RegExp(source, flags));
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
for (let index = 1; index < match.length; index += 1) {
|
||||
const candidate = String(match[index] || '').trim();
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
if (String(match[0] || '').trim()) {
|
||||
return String(match[0] || '').trim();
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore invalid runtime rule patterns and continue with other candidates.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const MONTH_INDEX_MAP = {
|
||||
jan: 0,
|
||||
feb: 1,
|
||||
@@ -165,16 +198,17 @@ function parseGmailTimestampText(rawText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
function extractVerificationCode(text, options = {}) {
|
||||
const normalized = String(text || '');
|
||||
const matchedByRule = extractCodeByRulePatterns(normalized, options?.codePatterns);
|
||||
if (matchedByRule) {
|
||||
return matchedByRule;
|
||||
}
|
||||
|
||||
const cnMatch = normalized.match(/(?:验证码|代码)[^0-9]{0,16}(\d{6})/i);
|
||||
if (cnMatch) return cnMatch[1];
|
||||
|
||||
const openAiLoginMatch = normalized.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (openAiLoginMatch) return openAiLoginMatch[1];
|
||||
|
||||
const enMatch = normalized.match(/(?:verification\s+code|temporary\s+verification\s+code|your\s+chatgpt\s+code|code(?:\s+is)?)[^0-9]{0,16}(\d{6})/i);
|
||||
const enMatch = normalized.match(/(?:verification\s+code|temporary\s+verification\s+code|log-?in\s+code|enter\s+this\s+code|code(?:\s+is)?)[^0-9]{0,24}(\d{6})/i);
|
||||
if (enMatch) return enMatch[1];
|
||||
|
||||
const plainMatch = normalized.match(/\b(\d{6})\b/);
|
||||
@@ -357,7 +391,7 @@ function collectThreadRows() {
|
||||
if (
|
||||
row.matches('tr.zA')
|
||||
|| row.querySelector('.bog, .y6, .y2, .afn, [data-thread-id], [data-legacy-thread-id], [data-legacy-last-message-id]')
|
||||
|| /openai|chatgpt|verify|verification|code|验证码/i.test(text)
|
||||
|| /verify|verification|code|验证码|log-?in/i.test(text)
|
||||
) {
|
||||
rows.push(row);
|
||||
}
|
||||
@@ -545,6 +579,7 @@ async function openRowAndGetMessageText(row) {
|
||||
|
||||
async function handlePollEmail(step, payload) {
|
||||
const {
|
||||
codePatterns = [],
|
||||
senderFilters = [],
|
||||
subjectFilters = [],
|
||||
maxAttempts = 5,
|
||||
@@ -618,7 +653,9 @@ async function handlePollEmail(step, payload) {
|
||||
}
|
||||
|
||||
const previewTargetState = getTargetEmailMatchState(preview.combinedText, targetEmail);
|
||||
const previewCode = extractVerificationCode(preview.combinedText);
|
||||
const previewCode = extractVerificationCode(preview.combinedText, {
|
||||
codePatterns,
|
||||
});
|
||||
if (previewCode) {
|
||||
if (excludedCodeSet.has(previewCode)) {
|
||||
log(`步骤 ${step}:跳过排除的验证码:${previewCode}`, 'info');
|
||||
@@ -644,7 +681,9 @@ async function handlePollEmail(step, payload) {
|
||||
|
||||
const openedText = await openRowAndGetMessageText(row);
|
||||
const openedTargetState = getTargetEmailMatchState(openedText, targetEmail);
|
||||
const bodyCode = extractVerificationCode(openedText);
|
||||
const bodyCode = extractVerificationCode(openedText, {
|
||||
codePatterns,
|
||||
});
|
||||
if (!bodyCode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+49
-6
@@ -43,6 +43,39 @@ if (shouldHandlePollEmailInCurrentFrame) {
|
||||
return String(value || '').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function normalizeRulePatternList(patterns = []) {
|
||||
return Array.isArray(patterns) ? patterns : [];
|
||||
}
|
||||
|
||||
function extractCodeByRulePatterns(text, patterns = []) {
|
||||
const normalizedText = String(text || '');
|
||||
for (const pattern of normalizeRulePatternList(patterns)) {
|
||||
try {
|
||||
const source = String(pattern?.source || '').trim();
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
const flags = String(pattern?.flags || '').replace(/[^dgimsuvy]/g, '');
|
||||
const match = normalizedText.match(new RegExp(source, flags));
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
for (let index = 1; index < match.length; index += 1) {
|
||||
const candidate = String(match[index] || '').trim();
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
if (String(match[0] || '').trim()) {
|
||||
return String(match[0] || '').trim();
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore invalid runtime rule patterns and continue with other candidates.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isVisibleElement(node) {
|
||||
return Boolean(node instanceof HTMLElement)
|
||||
&& (Boolean(node.offsetParent) || getComputedStyle(node).position === 'fixed');
|
||||
@@ -82,12 +115,15 @@ if (shouldHandlePollEmailInCurrentFrame) {
|
||||
].join('::')).slice(0, 240);
|
||||
}
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
function extractVerificationCode(text, options = {}) {
|
||||
const matchedByRule = extractCodeByRulePatterns(text, options?.codePatterns);
|
||||
if (matchedByRule) return matchedByRule;
|
||||
|
||||
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||
if (matchCn) return matchCn[1];
|
||||
|
||||
const matchOpenAiLogin = text.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchOpenAiLogin) return matchOpenAiLogin[1];
|
||||
const matchLoginCode = text.match(/(?:log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchLoginCode) return matchLoginCode[1];
|
||||
|
||||
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||
if (matchEn) return matchEn[1] || matchEn[2];
|
||||
@@ -276,7 +312,14 @@ if (shouldHandlePollEmailInCurrentFrame) {
|
||||
}
|
||||
|
||||
async function handlePollEmail(step, payload) {
|
||||
const { senderFilters, subjectFilters, maxAttempts, intervalMs, excludeCodes = [] } = payload;
|
||||
const {
|
||||
codePatterns = [],
|
||||
senderFilters,
|
||||
subjectFilters,
|
||||
maxAttempts,
|
||||
intervalMs,
|
||||
excludeCodes = [],
|
||||
} = payload;
|
||||
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
||||
const FALLBACK_AFTER = 3;
|
||||
const pollSessionKey = normalizePollSessionKey(payload);
|
||||
@@ -327,7 +370,7 @@ if (shouldHandlePollEmailInCurrentFrame) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let code = extractVerificationCode(meta.combinedText);
|
||||
let code = extractVerificationCode(meta.combinedText, { codePatterns });
|
||||
let opened = null;
|
||||
|
||||
if (!code) {
|
||||
@@ -339,7 +382,7 @@ if (shouldHandlePollEmailInCurrentFrame) {
|
||||
if (!openedSenderMatch && !openedSubjectMatch && !senderMatch && !subjectMatch) {
|
||||
continue;
|
||||
}
|
||||
code = extractVerificationCode(opened.combinedText);
|
||||
code = extractVerificationCode(opened.combinedText, { codePatterns });
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
|
||||
@@ -60,12 +60,55 @@ function normalizeText(value) {
|
||||
return (value || '').replace(/\s+/g, ' ').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
function normalizeRulePatternList(patterns = []) {
|
||||
return Array.isArray(patterns) ? patterns : [];
|
||||
}
|
||||
|
||||
function extractCodeByRulePatterns(text, patterns = []) {
|
||||
const normalizedText = String(text || '');
|
||||
for (const pattern of normalizeRulePatternList(patterns)) {
|
||||
try {
|
||||
const source = String(pattern?.source || '').trim();
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
const flags = String(pattern?.flags || '').replace(/[^dgimsuvy]/g, '');
|
||||
const match = normalizedText.match(new RegExp(source, flags));
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
for (let index = 1; index < match.length; index += 1) {
|
||||
const candidate = String(match[index] || '').trim();
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
if (String(match[0] || '').trim()) {
|
||||
return String(match[0] || '').trim();
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore invalid runtime rule patterns and continue with other candidates.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function matchesKeywordHints(text, keywords = []) {
|
||||
const normalizedText = normalizeText(text);
|
||||
const normalizedKeywords = Array.isArray(keywords) ? keywords : [];
|
||||
return normalizedKeywords.some((keyword) => normalizedText.includes(normalizeText(keyword)));
|
||||
}
|
||||
|
||||
function extractVerificationCode(text, options = {}) {
|
||||
const matchedByRule = extractCodeByRulePatterns(text, options?.codePatterns);
|
||||
if (matchedByRule) {
|
||||
return matchedByRule;
|
||||
}
|
||||
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||
if (matchCn) return matchCn[1];
|
||||
|
||||
const matchOpenAiLogin = text.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchOpenAiLogin) return matchOpenAiLogin[1];
|
||||
const matchLoginCode = text.match(/(?:log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchLoginCode) return matchLoginCode[1];
|
||||
|
||||
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||
if (matchEn) return matchEn[1] || matchEn[2];
|
||||
@@ -76,7 +119,7 @@ function extractVerificationCode(text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function rowMatchesFilters(mail, senderFilters, subjectFilters, targetEmail) {
|
||||
function rowMatchesFilters(mail, senderFilters, subjectFilters, targetEmail, options = {}) {
|
||||
const sender = normalizeText(mail.sender);
|
||||
const subject = normalizeText(mail.subject);
|
||||
const mailbox = normalizeText(mail.mailbox);
|
||||
@@ -87,8 +130,12 @@ function rowMatchesFilters(mail, senderFilters, subjectFilters, targetEmail) {
|
||||
const subjectMatch = subjectFilters.some(f => subject.includes(f.toLowerCase()) || combined.includes(f.toLowerCase()));
|
||||
const mailboxMatch = Boolean(targetLocal) && mailbox.includes(targetLocal);
|
||||
const forwardedDuck = /duckduckgo|forward(?:ed)?\s*by/i.test(mail.combinedText);
|
||||
const code = extractVerificationCode(mail.combinedText);
|
||||
const keywordMatch = /openai|chatgpt|verify|verification|confirm|log-?in|验证码|代码/.test(combined);
|
||||
const code = extractVerificationCode(mail.combinedText, {
|
||||
codePatterns: options?.codePatterns,
|
||||
});
|
||||
const keywordMatch = options?.requiredKeywords?.length
|
||||
? matchesKeywordHints(combined, options.requiredKeywords)
|
||||
: /verify|verification|confirm|log-?in|验证码|代码/.test(combined);
|
||||
|
||||
if (mailboxMatch) return { matched: true, mailboxMatch, code };
|
||||
if (senderMatch || subjectMatch) return { matched: true, mailboxMatch: false, code };
|
||||
@@ -170,6 +217,8 @@ async function deleteCurrentMailboxMessage(step) {
|
||||
|
||||
async function handleMailboxPollEmail(step, payload) {
|
||||
const {
|
||||
codePatterns = [],
|
||||
requiredKeywords = [],
|
||||
senderFilters = [],
|
||||
subjectFilters = [],
|
||||
maxAttempts = 20,
|
||||
@@ -208,14 +257,19 @@ async function handleMailboxPollEmail(step, payload) {
|
||||
if (seenMailIds.has(mail.mailId)) continue;
|
||||
if (!useFallback && existingMailIds.has(mail.mailId)) continue;
|
||||
|
||||
const match = rowMatchesFilters(mail, senderFilters, subjectFilters, '');
|
||||
const match = rowMatchesFilters(mail, senderFilters, subjectFilters, '', {
|
||||
codePatterns,
|
||||
requiredKeywords,
|
||||
});
|
||||
if (!match.matched) continue;
|
||||
|
||||
candidates.push({ ...mail, code: match.code });
|
||||
}
|
||||
|
||||
for (const mail of candidates) {
|
||||
const code = mail.code || extractVerificationCode(mail.combinedText);
|
||||
const code = mail.code || extractVerificationCode(mail.combinedText, {
|
||||
codePatterns,
|
||||
});
|
||||
if (!code) continue;
|
||||
if (excludedCodeSet.has(code)) {
|
||||
log(`步骤 ${step}:跳过排除的验证码:${code}`, 'info');
|
||||
|
||||
+60
-12
@@ -73,6 +73,39 @@ function normalizeText(value) {
|
||||
return String(value || '').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function normalizeRulePatternList(patterns = []) {
|
||||
return Array.isArray(patterns) ? patterns : [];
|
||||
}
|
||||
|
||||
function extractCodeByRulePatterns(text, patterns = []) {
|
||||
const normalizedText = String(text || '');
|
||||
for (const pattern of normalizeRulePatternList(patterns)) {
|
||||
try {
|
||||
const source = String(pattern?.source || '').trim();
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
const flags = String(pattern?.flags || '').replace(/[^dgimsuvy]/g, '');
|
||||
const match = normalizedText.match(new RegExp(source, flags));
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
for (let index = 1; index < match.length; index += 1) {
|
||||
const candidate = String(match[index] || '').trim();
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
if (String(match[0] || '').trim()) {
|
||||
return String(match[0] || '').trim();
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore invalid runtime rule patterns and continue with other candidates.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getNetEaseMailLabel(hostname) {
|
||||
const currentHostname = String(
|
||||
hostname || (typeof location !== 'undefined' ? location.hostname : '') || ''
|
||||
@@ -129,7 +162,7 @@ function isLikelyMailItemNode(node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /发件人|验证码|verification|chatgpt|openai|code|log-?in/i.test(summaryText);
|
||||
return /发件人|验证码|verification|code|log-?in/i.test(summaryText);
|
||||
}
|
||||
|
||||
function findMailItems() {
|
||||
@@ -406,7 +439,7 @@ function selectOpenedMailTextCandidate(item, candidates = [], options = {}) {
|
||||
if (sender && lower.includes(sender)) {
|
||||
return true;
|
||||
}
|
||||
return Boolean(extractVerificationCode(candidate) && /chatgpt|openai|verification|验证码|log-?in\s+code/i.test(lower));
|
||||
return Boolean(extractVerificationCode(candidate, { codePatterns: options.codePatterns }) && /verification|验证码|log-?in\s+code|enter\s+this\s+code|code/i.test(lower));
|
||||
}) || source[0] || '';
|
||||
|
||||
const filteredCandidates = candidates.filter((candidate) => !excludedSet.has(normalizeText(candidate)));
|
||||
@@ -443,9 +476,11 @@ async function returnToInbox() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function openMailAndGetMessageText(item) {
|
||||
async function openMailAndGetMessageText(item, options = {}) {
|
||||
const beforeCandidates = collectOpenedMailTextCandidates();
|
||||
const beforeText = selectOpenedMailTextCandidate(item, beforeCandidates);
|
||||
const beforeText = selectOpenedMailTextCandidate(item, beforeCandidates, {
|
||||
codePatterns: options.codePatterns,
|
||||
});
|
||||
if (typeof simulateClick === 'function') {
|
||||
simulateClick(item);
|
||||
} else {
|
||||
@@ -456,6 +491,7 @@ async function openMailAndGetMessageText(item) {
|
||||
for (let i = 0; i < 24; i += 1) {
|
||||
await sleep(250);
|
||||
const candidate = readOpenedMailText(item, {
|
||||
codePatterns: options.codePatterns,
|
||||
excludedTexts: beforeCandidates,
|
||||
allowExcludedFallback: false,
|
||||
});
|
||||
@@ -463,7 +499,7 @@ async function openMailAndGetMessageText(item) {
|
||||
continue;
|
||||
}
|
||||
openedText = candidate;
|
||||
if (extractVerificationCode(candidate)) {
|
||||
if (extractVerificationCode(candidate, { codePatterns: options.codePatterns })) {
|
||||
break;
|
||||
}
|
||||
if (candidate !== beforeText && candidate.length > beforeText.length + 24) {
|
||||
@@ -488,7 +524,15 @@ function scheduleEmailCleanup(item, step) {
|
||||
// ============================================================
|
||||
|
||||
async function handlePollEmail(step, payload) {
|
||||
const { senderFilters, subjectFilters, maxAttempts, intervalMs, excludeCodes = [], filterAfterTimestamp = 0 } = payload;
|
||||
const {
|
||||
codePatterns = [],
|
||||
senderFilters,
|
||||
subjectFilters,
|
||||
maxAttempts,
|
||||
intervalMs,
|
||||
excludeCodes = [],
|
||||
filterAfterTimestamp = 0,
|
||||
} = payload;
|
||||
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
||||
const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0);
|
||||
const mailLabel = getNetEaseMailLabel();
|
||||
@@ -581,12 +625,12 @@ async function handlePollEmail(step, payload) {
|
||||
});
|
||||
|
||||
if (senderMatch || subjectMatch) {
|
||||
let code = extractVerificationCode(combinedText);
|
||||
let code = extractVerificationCode(combinedText, { codePatterns });
|
||||
let codeSource = '邮件列表';
|
||||
|
||||
if (!code) {
|
||||
const openedText = await openMailAndGetMessageText(item);
|
||||
code = extractVerificationCode(openedText);
|
||||
const openedText = await openMailAndGetMessageText(item, { codePatterns });
|
||||
code = extractVerificationCode(openedText, { codePatterns });
|
||||
if (code) {
|
||||
codeSource = '邮件正文';
|
||||
}
|
||||
@@ -716,12 +760,16 @@ async function refreshInbox() {
|
||||
// Verification Code Extraction
|
||||
// ============================================================
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
function extractVerificationCode(text, options = {}) {
|
||||
const matchedByRule = extractCodeByRulePatterns(text, options?.codePatterns);
|
||||
if (matchedByRule) {
|
||||
return matchedByRule;
|
||||
}
|
||||
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||
if (matchCn) return matchCn[1];
|
||||
|
||||
const matchOpenAiLogin = text.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchOpenAiLogin) return matchOpenAiLogin[1];
|
||||
const matchLoginCode = text.match(/(?:log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchLoginCode) return matchLoginCode[1];
|
||||
|
||||
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||
if (matchEn) return matchEn[1] || matchEn[2];
|
||||
|
||||
+90
-21
@@ -670,7 +670,56 @@ function matchesMailFilters(text, senderFilters, subjectFilters) {
|
||||
return senderMatch || subjectMatch;
|
||||
}
|
||||
|
||||
function extractStrictChatGPTVerificationCode(text) {
|
||||
function normalizeRulePatternList(patterns = []) {
|
||||
return Array.isArray(patterns) ? patterns : [];
|
||||
}
|
||||
|
||||
function extractCodeByRulePatterns(text, patterns = []) {
|
||||
const normalizedText = String(text || '');
|
||||
for (const pattern of normalizeRulePatternList(patterns)) {
|
||||
try {
|
||||
const source = String(pattern?.source || '').trim();
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
const flags = String(pattern?.flags || '').replace(/[^dgimsuvy]/g, '');
|
||||
const match = normalizedText.match(new RegExp(source, flags));
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
for (let index = 1; index < match.length; index += 1) {
|
||||
const candidate = String(match[index] || '').trim();
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
if (String(match[0] || '').trim()) {
|
||||
return String(match[0] || '').trim();
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore invalid runtime rule patterns and continue with other candidates.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeTargetEmailHints(hints = [], targetEmail = '') {
|
||||
const normalizedHints = Array.isArray(hints) ? hints : [];
|
||||
const collected = normalizedHints
|
||||
.map((item) => String(item || '').trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
|
||||
if (normalizedTarget) {
|
||||
collected.push(normalizedTarget);
|
||||
const atIndex = normalizedTarget.indexOf('@');
|
||||
if (atIndex > 0) {
|
||||
collected.push(`${normalizedTarget.slice(0, atIndex)}=${normalizedTarget.slice(atIndex + 1)}`);
|
||||
}
|
||||
}
|
||||
return [...new Set(collected)];
|
||||
}
|
||||
|
||||
function extractLegacyStrictVerificationCode(text) {
|
||||
const normalized = String(text || '');
|
||||
const patterns = [
|
||||
/your\s+(?:temporary\s+)?chatgpt\s+(?:(?:log-?in|login)\s+)?code\s+is[\s\S]{0,80}?(\d{6})/i,
|
||||
@@ -742,11 +791,16 @@ function findSafeStandaloneSixDigitCode(text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractVerificationCode(text, strictChatGPTCodeOnly = false) {
|
||||
const strictCode = extractStrictChatGPTVerificationCode(text);
|
||||
if (strictChatGPTCodeOnly) {
|
||||
return strictCode;
|
||||
function extractVerificationCode(text, options = {}) {
|
||||
const legacyStrictMode = typeof options === 'boolean' ? options : false;
|
||||
const strictMode = legacyStrictMode || Boolean(options?.strictMode);
|
||||
const codePatterns = legacyStrictMode ? [] : options?.codePatterns;
|
||||
const strictCode = extractLegacyStrictVerificationCode(text);
|
||||
const matchedByRule = extractCodeByRulePatterns(text, codePatterns);
|
||||
if (strictMode) {
|
||||
return matchedByRule || strictCode;
|
||||
}
|
||||
if (matchedByRule) return matchedByRule;
|
||||
if (strictCode) return strictCode;
|
||||
|
||||
const normalized = String(text || '');
|
||||
@@ -754,11 +808,8 @@ function extractVerificationCode(text, strictChatGPTCodeOnly = false) {
|
||||
const matchCn = normalized.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||
if (matchCn) return matchCn[1];
|
||||
|
||||
const matchOpenAiLogin = normalized.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchOpenAiLogin) return matchOpenAiLogin[1];
|
||||
|
||||
const matchChatGPT = normalized.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
|
||||
if (matchChatGPT) return matchChatGPT[1];
|
||||
const matchLoginCode = normalized.match(/(?:log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchLoginCode) return matchLoginCode[1];
|
||||
|
||||
const matchEn = normalized.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||
if (matchEn) return matchEn[1] || matchEn[2];
|
||||
@@ -771,9 +822,9 @@ function extractEmails(text = '') {
|
||||
return [...new Set(matches.map((item) => item.toLowerCase()))];
|
||||
}
|
||||
|
||||
function extractForwardedTargetEmails(text = '') {
|
||||
function extractForwardedTargetEmails(text = '', targetEmailHints = []) {
|
||||
const normalizedText = String(text || '').toLowerCase();
|
||||
const matches = normalizedText.match(/bounce\+[a-z0-9._%+-]*-([a-z0-9._%+-]+)=([a-z0-9.-]+\.[a-z]{2,})@(?:tm\d*\.openai\.com|em\d+\.tm\.openai\.com)/gi) || [];
|
||||
const matches = normalizedText.match(/bounce\+[a-z0-9._%+-]*-([a-z0-9._%+-]+)=([a-z0-9.-]+\.[a-z]{2,})@[a-z0-9.-]+\.[a-z]{2,}/gi) || [];
|
||||
const decoded = matches
|
||||
.map((candidate) => {
|
||||
const match = String(candidate || '').match(/bounce\+[a-z0-9._%+-]*-([a-z0-9._%+-]+)=([a-z0-9.-]+\.[a-z]{2,})@/i);
|
||||
@@ -783,7 +834,19 @@ function extractForwardedTargetEmails(text = '') {
|
||||
return `${match[1].toLowerCase()}@${match[2].toLowerCase()}`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
return [...new Set(decoded)];
|
||||
const hinted = normalizeTargetEmailHints(targetEmailHints)
|
||||
.filter((hint) => hint.includes('@') || hint.includes('='))
|
||||
.flatMap((hint) => {
|
||||
if (hint.includes('@')) {
|
||||
return normalizedText.includes(hint) ? [hint] : [];
|
||||
}
|
||||
const match = hint.match(/^([^=]+)=([^=]+)$/);
|
||||
if (!match || !normalizedText.includes(hint)) {
|
||||
return [];
|
||||
}
|
||||
return [`${match[1]}@${match[2]}`];
|
||||
});
|
||||
return [...new Set([...decoded, ...hinted])];
|
||||
}
|
||||
|
||||
function emailMatchesTarget(candidate, targetEmail) {
|
||||
@@ -796,19 +859,20 @@ function emailMatchesTarget(candidate, targetEmail) {
|
||||
return normalizedCandidate === normalizedTarget;
|
||||
}
|
||||
|
||||
function getTargetEmailMatchState(text, targetEmail) {
|
||||
function getTargetEmailMatchState(text, targetEmail, options = {}) {
|
||||
const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
|
||||
if (!normalizedTarget) {
|
||||
return { matches: true, hasExplicitEmail: false };
|
||||
}
|
||||
|
||||
const normalizedText = String(text || '').toLowerCase();
|
||||
if (normalizedText.includes(normalizedTarget)) {
|
||||
const targetEmailHints = normalizeTargetEmailHints(options?.targetEmailHints, normalizedTarget);
|
||||
if (targetEmailHints.some((hint) => normalizedText.includes(hint))) {
|
||||
return { matches: true, hasExplicitEmail: true };
|
||||
}
|
||||
|
||||
const extractedEmails = extractEmails(normalizedText);
|
||||
const forwardedTargetEmails = extractForwardedTargetEmails(normalizedText);
|
||||
const forwardedTargetEmails = extractForwardedTargetEmails(normalizedText, targetEmailHints);
|
||||
if (!extractedEmails.length) {
|
||||
return forwardedTargetEmails.length
|
||||
? {
|
||||
@@ -1175,14 +1239,15 @@ async function ensureMail2925Session(payload = {}) {
|
||||
async function handlePollEmail(step, payload) {
|
||||
await ensureSeenCodesSession(step, payload);
|
||||
const {
|
||||
codePatterns = [],
|
||||
senderFilters,
|
||||
subjectFilters,
|
||||
maxAttempts,
|
||||
intervalMs,
|
||||
filterAfterTimestamp = 0,
|
||||
excludeCodes = [],
|
||||
strictChatGPTCodeOnly = false,
|
||||
targetEmail = '',
|
||||
targetEmailHints = [],
|
||||
mail2925MatchTargetEmail = false,
|
||||
} = payload || {};
|
||||
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
||||
@@ -1249,21 +1314,25 @@ async function handlePollEmail(step, payload) {
|
||||
continue;
|
||||
}
|
||||
const previewTargetState = mail2925MatchTargetEmail
|
||||
? getTargetEmailMatchState(previewText, targetEmail)
|
||||
? getTargetEmailMatchState(previewText, targetEmail, { targetEmailHints })
|
||||
: { matches: true, hasExplicitEmail: false };
|
||||
if (mail2925MatchTargetEmail && previewTargetState.hasExplicitEmail && !previewTargetState.matches) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const previewCode = extractVerificationCode(previewText, strictChatGPTCodeOnly);
|
||||
const previewCode = extractVerificationCode(previewText, {
|
||||
codePatterns,
|
||||
});
|
||||
const openedText = await openMailAndDeleteAfterRead(item, step);
|
||||
const openedTargetState = mail2925MatchTargetEmail
|
||||
? getTargetEmailMatchState(openedText, targetEmail)
|
||||
? getTargetEmailMatchState(openedText, targetEmail, { targetEmailHints })
|
||||
: { matches: true, hasExplicitEmail: false };
|
||||
if (mail2925MatchTargetEmail && openedTargetState.hasExplicitEmail && !openedTargetState.matches) {
|
||||
continue;
|
||||
}
|
||||
const bodyCode = extractVerificationCode(openedText, strictChatGPTCodeOnly);
|
||||
const bodyCode = extractVerificationCode(openedText, {
|
||||
codePatterns,
|
||||
});
|
||||
const candidateCode = bodyCode || previewCode;
|
||||
|
||||
if (!candidateCode) {
|
||||
|
||||
+50
-5
@@ -50,12 +50,52 @@ function getCurrentMailIds() {
|
||||
return ids;
|
||||
}
|
||||
|
||||
function normalizeRulePatternList(patterns = []) {
|
||||
return Array.isArray(patterns) ? patterns : [];
|
||||
}
|
||||
|
||||
function extractCodeByRulePatterns(text, patterns = []) {
|
||||
const normalizedText = String(text || '');
|
||||
for (const pattern of normalizeRulePatternList(patterns)) {
|
||||
try {
|
||||
const source = String(pattern?.source || '').trim();
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
const flags = String(pattern?.flags || '').replace(/[^dgimsuvy]/g, '');
|
||||
const match = normalizedText.match(new RegExp(source, flags));
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
for (let index = 1; index < match.length; index += 1) {
|
||||
const candidate = String(match[index] || '').trim();
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
if (String(match[0] || '').trim()) {
|
||||
return String(match[0] || '').trim();
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore invalid runtime rule patterns and continue with other candidates.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Email Polling
|
||||
// ============================================================
|
||||
|
||||
async function handlePollEmail(step, payload) {
|
||||
const { senderFilters, subjectFilters, maxAttempts, intervalMs, excludeCodes = [] } = payload;
|
||||
const {
|
||||
codePatterns = [],
|
||||
senderFilters,
|
||||
subjectFilters,
|
||||
maxAttempts,
|
||||
intervalMs,
|
||||
excludeCodes = [],
|
||||
} = payload;
|
||||
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
||||
|
||||
log(`步骤 ${step}:开始轮询邮箱(最多 ${maxAttempts} 次,每 ${intervalMs / 1000} 秒一次)`);
|
||||
@@ -103,7 +143,9 @@ async function handlePollEmail(step, payload) {
|
||||
const subjectMatch = subjectFilters.some(f => subject.includes(f.toLowerCase()));
|
||||
|
||||
if (senderMatch || subjectMatch) {
|
||||
const code = extractVerificationCode(subject + ' ' + digest);
|
||||
const code = extractVerificationCode(subject + ' ' + digest, {
|
||||
codePatterns,
|
||||
});
|
||||
if (code) {
|
||||
if (excludedCodeSet.has(code)) {
|
||||
log(`步骤 ${step}:跳过排除的验证码:${code}`, 'info');
|
||||
@@ -169,13 +211,16 @@ async function refreshInbox() {
|
||||
// Verification Code Extraction
|
||||
// ============================================================
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
function extractVerificationCode(text, options = {}) {
|
||||
const matchedByRule = extractCodeByRulePatterns(text, options?.codePatterns);
|
||||
if (matchedByRule) return matchedByRule;
|
||||
|
||||
// Pattern 1: Chinese format "代码为 370794" or "验证码...370794"
|
||||
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/);
|
||||
if (matchCn) return matchCn[1];
|
||||
|
||||
const matchOpenAiLogin = text.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchOpenAiLogin) return matchOpenAiLogin[1];
|
||||
const matchLoginCode = text.match(/(?:log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
|
||||
if (matchLoginCode) return matchLoginCode[1];
|
||||
|
||||
// Pattern 2: English format "code is 370794" or "code: 370794"
|
||||
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
|
||||
|
||||
Reference in New Issue
Block a user