| import puppeteer from "puppeteer"; |
|
|
| interface LinkedinMessageResult { |
| success: boolean; |
| logs: string[]; |
| error?: string; |
| } |
|
|
| export async function sendLinkedinConnectRequest( |
| cookie: string, |
| profileUrl: string, |
| message?: string |
| ): Promise<LinkedinMessageResult> { |
| const logs: string[] = []; |
| let browser; |
|
|
| logs.push(`🤖 Initializing LinkedIn automation for: ${profileUrl}`); |
|
|
| try { |
| browser = await puppeteer.launch({ |
| headless: true, |
| args: [ |
| "--no-sandbox", |
| "--disable-setuid-sandbox", |
| "--disable-dev-shm-usage", |
| "--disable-accelerated-2d-canvas", |
| "--no-first-run", |
| "--no-zygote", |
| "--single-process", |
| "--disable-gpu", |
| ], |
| }); |
|
|
| const page = await browser.newPage(); |
|
|
| |
| await page.setViewport({ width: 1280, height: 800 }); |
|
|
| |
| await page.setUserAgent( |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" |
| ); |
|
|
| |
| logs.push("🔑 Setting authentication cookies..."); |
| await page.setCookie({ |
| name: "li_at", |
| value: cookie, |
| domain: ".linkedin.com", |
| path: "/", |
| secure: true, |
| httpOnly: true, |
| sameSite: "None", |
| }); |
|
|
| |
| logs.push(`🚀 Navigating to profile: ${profileUrl}`); |
| await page.goto(profileUrl, { waitUntil: "domcontentloaded", timeout: 30000 }); |
|
|
| |
| await new Promise((r) => setTimeout(r, 2000 + Math.random() * 2000)); |
|
|
| |
| const isLoggedIn = await page.$(".global-nav__content"); |
| if (!isLoggedIn) { |
| |
| |
| const title = await page.title(); |
| if (title.includes("Sign In") || title.includes("Login")) { |
| throw new Error("LinkedIn Session Cookie appears invalid or expired. Please update it in Settings."); |
| } |
| } |
|
|
| |
| |
| |
| |
|
|
| |
| logs.push("🔍 Looking for 'Connect' button..."); |
|
|
| |
| const isPending = await page.evaluate(() => { |
| const buttons = Array.from(document.querySelectorAll('button')); |
| return buttons.some(b => (b as HTMLElement).innerText.includes('Pending') && !b.disabled); |
| }); |
|
|
| if (isPending) { |
| logs.push("⚠️ Connection request is already pending."); |
| return { success: true, logs }; |
| } |
|
|
| |
| let connectBtn = await page.evaluateHandle(() => { |
| const buttons = Array.from(document.querySelectorAll('button')); |
| |
| return buttons.find(b => (b as HTMLElement).innerText.trim() === 'Connect') || |
| buttons.find(b => (b as HTMLElement).innerText.trim().includes('Connect') && !(b as HTMLElement).innerText.includes('Remove')); |
| }); |
|
|
| if (!(connectBtn as unknown as { asElement: () => HTMLElement | null }).asElement()) { |
| |
| logs.push("ℹ️ 'Connect' button not found directly. Checking 'More' actions..."); |
| const moreBtn = await page.evaluateHandle(() => { |
| const buttons = Array.from(document.querySelectorAll('button, div[role="button"]')); |
| return buttons.find(b => b.ariaLabel?.includes('More actions') || (b as HTMLElement).innerText.trim() === 'More'); |
| }); |
|
|
| if ((moreBtn as unknown as { asElement: () => HTMLElement | null }).asElement()) { |
| ((moreBtn as unknown as { asElement: () => HTMLElement | null }).asElement() as HTMLElement)?.click(); |
| await new Promise(r => setTimeout(r, 1000)); |
|
|
| |
| connectBtn = (await page.evaluateHandle(() => { |
| const items = Array.from(document.querySelectorAll('div[role="button"], li')); |
| return items.find(b => (b as HTMLElement).innerText.trim() === 'Connect'); |
| })) as unknown as typeof connectBtn; |
| } |
| } |
|
|
| if ((connectBtn as unknown as { asElement: () => HTMLElement | null }).asElement()) { |
| logs.push("✅ 'Connect' button found. Clicking..."); |
| ((connectBtn as unknown as { asElement: () => HTMLElement | null }).asElement() as HTMLElement)?.click(); |
| await new Promise(r => setTimeout(r, 1500)); |
|
|
| |
| if (message) { |
| logs.push("📝 Adding custom note..."); |
| const addNoteBtn = await page.evaluateHandle(() => { |
| const buttons = Array.from(document.querySelectorAll('button')); |
| return buttons.find(b => b.innerText.trim() === 'Add a note'); |
| }); |
|
|
| if ((addNoteBtn as unknown as { asElement: () => HTMLElement | null }).asElement()) { |
| ((addNoteBtn as unknown as { asElement: () => HTMLElement | null }).asElement() as HTMLElement)?.click(); |
| await new Promise(r => setTimeout(r, 1000)); |
|
|
| logs.push("⌨️ Typing message..."); |
| await page.waitForSelector('textarea[name="message"]', { timeout: 5000 }); |
| await page.type('textarea[name="message"]', message); |
| await new Promise(r => setTimeout(r, 1000)); |
|
|
| |
| const sendBtn = await page.evaluateHandle(() => { |
| const buttons = Array.from(document.querySelectorAll('button')); |
| return buttons.find(b => b.innerText.trim() === 'Send'); |
| }); |
|
|
| if ((sendBtn as unknown as { asElement: () => HTMLElement | null }).asElement()) { |
| ((sendBtn as unknown as { asElement: () => HTMLElement | null }).asElement() as HTMLElement)?.click(); |
| logs.push("📨 Clicked 'Send'"); |
| } else { |
| throw new Error("Could not find 'Send' button for note"); |
| } |
| } else { |
| logs.push("⚠️ Could not find 'Add a note' button, sending without note (or already sent)"); |
| |
| |
| const sendWithoutNoteBtn = await page.evaluateHandle(() => { |
| const buttons = Array.from(document.querySelectorAll('button')); |
| return buttons.find(b => b.innerText.trim() === 'Send' || b.innerText.trim() === 'Send now'); |
| }); |
| if ((sendWithoutNoteBtn as unknown as { asElement: () => HTMLElement | null }).asElement()) { |
| ((sendWithoutNoteBtn as unknown as { asElement: () => HTMLElement | null }).asElement() as HTMLElement)?.click(); |
| } |
| } |
| } else { |
| |
| const sendBtn = await page.evaluateHandle(() => { |
| const buttons = Array.from(document.querySelectorAll('button')); |
| return buttons.find(b => b.innerText.trim() === 'Send' || b.innerText.trim() === 'Send now'); |
| }); |
| if ((sendBtn as unknown as { asElement: () => HTMLElement | null }).asElement()) { |
| ((sendBtn as unknown as { asElement: () => HTMLElement | null }).asElement() as HTMLElement)?.click(); |
| logs.push("📨 Clicked 'Send' (No note)"); |
| } |
| } |
|
|
| await new Promise(r => setTimeout(r, 2000)); |
| logs.push("✅ Connection request sent!"); |
|
|
| } else { |
| |
| const messageBtn = await page.evaluateHandle(() => { |
| const buttons = Array.from(document.querySelectorAll('button, a')); |
| return buttons.find(b => (b as HTMLElement).innerText.trim() === 'Message'); |
| }); |
|
|
| if ((messageBtn as unknown as { asElement: () => HTMLElement | null }).asElement()) { |
| logs.push("ℹ️ Already connected (Message button found). Sending direct message..."); |
| ((messageBtn as unknown as { asElement: () => HTMLElement | null }).asElement() as HTMLElement)?.click(); |
| |
| |
| logs.push("⚠️ Direct messaging existing connections is not fully implemented in this version. Request marked as skipped."); |
| return { success: true, logs }; |
| } |
|
|
| throw new Error("Could not find 'Connect' or 'Message' button. Profile might be private or unreachable."); |
| } |
|
|
| return { success: true, logs }; |
|
|
| } catch (error) { |
| const msg = error instanceof Error ? error.message : String(error); |
| logs.push(`❌ Error: ${msg}`); |
| |
| return { success: false, logs, error: msg }; |
| } finally { |
| if (browser) { |
| await browser.close(); |
| } |
| } |
| } |
|
|