152 lines
4.1 KiB
JavaScript
152 lines
4.1 KiB
JavaScript
// 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, // headless mode pour serveur
|
|
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
});
|
|
|
|
const page = await browser.newPage();
|
|
|
|
// 1. Login
|
|
await page.goto("https://www.linkedin.com/login", {
|
|
waitUntil: "networkidle",
|
|
});
|
|
console.log("Login page loaded");
|
|
|
|
await page.fill("#username", email);
|
|
await page.fill("#password", password);
|
|
await Promise.all([
|
|
page.click('[type="submit"]'),
|
|
page.waitForNavigation({ waitUntil: "networkidle" }),
|
|
]);
|
|
console.log("Logged in");
|
|
|
|
// 2. Aller sur le profil
|
|
await page.goto(profileUrl, { waitUntil: "networkidle" });
|
|
|
|
// 3. Extraire 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(".pv-text-details__left-panel .text-body-medium"),
|
|
location: getText(".pv-text-details__left-panel .text-body-small"),
|
|
about:
|
|
getText(".pv-about-section") ||
|
|
getText(".pv-shared-text-with-see-more"),
|
|
experiences: getAllText("section#experience-section li"),
|
|
education: getAllText("section#education-section li"),
|
|
skills: getAllText(".pv-skill-category-entity__name-text"),
|
|
};
|
|
});
|
|
|
|
await browser.close();
|
|
return profileData;
|
|
};
|
|
|
|
module.exports = {
|
|
authenticateUser,
|
|
handleCallback,
|
|
getUserProfile,
|
|
scrapeLinkedInProfile,
|
|
};
|