feat: 添加对 Duck 生成的邮箱的比较基线支持,增强邮箱生成逻辑

This commit is contained in:
QLHazyCoder
2026-05-11 16:41:54 +08:00
parent c063561a11
commit 1b0f4bb174
4 changed files with 356 additions and 24 deletions
+93 -21
View File
@@ -6,9 +6,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type !== 'FETCH_DUCK_EMAIL') return;
resetStopState();
fetchDuckEmail(message.payload).then(result => {
fetchDuckEmail(message.payload).then((result) => {
sendResponse(result);
}).catch(err => {
}).catch((err) => {
if (isStopError(err)) {
log('Duck 邮箱:已被用户停止。', 'warn');
sendResponse({ stopped: true, error: err.message });
@@ -21,7 +21,10 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
});
async function fetchDuckEmail(payload = {}) {
const { generateNew = true } = payload;
const {
generateNew = true,
previousEmail = '',
} = payload;
log(`Duck 邮箱:正在${generateNew ? '生成' : '读取'}私有地址...`);
@@ -31,8 +34,30 @@ async function fetchDuckEmail(payload = {}) {
);
const GENERATE_BUTTON_PATTERN = /generate\s+private\s+duck\s+address|new\s+private\s+duck\s+address|generate\s+new|new\s+address|生成.*duck.*地址|生成.*私有.*地址|生成.*地址|新.*地址/i;
const DUCK_EMAIL_PATTERN = /([a-z0-9._%+-]+@duck\.com)/i;
const ADDRESS_VALUE_SELECTORS = [
'input.AutofillSettingsPanel__PrivateDuckAddressValue',
'input[class*="PrivateDuckAddressValue"]',
'input[data-testid*="PrivateDuckAddressValue"]',
'input[value*="@duck.com" i]',
];
const getAddressInput = () => document.querySelector('input.AutofillSettingsPanel__PrivateDuckAddressValue');
const normalizeDuckEmail = (value) => {
const match = String(value || '').trim().match(DUCK_EMAIL_PATTERN);
return match ? match[1].toLowerCase() : '';
};
const getAddressInputs = () => {
const seen = new Set();
return ADDRESS_VALUE_SELECTORS
.flatMap((selector) => Array.from(document.querySelectorAll(selector) || []))
.filter((element) => {
if (!element || seen.has(element)) {
return false;
}
seen.add(element);
return true;
});
};
const getGeneratorButton = () => {
const direct = document.querySelector('button.AutofillSettingsPanel__GeneratorButton');
if (direct) return direct;
@@ -45,7 +70,7 @@ async function fetchDuckEmail(payload = {}) {
'[role="button"]',
'button',
];
const candidates = selectors.flatMap((selector) => Array.from(document.querySelectorAll(selector)));
const candidates = selectors.flatMap((selector) => Array.from(document.querySelectorAll(selector) || []));
return candidates.find((btn) => {
const text = [
btn.textContent,
@@ -60,22 +85,57 @@ async function fetchDuckEmail(payload = {}) {
}) || null;
};
const readEmail = () => {
const value = getAddressInput()?.value?.trim() || '';
return value.includes('@duck.com') ? value : '';
for (const input of getAddressInputs()) {
const candidates = [
input?.value,
input?.getAttribute?.('value'),
input?.textContent,
input?.innerText,
input?.getAttribute?.('aria-label'),
input?.getAttribute?.('title'),
];
for (const candidate of candidates) {
const email = normalizeDuckEmail(candidate);
if (email) {
return email;
}
}
}
return '';
};
const waitForVisibleEmail = async (attemptLimit = 12) => {
for (let i = 0; i < attemptLimit; i++) {
const visibleEmail = readEmail();
if (visibleEmail) {
return visibleEmail;
}
await sleep(150);
}
return '';
};
const waitForEmailValue = async (previousValues = []) => {
const blockedValues = new Set(
(Array.isArray(previousValues) ? previousValues : [previousValues])
.map((value) => normalizeDuckEmail(value))
.filter(Boolean)
);
const waitForEmailValue = async (previousValue = '') => {
for (let i = 0; i < 100; i++) {
const nextValue = readEmail();
if (nextValue && nextValue !== previousValue) {
if (nextValue && !blockedValues.has(nextValue)) {
return nextValue;
}
await sleep(150);
}
throw new Error('等待 Duck 地址出现超时。');
throw new Error('等待 Duck 地址变化超时。');
};
const currentEmail = readEmail();
const fallbackPreviousEmail = normalizeDuckEmail(previousEmail);
let currentEmail = readEmail();
if (!currentEmail) {
currentEmail = await waitForVisibleEmail(generateNew ? 12 : 20);
}
if (currentEmail && !generateNew) {
log(`Duck 邮箱:已发现现有地址 ${currentEmail}`);
return { email: currentEmail, generated: false };
@@ -84,35 +144,47 @@ async function fetchDuckEmail(payload = {}) {
const generatorButton = getGeneratorButton();
if (!generatorButton) {
if (generateNew) {
throw new Error('未找到“生成新 Duck 地址”按钮(可能是页面文案/语言变化、未登录或页面结构更新)。');
throw new Error('未找到“生成新 Duck 地址”按钮(可能是页面结构、文案或登录状态发生变化)。');
}
if (currentEmail) {
log(`Duck 邮箱:未找到生成按钮,复用现有地址 ${currentEmail}`, 'warn');
return { email: currentEmail, generated: false };
}
throw new Error('未找到“生成 Duck 私有地址按钮。');
throw new Error('未找到 Duck 私有地址生成按钮。');
}
const comparisonEmails = [currentEmail, fallbackPreviousEmail].filter(Boolean);
if (!currentEmail && fallbackPreviousEmail) {
log(`Duck 邮箱:当前地址尚未显示,改用上次地址 ${fallbackPreviousEmail} 作为对比基线。`, 'warn');
}
for (let attempt = 1; attempt <= 2; attempt++) {
await humanPause(500, 1300);
await window.CodexOperationDelay.performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'click', label: 'duck-generate-address' }, async () => {
if (typeof simulateClick === 'function') {
simulateClick(generatorButton);
} else {
generatorButton.click();
await window.CodexOperationDelay.performOperationWithDelay(
{
stepKey: 'fetch-signup-code',
kind: 'click',
label: 'duck-generate-address',
},
async () => {
if (typeof simulateClick === 'function') {
simulateClick(generatorButton);
} else {
generatorButton.click();
}
}
});
);
log(`Duck 邮箱:已点击“生成 Duck 私有地址”按钮(${attempt}/2`);
try {
const nextEmail = await waitForEmailValue(currentEmail);
const nextEmail = await waitForEmailValue(comparisonEmails);
log(`Duck 邮箱:地址已就绪 ${nextEmail}`, 'ok');
return { email: nextEmail, generated: true };
} catch (err) {
if (attempt >= 2) {
throw err;
}
log('Duck 邮箱:首次生成后地址未变化,准备试一次...', 'warn');
log('Duck 邮箱:首次生成后地址未变化,准备试一次...', 'warn');
await sleep(800);
}
}