cvgen/controllers/linkedin.js
2025-09-09 16:17:42 +02:00

153 lines
4.3 KiB
JavaScript

// filepath: c:\Users\amaizy\Desktop\cvgen\controllers\linkedin.js
const axios = require("axios");
const querystring = require("querystring");
const puppeteer = require("puppeteer");
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 puppeteer.launch({
headless: false,
args: ["--no-sandbox", "--disable-setuid-sandbox"],
}); // headless: true si tu veux sans UI
const page = await browser.newPage();
console.log("Using email:", email);
// 1. Aller sur la page de login
await page.goto("https://www.linkedin.com/login", {
waitUntil: "networkidle2",
});
console.log("Login page loaded");
// 2. Connexion avec identifiants
await page.type("#username", email, { delay: 50 });
await page.type("#password", password, { delay: 50 });
await Promise.all([
page.click('[type="submit"]'),
page.waitForNavigation({ waitUntil: "networkidle2" }),
]);
// 3. Aller sur le profil
await page.goto(profileUrl, { waitUntil: "networkidle2" });
// 4. 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(".pv-text-details__left-panel h1"),
headline: getText(".pv-text-details__left-panel .text-body-medium"),
location: getText(".pv-text-details__left-panel .text-body-small"),
about:
getText(".pv-about-section") ||
getText(".display-flex.ph5.pv3 .break-words"),
experiences: getAllText(".pv-entity__summary-info"),
education: getAllText(".pv-education-entity"),
skills: getAllText(".pv-skill-category-entity__name-text"),
};
});
await browser.close();
return profileData;
};
module.exports = {
authenticateUser,
handleCallback,
getUserProfile,
scrapeLinkedInProfile,
};