Auto-deploy: 2026-06-05 22:54:49
This commit is contained in:
@@ -44,50 +44,36 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Extract Name (Prioritize links over generic spans)
|
// The most robust way to extract data on LinkedIn is reading the visible text lines.
|
||||||
const profileLinks = Array.from(cardEl.querySelectorAll('a[href*="/in/"]'))
|
// Profile cards always follow: Name -> Degree -> Headline -> Location -> ...
|
||||||
.filter(a => a.innerText.trim().length > 2 && !a.innerText.includes('LinkedIn'));
|
const rawLines = cardEl.innerText.split('\n').map(s => s.trim()).filter(s => s.length > 1);
|
||||||
|
|
||||||
if (profileLinks.length > 0) {
|
// Remove lines that are just action buttons or connection degrees
|
||||||
data.name = profileLinks[0].innerText.trim().split('\n')[0];
|
const lines = rawLines.filter(s => {
|
||||||
} else {
|
const low = s.toLowerCase();
|
||||||
const nameEl = cardEl.querySelector('.entity-result__title-text, .search-result__title, span[dir="ltr"]');
|
return !low.includes('degree connection') &&
|
||||||
if (nameEl) data.name = nameEl.innerText.trim().split('\n')[0];
|
!['1st', '2nd', '3rd', '3rd+'].includes(low) &&
|
||||||
}
|
!low.includes('connect') &&
|
||||||
|
!low.includes('message') &&
|
||||||
|
!low.includes('pending') &&
|
||||||
|
!low.includes('follow') &&
|
||||||
|
!low.includes('view profile');
|
||||||
|
});
|
||||||
|
|
||||||
// 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 > 0) data.name = lines[0];
|
||||||
if (lines.length > 1 && !data.headline) data.headline = lines[1];
|
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(' | ');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final fallback to avoid empty name
|
// Cleanup name if it accidentally caught something weird
|
||||||
if (!data.name) {
|
if (data.name && data.name.includes('LinkedIn')) {
|
||||||
data.name = 'مستثمر محتمل';
|
data.name = 'مستثمر محتمل';
|
||||||
data.summary = cardEl.innerText ? cardEl.innerText.substring(0, 300) : '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[LJA] Extraction failed', e);
|
console.error('[LJA] Extraction failed', e);
|
||||||
data.name = 'مستثمر محتمل';
|
data.name = 'مستثمر محتمل';
|
||||||
@@ -101,52 +87,36 @@
|
|||||||
function findCards() {
|
function findCards() {
|
||||||
let cards = [];
|
let cards = [];
|
||||||
|
|
||||||
// Method 1: Try common structural wrappers for search results
|
// LinkedIn search results are usually list items with an image and an action button
|
||||||
let listItems = Array.from(document.querySelectorAll('ul > li'));
|
let listItems = Array.from(document.querySelectorAll('li, .reusable-search__result-container, .search-entity'));
|
||||||
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
|
// Filter to only those that look like profile cards
|
||||||
if (cards.length === 0) {
|
let validCards = listItems.filter(el => {
|
||||||
let subtitles = document.querySelectorAll('.entity-result__primary-subtitle, [class*="subtitle"], .linked-area');
|
const txt = el.innerText.toLowerCase();
|
||||||
let validSubtitles = Array.from(subtitles).filter(s => s.innerText.trim().length > 5);
|
// Must have significant text, an image, and a typical LinkedIn action button
|
||||||
if (validSubtitles.length > 0) {
|
const hasAction = txt.includes('connect') || txt.includes('message') || txt.includes('pending') || txt.includes('follow');
|
||||||
let uniqueCards = new Set();
|
const hasImage = el.querySelector('img');
|
||||||
validSubtitles.forEach(s => {
|
const isNotSidebar = !txt.includes('about') && !txt.includes('accessibility');
|
||||||
let container = s.closest('li') || s.closest('div[data-chameleon-result-urn]') || s.parentElement.parentElement.parentElement;
|
return txt.length > 50 && hasAction && hasImage && isNotSidebar;
|
||||||
if (container) uniqueCards.add(container);
|
|
||||||
});
|
});
|
||||||
cards = Array.from(uniqueCards);
|
|
||||||
console.log('[LJA] Found cards via subtitles:', cards.length);
|
if (validCards.length > 0) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method 3: Deep heuristic using profile links
|
return uniqueCards;
|
||||||
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 ─────────────────────────────────────────────────
|
// ─── Inject UI into Card ─────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user