UNPKG

naukri-ninja

Version:

Naukri automation tool to fetch, filter , and apply for jobs automatically using gen ai.

343 lines (321 loc) 10.9 kB
const prompts = require("@inquirer/prompts"); const { getFileData, getDataFromFile, writeFileData, deleteFolder, } = require("./ioUtils"); const { getProfileDetailsAPI, loginAPI, incrementCounterAPI } = require("../api"); const { getGeminiUserConfiguration } = require("../gemini"); const { jobMatchStrategyMenu, enableManualAnsweringMenu, getConfirmation, selectProfileMenu, enableGenAiMenu, passwordPrompt, } = require("./prompts"); const spinner = require('./spinniesUtils'); const analytics = require('./analyticsUtils'); const { getAuthorInfo } = require("./about"); const constructUser = async (apiData) => { const user = { id: apiData.profile[0].name, userDetails: { email: apiData.user.email, mobile: apiData.user.mobile, name: apiData.profile[0].name, }, skills: apiData.itskills.map((skill) => ({ skillName: skill.skill, experienceYears: skill.experienceTime.year, experienceMonths: skill.experienceTime.month, })), education: apiData.educations.map((edu) => ({ degree: edu.course.value, specialization: edu.specialisation.value, institute: edu.institute, marks: edu.marks, startYear: edu.yearOfStart, completionYear: edu.yearOfCompletion, })), employmentHistory: apiData.employments.map((emp) => ({ designation: emp.designation, organization: emp.organization, startDate: emp.startDate, endDate: emp.endDate || "Present", jobDescription: emp.jobDescription, })), schools: apiData.schools.map((school) => ({ board: school.schoolBoard.value, completionYear: school.schoolCompletionYear, percentage: school.schoolPercentage.value, educationType: school.educationType.value, })), languages: apiData.languages.map((lang) => ({ language: lang.lang, proficiency: lang.proficiency.value, abilities: lang.ability, })), profile: { profileId: apiData.profileAdditional.profileId, keySkills: apiData.profile[0].keySkills, birthDate: apiData.profile[0].birthDate, gender: apiData.profile[0].gender == "M" ? "Male" : "Female", maritalStatus: apiData.profile[0].maritalStatus.value, pinCode: apiData.profile[0].pincode, desiredRole: apiData.profile[0].desiredRole.map((role) => role.value), locationPreference: apiData.profile[0].locationPrefId.map( (loc) => loc.value ), expectedCtc: Number(apiData.profile[0].absoluteExpectedCtc), disability: apiData.profile[0].disability.isDisabled == "N" ? "No" : "Yes", noticePeriod: apiData.profile[0]?.noticePeriod?.value, noticeEndDate: apiData.noticePeriod[0]?.noticeEndDate, currentCtc: Number(apiData.profile[0].absoluteCtc), totalExperience: { year: apiData.profile[0].experience.year, month: apiData.profile[0].experience.month, }, desiredRole: apiData.profile[0].desiredRole.map((role) => role.value), currentLocation: ` ${apiData.profile[0].city.value}, ${apiData.profile[0].country.value}`, }, onlineProfile: apiData.onlineProfile.map((profile) => ({ type: profile.profile, url: profile.url, })), workSamples: apiData.workSample, }; if (!user.onlineProfile.find((profile) => profile.type === "LinkedIn")) { const linkedInProfile = await getLinkedInProfile(); user.onlineProfile.push({ type: "LinkedIn", url: linkedInProfile, }); } return user; }; const manageProfiles = async (profile, loginInfo) => { const profiles = await getFileData("profiles"); const data = { id: profile.id, creds: loginInfo.creds, }; if (!profiles) { writeFileData([data], "profiles"); return [data]; } const profileIndex = profiles.findIndex((p) => p.id === profile.id); if (profileIndex !== -1) { profiles[profileIndex] = { ...profiles[profileIndex], ...data }; } else { profiles.push(data); analytics.setCreateDate(data.id); console.log("new user added") await incrementCounterAPI('newUser'); } return profiles; }; const getLinkedInProfile = async () => { const linkedInProfile = await prompts.input({ message: "Please enter your LinkedIn profile URL.", }); return linkedInProfile; }; const getPreferences = async (user) => { let preferences = await getDataFromFile("preferences", user.id); let doConfiguration = true; //preferences ? false : true; // if (preferences) // doConfiguration = await getConfirmation("Do you want to configure your preferences ?"); if (!preferences) { preferences = {}; } let matchStrategy = "naukriMatching"; //doConfiguration ? null : preferences.matchStrategy; if (!matchStrategy) { matchStrategy = await jobMatchStrategyMenu(); preferences.matchStrategy = matchStrategy; } let enableGenAi = doConfiguration ? null : preferences.enableGenAi; if (enableGenAi === null || enableGenAi === undefined) { enableGenAi = await enableGenAiMenu(); preferences.enableGenAi = enableGenAi; if (enableGenAi || matchStrategy === "ai") { let res = "gemini"; // let res = if (res === "gemini") { const { config, enableGenAi } = await getGeminiUserConfiguration( preferences ); preferences.genAiConfig = config; preferences.enableGenAi = enableGenAi; } preferences.genAiModel = res; preferences.matchStrategy = matchStrategy; } else { const enableManualAnswering = await enableManualAnsweringMenu(); preferences.enableManualAnswering = enableManualAnswering; } } if (!preferences?.desiredRole) preferences.desiredRole = user.profile.desiredRole; const desiredRoles = preferences.desiredRole ? preferences.desiredRole.join(", ") : "None"; if (!preferences?.keywords) { preferences.keywords = user.profile.keySkills .split(",") .map((skill) => skill.trim()); } const keywords = preferences.keywords ? preferences.keywords.join(", ") : "None"; if (doConfiguration || desiredRoles === "None" || keywords === "None") { let res = await prompts.input({ message: `Here are current desired roles: ${desiredRoles} Please enter more desired roles in comma separated format (for example: "Software Engineer, Developer, Analyst") Hit enter to skip\n`, }); // ; if (res !== "") { res.split(",").forEach((role) => { preferences.desiredRole.push(role.trim()); }); } res = await prompts.input({ message: `Current keywords to match the jobs: ${keywords} Please enter more keywords to match the jobs in comma separated format (Java, React, HTML, CSS, etc.) Hit enter to skip Note: Include variation of the keywords as well to match correctly.(for example: use reactjs instead of react.js)\n`, }); if (res !== "") { res.split(",").forEach((keyword) => { preferences.keywords.push(keyword.trim().toLowerCase()); }); } } if (doConfiguration || !preferences.noOfPages || !preferences.dailyQuota) { // let res = await prompts.number({ // message: "Enter the number of pages to search for jobs", // default: 5, // min: 1, // max: 10, // }); preferences.noOfPages = 5; res = await prompts.number({ message: "Enter the number of jobs to apply for on daily basis", default: 40, min: 1, max: 50, description: "This is the number of jobs you want to apply for on daily basis, Maximum quota is 50", }); preferences.dailyQuota = res; } return preferences; }; const getUserProfile = async () => { const response = await getProfileDetailsAPI(); const userData = await response.json(); return constructUser(userData); }; const login = async (profile) => { if (!profile?.creds) { const email = await prompts.input({ message: "Enter your email : " }); const password = await passwordPrompt("Enter your password : "); profile = { creds: { username: email, password: password } }; } spinner.start("Logging in..."); const response = await loginAPI(profile.creds); if (!response.ok) { spinner.fail('There was an error while logging in'); const data = await response.json(); const errors = [ ...(data?.validationErrors || []), ...(data?.fieldValidationErrors || []), ]; errors.forEach((error) => { console.log(`${error.field}: ${error.message} `); }); throw new Error(`HTTP error! status: ${response.status}`); } const cookies = {}; const setCookie = response.headers.get("set-cookie"); const cookie = setCookie.split(";"); cookie?.forEach((cookieStr) => { const [name, value] = cookieStr.split("="); cookies[name] = value; }); // writeToFile(cookies.nauk_at, "accessToken", profile.id); spinner.succeed('Logged in successfully'); const loginInfo = { creds: profile.creds, authorization: cookies.nauk_at, }; return loginInfo; }; const resetAccount = async (user) => { const res = await getConfirmation( "Are you sure you want to reset your account?" ); if (!res) { console.log("Account reset cancelled"); return profiles; } const profiles = await getFileData("profiles"); const profileIndex = profiles.findIndex((p) => p.id === user.id); if (profileIndex !== -1) { profiles.splice(profileIndex, 1); } writeFileData(profiles, "profiles"); deleteFolder(`data/${user.id}`); console.log("Account reset successfully"); return profiles; }; const compressProfile = (profile) => { delete profile.profile.keySkills; profile.employmentHistory.map((item) => delete item.jobDescription); return profile; }; // select a profile const selectProfile = async () => { let selectedProfile = null; // if (profiles.length === 1) { // selectedProfile = profiles[0]; // } const profiles = await getFileData("profiles"); if (!profiles || profiles.length === 0) { console.log("No profiles found. Please add profiles to continue."); return null; } while (!selectedProfile) { const ans = await selectProfileMenu(profiles); if (ans === -1) return null; if (ans === "exit") throw new Error("ExitPromptError"); if (ans === "about") { await getAuthorInfo(); const res = await getConfirmation("Press ENTER to continue...", true, true); continue; }; const index = parseInt(ans, 10); // Convert ans to a number if (isNaN(index) || index <= 0 || index > profiles.length) { console.log("Invalid profile number. Please try again."); } else { selectedProfile = profiles[index - 1]; } } return selectedProfile; }; module.exports = { constructUser, manageProfiles, getLinkedInProfile, login, getUserProfile, getPreferences, resetAccount, selectProfile, compressProfile, };