// content/mail-163.js — Content script for 163 Mail (steps 4, 7) // Injected on: mail.163.com // // DOM structure: // Mail item: div[sign="letter"] with aria-label="你的 ChatGPT 代码为 479637 发件人 : OpenAI ..." // Sender: .nui-user (e.g., "OpenAI") // Subject: span.da0 (e.g., "你的 ChatGPT 代码为 479637") // Right-click menu: .nui-menu → .nui-menu-item with text "删除邮件" const MAIL163_PREFIX = '[MultiPage:mail-163]'; const isTopFrame = window === window.top; console.log(MAIL163_PREFIX, 'Content script loaded on', location.href, 'frame:', isTopFrame ? 'top' : 'child'); // Only operate in the top frame if (!isTopFrame) { console.log(MAIL163_PREFIX, 'Skipping child frame'); } else { // Track codes we've already seen across polls to avoid duplicates const seenCodes = new Set(); // ============================================================ // Message Handler (top frame only) // ============================================================ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'POLL_EMAIL') { handlePollEmail(message.step, message.payload).then(result => { sendResponse(result); }).catch(err => { reportError(message.step, err.message); sendResponse({ error: err.message }); }); return true; } }); // ============================================================ // Find mail items // ============================================================ function findMailItems() { return document.querySelectorAll('div[sign="letter"]'); } function getCurrentMailIds() { const ids = new Set(); findMailItems().forEach(item => { const id = item.getAttribute('id') || ''; if (id) ids.add(id); }); return ids; } // ============================================================ // Email Polling // ============================================================ async function handlePollEmail(step, payload) { const { senderFilters, subjectFilters, maxAttempts, intervalMs } = payload; log(`Step ${step}: Starting email poll on 163 Mail (max ${maxAttempts} attempts)`); // Click inbox in sidebar to ensure we're in inbox view log(`Step ${step}: Waiting for sidebar...`); try { const inboxLink = await waitForElement('.nui-tree-item-text[title="收件箱"]', 5000); inboxLink.click(); log(`Step ${step}: Clicked inbox`); } catch { log(`Step ${step}: Inbox link not found, proceeding...`, 'warn'); } // Wait for mail list to appear log(`Step ${step}: Waiting for mail list...`); let items = []; for (let i = 0; i < 20; i++) { items = findMailItems(); if (items.length > 0) break; await sleep(500); } if (items.length === 0) { await refreshInbox(); await sleep(2000); items = findMailItems(); } if (items.length === 0) { throw new Error('163 Mail list did not load. Make sure inbox is open.'); } log(`Step ${step}: Mail list loaded, ${items.length} items`); // Snapshot existing mail IDs const existingMailIds = getCurrentMailIds(); log(`Step ${step}: Snapshotted ${existingMailIds.size} existing emails`); const FALLBACK_AFTER = 3; for (let attempt = 1; attempt <= maxAttempts; attempt++) { log(`Polling 163 Mail... attempt ${attempt}/${maxAttempts}`); if (attempt > 1) { await refreshInbox(); await sleep(1000); } const allItems = findMailItems(); const useFallback = attempt > FALLBACK_AFTER; for (const item of allItems) { const id = item.getAttribute('id') || ''; if (!useFallback && existingMailIds.has(id)) continue; const senderEl = item.querySelector('.nui-user'); const sender = senderEl ? senderEl.textContent.toLowerCase() : ''; const subjectEl = item.querySelector('span.da0'); const subject = subjectEl ? subjectEl.textContent : ''; const ariaLabel = (item.getAttribute('aria-label') || '').toLowerCase(); const senderMatch = senderFilters.some(f => sender.includes(f.toLowerCase()) || ariaLabel.includes(f.toLowerCase())); const subjectMatch = subjectFilters.some(f => subject.toLowerCase().includes(f.toLowerCase()) || ariaLabel.includes(f.toLowerCase())); if (senderMatch || subjectMatch) { const code = extractVerificationCode(subject + ' ' + ariaLabel); if (code && !seenCodes.has(code)) { seenCodes.add(code); const source = useFallback && existingMailIds.has(id) ? 'fallback' : 'new'; log(`Step ${step}: Code found: ${code} (${source}, subject: ${subject.slice(0, 40)})`, 'ok'); // Delete this email via right-click menu await deleteEmail(item, step); return { ok: true, code, emailTimestamp: Date.now(), mailId: id }; } else if (code && seenCodes.has(code)) { log(`Step ${step}: Skipping already-seen code: ${code}`, 'info'); } } } if (attempt === FALLBACK_AFTER + 1) { log(`Step ${step}: No new emails after ${FALLBACK_AFTER} attempts, falling back to first match`, 'warn'); } if (attempt < maxAttempts) { await sleep(intervalMs); } } throw new Error( `No new matching email found on 163 Mail after ${(maxAttempts * intervalMs / 1000).toFixed(0)}s. ` + 'Check inbox manually.' ); } // ============================================================ // Delete Email via Right-Click Menu // ============================================================ async function deleteEmail(item, step) { try { log(`Step ${step}: Deleting email...`); // Right-click on the mail item to trigger context menu item.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, cancelable: true, button: 2, clientX: item.getBoundingClientRect().x + 100, clientY: item.getBoundingClientRect().y + 10, })); await sleep(500); // Find the context menu and click "删除邮件" const menuItems = document.querySelectorAll('.nui-menu-item .nui-menu-item-text'); for (const menuItem of menuItems) { if (menuItem.textContent.trim() === '删除邮件') { menuItem.closest('.nui-menu-item').click(); log(`Step ${step}: Email deleted`, 'ok'); await sleep(500); return; } } log(`Step ${step}: Could not find "删除邮件" in context menu`, 'warn'); } catch (err) { log(`Step ${step}: Failed to delete email: ${err.message}`, 'warn'); } } // ============================================================ // Inbox Refresh // ============================================================ async function refreshInbox() { // Try toolbar "刷 新" button const toolbarBtns = document.querySelectorAll('.nui-btn .nui-btn-text'); for (const btn of toolbarBtns) { if (btn.textContent.replace(/\s/g, '') === '刷新') { btn.closest('.nui-btn').click(); console.log(MAIL163_PREFIX, 'Clicked "刷新" button'); await sleep(800); return; } } // Fallback: click sidebar "收 信" const shouXinBtns = document.querySelectorAll('.ra0'); for (const btn of shouXinBtns) { if (btn.textContent.replace(/\s/g, '').includes('收信')) { btn.click(); console.log(MAIL163_PREFIX, 'Clicked "收信" button'); await sleep(800); return; } } console.log(MAIL163_PREFIX, 'Could not find refresh button'); } // ============================================================ // Verification Code Extraction // ============================================================ function extractVerificationCode(text) { const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/); if (matchCn) return matchCn[1]; const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i); if (matchEn) return matchEn[1] || matchEn[2]; const match6 = text.match(/\b(\d{6})\b/); if (match6) return match6[1]; return null; } } // end of isTopFrame else block