Merge branch 'dev' of https://github.com/QLHazyCoder/codex-oauth-automation-extension into dev
This commit is contained in:
+78
-9
@@ -3509,21 +3509,16 @@ async function resolveLuckmailVerificationMail(client, token, filters = {}, toke
|
|||||||
return match || null;
|
return match || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pollLuckmailVerificationCode(step, state, pollPayload = {}) {
|
async function legacyPollLuckmailVerificationCode(step, state, pollPayload = {}) {
|
||||||
const purchase = getCurrentLuckmailPurchase(state);
|
const purchase = getCurrentLuckmailPurchase(state);
|
||||||
if (!purchase?.token) {
|
if (!purchase?.token) {
|
||||||
throw new Error('LuckMail 当前没有可用 token,请先执行步骤 3 购买邮箱。');
|
throw new Error('LuckMail 当前没有可用 token,请先执行步骤 3 购买邮箱。');
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = createLuckmailClient(state);
|
const client = createLuckmailClient(state);
|
||||||
const maxAttempts = Math.max(1, Number(pollPayload.maxAttempts) || 5);
|
const maxAttempts = Math.max(1, Number(pollPayload.maxAttempts) || 3);
|
||||||
const intervalMs = Math.max(1000, Number(pollPayload.intervalMs) || 3000);
|
const intervalMs = Math.max(15000, Number(pollPayload.intervalMs) || 15000);
|
||||||
const filters = {
|
const excludedCodes = new Set((pollPayload.excludeCodes || []).filter(Boolean));
|
||||||
afterTimestamp: pollPayload.filterAfterTimestamp || 0,
|
|
||||||
senderFilters: pollPayload.senderFilters || [],
|
|
||||||
subjectFilters: pollPayload.subjectFilters || [],
|
|
||||||
excludeCodes: pollPayload.excludeCodes || [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialCursor = normalizeLuckmailMailCursor((await getState()).currentLuckmailMailCursor);
|
const initialCursor = normalizeLuckmailMailCursor((await getState()).currentLuckmailMailCursor);
|
||||||
if (!initialCursor.messageId && !initialCursor.receivedAt) {
|
if (!initialCursor.messageId && !initialCursor.receivedAt) {
|
||||||
@@ -3587,6 +3582,80 @@ async function pollLuckmailVerificationCode(step, state, pollPayload = {}) {
|
|||||||
throw lastError || new Error(`步骤 ${step}:未在 LuckMail 邮箱中找到新的匹配验证码。`);
|
throw lastError || new Error(`步骤 ${step}:未在 LuckMail 邮箱中找到新的匹配验证码。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function pollLuckmailVerificationCode(step, state, pollPayload = {}) {
|
||||||
|
const purchase = getCurrentLuckmailPurchase(state);
|
||||||
|
if (!purchase?.token) {
|
||||||
|
throw new Error('LuckMail 当前没有可用 token,请先执行步骤 3 购买邮箱。');
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = createLuckmailClient(state);
|
||||||
|
const maxAttempts = Math.max(1, Number(pollPayload.maxAttempts) || 3);
|
||||||
|
const intervalMs = Math.max(15000, Number(pollPayload.intervalMs) || 15000);
|
||||||
|
const excludedCodes = new Set((pollPayload.excludeCodes || []).filter(Boolean));
|
||||||
|
|
||||||
|
const initialCursor = normalizeLuckmailMailCursor((await getState()).currentLuckmailMailCursor);
|
||||||
|
if (!initialCursor.messageId && !initialCursor.receivedAt) {
|
||||||
|
const mailList = await client.user.getTokenMails(purchase.token);
|
||||||
|
const baselineCursor = buildLuckmailBaselineCursor(mailList?.mails || []);
|
||||||
|
await setLuckmailMailCursorState(baselineCursor);
|
||||||
|
if (baselineCursor?.messageId || baselineCursor?.receivedAt) {
|
||||||
|
await addLog(`步骤 ${step}:LuckMail 已保存当前邮箱旧邮件快照,后续仅使用新收到的验证码。`, 'info');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastError = null;
|
||||||
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
|
throwIfStopped();
|
||||||
|
await addLog(`步骤 ${step}:正在通过 LuckMail /code 接口轮询验证码(${attempt}/${maxAttempts})...`, 'info');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tokenCode = await client.user.getTokenCode(purchase.token);
|
||||||
|
const remoteEmail = String(tokenCode?.email_address || '').trim().toLowerCase();
|
||||||
|
const expectedEmail = String(purchase.email_address || state?.email || '').trim().toLowerCase();
|
||||||
|
if (remoteEmail && expectedEmail && remoteEmail !== expectedEmail) {
|
||||||
|
throw new Error(`步骤 ${step}:LuckMail token 对应邮箱与当前邮箱不一致。当前邮箱:${expectedEmail};token 邮箱:${remoteEmail}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenMail = tokenCode.verification_code && tokenCode.mail && !tokenCode.mail.verification_code
|
||||||
|
? {
|
||||||
|
...tokenCode.mail,
|
||||||
|
verification_code: tokenCode.verification_code,
|
||||||
|
}
|
||||||
|
: tokenCode.mail;
|
||||||
|
const code = String(tokenCode?.verification_code || tokenMail?.verification_code || '').trim();
|
||||||
|
const cursor = normalizeLuckmailMailCursor((await getState()).currentLuckmailMailCursor);
|
||||||
|
|
||||||
|
if (!code || !tokenMail) {
|
||||||
|
lastError = new Error(`步骤 ${step}:LuckMail /code 接口暂未返回新的验证码。`);
|
||||||
|
} else if (excludedCodes.has(code)) {
|
||||||
|
lastError = new Error(`步骤 ${step}:LuckMail 返回的验证码 ${code} 已试过,等待 15 秒后再次轮询。`);
|
||||||
|
} else if (!isLuckmailMailNewerThanCursor(tokenMail, cursor)) {
|
||||||
|
lastError = new Error(`步骤 ${step}:LuckMail /code 返回的最新邮件仍是旧验证码。`);
|
||||||
|
} else {
|
||||||
|
await setLuckmailMailCursorState(buildLuckmailMailCursor(tokenMail));
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
code,
|
||||||
|
emailTimestamp: normalizeLuckmailTimestamp(tokenMail.received_at) || Date.now(),
|
||||||
|
mailId: tokenMail.message_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (isStopError(err)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
lastError = err;
|
||||||
|
await addLog(`步骤 ${step}:LuckMail /code 轮询失败:${err.message}`, 'warn');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt < maxAttempts) {
|
||||||
|
await sleepWithStop(intervalMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError || new Error(`步骤 ${step}:未在 LuckMail /code 接口中获取到新的验证码。`);
|
||||||
|
}
|
||||||
|
|
||||||
function summarizeCloudflareTempEmailMessagesForLog(messages) {
|
function summarizeCloudflareTempEmailMessagesForLog(messages) {
|
||||||
return (messages || [])
|
return (messages || [])
|
||||||
.slice()
|
.slice()
|
||||||
|
|||||||
@@ -537,6 +537,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof ensureContentScriptReadyOnTab === 'function') {
|
||||||
|
await ensureContentScriptReadyOnTab(MAIL2925_SOURCE, tabId, {
|
||||||
|
inject: MAIL2925_INJECT,
|
||||||
|
injectSource: MAIL2925_INJECT_SOURCE,
|
||||||
|
timeoutMs: 20000,
|
||||||
|
retryDelayMs: 800,
|
||||||
|
logMessage: '步骤 0:2925 登录页内容脚本未就绪,正在等待页面稳定后继续登录...',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!forceRelogin && !isMail2925LoginUrl(openedUrl) && !normalizedExpectedMailboxEmail) {
|
if (!forceRelogin && !isMail2925LoginUrl(openedUrl) && !normalizedExpectedMailboxEmail) {
|
||||||
await addLog('2925:当前邮箱页未跳转到登录页,将直接复用已登录会话。', 'info');
|
await addLog('2925:当前邮箱页未跳转到登录页,将直接复用已登录会话。', 'info');
|
||||||
return buildSuccessPayload();
|
return buildSuccessPayload();
|
||||||
@@ -553,16 +563,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof ensureContentScriptReadyOnTab === 'function') {
|
|
||||||
await ensureContentScriptReadyOnTab(MAIL2925_SOURCE, tabId, {
|
|
||||||
inject: MAIL2925_INJECT,
|
|
||||||
injectSource: MAIL2925_INJECT_SOURCE,
|
|
||||||
timeoutMs: 20000,
|
|
||||||
retryDelayMs: 800,
|
|
||||||
logMessage: '步骤 0:2925 登录页内容脚本未就绪,正在等待页面稳定后继续登录...',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceRelogin && typeof sleepWithStop === 'function') {
|
if (forceRelogin && typeof sleepWithStop === 'function') {
|
||||||
await addLog('2925:登录页已打开,等待 3 秒后开始检查输入框并执行登录...', 'info');
|
await addLog('2925:登录页已打开,等待 3 秒后开始检查输入框并执行登录...', 'info');
|
||||||
await sleepWithStop(3000);
|
await sleepWithStop(3000);
|
||||||
|
|||||||
@@ -268,9 +268,11 @@
|
|||||||
getRemainingTimeMs: getStep8RemainingTimeResolver(state?.oauthUrl || '', visibleStep),
|
getRemainingTimeMs: getStep8RemainingTimeResolver(state?.oauthUrl || '', visibleStep),
|
||||||
requestFreshCodeFirst: false,
|
requestFreshCodeFirst: false,
|
||||||
targetEmail: fixedTargetEmail,
|
targetEmail: fixedTargetEmail,
|
||||||
resendIntervalMs: (mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
|
resendIntervalMs: mail.provider === LUCKMAIL_PROVIDER
|
||||||
|
? 15000
|
||||||
|
: ((mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
|
||||||
? 0
|
? 0
|
||||||
: STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
: STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@
|
|||||||
|
|
||||||
const shouldRequestFreshCodeFirst = ![
|
const shouldRequestFreshCodeFirst = ![
|
||||||
HOTMAIL_PROVIDER,
|
HOTMAIL_PROVIDER,
|
||||||
|
LUCKMAIL_PROVIDER,
|
||||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
|
||||||
].includes(mail.provider);
|
].includes(mail.provider);
|
||||||
|
|
||||||
@@ -157,9 +158,11 @@
|
|||||||
sessionKey: verificationSessionKey,
|
sessionKey: verificationSessionKey,
|
||||||
disableTimeBudgetCap: mail.provider === '2925',
|
disableTimeBudgetCap: mail.provider === '2925',
|
||||||
requestFreshCodeFirst: shouldRequestFreshCodeFirst,
|
requestFreshCodeFirst: shouldRequestFreshCodeFirst,
|
||||||
resendIntervalMs: (mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
|
resendIntervalMs: mail.provider === LUCKMAIL_PROVIDER
|
||||||
|
? 15000
|
||||||
|
: ((mail.provider === HOTMAIL_PROVIDER || mail.provider === '2925')
|
||||||
? 0
|
? 0
|
||||||
: STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
: STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -917,7 +917,7 @@
|
|||||||
getLegacyVerificationResendCountDefault(step, { requestFreshCodeFirst })
|
getLegacyVerificationResendCountDefault(step, { requestFreshCodeFirst })
|
||||||
)
|
)
|
||||||
: getConfiguredVerificationResendCount(step, state, { requestFreshCodeFirst });
|
: getConfiguredVerificationResendCount(step, state, { requestFreshCodeFirst });
|
||||||
const maxSubmitAttempts = 15;
|
const maxSubmitAttempts = mail.provider === LUCKMAIL_PROVIDER ? 3 : 15;
|
||||||
const resendIntervalMs = Math.max(0, Number(options.resendIntervalMs) || 0);
|
const resendIntervalMs = Math.max(0, Number(options.resendIntervalMs) || 0);
|
||||||
let lastResendAt = Number(options.lastResendAt) || 0;
|
let lastResendAt = Number(options.lastResendAt) || 0;
|
||||||
|
|
||||||
@@ -1002,6 +1002,12 @@
|
|||||||
throw new Error(`步骤 ${step}:验证码连续失败,已达到 ${maxSubmitAttempts} 次重试上限。`);
|
throw new Error(`步骤 ${step}:验证码连续失败,已达到 ${maxSubmitAttempts} 次重试上限。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mail.provider === LUCKMAIL_PROVIDER) {
|
||||||
|
await addLog(`步骤 ${step}:LuckMail 验证码提交失败,等待 15 秒后重新轮询 /code 接口(${attempt + 1}/${maxSubmitAttempts})...`, 'warn');
|
||||||
|
await sleepWithStop(15000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const remainingBeforeResendMs = resendIntervalMs > 0 && lastResendAt > 0
|
const remainingBeforeResendMs = resendIntervalMs > 0 && lastResendAt > 0
|
||||||
? Math.max(0, resendIntervalMs - (Date.now() - lastResendAt))
|
? Math.max(0, resendIntervalMs - (Date.now() - lastResendAt))
|
||||||
: 0;
|
: 0;
|
||||||
|
|||||||
+50
-2
@@ -699,10 +699,29 @@ function extractEmails(text = '') {
|
|||||||
return [...new Set(matches.map((item) => item.toLowerCase()))];
|
return [...new Set(matches.map((item) => item.toLowerCase()))];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractForwardedTargetEmails(text = '') {
|
||||||
|
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 decoded = matches
|
||||||
|
.map((candidate) => {
|
||||||
|
const match = String(candidate || '').match(/bounce\+[a-z0-9._%+-]*-([a-z0-9._%+-]+)=([a-z0-9.-]+\.[a-z]{2,})@/i);
|
||||||
|
if (!match) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `${match[1].toLowerCase()}@${match[2].toLowerCase()}`;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
return [...new Set(decoded)];
|
||||||
|
}
|
||||||
|
|
||||||
function emailMatchesTarget(candidate, targetEmail) {
|
function emailMatchesTarget(candidate, targetEmail) {
|
||||||
const normalizedCandidate = String(candidate || '').trim().toLowerCase();
|
const normalizedCandidate = String(candidate || '').trim().toLowerCase();
|
||||||
const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
|
const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
|
||||||
return Boolean(normalizedCandidate && normalizedTarget && normalizedCandidate === normalizedTarget);
|
if (!normalizedCandidate || !normalizedTarget) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedCandidate === normalizedTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTargetEmailMatchState(text, targetEmail) {
|
function getTargetEmailMatchState(text, targetEmail) {
|
||||||
@@ -717,12 +736,30 @@ function getTargetEmailMatchState(text, targetEmail) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const extractedEmails = extractEmails(normalizedText);
|
const extractedEmails = extractEmails(normalizedText);
|
||||||
|
const forwardedTargetEmails = extractForwardedTargetEmails(normalizedText);
|
||||||
if (!extractedEmails.length) {
|
if (!extractedEmails.length) {
|
||||||
|
return forwardedTargetEmails.length
|
||||||
|
? {
|
||||||
|
matches: forwardedTargetEmails.some((candidate) => emailMatchesTarget(candidate, normalizedTarget)),
|
||||||
|
hasExplicitEmail: true,
|
||||||
|
}
|
||||||
|
: { matches: true, hasExplicitEmail: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetDomain = normalizedTarget.includes('@')
|
||||||
|
? normalizedTarget.split('@').pop()
|
||||||
|
: '';
|
||||||
|
const comparableEmails = [...new Set(
|
||||||
|
(targetDomain
|
||||||
|
? [...extractedEmails, ...forwardedTargetEmails].filter((candidate) => String(candidate || '').trim().toLowerCase().endsWith(`@${targetDomain}`))
|
||||||
|
: [...extractedEmails, ...forwardedTargetEmails])
|
||||||
|
)];
|
||||||
|
if (!comparableEmails.length) {
|
||||||
return { matches: true, hasExplicitEmail: false };
|
return { matches: true, hasExplicitEmail: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
matches: extractedEmails.some((candidate) => emailMatchesTarget(candidate, normalizedTarget)),
|
matches: comparableEmails.some((candidate) => emailMatchesTarget(candidate, normalizedTarget)),
|
||||||
hasExplicitEmail: true,
|
hasExplicitEmail: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -782,6 +819,17 @@ function parseMailItemTimestamp(item) {
|
|||||||
return date.getTime();
|
return date.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match = timeText.match(/(\d{1,2})月(\d{1,2})日(?:\s*(\d{1,2}):(\d{2}))?/);
|
||||||
|
if (match) {
|
||||||
|
date.setMonth(Number(match[1]) - 1, Number(match[2]));
|
||||||
|
if (match[3] && match[4]) {
|
||||||
|
date.setHours(Number(match[3]), Number(match[4]), 0, 0);
|
||||||
|
} else {
|
||||||
|
date.setHours(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
return date.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
match = timeText.match(/(\d{4})-(\d{1,2})-(\d{1,2})\s*(\d{1,2}):(\d{2})/);
|
match = timeText.match(/(\d{4})-(\d{1,2})-(\d{1,2})\s*(\d{1,2}):(\d{2})/);
|
||||||
if (match) {
|
if (match) {
|
||||||
return new Date(
|
return new Date(
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ test('ensureMail2925MailboxSession reuses current mailbox page without sending l
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(sendCalls, 0);
|
assert.equal(sendCalls, 0);
|
||||||
assert.equal(readyCalls, 0);
|
assert.equal(readyCalls, 1);
|
||||||
assert.equal(result.result.usedExistingSession, true);
|
assert.equal(result.result.usedExistingSession, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ return {
|
|||||||
test('handlePollEmail skips explicit mismatched target emails when receive-mode matching is enabled', async () => {
|
test('handlePollEmail skips explicit mismatched target emails when receive-mode matching is enabled', async () => {
|
||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('extractEmails'),
|
extractFunction('extractEmails'),
|
||||||
|
extractFunction('extractForwardedTargetEmails'),
|
||||||
extractFunction('emailMatchesTarget'),
|
extractFunction('emailMatchesTarget'),
|
||||||
extractFunction('getTargetEmailMatchState'),
|
extractFunction('getTargetEmailMatchState'),
|
||||||
extractFunction('normalizeMinuteTimestamp'),
|
extractFunction('normalizeMinuteTimestamp'),
|
||||||
|
|||||||
Reference in New Issue
Block a user