// filepath: c:\Users\amaizy\Desktop\cvgen\controllers\linkedin.js const axios = require("axios"); const querystring = require("querystring"); const { chromium } = require("playwright"); const clientId = "780w7gsy8eysmj"; const clientSecret = "WPL_AP1.w6OTTkAndAdT3PYF.UZEcwQ=="; const redirectUri = "http://localhost:4200/api/auth/linkedin/callback"; const scope = "openid profile email"; const email = "amaury@maizy.net"; const password = "2Qh*fJrp+l7M6g>8P~}/S$Bc2Yvf&-vd"; let accessToken = ""; const authenticateUser = (req, res) => { const authUrl = `https://www.linkedin.com/oauth/v2/authorization?${querystring.stringify( { response_type: "code", client_id: clientId, redirect_uri: redirectUri, scope: scope, } )}`; res.redirect(authUrl); }; const handleCallback = async (req, res) => { const { code } = req.query; try { const tokenResponse = await axios.post( "https://www.linkedin.com/oauth/v2/accessToken", querystring.stringify({ grant_type: "authorization_code", code: code, redirect_uri: redirectUri, client_id: clientId, client_secret: clientSecret, }) ); const accessToken = tokenResponse.data.access_token; // Stocke dans la session req.session.user = { accessToken, }; // Redirige vers /profile après authentification res.redirect("/profile"); } catch (error) { console.error( "LinkedIn token error:", error.response ? error.response.data : error.message ); res.status(500).send("Error retrieving access token."); } }; const getUserProfile = async (req, res) => { try { const token = req.session?.user?.accessToken; if (!token) { return res.status(401).json({ error: "Aucun accessToken en session." }); } const { data } = await axios.get("https://api.linkedin.com/v2/userinfo", { headers: { Authorization: `Bearer ${token}`, Accept: "application/json", }, }); // Enregistre les infos utiles en session req.session.user.nom = data.given_name + " " + data.family_name; req.session.user.img = data.picture; req.session.user.email = data.email; // Redirige vers /me après authentification res.redirect("/me"); } catch (error) { console.error( "LinkedIn OIDC error:", error.response?.data || error.message ); return res .status(500) .json({ error: "Erreur lors de la récupération du profil." }); } }; const scrapeLinkedInProfile = async (profileUrl) => { console.log("Scraping LinkedIn profile:", profileUrl); const browser = await chromium.launch({ headless: true, args: ["--no-sandbox", "--disable-setuid-sandbox"], }); const page = await browser.newPage(); try { // === 1. Login === await page.goto("https://www.linkedin.com/login", { waitUntil: "domcontentloaded", timeout: 60000, }); console.log("Login page loaded"); await page.screenshot({ path: "public/error/step1_login.png", fullPage: true, }); await page.fill("#username", email, { delay: 50 }); await page.fill("#password", password, { delay: 50 }); await Promise.all([ page.click('[type="submit"]'), page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: 60000 }), ]); console.log("Logged in"); await page.screenshot({ path: "public/error/step2_logged_in.png", fullPage: true, }); // === 2. Aller sur le profil === await page.goto(profileUrl, { waitUntil: "domcontentloaded", timeout: 60000, }); await page.waitForSelector("h1", { timeout: 60000 }); console.log("Profile page loaded"); await page.screenshot({ path: "public/error/step3_profile_loaded.png", fullPage: true, }); // Scroll pour charger contenu lazy await page.evaluate(() => window.scrollBy(0, window.innerHeight)); await page.waitForTimeout(2000); await page.screenshot({ path: "public/error/step4_scrolled.png", fullPage: true, }); // === 3. Extraire les infos === const profileData = await page.evaluate(() => { const getText = (selector) => document.querySelector(selector)?.innerText || null; const getAllText = (selector) => Array.from(document.querySelectorAll(selector)).map((el) => el.innerText.trim() ); return { name: getText("h1"), // headline: getText(""), location: getText( "span.text-body-small.inline.t-black--light.break-words" ), // about: getText(""), // experiences: getAllText( // "" // ), // education: getAllText(""), // skills: getAllText(""), }; }); await page.screenshot({ path: "public/error/step5_data_extracted.png", fullPage: true, }); console.log("Data extracted:", profileData); await browser.close(); return profileData; } catch (err) { console.error("❌ Erreur pendant le scraping:", err.message); await page.screenshot({ path: "public/error/error.png", fullPage: true }); await browser.close(); throw err; } }; module.exports = { authenticateUser, handleCallback, getUserProfile, scrapeLinkedInProfile, };