Merge branch 'dev' of https://github.com/QLHazyCoder/codex-oauth-automation-extension into dev
This commit is contained in:
@@ -3113,6 +3113,16 @@ async function pollLuckmailVerificationCode(step, state, pollPayload = {}) {
|
||||
excludeCodes: pollPayload.excludeCodes || [],
|
||||
};
|
||||
|
||||
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++) {
|
||||
throwIfStopped();
|
||||
|
||||
@@ -1,14 +1,142 @@
|
||||
(function attachBackgroundStep1(root, factory) {
|
||||
root.MultiPageBackgroundStep1 = factory();
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep1Module() {
|
||||
const STEP1_COOKIE_CLEAR_DOMAINS = [
|
||||
'chatgpt.com',
|
||||
'chat.openai.com',
|
||||
'openai.com',
|
||||
'auth.openai.com',
|
||||
'auth0.openai.com',
|
||||
'accounts.openai.com',
|
||||
];
|
||||
const STEP1_COOKIE_CLEAR_ORIGINS = [
|
||||
'https://chatgpt.com',
|
||||
'https://chat.openai.com',
|
||||
'https://auth.openai.com',
|
||||
'https://auth0.openai.com',
|
||||
'https://accounts.openai.com',
|
||||
'https://openai.com',
|
||||
];
|
||||
|
||||
function normalizeCookieDomainForStep1(domain) {
|
||||
return String(domain || '').trim().replace(/^\.+/, '').toLowerCase();
|
||||
}
|
||||
|
||||
function shouldClearStep1Cookie(cookie) {
|
||||
const domain = normalizeCookieDomainForStep1(cookie?.domain);
|
||||
if (!domain) return false;
|
||||
return STEP1_COOKIE_CLEAR_DOMAINS.some((target) => (
|
||||
domain === target || domain.endsWith(`.${target}`)
|
||||
));
|
||||
}
|
||||
|
||||
function buildStep1CookieRemovalUrl(cookie) {
|
||||
const host = normalizeCookieDomainForStep1(cookie?.domain);
|
||||
const rawPath = String(cookie?.path || '/');
|
||||
const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`;
|
||||
return `https://${host}${path}`;
|
||||
}
|
||||
|
||||
function getStep1ErrorMessage(error) {
|
||||
return error?.message || String(error || '未知错误');
|
||||
}
|
||||
|
||||
async function collectStep1Cookies(chromeApi) {
|
||||
if (!chromeApi.cookies?.getAll) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const stores = chromeApi.cookies.getAllCookieStores
|
||||
? await chromeApi.cookies.getAllCookieStores()
|
||||
: [{ id: undefined }];
|
||||
const cookies = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const store of stores) {
|
||||
const storeId = store?.id;
|
||||
const batch = await chromeApi.cookies.getAll(storeId ? { storeId } : {});
|
||||
for (const cookie of batch || []) {
|
||||
if (!shouldClearStep1Cookie(cookie)) continue;
|
||||
const key = [
|
||||
cookie.storeId || storeId || '',
|
||||
cookie.domain || '',
|
||||
cookie.path || '',
|
||||
cookie.name || '',
|
||||
cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : '',
|
||||
].join('|');
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
cookies.push(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
async function removeStep1Cookie(chromeApi, cookie) {
|
||||
const details = {
|
||||
url: buildStep1CookieRemovalUrl(cookie),
|
||||
name: cookie.name,
|
||||
};
|
||||
if (cookie.storeId) {
|
||||
details.storeId = cookie.storeId;
|
||||
}
|
||||
if (cookie.partitionKey) {
|
||||
details.partitionKey = cookie.partitionKey;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await chromeApi.cookies.remove(details);
|
||||
return Boolean(result);
|
||||
} catch (error) {
|
||||
console.warn('[MultiPage:step1] remove cookie failed', {
|
||||
domain: cookie?.domain,
|
||||
name: cookie?.name,
|
||||
message: getStep1ErrorMessage(error),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function createStep1Executor(deps = {}) {
|
||||
const {
|
||||
addLog,
|
||||
chrome: chromeApi = globalThis.chrome,
|
||||
completeStepFromBackground,
|
||||
openSignupEntryTab,
|
||||
} = deps;
|
||||
|
||||
async function clearOpenAiCookiesBeforeStep1() {
|
||||
if (!chromeApi?.cookies?.getAll || !chromeApi.cookies?.remove) {
|
||||
await addLog('步骤 1:当前浏览器不支持 cookies API,跳过打开官网前 cookie 清理。', 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
await addLog('步骤 1:打开 ChatGPT 官网前清理 ChatGPT / OpenAI cookies...', 'info');
|
||||
const cookies = await collectStep1Cookies(chromeApi);
|
||||
let removedCount = 0;
|
||||
for (const cookie of cookies) {
|
||||
if (await removeStep1Cookie(chromeApi, cookie)) {
|
||||
removedCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (chromeApi.browsingData?.removeCookies) {
|
||||
try {
|
||||
await chromeApi.browsingData.removeCookies({
|
||||
since: 0,
|
||||
origins: STEP1_COOKIE_CLEAR_ORIGINS,
|
||||
});
|
||||
} catch (error) {
|
||||
await addLog(`步骤 1:browsingData 补扫 cookies 失败:${getStep1ErrorMessage(error)}`, 'warn');
|
||||
}
|
||||
}
|
||||
|
||||
await addLog(`步骤 1:已清理 ${removedCount} 个 ChatGPT / OpenAI cookies。`, 'ok');
|
||||
}
|
||||
|
||||
async function executeStep1() {
|
||||
await clearOpenAiCookiesBeforeStep1();
|
||||
await addLog('步骤 1:正在打开 ChatGPT 官网...');
|
||||
await openSignupEntryTab(1);
|
||||
await completeStepFromBackground(1, {});
|
||||
|
||||
@@ -291,6 +291,152 @@ return {
|
||||
assert.equal(snapshot.buildCalls.length, 1);
|
||||
});
|
||||
|
||||
test('pollLuckmailVerificationCode snapshots existing mails before accepting new LuckMail code', async () => {
|
||||
const bundle = extractFunction('pollLuckmailVerificationCode');
|
||||
|
||||
const factory = new Function(`
|
||||
let currentState = {
|
||||
currentLuckmailPurchase: {
|
||||
id: 7,
|
||||
email_address: 'luck@example.com',
|
||||
token: 'tok-luck',
|
||||
},
|
||||
currentLuckmailMailCursor: null,
|
||||
};
|
||||
const logs = [];
|
||||
const cursorWrites = [];
|
||||
let tokenCodeCalls = 0;
|
||||
|
||||
function getCurrentLuckmailPurchase(state) {
|
||||
return state.currentLuckmailPurchase;
|
||||
}
|
||||
function createLuckmailClient() {
|
||||
return {
|
||||
user: {
|
||||
async getTokenMails() {
|
||||
if (tokenCodeCalls === 0) {
|
||||
return {
|
||||
mails: [
|
||||
{ message_id: 'old-mail', received_at: '2026-04-14 13:31:15', verification_code: '111111' },
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
mails: [
|
||||
{ message_id: 'new-mail', received_at: '2026-04-14 13:32:05', verification_code: '222222' },
|
||||
{ message_id: 'old-mail', received_at: '2026-04-14 13:31:15', verification_code: '111111' },
|
||||
],
|
||||
};
|
||||
},
|
||||
async getTokenCode() {
|
||||
tokenCodeCalls += 1;
|
||||
return tokenCodeCalls === 1
|
||||
? {
|
||||
has_new_mail: true,
|
||||
verification_code: '111111',
|
||||
mail: { message_id: 'old-mail', received_at: '2026-04-14 13:31:15', verification_code: '111111' },
|
||||
}
|
||||
: {
|
||||
has_new_mail: true,
|
||||
verification_code: '222222',
|
||||
mail: { message_id: 'new-mail', received_at: '2026-04-14 13:32:05', verification_code: '222222' },
|
||||
};
|
||||
},
|
||||
async getTokenMailDetail(_token, messageId) {
|
||||
return { message_id: messageId, received_at: '2026-04-14 13:32:05', verification_code: '222222' };
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
async function getState() {
|
||||
return currentState;
|
||||
}
|
||||
async function setLuckmailMailCursorState(cursor) {
|
||||
currentState = { ...currentState, currentLuckmailMailCursor: cursor };
|
||||
cursorWrites.push(cursor);
|
||||
}
|
||||
function normalizeLuckmailMailCursor(cursor) {
|
||||
return {
|
||||
messageId: cursor?.messageId || cursor?.message_id || '',
|
||||
receivedAt: cursor?.receivedAt || cursor?.received_at || '',
|
||||
};
|
||||
}
|
||||
function normalizeLuckmailTimestamp(value) {
|
||||
return Date.parse(String(value || '').replace(' ', 'T') + 'Z') || 0;
|
||||
}
|
||||
function buildLuckmailMailCursor(mail) {
|
||||
return { messageId: mail.message_id || '', receivedAt: mail.received_at || '' };
|
||||
}
|
||||
function buildLuckmailBaselineCursor(mails) {
|
||||
const latest = mails[0] || null;
|
||||
return latest ? buildLuckmailMailCursor(latest) : null;
|
||||
}
|
||||
function isLuckmailMailNewerThanCursor(mail, cursor) {
|
||||
if (!cursor?.messageId && !cursor?.receivedAt) return true;
|
||||
if (mail.message_id === cursor.messageId) return false;
|
||||
return normalizeLuckmailTimestamp(mail.received_at) > normalizeLuckmailTimestamp(cursor.receivedAt);
|
||||
}
|
||||
function pickLuckmailVerificationMail(mails, filters) {
|
||||
const excludeCodes = new Set(filters.excludeCodes || []);
|
||||
for (const mail of mails || []) {
|
||||
if (!mail.verification_code || excludeCodes.has(mail.verification_code)) continue;
|
||||
return { mail, code: mail.verification_code };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function normalizeLuckmailTokenCode(result) {
|
||||
return result;
|
||||
}
|
||||
async function resolveLuckmailVerificationMail(client, token, filters, tokenCodeResult) {
|
||||
if (tokenCodeResult?.mail) {
|
||||
const inline = pickLuckmailVerificationMail([tokenCodeResult.mail], filters);
|
||||
if (inline) return inline;
|
||||
}
|
||||
const mailList = await client.user.getTokenMails(token);
|
||||
return pickLuckmailVerificationMail(mailList.mails, filters);
|
||||
}
|
||||
async function addLog(message, level) {
|
||||
logs.push({ message, level });
|
||||
}
|
||||
function throwIfStopped() {}
|
||||
function isStopError() {
|
||||
return false;
|
||||
}
|
||||
async function sleepWithStop() {}
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
pollLuckmailVerificationCode,
|
||||
snapshot() {
|
||||
return { currentState, cursorWrites, logs, tokenCodeCalls };
|
||||
},
|
||||
};
|
||||
`);
|
||||
|
||||
const api = factory();
|
||||
const result = await api.pollLuckmailVerificationCode(4, await api.snapshot().currentState, {
|
||||
maxAttempts: 2,
|
||||
intervalMs: 1000,
|
||||
senderFilters: ['openai'],
|
||||
subjectFilters: ['code'],
|
||||
excludeCodes: [],
|
||||
});
|
||||
|
||||
assert.equal(result.code, '222222');
|
||||
const snapshot = api.snapshot();
|
||||
assert.deepStrictEqual(snapshot.cursorWrites[0], {
|
||||
messageId: 'old-mail',
|
||||
receivedAt: '2026-04-14 13:31:15',
|
||||
});
|
||||
assert.deepStrictEqual(snapshot.cursorWrites.at(-1), {
|
||||
messageId: 'new-mail',
|
||||
receivedAt: '2026-04-14 13:32:05',
|
||||
});
|
||||
assert.equal(snapshot.logs.some((entry) => /已保存当前邮箱旧邮件快照/.test(entry.message)), true);
|
||||
assert.equal(snapshot.tokenCodeCalls, 2);
|
||||
});
|
||||
|
||||
test('listLuckmailPurchasesByProject only keeps openai purchases', async () => {
|
||||
const bundle = extractFunction('listLuckmailPurchasesByProject');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user