Spaces:
Running
Running
| // gmail_scanner.js | |
| console.log("PhishGuard AI: Gmail Scanner loaded."); | |
| // Gmail's DOM can change, but commonly the email text is stored in elements with class '.a3s' or '.ii.gt' | |
| const EMAIL_BODY_SELECTOR = '.a3s, .ii.gt'; | |
| // Function to inject a visible warning banner into the Gmail UI | |
| function injectWarningBanner(emailContainer, message) { | |
| // Prevent duplicate banners if one is already injected | |
| if (emailContainer.parentNode.querySelector('.phishguard-banner')) { | |
| return; | |
| } | |
| const banner = document.createElement('div'); | |
| banner.className = 'phishguard-banner'; | |
| // Styling the banner to look native but urgent, fitting Google's Material Design | |
| banner.style.backgroundColor = '#fce8e6'; | |
| banner.style.color = '#c5221f'; | |
| banner.style.border = '1px solid #faa59f'; | |
| banner.style.borderRadius = '8px'; | |
| banner.style.padding = '12px 16px'; | |
| // Added margin to ensure it doesn't overlap text awkwardly | |
| banner.style.margin = '16px auto'; | |
| banner.style.fontFamily = '"Google Sans", Roboto, Arial, sans-serif'; | |
| banner.style.fontSize = '14px'; | |
| banner.style.fontWeight = '500'; | |
| banner.style.lineHeight = '20px'; | |
| banner.style.display = 'flex'; | |
| banner.style.alignItems = 'center'; | |
| banner.style.boxShadow = '0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15)'; | |
| // Add SVG Icon for warning | |
| const iconSvg = ` | |
| <svg focusable="false" width="24" height="24" viewBox="0 0 24 24" style="fill: #c5221f; margin-right: 16px; flex-shrink: 0;"> | |
| <path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path> | |
| </svg> | |
| `; | |
| const textContent = document.createElement('span'); | |
| textContent.innerText = message || '🚨 PhishGuard Warning: This email contains suspicious links.'; | |
| // Construct the banner | |
| banner.innerHTML = iconSvg; | |
| banner.appendChild(textContent); | |
| // Insert banner at the top of the email container. | |
| // By inserting before the email container, we keep it visible at the top of the body. | |
| if (emailContainer.parentNode) { | |
| emailContainer.parentNode.insertBefore(banner, emailContainer); | |
| } | |
| } | |
| // Helper function to extract all unique URLs from the email body | |
| function extractUrlsFromBody(emailContainer) { | |
| const links = emailContainer.querySelectorAll('a[href]'); | |
| const urls = new Set(); // Use a Set to store unique URLs | |
| links.forEach(link => { | |
| const href = link.href; | |
| // Basic filter to ignore mailto: or javascript: links, and only keep http/https | |
| if (href && (href.startsWith('http://') || href.startsWith('https://'))) { | |
| urls.add(href); | |
| } | |
| }); | |
| return Array.from(urls); | |
| } | |
| // Function to safely extract the sender's actual email address | |
| function extractSenderEmail(emailContainer) { | |
| // Gmail usually groups each email message into a container block. | |
| // We traverse up to find a common parent containing both header and body. | |
| // '.kv' or 'table' or '.adn' is often the parent wrapper for an individual message. | |
| let messageWrapper = emailContainer.closest('.adn') || emailContainer.closest('.kv') || document; | |
| // The sender element usually has the class '.gD' and contains the 'email' attribute. | |
| const senderElement = messageWrapper.querySelector('.gD'); | |
| if (senderElement && senderElement.getAttribute('email')) { | |
| return senderElement.getAttribute('email'); | |
| } | |
| // Fallback: Sometimes the email address is enclosed in brackets inside a '.go' element. | |
| const fallbackSenderElement = messageWrapper.querySelector('.go'); | |
| if (fallbackSenderElement && fallbackSenderElement.innerText) { | |
| // e.g. "<sender@example.com>" -> matched and extracted | |
| const match = fallbackSenderElement.innerText.match(/<([^>]+)>/); | |
| if (match && match[1]) { | |
| return match[1]; | |
| } | |
| } | |
| return "Unknown Sender"; | |
| } | |
| // Function to handle newly opened emails | |
| function handleEmailOpened(emailContainer) { | |
| // Prevent re-scanning the same email element | |
| if (emailContainer.dataset.pgScanned === "true") { | |
| return; | |
| } | |
| console.log("PhishGuard AI: New email thread opened. Extracting data..."); | |
| // Mark as scanned | |
| emailContainer.dataset.pgScanned = "true"; | |
| // 1. Extract plain text content | |
| const emailBodyText = emailContainer.innerText; | |
| // 2. Extract embedded URLs | |
| const urls = extractUrlsFromBody(emailContainer); | |
| // 3. Extract sender email address | |
| const sender = extractSenderEmail(emailContainer); | |
| // 4. Extract subject line (.hP is a standard class for Gmail's main subject line) | |
| // Sometimes the subject might be in a '.bog' element. We'll default to 'h2.hP'. | |
| const subjectElement = document.querySelector('h2.hP') || document.querySelector('.bog'); | |
| const subject = subjectElement ? subjectElement.innerText.trim() : "No Subject Found"; | |
| // Package the extracted data into a JSON payload | |
| const emailPayload = { | |
| sender: sender, | |
| subject: subject, | |
| body: emailBodyText, | |
| urls: urls, | |
| timestamp: new Date().toISOString() | |
| }; | |
| console.log("PhishGuard AI extracted payload:", emailPayload); | |
| // Send background message to service worker | |
| chrome.runtime.sendMessage( | |
| { | |
| action: "analyzeEmail", | |
| data: emailPayload | |
| }, | |
| (response) => { | |
| if (chrome.runtime.lastError) { | |
| console.error("PhishGuard AI: Error communicating with background script:", chrome.runtime.lastError); | |
| return; | |
| } | |
| console.log("PhishGuard AI Background Analysis Response:", response); | |
| // Assume the background script returns `response.analysis` containing `probability` or `isPhishing` flag | |
| const analysis = response && response.analysis ? response.analysis : {}; | |
| if (analysis.isPhishing === true || analysis.probability > 0.70) { | |
| console.warn("PhishGuard AI: High risk email detected! Injecting banner..."); | |
| injectWarningBanner( | |
| emailContainer, | |
| '🚨 PhishGuard Warning: This email contains suspicious links and exhibits high-risk phishing behavior.' | |
| ); | |
| } | |
| } | |
| ); | |
| } | |
| // Set up a MutationObserver to watch for DOM changes | |
| // This effectively detects when Gmail dynamically loads an individual email view into the DOM | |
| const observer = new MutationObserver((mutationsList) => { | |
| for (const mutation of mutationsList) { | |
| if (mutation.type === 'childList') { | |
| mutation.addedNodes.forEach(node => { | |
| if (node.nodeType === Node.ELEMENT_NODE) { | |
| // Check if the added node itself is the email body container | |
| if (node.matches && node.matches(EMAIL_BODY_SELECTOR)) { | |
| handleEmailOpened(node); | |
| } | |
| // Also search securely within the added node structure for the email body | |
| if (node.querySelectorAll) { | |
| const emailBodies = node.querySelectorAll(EMAIL_BODY_SELECTOR); | |
| emailBodies.forEach(body => handleEmailOpened(body)); | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| }); | |
| // Start observing the document body for deeper added nodes (like when navigating between emails) | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| console.log("PhishGuard AI: MutationObserver is listening for email thread opens."); | |