/** * LinkScout Combined Content Script * Smart Analysis. Simple Answers. * Combines highlighting, sidebar, and chunk analysis from both extensions */ // Configuration const CONFIG = { API_ENDPOINT: 'http://localhost:5000/api/v1/analyze-chunks', MIN_TEXT_LENGTH: 100, REQUEST_TIMEOUT: 180000, // 3 minutes AUTO_SCAN_DELAY: 3000 }; // State let isAnalyzing = false; let analysisResults = null; let sidebarOpen = false; let highlightedElements = []; // Initialize console.log('🔍 LinkScout content script loaded'); // Listen for messages from popup/background chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.log('📨 Message received:', request.action); if (request.action === 'ping') { sendResponse({ success: true, ready: true }); return true; } if (request.action === 'analyzeCurrentPage') { analyzeAndDisplay().then(sendResponse); return true; } if (request.action === 'highlightSuspicious') { if (analysisResults) { highlightSuspiciousContent(analysisResults); sendResponse({ success: true }); } else { sendResponse({ success: false, error: 'No analysis results' }); } return true; } if (request.action === 'clearHighlights') { clearAllHighlights(); sendResponse({ success: true }); return true; } if (request.action === 'showResults') { if (analysisResults) { openSidebar(); sendResponse({ success: true }); } else { sendResponse({ success: false, error: 'No results' }); } return true; } if (request.action === 'getResults') { sendResponse({ success: analysisResults !== null, result: analysisResults }); return true; } if (request.action === 'scrollToParagraph') { const chunkIndex = request.chunkIndex; console.log('📍 Scrolling to paragraph:', chunkIndex); scrollToChunk(chunkIndex); sendResponse({ success: true }); return true; } }); // ============= MAIN ANALYSIS FUNCTION ============= async function analyzeAndDisplay() { if (isAnalyzing) { return { success: false, error: 'Analysis in progress' }; } isAnalyzing = true; try { // Extract content const extracted = window.ContentExtractorV2 ? window.ContentExtractorV2.extractFullContent() : extractContentFallback(); if (!extracted || extracted.paragraphs.length === 0) { throw new Error('No content found on page'); } console.log(`📊 Analyzing ${extracted.paragraphs.length} paragraphs...`); // Show loading indicator showLoadingNotification(extracted.paragraphs.length); // Prepare payload const payload = { paragraphs: extracted.paragraphs.map(p => ({ index: p.index, text: p.text, type: p.type })), title: extracted.title, url: window.location.href, source: window.location.hostname, html: document.documentElement.outerHTML // ✅ ADD HTML FOR IMAGE ANALYSIS }; // Send to server console.log('📡 Sending POST request to:', CONFIG.API_ENDPOINT); console.log('📦 Payload size:', JSON.stringify(payload).length, 'bytes'); const response = await fetch(CONFIG.API_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(payload) }); console.log('📨 Response status:', response.status, response.statusText); if (!response.ok) { const errorText = await response.text(); console.error('❌ Server error response:', errorText); throw new Error(`Server error: ${response.status} - ${errorText.substring(0, 100)}`); } const result = await response.json(); // Store results analysisResults = { ...result, extractedContent: extracted }; // Hide loading hideLoadingNotification(); // Show completion notification showCompletionNotification(result.overall); // Automatically highlight suspicious content if (result.chunks) { highlightSuspiciousContent(result); } // Open sidebar with results openSidebar(); console.log('✅ Analysis complete:', result.verdict); return { success: true, result: analysisResults }; } catch (error) { console.error('❌ Analysis error:', error); console.error(' Error type:', error.name); console.error(' Error message:', error.message); console.error(' Error stack:', error.stack); hideLoadingNotification(); showErrorNotification(error.message); return { success: false, error: error.message }; } finally { isAnalyzing = false; } } // ============= CONTENT EXTRACTION ============= function extractContentFallback() { const paragraphs = []; const elements = document.querySelectorAll('p, h1, h2, h3, article'); elements.forEach((el, index) => { const text = el.textContent.trim(); if (text.length > CONFIG.MIN_TEXT_LENGTH) { paragraphs.push({ index, text, type: el.tagName.toLowerCase(), element: el }); } }); const title = document.querySelector('title')?.textContent || document.querySelector('h1')?.textContent || 'Untitled'; return { title, paragraphs }; } // ============= HIGHLIGHTING ============= function highlightSuspiciousContent(result) { clearAllHighlights(); console.log('🎨 [HIGHLIGHT] Starting highlighting process...'); console.log('🎨 [HIGHLIGHT] Result object:', result); if (!result) { console.error('❌ [HIGHLIGHT] No result object provided!'); return; } if (!result.chunks) { console.error('❌ [HIGHLIGHT] No chunks property in result!'); console.log('🎨 [HIGHLIGHT] Available properties:', Object.keys(result)); return; } console.log(`🎨 [HIGHLIGHT] Total chunks received: ${result.chunks.length}`); // ✅ STRICTER: Only highlight genuinely suspicious paragraphs (60+) const suspiciousChunks = result.chunks.filter(c => c.suspicious_score >= 60); console.log(`🎨 [HIGHLIGHT] Chunks with score >= 60: ${suspiciousChunks.length}`); if (suspiciousChunks.length > 0) { console.log('🎨 [HIGHLIGHT] Suspicious chunks:', suspiciousChunks.map(c => ({ index: c.index, score: c.suspicious_score, preview: c.text_preview?.substring(0, 50), fullTextLength: c.text?.length || 0 }))); } if (suspiciousChunks.length === 0) { console.log('✅ No suspicious content to highlight'); return; } console.log(`🎨 Highlighting ${suspiciousChunks.length} suspicious items`); suspiciousChunks.forEach(chunk => { const text = chunk.text_preview || chunk.text; if (!text) { console.warn('⚠️ Chunk has no text:', chunk); return; } console.log(`🔍 Looking for chunk ${chunk.index}: "${text.substring(0, 80)}..."`); // Find elements containing this text const elements = findElementsContainingText(text); if (elements.length === 0) { console.warn(`❌ Could not find element for chunk ${chunk.index}`); } elements.forEach(element => { highlightElement(element, chunk.suspicious_score, chunk.index); }); }); // Show notification const notification = createNotification(); notification.style.background = '#3b82f6'; notification.innerHTML = `
🎨
${suspiciousChunks.length} Suspicious Paragraphs Highlighted
Click sidebar paragraphs to jump to them
`; document.body.appendChild(notification); setTimeout(() => notification.remove(), 4000); } function findElementsContainingText(searchText) { // Use more of the text for better matching const searchLower = searchText.toLowerCase(); const searchStart = searchLower.substring(0, 150); // First 150 chars const searchEnd = searchLower.substring(Math.max(0, searchLower.length - 150)); // Last 150 chars console.log(`🔍 [MATCH] Searching for text (${searchText.length} chars)`); console.log(`🔍 [MATCH] Start: "${searchStart.substring(0, 50)}..."`); // Strategy 1: Find exact paragraph match const allParagraphs = Array.from(document.querySelectorAll('p, li, blockquote, td, div[class*="paragraph"]')); let bestMatch = null; let bestScore = -1; for (const para of allParagraphs) { // Skip LinkScout elements if (para.closest('#linkscout-sidebar, [id*="linkscout"]')) continue; const paraText = para.textContent.toLowerCase(); // Check if contains search text (start OR end) const containsStart = paraText.includes(searchStart.substring(0, 80)); const containsEnd = searchEnd.length > 80 && paraText.includes(searchEnd.substring(searchEnd.length - 80)); if (containsStart || containsEnd) { // Score: closer length = better match const lengthRatio = Math.min(paraText.length, searchText.length) / Math.max(paraText.length, searchText.length); let score = lengthRatio * 1000; // Bonus for exact match if (Math.abs(paraText.length - searchText.length) < 50) { score += 500; } if (score > bestScore) { bestScore = score; bestMatch = para; } } } if (bestMatch) { console.log(`✅ Found best match: ${bestMatch.tagName}, length: ${bestMatch.textContent.length}, score: ${bestScore.toFixed(1)}`); return [bestMatch]; } // Strategy 2: Try finding parent div (as fallback) const allDivs = Array.from(document.querySelectorAll('div[class*="content"], div[class*="article"], div[class*="text"], div[class*="paragraph"]')); for (const div of allDivs) { if (div.closest('#linkscout-sidebar, [id*="linkscout"]')) continue; const divText = div.textContent.toLowerCase(); if (divText.includes(searchLower.substring(0, 100)) && divText.length < searchText.length * 2) { console.log(`✅ Found fallback div match: ${div.className}`); return [div]; } } console.log('❌ No match found for text:', searchText.substring(0, 50)); return []; } function highlightElement(element, score, chunkIndex) { if (!element || highlightedElements.includes(element)) return; // Skip sidebar elements if (element.id && element.id.includes('linkscout')) return; if (element.closest('#linkscout-sidebar')) return; // Determine color based on score (stricter thresholds) let bgColor, borderColor; if (score >= 70) { bgColor = 'rgba(239, 68, 68, 0.15)'; borderColor = '#ef4444'; } else if (score >= 60) { // Changed from 40 to 60 bgColor = 'rgba(245, 158, 11, 0.15)'; borderColor = '#f59e0b'; } else { bgColor = 'rgba(59, 130, 246, 0.15)'; borderColor = '#3b82f6'; } // Store original style const originalStyle = { background: element.style.background, borderLeft: element.style.borderLeft, paddingLeft: element.style.paddingLeft }; element.setAttribute('data-linkscout-original-style', JSON.stringify(originalStyle)); // Mark with chunk index if provided if (chunkIndex !== undefined) { element.setAttribute('data-linkscout-chunk', chunkIndex); } // Apply highlight element.style.background = bgColor; element.style.borderLeft = `4px solid ${borderColor}`; element.style.paddingLeft = '12px'; element.style.transition = 'all 0.3s ease'; // Add to highlighted list highlightedElements.push(element); // Add tooltip element.setAttribute('title', `LinkScout: ${score}% suspicious - Click sidebar paragraph to jump here`); element.classList.add('linkscout-highlighted'); } function clearAllHighlights() { highlightedElements.forEach(element => { try { const originalStyle = element.getAttribute('data-linkscout-original-style'); if (originalStyle) { const styles = JSON.parse(originalStyle); element.style.background = styles.background; element.style.borderLeft = styles.borderLeft; element.style.paddingLeft = styles.paddingLeft; element.removeAttribute('data-linkscout-original-style'); } element.removeAttribute('title'); element.classList.remove('linkscout-highlighted'); } catch (e) { console.error('Error clearing highlight:', e); } }); highlightedElements = []; console.log('✅ All highlights cleared'); } // ============= SIDEBAR ============= function openSidebar() { let sidebar = document.getElementById('linkscout-sidebar'); if (sidebar) { sidebar.style.display = 'block'; updateSidebarContent(); } else { createSidebar(); } sidebarOpen = true; } function createSidebar() { const sidebar = document.createElement('div'); sidebar.id = 'linkscout-sidebar'; sidebar.style.cssText = ` position: fixed; top: 0; right: 0; width: 400px; height: 100vh; background: white; box-shadow: -4px 0 12px rgba(0,0,0,0.15); z-index: 999999; overflow-y: auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; animation: slideIn 0.3s ease; `; sidebar.innerHTML = `
`; document.body.appendChild(sidebar); updateSidebarContent(); } function updateSidebarContent() { const content = document.getElementById('linkscout-sidebar-content'); if (!content || !analysisResults) return; const result = analysisResults; const overall = result.overall || {}; const percentage = overall.suspicious_score || 0; const pretrained = result.pretrained_models || {}; const research = result.research || {}; const linguistic = result.linguistic_fingerprint || {}; const claims = result.claim_verification || {}; const source = result.source_credibility || {}; const propaganda = result.propaganda_analysis || {}; const entities = result.entity_verification || {}; const contradictions = result.contradiction_analysis || {}; const network = result.network_analysis || {}; // Determine colors let bgColor, textColor, verdict, icon; if (percentage > 70) { bgColor = '#FF3B30'; textColor = '#FFFFFF'; verdict = '🚨 FAKE NEWS'; icon = '🚨'; } else if (percentage > 40) { bgColor = '#FFCC00'; textColor = '#000000'; verdict = '⚠️ SUSPICIOUS'; icon = '⚠️'; } else { bgColor = '#34C759'; textColor = '#FFFFFF'; verdict = '✅ LOOKS SAFE'; icon = '✅'; } let html = `
${icon}
${verdict}
Suspicious: ${percentage}%
${overall.total_paragraphs || 0}
Analyzed
${(overall.fake_paragraphs || 0) + (overall.suspicious_paragraphs || 0)}
Flagged
${overall.credibility_score || (100 - percentage)}%
Credible
${result.combined_analysis ? `

🎯 OVERALL CREDIBILITY

Risk Score ${(result.combined_analysis.overall_score || 0).toFixed(0)}/100
${result.combined_analysis.verdict || 'UNKNOWN'}
0 - Highly Credible 100 - Not Credible
${overall.fake_paragraphs || 0}
High Risk
${overall.suspicious_paragraphs || 0}
Medium Risk
` : ''} ${pretrained.categories && Array.isArray(pretrained.categories) && pretrained.categories.length > 0 ? `

🏷️ ARTICLE CATEGORIES

${pretrained.categories.map(cat => `${cat}`).join(' ')}
` : ''} ${research.research_findings ? `

🤖 GROQ AI RESEARCH REPORT

${research.research_findings.replace(/\*\*(.*?)\*\*/g, '$1').replace(/\n/g, '
')}
` : ''} ${research.detailed_analysis ? `

🔬 DETAILED ANALYSIS

${research.detailed_analysis.replace(/\*\*(.*?)\*\*/g, '$1').replace(/\n/g, '
')}
` : ''} ${research.final_conclusion ? `

✅ FINAL CONCLUSION

${research.final_conclusion.replace(/\*\*(.*?)\*\*/g, '$1').replace(/\n/g, '
')}
` : ''} ${pretrained.named_entities && Array.isArray(pretrained.named_entities) && pretrained.named_entities.length > 0 ? `

👥 KEY ENTITIES

${pretrained.named_entities.map(entity => `${entity}`).join(' ')}
` : ''} ${research.google_results && research.google_results.length > 0 ? `

🔗 GOOGLE SEARCH RESULTS

${research.google_results.slice(0, 5).map((r, i) => `
${i + 1}. ${r.title}
${r.snippet || ''}
`).join('')}
` : ''} ${result.chunks && result.chunks.filter(c => c.suspicious_score >= 60).length > 0 ? `

🚨 Suspicious Paragraphs (${result.chunks.filter(c => c.suspicious_score >= 60).length})

${result.chunks.filter(c => c.suspicious_score >= 60).map(chunk => `
📍 Paragraph ${chunk.index + 1} ${chunk.suspicious_score}/100
"${chunk.text_preview || chunk.text || 'N/A'}"
${chunk.why_flagged ? `
🔍 Why Flagged:
${chunk.why_flagged.replace(/\n/g, '
')}
` : ''}
👆 Click to jump to this paragraph on the page
`).join('')}
` : `

✅ All Clear

No suspicious content detected! All paragraphs appear credible.

`}
Powered by LinkScout AI
${pretrained.fake_probability !== undefined ? '✓ 8 ML Models Active' : ''}
${research.research_findings ? '✓ Groq AI Active' : ''}
`; content.innerHTML = html; // Add close button functionality setTimeout(() => { const closeBtn = document.getElementById('linkscout-sidebar-close'); if (closeBtn) { closeBtn.addEventListener('click', () => { const sidebar = document.getElementById('linkscout-sidebar'); if (sidebar) { sidebar.style.display = 'none'; sidebarOpen = false; // Clear highlights when closing sidebar clearAllHighlights(); } }); } // Add click-to-scroll functionality for suspicious paragraphs document.querySelectorAll('.linkscout-sidebar-chunk').forEach(chunkDiv => { chunkDiv.addEventListener('click', () => { const chunkIndex = parseInt(chunkDiv.getAttribute('data-chunk-index')); scrollToChunk(chunkIndex); }); }); }, 100); } // ============= SCROLL TO CHUNK ============= function scrollToChunk(chunkIndex) { console.log(`📍 Scrolling to chunk ${chunkIndex}`); if (!analysisResults || !analysisResults.chunks) { console.error('No analysis results available'); return; } const chunk = analysisResults.chunks.find(c => c.index === chunkIndex); if (!chunk) { console.error(`Chunk ${chunkIndex} not found`); return; } // Try to find the element by data attribute first (if already highlighted) let element = document.querySelector(`[data-linkscout-chunk="${chunkIndex}"]`); // If not found, search by text content if (!element) { const searchText = chunk.text || chunk.text_preview; if (!searchText) { console.error('No text to search for'); return; } // Search for paragraph containing this text - use more specific selectors const allParagraphs = document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, blockquote, li'); for (let candidate of allParagraphs) { // Skip sidebar elements if (candidate.id && candidate.id.includes('linkscout')) continue; if (candidate.closest('#linkscout-sidebar')) continue; const candidateText = candidate.textContent.trim(); // Skip very short elements if (candidateText.length < 30) continue; // Check if text matches (compare first 150 chars for better accuracy) const searchSnippet = searchText.substring(0, 150).trim(); const candidateSnippet = candidateText.substring(0, 150).trim(); // Use more precise matching if (candidateSnippet === searchSnippet || candidateText.includes(searchSnippet)) { element = candidate; console.log(`✅ Found matching element: <${element.tagName}> with text: "${candidateSnippet.substring(0, 50)}..."`); break; } } } if (element) { // Clear ALL previous highlights first clearAllHighlights(); // Mark element with data attribute element.setAttribute('data-linkscout-chunk', chunkIndex); // Highlight ONLY this specific element highlightElement(element, chunk.suspicious_score, chunkIndex); // Scroll to element element.scrollIntoView({ behavior: 'smooth', block: 'center' }); // Flash animation - use a pulsing blue effect for the specific paragraph const flashAnimation = () => { let pulseCount = 0; const pulseInterval = setInterval(() => { if (pulseCount >= 3) { clearInterval(pulseInterval); return; } // Pulse effect element.style.boxShadow = '0 0 25px rgba(59, 130, 246, 0.8)'; element.style.transform = 'scale(1.01)'; setTimeout(() => { element.style.boxShadow = 'none'; element.style.transform = 'scale(1)'; }, 300); pulseCount++; }, 600); }; setTimeout(flashAnimation, 300); console.log(`✅ Scrolled to and highlighted chunk ${chunkIndex}`); } else { console.error(`❌ Could not find element for chunk ${chunkIndex}`); console.log('Chunk text:', chunk.text_preview || chunk.text); alert(`Could not locate paragraph ${chunkIndex + 1} on the page. The content may have changed.`); } } // ============= NOTIFICATIONS ============= function showLoadingNotification(paragraphCount) { const notification = createNotification(); notification.style.background = '#3b82f6'; notification.innerHTML = `
Analyzing ${paragraphCount} paragraphs...
`; document.body.appendChild(notification); } function showCompletionNotification(overall) { const notification = createNotification(); const verdict = overall.verdict || 'unknown'; const color = verdict === 'fake' ? '#ef4444' : verdict === 'suspicious' ? '#f59e0b' : '#10b981'; const icon = verdict === 'fake' ? '🚨' : verdict === 'suspicious' ? '⚠️' : '✅'; notification.style.background = color; notification.innerHTML = `
${icon}
Analysis Complete
${verdict.toUpperCase()}
`; document.body.appendChild(notification); setTimeout(() => notification.remove(), 4000); } function showErrorNotification(message) { const notification = createNotification(); notification.style.background = '#ef4444'; notification.innerHTML = `
Error
${message}
`; document.body.appendChild(notification); setTimeout(() => notification.remove(), 4000); } function hideLoadingNotification() { const notification = document.getElementById('linkscout-notification'); if (notification) notification.remove(); } function createNotification() { const existing = document.getElementById('linkscout-notification'); if (existing) existing.remove(); const notification = document.createElement('div'); notification.id = 'linkscout-notification'; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; background: #3b82f6; color: white; padding: 14px 18px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); font-family: -apple-system, sans-serif; font-size: 13px; z-index: 1000000; animation: slideDown 0.3s ease; `; const style = document.createElement('style'); style.textContent = ` @keyframes slideDown { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } `; notification.appendChild(style); return notification; } console.log('✅ LinkScout content script ready - Smart Analysis. Simple Answers.');