// search_analyzer.js — LinkedIn People Search Investor Analyzer // Operates on linkedin.com/search/results/people/* (function () { 'use strict'; console.log('[LJA] Search Analyzer Script Loaded'); // Prevent double injection if (window.__linkedinSearchAnalyzerLoaded) return; window.__linkedinSearchAnalyzerLoaded = true; // ─── Utility: get stored settings ──────────────────────────────────────── function getSettings() { return new Promise(resolve => { if (!chrome || !chrome.storage) { resolve({}); return; } chrome.storage.sync.get(['apiKey', 'language', 'userProfile'], (syncData) => { if (syncData && syncData.apiKey) { resolve(syncData); } else { chrome.storage.local.get(['apiKey', 'language', 'userProfile'], (localData) => { resolve(localData || {}); }); } }); }); } // ─── Find Result Container ─────────────────────────────────────────────── function getSearchResultsContainer() { return document.querySelector('.search-results-container, .reusable-search__entity-result-list, ul.reusable-search__entity-result-list, .search-results__list'); } // ─── Extract Person Data ───────────────────────────────────────────────── function extractPersonData(cardEl) { const data = { name: '', headline: '', location: '', summary: '' }; // 1. Extract Name (Prioritize links over generic spans) const profileLinks = Array.from(cardEl.querySelectorAll('a[href*="/in/"]')) .filter(a => a.innerText.trim().length > 2 && !a.innerText.includes('LinkedIn')); if (profileLinks.length > 0) { data.name = profileLinks[0].innerText.trim().split('\n')[0]; } else { const nameEl = cardEl.querySelector('.entity-result__title-text, .search-result__title, span[dir="ltr"]'); if (nameEl) data.name = nameEl.innerText.trim().split('\n')[0]; } // 2. Extract Headline const headlineEl = cardEl.querySelector('.entity-result__primary-subtitle, [class*="subtitle"], .linked-area'); if (headlineEl) { data.headline = headlineEl.innerText.trim(); } // 3. Extract Location const locationEl = cardEl.querySelector('.entity-result__secondary-subtitle, .search-result__info'); if (locationEl) { data.location = locationEl.innerText.trim(); } // 4. Extract Summary const summaryEl = cardEl.querySelector('.entity-result__summary, .search-result__snippets'); if (summaryEl) { data.summary = summaryEl.innerText.trim(); } // Clean up if (data.name) data.name = data.name.replace(/View .* profile/gi, '').trim(); // Ultimate Fallback: just take the first lines of text in the card if (!data.name || data.name.length < 2) { const lines = cardEl.innerText.split('\n').map(s => s.trim()).filter(s => s.length > 2 && !s.includes('Degree connection')); if (lines.length > 0) data.name = lines[0]; if (lines.length > 1 && !data.headline) data.headline = lines[1]; } return data; } // ─── Find Cards Logic ────────────────────────────────────────────────────── function findCards() { let cards = []; // Method 1: Try common structural wrappers for search results let listItems = Array.from(document.querySelectorAll('ul > li')); let validListItems = listItems.filter(li => li.innerText.length > 20 && li.querySelector('img')); if (validListItems.length > 0) { cards = validListItems; console.log('[LJA] Found cards via ul > li with images:', cards.length); } // Method 2: Try finding elements containing headlines if (cards.length === 0) { let subtitles = document.querySelectorAll('.entity-result__primary-subtitle, [class*="subtitle"], .linked-area'); let validSubtitles = Array.from(subtitles).filter(s => s.innerText.trim().length > 5); if (validSubtitles.length > 0) { let uniqueCards = new Set(); validSubtitles.forEach(s => { let container = s.closest('li') || s.closest('div[data-chameleon-result-urn]') || s.parentElement.parentElement.parentElement; if (container) uniqueCards.add(container); }); cards = Array.from(uniqueCards); console.log('[LJA] Found cards via subtitles:', cards.length); } } // Method 3: Deep heuristic using profile links if (cards.length === 0) { let profileLinks = Array.from(document.querySelectorAll('a[href*="/in/"]')).filter(a => a.innerText.trim().length > 3 && !a.querySelector('img')); console.log('[LJA] Profile links found:', profileLinks.length); let uniqueCards = new Set(); profileLinks.forEach(link => { let container = link.parentElement; let bestContainer = null; for(let i = 0; i < 8; i++) { if (!container) break; if (container.innerText.length > 50 && container.querySelectorAll('button').length > 0) { bestContainer = container; } container = container.parentElement; } if (bestContainer) uniqueCards.add(bestContainer); }); cards = Array.from(uniqueCards); console.log('[LJA] Found cards via link heuristics:', cards.length); } return Array.from(new Set(cards)); } // ─── Inject UI into Card ───────────────────────────────────────────────── function injectScanButton(cardEl) { if (cardEl.querySelector('.lja-scan-person-btn') || cardEl.querySelector('.lja-investor-result')) return; const btn = document.createElement('button'); btn.className = 'lja-scan-person-btn'; btn.innerHTML = '🔍 Scan Investor'; btn.style.margin = '10px 0'; btn.style.padding = '5px 15px'; btn.style.cursor = 'pointer'; btn.style.backgroundColor = '#6C63FF'; btn.style.color = '#fff'; btn.style.border = 'none'; btn.style.borderRadius = '5px'; btn.style.fontWeight = 'bold'; btn.style.zIndex = '999'; btn.style.position = 'relative'; const resultContainer = document.createElement('div'); resultContainer.className = 'lja-result-wrapper'; resultContainer.style.width = '100%'; resultContainer.style.marginTop = '10px'; btn.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); await scanPerson(cardEl, btn, resultContainer); }); // Try to append to actions area const actionArea = cardEl.querySelector('.entity-result__actions, .search-result__actions'); if (actionArea) { actionArea.prepend(btn); actionArea.appendChild(resultContainer); } else { // Fallback: Find the name link and put it under it const profileLink = Array.from(cardEl.querySelectorAll('a[href*="/in/"]')).find(a => a.innerText.trim().length > 0); if (profileLink && profileLink.parentElement) { profileLink.parentElement.appendChild(btn); profileLink.parentElement.appendChild(resultContainer); } else { cardEl.appendChild(btn); cardEl.appendChild(resultContainer); } } console.log('[LJA] Injected button for a profile:', extractPersonData(cardEl).name); } // ─── Scan a Single Person ──────────────────────────────────────────────── async function scanPerson(cardEl, btnEl, resultContainer) { const settings = await getSettings(); if (!settings || !settings.apiKey) { alert('Please set your Gemini API key in the extension popup first.'); return; } const data = extractPersonData(cardEl); if (!data.name && !data.headline) { alert('Could not extract person details.'); return; } btnEl.disabled = true; btnEl.innerHTML = ' Scanning...'; const prompt = `أنت مستشار استثماري خبير في الشركات الناشئة (Startups) في الشرق الأوسط ومصر. المستخدم يبحث عن مستثمرين (Angel Investors) أو أشخاص يمكنهم توصيله بمستثمرين لتطبيقاته "انطلق" (Intaleq) و "تريبز" (Tripz) وهي تطبيقات نقل ذكي (Ride-hailing) في مصر والشرق الأوسط. يرجى تقييم هذا الشخص بناءً على المعلومات التالية المستخرجة من لينكد إن: الاسم: ${data.name} المسمى الوظيفي: ${data.headline} الموقع: ${data.location} نبذة: ${data.summary} هل هذا الشخص مناسب للتواصل معه وعرض الاستثمار أو طلب توصية بمستثمرين؟ إذا كان الشخص مستثمرًا فعليًا أو يعمل في صندوق استثماري (VC) أو لديه شبكة علاقات قوية في ريادة الأعمال أو يدعم الشركات الناشئة، أعطه العلامة الخضراء (green). إذا كان الشخص لا يبدو مرتبطًا بالاستثمار (مثلاً مجرد موظف عادي، أو يبحث عن عمل، أو في مجال بعيد جداً عن دعم الشركات)، أعطه العلامة الحمراء (red) لكي لا يضيع المستخدم وقته معه. يجب أن يكون الرد بصيغة JSON فقط بهذا الشكل: { "status": "green" أو "red", "reason": "سبب قصير جداً باللغة العربية يشرح لماذا (سطر واحد)" } لا تقم بإضافة أي نص آخر أو Markdown.`; try { const response = await chrome.runtime.sendMessage({ type: 'GEMINI_REQUEST', payload: { apiKey: settings.apiKey, action: 'generateText', prompt: prompt } }); if (!response.success) { throw new Error(response.error || 'Unknown error'); } let rawText = response.data.text || response.data; rawText = rawText.replace(/```json/gi, '').replace(/```/g, '').trim(); let resultData; try { resultData = JSON.parse(rawText); } catch (parseError) { throw new Error('Failed to parse AI response. Raw: ' + rawText); } const isGreen = resultData.status === 'green'; const badgeIcon = isGreen ? '✅' : '❌'; const badgeText = isGreen ? 'تواصل معه' : 'تجاهله'; const colorClass = isGreen ? 'green' : 'red'; resultContainer.innerHTML = `