duck-mail.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. // content/duck-mail.js - Content script for DuckDuckGo Email Protection autofill settings
  2. console.log('[MultiPage:duck-mail] Content script loaded on', location.href);
  3. chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  4. if (message.type !== 'FETCH_DUCK_EMAIL') return;
  5. resetStopState();
  6. fetchDuckEmail(message.payload).then(result => {
  7. sendResponse(result);
  8. }).catch(err => {
  9. if (isStopError(err)) {
  10. log('Duck 邮箱:已被用户停止。', 'warn');
  11. sendResponse({ stopped: true, error: err.message });
  12. return;
  13. }
  14. sendResponse({ error: err.message });
  15. });
  16. return true;
  17. });
  18. async function fetchDuckEmail(payload = {}) {
  19. const { generateNew = true } = payload;
  20. log(`Duck 邮箱:正在${generateNew ? '生成' : '读取'}私有地址...`);
  21. await waitForElement(
  22. 'input.AutofillSettingsPanel__PrivateDuckAddressValue, button.AutofillSettingsPanel__GeneratorButton',
  23. 15000
  24. );
  25. 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;
  26. const getAddressInput = () => document.querySelector('input.AutofillSettingsPanel__PrivateDuckAddressValue');
  27. const getGeneratorButton = () => {
  28. const direct = document.querySelector('button.AutofillSettingsPanel__GeneratorButton');
  29. if (direct) return direct;
  30. const selectors = [
  31. 'button[data-testid*="Generator"]',
  32. 'button[class*="Generator"]',
  33. 'button[aria-label*="duck" i]',
  34. 'button[title*="duck" i]',
  35. '[role="button"]',
  36. 'button',
  37. ];
  38. const candidates = selectors.flatMap((selector) => Array.from(document.querySelectorAll(selector)));
  39. return candidates.find((btn) => {
  40. const text = [
  41. btn.textContent,
  42. btn.getAttribute?.('aria-label'),
  43. btn.getAttribute?.('title'),
  44. ]
  45. .filter(Boolean)
  46. .join(' ')
  47. .replace(/\s+/g, ' ')
  48. .trim();
  49. return GENERATE_BUTTON_PATTERN.test(text);
  50. }) || null;
  51. };
  52. const readEmail = () => {
  53. const value = getAddressInput()?.value?.trim() || '';
  54. return value.includes('@duck.com') ? value : '';
  55. };
  56. const waitForEmailValue = async (previousValue = '') => {
  57. for (let i = 0; i < 100; i++) {
  58. const nextValue = readEmail();
  59. if (nextValue && nextValue !== previousValue) {
  60. return nextValue;
  61. }
  62. await sleep(150);
  63. }
  64. throw new Error('等待 Duck 地址出现超时。');
  65. };
  66. const currentEmail = readEmail();
  67. if (currentEmail && !generateNew) {
  68. log(`Duck 邮箱:已发现现有地址 ${currentEmail}`);
  69. return { email: currentEmail, generated: false };
  70. }
  71. const generatorButton = getGeneratorButton();
  72. if (!generatorButton) {
  73. if (generateNew) {
  74. throw new Error('未找到“生成新 Duck 地址”按钮(可能是页面文案/语言变化、未登录或页面结构更新)。');
  75. }
  76. if (currentEmail) {
  77. log(`Duck 邮箱:未找到生成按钮,复用现有地址 ${currentEmail}`, 'warn');
  78. return { email: currentEmail, generated: false };
  79. }
  80. throw new Error('未找到“生成 Duck 私有地址”按钮。');
  81. }
  82. for (let attempt = 1; attempt <= 2; attempt++) {
  83. await humanPause(500, 1300);
  84. if (typeof simulateClick === 'function') {
  85. simulateClick(generatorButton);
  86. } else {
  87. generatorButton.click();
  88. }
  89. log(`Duck 邮箱:已点击“生成 Duck 私有地址”按钮(${attempt}/2)`);
  90. try {
  91. const nextEmail = await waitForEmailValue(currentEmail);
  92. log(`Duck 邮箱:地址已就绪 ${nextEmail}`, 'ok');
  93. return { email: nextEmail, generated: true };
  94. } catch (err) {
  95. if (attempt >= 2) {
  96. throw err;
  97. }
  98. log('Duck 邮箱:首次生成后地址未变化,准备重试一次...', 'warn');
  99. await sleep(800);
  100. }
  101. }
  102. throw new Error('Duck 地址生成失败。');
  103. }