Auto-deploy: 2026-06-05 22:59:02

This commit is contained in:
Hamza-Ayed
2026-06-05 22:59:02 +03:00
parent 2e1df70eaf
commit 4516c2878f

View File

@@ -44,40 +44,40 @@
}; };
try { try {
// The most robust way to extract data on LinkedIn is reading the visible text lines. // 1. Name: The most reliable way is the profile link
// Profile cards always follow: Name -> Degree -> Headline -> Location -> ... const profileLinks = Array.from(cardEl.querySelectorAll('a[href*="/in/"]'))
const rawLines = cardEl.innerText.split('\n').map(s => s.trim()).filter(s => s.length > 1); .filter(a => a.innerText.trim().length > 2 && !a.innerText.includes('LinkedIn'));
// Remove lines that are just action buttons or connection degrees if (profileLinks.length > 0) {
const lines = rawLines.filter(s => { // Remove any injected language tags
const low = s.toLowerCase(); let rawName = profileLinks[0].innerText.trim().split('\n')[0];
return !low.includes('degree connection') && data.name = rawName.replace(/English \(Australia\)|Auto|Translate/gi, '').trim();
!['1st', '2nd', '3rd', '3rd+'].includes(low) && } else {
!low.includes('connect') && const nameEl = cardEl.querySelector('.entity-result__title-text, .search-result__title, span[dir="ltr"], h3');
!low.includes('message') && if (nameEl) data.name = nameEl.innerText.trim().split('\n')[0].replace(/English \(Australia\)|Auto/gi, '').trim();
!low.includes('pending') &&
!low.includes('follow') &&
!low.includes('view profile');
});
if (lines.length > 0) data.name = lines[0];
if (lines.length > 1) data.headline = lines[1];
if (lines.length > 2) data.location = lines[2];
// Summary is everything else
if (lines.length > 3) {
data.summary = lines.slice(3, 8).join(' | ');
} }
// Cleanup name if it accidentally caught something weird // 2. Headline
if (data.name && data.name.includes('LinkedIn')) { const headlineEl = cardEl.querySelector('.entity-result__primary-subtitle, [class*="subtitle"], .linked-area');
if (headlineEl) data.headline = headlineEl.innerText.trim();
// 3. Location
const locationEl = cardEl.querySelector('.entity-result__secondary-subtitle, .search-result__info');
if (locationEl) data.location = locationEl.innerText.trim();
// 4. 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();
if (!data.name) {
data.name = 'مستثمر محتمل'; data.name = 'مستثمر محتمل';
} }
} catch (e) { } catch (e) {
console.error('[LJA] Extraction failed', e); console.error('[LJA] Extraction failed', e);
data.name = 'مستثمر محتمل'; data.name = 'مستثمر محتمل';
data.summary = cardEl.innerText ? cardEl.innerText.substring(0, 300) : '';
} }
return data; return data;
@@ -86,37 +86,40 @@
// ─── Find Cards Logic ────────────────────────────────────────────────────── // ─── Find Cards Logic ──────────────────────────────────────────────────────
function findCards() { function findCards() {
let cards = []; let cards = [];
let uniqueCards = new Set();
// LinkedIn search results are usually list items with an image and an action button // Method 1: The official LinkedIn search result container class
let listItems = Array.from(document.querySelectorAll('li, .reusable-search__result-container, .search-entity')); let containerElements = document.querySelectorAll('.reusable-search__result-container, .search-entity, .entity-result__item');
if (containerElements.length > 0) {
containerElements.forEach(el => uniqueCards.add(el));
console.log('[LJA] Found cards via container classes:', uniqueCards.size);
}
// Filter to only those that look like profile cards // Method 2: Fallback to finding profile links and going up to the list item
let validCards = listItems.filter(el => { if (uniqueCards.size === 0) {
const txt = el.innerText.toLowerCase(); let profileLinks = Array.from(document.querySelectorAll('a[href*="/in/"]'))
// Must have significant text, an image, and a typical LinkedIn action button .filter(a => a.innerText.trim().length > 2 && !a.querySelector('img'));
const hasAction = txt.includes('connect') || txt.includes('message') || txt.includes('pending') || txt.includes('follow');
const hasImage = el.querySelector('img'); profileLinks.forEach(link => {
const isNotSidebar = !txt.includes('about') && !txt.includes('accessibility'); // Find the nearest list item or large div container
return txt.length > 50 && hasAction && hasImage && isNotSidebar; let container = link.closest('li') || link.closest('div.mb1') || link.parentElement.parentElement.parentElement;
if (container && container.innerText.length > 20) {
uniqueCards.add(container);
}
});
console.log('[LJA] Found cards via profile links:', uniqueCards.size);
}
// Convert Set to Array and filter out translation extension dropdowns
cards = Array.from(uniqueCards).filter(card => {
const txt = card.innerText.toLowerCase();
// Must not be a language selector dropdown
if (txt.includes('english (australia)') && txt.length < 50) return false;
// Should have some decent amount of text
return txt.length > 20;
}); });
if (validCards.length > 0) { return cards;
cards = validCards;
console.log('[LJA] Found cards via strict heuristics:', cards.length);
}
// Deduplicate cards by their text content to avoid nested matches
const uniqueCards = [];
const seenText = new Set();
for (const card of cards) {
const txt = card.innerText.trim().substring(0, 100); // use first 100 chars as signature
if (!seenText.has(txt)) {
seenText.add(txt);
uniqueCards.push(card);
}
}
return uniqueCards;
} }
// ─── Inject UI into Card ───────────────────────────────────────────────── // ─── Inject UI into Card ─────────────────────────────────────────────────