207 lines
6.4 KiB
JavaScript
207 lines
6.4 KiB
JavaScript
// content/duck-mail.js - Content script for DuckDuckGo Email Protection autofill settings
|
||
|
||
console.log('[MultiPage:duck-mail] Content script loaded on', location.href);
|
||
|
||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||
if (message.type !== 'FETCH_DUCK_EMAIL') return;
|
||
|
||
resetStopState();
|
||
fetchDuckEmail(message.payload).then((result) => {
|
||
sendResponse(result);
|
||
}).catch((err) => {
|
||
if (isStopError(err)) {
|
||
log('Duck 邮箱:已被用户停止。', 'warn');
|
||
sendResponse({ stopped: true, error: err.message });
|
||
return;
|
||
}
|
||
sendResponse({ error: err.message });
|
||
});
|
||
|
||
return true;
|
||
});
|
||
|
||
async function fetchDuckEmail(payload = {}) {
|
||
const {
|
||
generateNew = true,
|
||
baselineEmail = '',
|
||
} = payload;
|
||
|
||
log(`Duck 邮箱:正在${generateNew ? '生成' : '读取'}私有地址...`);
|
||
|
||
await waitForElement(
|
||
'input.AutofillSettingsPanel__PrivateDuckAddressValue, button.AutofillSettingsPanel__GeneratorButton',
|
||
15000
|
||
);
|
||
|
||
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 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;
|
||
|
||
const selectors = [
|
||
'button[data-testid*="Generator"]',
|
||
'button[class*="Generator"]',
|
||
'button[aria-label*="duck" i]',
|
||
'button[title*="duck" i]',
|
||
'[role="button"]',
|
||
'button',
|
||
];
|
||
const candidates = selectors.flatMap((selector) => Array.from(document.querySelectorAll(selector) || []));
|
||
return candidates.find((btn) => {
|
||
const text = [
|
||
btn.textContent,
|
||
btn.getAttribute?.('aria-label'),
|
||
btn.getAttribute?.('title'),
|
||
]
|
||
.filter(Boolean)
|
||
.join(' ')
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
return GENERATE_BUTTON_PATTERN.test(text);
|
||
}) || null;
|
||
};
|
||
const readEmail = () => {
|
||
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)
|
||
);
|
||
let stableCandidate = '';
|
||
let stableCount = 0;
|
||
|
||
for (let i = 0; i < 100; i++) {
|
||
const nextValue = readEmail();
|
||
if (nextValue && !blockedValues.has(nextValue)) {
|
||
if (nextValue === stableCandidate) {
|
||
stableCount += 1;
|
||
} else {
|
||
stableCandidate = nextValue;
|
||
stableCount = 1;
|
||
}
|
||
if (stableCount >= 2) {
|
||
return nextValue;
|
||
}
|
||
} else {
|
||
stableCandidate = '';
|
||
stableCount = 0;
|
||
}
|
||
await sleep(150);
|
||
}
|
||
throw new Error('等待 Duck 地址变化超时。');
|
||
};
|
||
|
||
const knownBaselineEmail = normalizeDuckEmail(baselineEmail);
|
||
let currentEmail = readEmail();
|
||
if (!currentEmail) {
|
||
currentEmail = await waitForVisibleEmail(generateNew ? (knownBaselineEmail ? 12 : 30) : 20);
|
||
}
|
||
|
||
if (currentEmail && !generateNew) {
|
||
log(`Duck 邮箱:已发现现有地址 ${currentEmail}`);
|
||
return { email: currentEmail, generated: false };
|
||
}
|
||
|
||
const generatorButton = getGeneratorButton();
|
||
if (!generatorButton) {
|
||
if (generateNew) {
|
||
throw new Error('未找到“生成新 Duck 地址”按钮(可能是页面结构、文案或登录状态发生变化)。');
|
||
}
|
||
if (currentEmail) {
|
||
log(`Duck 邮箱:未找到生成按钮,复用现有地址 ${currentEmail}`, 'warn');
|
||
return { email: currentEmail, generated: false };
|
||
}
|
||
throw new Error('未找到 Duck 私有地址生成按钮。');
|
||
}
|
||
|
||
const comparisonEmails = [currentEmail, knownBaselineEmail].filter(Boolean);
|
||
if (!currentEmail && knownBaselineEmail) {
|
||
log(`Duck 邮箱:当前地址尚未显示,改用已知基线 ${knownBaselineEmail} 作为对比基线。`, '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();
|
||
}
|
||
}
|
||
);
|
||
log(`Duck 邮箱:已点击“生成 Duck 私有地址”按钮(${attempt}/2)`);
|
||
|
||
try {
|
||
const nextEmail = await waitForEmailValue(comparisonEmails);
|
||
log(`Duck 邮箱:地址已就绪 ${nextEmail}`, 'ok');
|
||
return { email: nextEmail, generated: true };
|
||
} catch (err) {
|
||
if (attempt >= 2) {
|
||
throw err;
|
||
}
|
||
log('Duck 邮箱:首次生成后地址未变化,准备再试一次...', 'warn');
|
||
await sleep(800);
|
||
}
|
||
}
|
||
|
||
throw new Error('Duck 地址生成失败。');
|
||
}
|