ilml-plugin-linkedin 1.20.20 → 1.20.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/enrichProfiles.mjs +1 -1
- package/dist/version.mjs +1 -1
- package/dist/visit.mjs +2 -2
- package/ilml-plugin.json +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.20.21
|
|
4
|
+
- **`visit` now defaults to 10 profiles per run (was 50) and is labelled HIGH ban-risk.** Visiting profiles with no follow-up action — no message, connect or like — is the exact behaviour LinkedIn flags (the same reason `enrich-profiles` is rated HIGH), and accounts have been warned for it. The lower default keeps you well under the risk; raise it deliberately with `--max=N`, keep the cadence human, and prefer visiting people you'll then actually contact.
|
|
5
|
+
- No schema change.
|
|
6
|
+
|
|
3
7
|
## 1.20.20
|
|
4
8
|
- **Fixed: end-of-run reports from commands that exit the moment they finish (like `connect`) weren't reaching your `NODE_RUN_REPORTS` node.** The report was fired off without waiting for delivery, so such a command could quit before the message actually went out. Each stage now sends and confirms its report before the command exits, so every summary reliably lands — completing what 1.20.19 started.
|
|
5
9
|
- No schema change.
|
package/dist/enrichProfiles.mjs
CHANGED
|
@@ -237,7 +237,7 @@ function findEntryContainer(root) {
|
|
|
237
237
|
candidates.sort((a, b) => b.count - a.count || b.totalText - a.totalText);
|
|
238
238
|
return candidates[0]?.el;
|
|
239
239
|
}
|
|
240
|
-
`});var rn=W(()=>{});import"dotenv/config";import*as N from"fs";import*as O from"path";import{fileURLToPath as Ro}from"url";function Oo(){N.existsSync(re)||N.mkdirSync(re,{recursive:!0})}function No(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";N.writeFileSync(o,n);for(let r=0;r<3;r++)try{N.renameSync(o,e);return}catch(s){if(r<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function jo(e){try{if(N.existsSync(e))return JSON.parse(N.readFileSync(e,"utf-8"))}catch{}return null}function ln(){Lo=null,Po=null,Fo=null,Mo=null,oe=null}function Je(){return oe||(oe=jo(sn)||{visits:[]},Array.isArray(oe.visits)||(oe.visits=[]),oe)}function an(e,t={}){if(!e)return;Oo();let n=Je();n.visits.push({date:new Date().toISOString(),profileUrl:e,name:t.name||null,source:t.source||"manual"});let o=Date.now()-90*24*60*60*1e3;n.visits=n.visits.filter(r=>new Date(r.date).getTime()>o);try{No(sn,n)}catch(r){console.error(`[!] Failed to save visit-log.json: ${r.message}`)}}function cn(e){let t=Je();for(let n=t.visits.length-1;n>=0;n--)if(t.visits[n].profileUrl===e)return t.visits[n].date;return null}var To,Do,re,Ws,Gs,sn,Ys,Ks,Lo,Po,Fo,Mo,oe,We=W(()=>{rn();To=Ro(import.meta.url),Do=O.dirname(O.dirname(To)),re=process.env.DATA_DIR?O.resolve(process.env.DATA_DIR,"market-research"):O.join(Do,"market-research"),Ws=O.join(re,"scouted-jobs.json"),Gs=O.join(re,"discovered-people.json"),sn=O.join(re,"visit-log.json"),Ys=O.join(re,"companies.json"),Ks=O.join(re,"job-decisions.json");Lo=null,Po=null,Fo=null,Mo=null,oe=null});import oo from"puppeteer";import{addExtra as ro}from"puppeteer-extra";import so from"puppeteer-extra-plugin-stealth";he();await Ne();Ee();Ee();import je from"fs";import Qn from"path";var Xn={headless:!1,defaultViewport:null,protocolTimeout:3e5},ye=new Set,Lt=!1;async function Ae(){let e=[...ye].map(t=>{try{return Promise.resolve(t.close()).catch(()=>{})}catch{return Promise.resolve()}});ye.clear(),await Promise.race([Promise.all(e),new Promise(t=>setTimeout(t,5e3))])}function Zn(){if(Lt)return;Lt=!0;let e=t=>async()=>{await Ae(),process.exit(t)};process.once("SIGINT",e(130)),process.once("SIGTERM",e(143)),process.once("SIGHUP",e(129)),process.once("uncaughtException",async t=>{await Ae(),console.error(t&&t.stack||t),process.exit(1)}),process.once("unhandledRejection",async t=>{await Ae(),console.error(t),process.exit(1)}),process.stdout.on("error",async t=>{t&&t.code==="EPIPE"&&(await Ae(),process.exit(0))}),process.on("exit",()=>{for(let t of ye)try{let n=t.process&&t.process();n&&n.kill("SIGKILL")}catch{}})}function Le(e){return Qn.join(e,".ilml-browser-session.json")}function Ut(e,t={}){try{je.writeFileSync(Le(e),JSON.stringify({ownerPid:process.pid,startedAt:Date.now(),...t}))}catch{}}function eo(e,t){try{let n=t.wsEndpoint&&t.wsEndpoint();Ut(e,n?{wsEndpoint:n}:{})}catch{}}function to(e){let t=qt(e);t&&t.ownerPid===process.pid&&ge(e)}function ge(e){try{je.rmSync(Le(e),{force:!0})}catch{}}function qt(e){try{return JSON.parse(je.readFileSync(Le(e),"utf-8"))}catch{return null}}function Pt(e){if(!e||e===process.pid)return!0;try{return process.kill(e,0),!0}catch(t){return!t||t.code!=="ESRCH"}}function Ft(e,t,n){let o,r=new Promise(i=>{o=setTimeout(()=>i(n),t)}),s=Promise.resolve(e).then(i=>(clearTimeout(o),i),i=>{throw clearTimeout(o),i});return Promise.race([s,r])}async function Mt(e,t,{probeTimeoutMs:n=4e3}={}){let o=qt(t);if(!o)return"free";if(!o.wsEndpoint)return o.ownerPid&&o.ownerPid!==process.pid&&Pt(o.ownerPid)?"live":(ge(t),"free");let r=Symbol("timeout"),s=e.connect({browserWSEndpoint:o.wsEndpoint,defaultViewport:null}).catch(()=>null),i=await Ft(s,n,r);if(i===r)return s.then(a=>{if(a)try{a.disconnect()}catch{}}),"live";if(!i)return ge(t),"free";if(Pt(o.ownerPid)){try{i.disconnect()}catch{}return"live"}if(console.log(" (recovered an orphaned automation browser from a previous run \u2014 closing it)"),await Ft(i.close().then(()=>!0).catch(()=>!1),n,!1))return ge(t),"free";try{i.disconnect()}catch{}return"live"}function no(e,t){e&&(Zn(),ye.add(e),e.on("disconnected",()=>{ye.delete(e),t&&ge(t)}))}async function Bt(e,t={}){let n=xt(),o={...Xn,...t,userDataDir:n},r=`Could not open the LinkedIn automation Chrome profile at ${n}. Another LinkedIn automation command is already running (or its window is still open) \u2014 wait for it to finish or close that window, then retry.`;if(await Mt(e,n)==="live")throw new Error(r);Ut(n);let s;try{s=await e.launch(o)}catch{if(await Mt(e,n)==="live")throw new Error(r);$t(n);try{s=await e.launch(o)}catch(l){throw to(n),new Error(`Could not open the LinkedIn automation Chrome profile at ${n}. If a LinkedIn automation window is already open, close it and retry. (Chrome: ${l.message})`)}}return no(s,n),eo(n,s),{browser:s,profileDir:n,needsSeed:At(n)}}async function Ht(e,t){if(!t||!t.needsSeed)return!1;if(!kt())return console.warn(" \u26A0 No saved LinkedIn session found. If this command comes back empty or redirects to login, run: ilml linkedin login"),!1;try{return await e.setCookie(...vt()),_t(t.profileDir),console.log(" (seeded persistent profile from saved cookies \u2014 one-time)"),!0}catch{return!1}}var Vt=ro(oo);Vt.use(so());async function Jt(){let{browser:e,profileDir:t,needsSeed:n}=await Bt(Vt),o=await e.newPage();return await o.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})}),await Ht(o,{profileDir:t,needsSeed:n}),console.log(" Browser launched (persistent profile)."),{browser:e,page:o}}he();await we();async function Io(e,t){let n=e.url();if(n.includes("/in/"))return!1;if(n.includes("/login")||n.includes("/authwall")||n.includes("/checkpoint"))throw new Error("Session expired \u2014 redirected to login. Run: ilml linkedin login");console.log(` Off-track: ${n.slice(0,80)}`),console.log(" Navigating back to profile..."),await e.goto(t,{waitUntil:"domcontentloaded",timeout:6e4}),await k(3e3);let o=e.url();return o.includes("/in/")||console.log(` Recovery failed \u2014 still on: ${o.slice(0,80)}`),!0}async function on(e,t,n={}){let{extractExperience:o=!1,extractFeatured:r=!1,extractEducation:s=!1,extractSkills:i=!1,extractRecommendations:l=!1,extractCertifications:a=!1}=n;try{await ue(e,t,{readySelector:".scaffold-layout__main, main.scaffold-layout__main, .pv-top-card, section.pv-top-card",label:"profile"}),await k(1500),await Tt(e)&&(console.log(" Dismissed Premium overlay"),await k(1500),await Io(e,t));let c=0;for(let x=0;x<2;x++){let Z=await e.evaluate(()=>document.body.scrollHeight);for(let V=1;V<=6;V++)await e.evaluate(D=>window.scrollTo(0,D),Z*V/6),await k(900);let ee=await e.evaluate(()=>document.body.scrollHeight);if(ee===c)break;c=ee}await k(800);let m=null;try{let x=e.url();x&&/\/in\/[^/?#]/.test(x)&&(m=x.split("?")[0].replace(/\/+$/,""))}catch{}let f=await e.evaluate(()=>{let x=new Set(["Highlights","About","Activity","Experience","Education","Skills","Recommendations","Languages","Interests","Volunteer experience","Featured","Honors & awards","Honors and awards","Projects","Publications","Patents","Certifications","Licenses & certifications","Courses","Test scores","Organizations"]),Z=new Set(["Ad Options","Don\u2019t want to see this","0 notifications","Explore Premium profiles","You might like","People you may know","Suggested for you","More profiles for you","Promoted"]),ee=h=>{let g=(h||"").trim().replace(/^[·•]\s*/,"");return/^(1st|2nd|3rd|she\/her|he\/him|they\/them|their\/them)$/i.test(g)},V={name:null,title:null,location:null,currentCompany:null,currentSchool:null,connectionDegree:null,hasVerifiedBadge:!1,photoUrl:null,connectionsLevel:null,mutualConnectionsCount:null,followers:null,openToWork:!1,hiring:!1,about:null,topSkills:null,availableSections:[]},D=document.querySelector("main");if(!D)return V;let b=[...Array.from(D.querySelectorAll("h1")),...Array.from(D.querySelectorAll("h2"))],$=Array.from(D.querySelectorAll("h2")),ae=b.find(h=>{let g=h.textContent&&h.textContent.trim(),y=(g||"").replace(/\s*\(\d+\)$/,"").trim();return g&&g.length>1&&g.length<100&&!x.has(y)&&!Z.has(y)}),ce=ae?ae.textContent.trim():null,I=ae?ae.parentElement:null;for(;I&&I!==D&&!Array.from(I.children).filter(g=>g.tagName==="P").some(g=>{let y=g.textContent&&g.textContent.trim();return y&&y.length>5&&!ee(y)});)I=I.parentElement;let it=null,lt=null,at=null,Te=null;if(I&&I!==D){let h=Array.from(I.children).filter(y=>y.tagName==="P").filter(y=>{let J=y.textContent&&y.textContent.trim();return J&&J.length>5&&!ee(J)});if(h[0]&&(it=h[0].textContent.trim()),h[1]){let y=h[1].textContent.trim().split(/\s*[·•]\s*/);lt=y[0]||null,y.length>1&&(at=y.slice(1).join(" \xB7 "))}let g=Array.from(I.children).filter(y=>y.tagName==="DIV");for(let y=g.length-1;y>=0;y--){let J=g[y].textContent&&g[y].textContent.trim();if(J&&!(ce&&J.startsWith(ce))&&(Te=J.split(/[·•]/)[0].trim(),Te))break}}let ct=null;if(I){let h=I.querySelectorAll("p, span, div");for(let g of h){let y=(g.textContent||"").trim().replace(/^[·•]\s*/,"");if(y==="1st"||y==="2nd"||y==="3rd"){ct=y;break}}}let Rn=!!D.querySelector('svg[data-test-icon="verified"], [aria-label*="Verified" i]'),ut=document.querySelector('main img[width="200"], button img[alt*="photo" i]'),Tn=ut&&ut.src||null,Ce=D.textContent||"",ft=null,dt=Ce.match(/(\d+\+?)\s+connections\b/i);dt&&(ft=dt[1]+" connections");let xe=null,mt=Ce.match(/(\d+)\s+mutual connection/i),pt=Ce.match(/and\s+(\d+)\s+other mutual/i),Dn=Ce.match(/([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+and\s+([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+are\s+mutual/);mt?xe=parseInt(mt[1],10):pt?xe=2+parseInt(pt[1],10):Dn&&(xe=2);let ht=null,gt=null,yt=$.find(h=>h.textContent&&h.textContent.trim()==="About");if(yt){let h=yt.parentElement;for(;h&&h.tagName!=="SECTION";)h=h.parentElement;if(h){let g=(h.textContent||"").trim();g=g.replace(/^About\s*/,"").trim();let y=g.match(/Top skills\s*(.+?)$/i);if(y){let De=y[1].split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim().split(/\s*[·•]\s*/).map(Ln=>Ln.trim()).filter(Boolean);De.length>0&&De.length<10&&(gt=De)}g=g.split(/\n?…\s*more/i)[0].trim(),g=g.split(/\n?\.\.\.\s*more/i)[0].trim(),g=g.split(/\bTop skills\b/i)[0].trim(),g=g.split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim(),ht=g||null}}let wt=null,bt=$.find(h=>h.textContent&&h.textContent.trim()==="Activity");if(bt){let h=bt.parentElement;for(;h&&h.tagName!=="SECTION";)h=h.parentElement;if(h){let g=(h.textContent||"").slice(0,200).match(/([\d,]+)\s+followers/i);g&&(wt=parseInt(g[1].replace(/,/g,""),10))}}let On=[...new Set($.map(h=>(h.textContent||"").trim().replace(/\s*\(\d+\)$/,"").trim()).filter(h=>h&&h.length>1&&h.length<60&&h!==ce).filter(h=>x.has(h)))],me=document.body.textContent||"",Nn=/#OpenToWork\b/i.test(me)||/\bOpen to work\b/.test(me),jn=/#Hiring\b/i.test(me)||/\bI'?m hiring\b/i.test(me)||/\bWe are hiring\b/i.test(me);return{name:ce,title:it,location:Te,currentCompany:lt,currentSchool:at,connectionDegree:ct,hasVerifiedBadge:Rn,photoUrl:Tn,connectionsLevel:ft,mutualConnectionsCount:xe,followers:wt,openToWork:Nn,hiring:jn,about:ht,topSkills:gt,availableSections:On}});console.log(`Viewed: ${f.name} - ${f.title}`);let p=null,v=null,w=null,A=null,Q=null,X=null;if(o||r||s||i||l||a){let x=await Promise.resolve().then(()=>(nn(),tn));o&&(p=await x.extractExperienceFromDetails(e,t),console.log(` + Experience: ${p.length} entries`)),r&&(v=await x.extractFeaturedFromDetails(e,t),console.log(` + Featured: ${v.length} entries`)),s&&(w=await x.extractEducationFromDetails(e,t),console.log(` + Education: ${w.length} entries`)),i&&(A=await x.extractSkillsFromDetails(e,t),console.log(` + Skills: ${A.length} entries`)),l&&(Q=await x.extractRecommendationsFromDetails(e,t),console.log(` + Recommendations: ${Q.length} entries`)),a&&(X=await x.extractCertificationsFromDetails(e,t),console.log(` + Certifications: ${X.length} entries`))}return{...f,profileUrl:t,resolvedUrl:m,...p!==null&&{experience:p},...v!==null&&{featured:v},...w!==null&&{education:w},...A!==null&&{skillsFull:A},...Q!==null&&{recommendations:Q},...X!==null&&{certifications:X}}}catch(u){console.error(`Error viewing profile ${t}:`,u.message);try{await e.goto("about:blank",{timeout:1e4}),await k(1e3)}catch{}if(/session expired|bounced to|authwall|checkpoint|redirected to login/i.test(u.message||""))throw u;return null}}We();await we();he();async function un(e,t,n={}){let{maxVisits:o=50,delayBetween:r=4e3,skipRecentDays:s=7,verbose:i=!1,extractExperience:l=!1}=n,a={visited:0,skipped:0,errors:0},u=Date.now()-s*24*60*60*1e3;for(let c of t){if(a.visited>=o)break;if(!c.profileUrl)continue;let m=cn(c.profileUrl);if(m&&new Date(m).getTime()>u){a.skipped++;continue}try{let f=await on(e,c.profileUrl,{extractExperience:l}),p=c.source||"visit";if(f){let w={...f,source:p};l&&Array.isArray(f.experience)&&f.experience.length>0&&(w._lastEnrichedAt=new Date().toISOString()),He(c.profileUrl,w),f.resolvedUrl&&Ve(c.profileUrl,f.resolvedUrl)}an(c.profileUrl,{name:f?.name||c.name||null,source:p}),a.visited++,i?console.log(` [${a.visited}] ${f?.name||c.profileUrl} \u2014 ${f?.title?.slice(0,40)||"?"}`):a.visited%10===0&&console.log(` Visited: ${a.visited}`);let v=Math.random()*4e3;await k(r+v)}catch(f){a.errors++,i&&console.error(` Error visiting ${c.name||c.profileUrl}: ${f.message?.slice(0,60)}`);let p=(f.message||"").toLowerCase();if(p.includes("detached")||p.includes("session closed")||p.includes("target closed")||p.includes("session expired")||p.includes("bounced to")||p.includes("authwall")||p.includes("checkpoint")||p.includes("redirected to login")){console.log(" Fatal browser/session error \u2014 stopping visits");break}}}return a}await we();import"dotenv/config";import*as j from"fs";import*as Y from"path";import{fileURLToPath as Go}from"url";import"dotenv/config";import{createGraphClient as Uo,resolveToken as qo}from"@ilivemylife/graph-sdk";import{config as Bo}from"dotenv";Bo();var fn=qo()||process.env.TOKEN;fn||(console.error(`
|
|
240
|
+
`});var rn=W(()=>{});import"dotenv/config";import*as N from"fs";import*as O from"path";import{fileURLToPath as Ro}from"url";function Oo(){N.existsSync(re)||N.mkdirSync(re,{recursive:!0})}function No(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";N.writeFileSync(o,n);for(let r=0;r<3;r++)try{N.renameSync(o,e);return}catch(s){if(r<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function jo(e){try{if(N.existsSync(e))return JSON.parse(N.readFileSync(e,"utf-8"))}catch{}return null}function ln(){Lo=null,Po=null,Fo=null,Mo=null,oe=null}function Je(){return oe||(oe=jo(sn)||{visits:[]},Array.isArray(oe.visits)||(oe.visits=[]),oe)}function an(e,t={}){if(!e)return;Oo();let n=Je();n.visits.push({date:new Date().toISOString(),profileUrl:e,name:t.name||null,source:t.source||"manual"});let o=Date.now()-90*24*60*60*1e3;n.visits=n.visits.filter(r=>new Date(r.date).getTime()>o);try{No(sn,n)}catch(r){console.error(`[!] Failed to save visit-log.json: ${r.message}`)}}function cn(e){let t=Je();for(let n=t.visits.length-1;n>=0;n--)if(t.visits[n].profileUrl===e)return t.visits[n].date;return null}var To,Do,re,Ws,Gs,sn,Ys,Ks,Lo,Po,Fo,Mo,oe,We=W(()=>{rn();To=Ro(import.meta.url),Do=O.dirname(O.dirname(To)),re=process.env.DATA_DIR?O.resolve(process.env.DATA_DIR,"market-research"):O.join(Do,"market-research"),Ws=O.join(re,"scouted-jobs.json"),Gs=O.join(re,"discovered-people.json"),sn=O.join(re,"visit-log.json"),Ys=O.join(re,"companies.json"),Ks=O.join(re,"job-decisions.json");Lo=null,Po=null,Fo=null,Mo=null,oe=null});import oo from"puppeteer";import{addExtra as ro}from"puppeteer-extra";import so from"puppeteer-extra-plugin-stealth";he();await Ne();Ee();Ee();import je from"fs";import Qn from"path";var Xn={headless:!1,defaultViewport:null,protocolTimeout:3e5},ye=new Set,Lt=!1;async function Ae(){let e=[...ye].map(t=>{try{return Promise.resolve(t.close()).catch(()=>{})}catch{return Promise.resolve()}});ye.clear(),await Promise.race([Promise.all(e),new Promise(t=>setTimeout(t,5e3))])}function Zn(){if(Lt)return;Lt=!0;let e=t=>async()=>{await Ae(),process.exit(t)};process.once("SIGINT",e(130)),process.once("SIGTERM",e(143)),process.once("SIGHUP",e(129)),process.once("uncaughtException",async t=>{await Ae(),console.error(t&&t.stack||t),process.exit(1)}),process.once("unhandledRejection",async t=>{await Ae(),console.error(t),process.exit(1)}),process.stdout.on("error",async t=>{t&&t.code==="EPIPE"&&(await Ae(),process.exit(0))}),process.on("exit",()=>{for(let t of ye)try{let n=t.process&&t.process();n&&n.kill("SIGKILL")}catch{}})}function Le(e){return Qn.join(e,".ilml-browser-session.json")}function Ut(e,t={}){try{je.writeFileSync(Le(e),JSON.stringify({ownerPid:process.pid,startedAt:Date.now(),...t}))}catch{}}function eo(e,t){try{let n=t.wsEndpoint&&t.wsEndpoint();Ut(e,n?{wsEndpoint:n}:{})}catch{}}function to(e){let t=qt(e);t&&t.ownerPid===process.pid&&ge(e)}function ge(e){try{je.rmSync(Le(e),{force:!0})}catch{}}function qt(e){try{return JSON.parse(je.readFileSync(Le(e),"utf-8"))}catch{return null}}function Pt(e){if(!e||e===process.pid)return!0;try{return process.kill(e,0),!0}catch(t){return!t||t.code!=="ESRCH"}}function Ft(e,t,n){let o,r=new Promise(i=>{o=setTimeout(()=>i(n),t)}),s=Promise.resolve(e).then(i=>(clearTimeout(o),i),i=>{throw clearTimeout(o),i});return Promise.race([s,r])}async function Mt(e,t,{probeTimeoutMs:n=4e3}={}){let o=qt(t);if(!o)return"free";if(!o.wsEndpoint)return o.ownerPid&&o.ownerPid!==process.pid&&Pt(o.ownerPid)?"live":(ge(t),"free");let r=Symbol("timeout"),s=e.connect({browserWSEndpoint:o.wsEndpoint,defaultViewport:null}).catch(()=>null),i=await Ft(s,n,r);if(i===r)return s.then(a=>{if(a)try{a.disconnect()}catch{}}),"live";if(!i)return ge(t),"free";if(Pt(o.ownerPid)){try{i.disconnect()}catch{}return"live"}if(console.log(" (recovered an orphaned automation browser from a previous run \u2014 closing it)"),await Ft(i.close().then(()=>!0).catch(()=>!1),n,!1))return ge(t),"free";try{i.disconnect()}catch{}return"live"}function no(e,t){e&&(Zn(),ye.add(e),e.on("disconnected",()=>{ye.delete(e),t&&ge(t)}))}async function Bt(e,t={}){let n=xt(),o={...Xn,...t,userDataDir:n},r=`Could not open the LinkedIn automation Chrome profile at ${n}. Another LinkedIn automation command is already running (or its window is still open) \u2014 wait for it to finish or close that window, then retry.`;if(await Mt(e,n)==="live")throw new Error(r);Ut(n);let s;try{s=await e.launch(o)}catch{if(await Mt(e,n)==="live")throw new Error(r);$t(n);try{s=await e.launch(o)}catch(l){throw to(n),new Error(`Could not open the LinkedIn automation Chrome profile at ${n}. If a LinkedIn automation window is already open, close it and retry. (Chrome: ${l.message})`)}}return no(s,n),eo(n,s),{browser:s,profileDir:n,needsSeed:At(n)}}async function Ht(e,t){if(!t||!t.needsSeed)return!1;if(!kt())return console.warn(" \u26A0 No saved LinkedIn session found. If this command comes back empty or redirects to login, run: ilml linkedin login"),!1;try{return await e.setCookie(...vt()),_t(t.profileDir),console.log(" (seeded persistent profile from saved cookies \u2014 one-time)"),!0}catch{return!1}}var Vt=ro(oo);Vt.use(so());async function Jt(){let{browser:e,profileDir:t,needsSeed:n}=await Bt(Vt),o=await e.newPage();return await o.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})}),await Ht(o,{profileDir:t,needsSeed:n}),console.log(" Browser launched (persistent profile)."),{browser:e,page:o}}he();await we();async function Io(e,t){let n=e.url();if(n.includes("/in/"))return!1;if(n.includes("/login")||n.includes("/authwall")||n.includes("/checkpoint"))throw new Error("Session expired \u2014 redirected to login. Run: ilml linkedin login");console.log(` Off-track: ${n.slice(0,80)}`),console.log(" Navigating back to profile..."),await e.goto(t,{waitUntil:"domcontentloaded",timeout:6e4}),await k(3e3);let o=e.url();return o.includes("/in/")||console.log(` Recovery failed \u2014 still on: ${o.slice(0,80)}`),!0}async function on(e,t,n={}){let{extractExperience:o=!1,extractFeatured:r=!1,extractEducation:s=!1,extractSkills:i=!1,extractRecommendations:l=!1,extractCertifications:a=!1}=n;try{await ue(e,t,{readySelector:".scaffold-layout__main, main.scaffold-layout__main, .pv-top-card, section.pv-top-card",label:"profile"}),await k(1500),await Tt(e)&&(console.log(" Dismissed Premium overlay"),await k(1500),await Io(e,t));let c=0;for(let x=0;x<2;x++){let Z=await e.evaluate(()=>document.body.scrollHeight);for(let V=1;V<=6;V++)await e.evaluate(D=>window.scrollTo(0,D),Z*V/6),await k(900);let ee=await e.evaluate(()=>document.body.scrollHeight);if(ee===c)break;c=ee}await k(800);let m=null;try{let x=e.url();x&&/\/in\/[^/?#]/.test(x)&&(m=x.split("?")[0].replace(/\/+$/,""))}catch{}let f=await e.evaluate(()=>{let x=new Set(["Highlights","About","Activity","Experience","Education","Skills","Recommendations","Languages","Interests","Volunteer experience","Featured","Honors & awards","Honors and awards","Projects","Publications","Patents","Certifications","Licenses & certifications","Courses","Test scores","Organizations"]),Z=new Set(["Ad Options","Don\u2019t want to see this","0 notifications","Explore Premium profiles","You might like","People you may know","Suggested for you","More profiles for you","Promoted"]),ee=h=>{let g=(h||"").trim().replace(/^[·•]\s*/,"");return/^(1st|2nd|3rd|she\/her|he\/him|they\/them|their\/them)$/i.test(g)},V={name:null,title:null,location:null,currentCompany:null,currentSchool:null,connectionDegree:null,hasVerifiedBadge:!1,photoUrl:null,connectionsLevel:null,mutualConnectionsCount:null,followers:null,openToWork:!1,hiring:!1,about:null,topSkills:null,availableSections:[]},D=document.querySelector("main");if(!D)return V;let b=[...Array.from(D.querySelectorAll("h1")),...Array.from(D.querySelectorAll("h2"))],$=Array.from(D.querySelectorAll("h2")),ae=b.find(h=>{let g=h.textContent&&h.textContent.trim(),y=(g||"").replace(/\s*\(\d+\)$/,"").trim();return g&&g.length>1&&g.length<100&&!x.has(y)&&!Z.has(y)}),ce=ae?ae.textContent.trim():null,I=ae?ae.parentElement:null;for(;I&&I!==D&&!Array.from(I.children).filter(g=>g.tagName==="P").some(g=>{let y=g.textContent&&g.textContent.trim();return y&&y.length>5&&!ee(y)});)I=I.parentElement;let it=null,lt=null,at=null,Te=null;if(I&&I!==D){let h=Array.from(I.children).filter(y=>y.tagName==="P").filter(y=>{let J=y.textContent&&y.textContent.trim();return J&&J.length>5&&!ee(J)});if(h[0]&&(it=h[0].textContent.trim()),h[1]){let y=h[1].textContent.trim().split(/\s*[·•]\s*/);lt=y[0]||null,y.length>1&&(at=y.slice(1).join(" \xB7 "))}let g=Array.from(I.children).filter(y=>y.tagName==="DIV");for(let y=g.length-1;y>=0;y--){let J=g[y].textContent&&g[y].textContent.trim();if(J&&!(ce&&J.startsWith(ce))&&(Te=J.split(/[·•]/)[0].trim(),Te))break}}let ct=null;if(I){let h=I.querySelectorAll("p, span, div");for(let g of h){let y=(g.textContent||"").trim().replace(/^[·•]\s*/,"");if(y==="1st"||y==="2nd"||y==="3rd"){ct=y;break}}}let Rn=!!D.querySelector('svg[data-test-icon="verified"], [aria-label*="Verified" i]'),ut=document.querySelector('main img[width="200"], button img[alt*="photo" i]'),Tn=ut&&ut.src||null,Ce=D.textContent||"",ft=null,dt=Ce.match(/(\d+\+?)\s+connections\b/i);dt&&(ft=dt[1]+" connections");let xe=null,mt=Ce.match(/(\d+)\s+mutual connection/i),pt=Ce.match(/and\s+(\d+)\s+other mutual/i),Dn=Ce.match(/([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+and\s+([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+are\s+mutual/);mt?xe=parseInt(mt[1],10):pt?xe=2+parseInt(pt[1],10):Dn&&(xe=2);let ht=null,gt=null,yt=$.find(h=>h.textContent&&h.textContent.trim()==="About");if(yt){let h=yt.parentElement;for(;h&&h.tagName!=="SECTION";)h=h.parentElement;if(h){let g=(h.textContent||"").trim();g=g.replace(/^About\s*/,"").trim();let y=g.match(/Top skills\s*(.+?)$/i);if(y){let De=y[1].split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim().split(/\s*[·•]\s*/).map(Ln=>Ln.trim()).filter(Boolean);De.length>0&&De.length<10&&(gt=De)}g=g.split(/\n?…\s*more/i)[0].trim(),g=g.split(/\n?\.\.\.\s*more/i)[0].trim(),g=g.split(/\bTop skills\b/i)[0].trim(),g=g.split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim(),ht=g||null}}let wt=null,bt=$.find(h=>h.textContent&&h.textContent.trim()==="Activity");if(bt){let h=bt.parentElement;for(;h&&h.tagName!=="SECTION";)h=h.parentElement;if(h){let g=(h.textContent||"").slice(0,200).match(/([\d,]+)\s+followers/i);g&&(wt=parseInt(g[1].replace(/,/g,""),10))}}let On=[...new Set($.map(h=>(h.textContent||"").trim().replace(/\s*\(\d+\)$/,"").trim()).filter(h=>h&&h.length>1&&h.length<60&&h!==ce).filter(h=>x.has(h)))],me=document.body.textContent||"",Nn=/#OpenToWork\b/i.test(me)||/\bOpen to work\b/.test(me),jn=/#Hiring\b/i.test(me)||/\bI'?m hiring\b/i.test(me)||/\bWe are hiring\b/i.test(me);return{name:ce,title:it,location:Te,currentCompany:lt,currentSchool:at,connectionDegree:ct,hasVerifiedBadge:Rn,photoUrl:Tn,connectionsLevel:ft,mutualConnectionsCount:xe,followers:wt,openToWork:Nn,hiring:jn,about:ht,topSkills:gt,availableSections:On}});console.log(`Viewed: ${f.name} - ${f.title}`);let p=null,v=null,w=null,A=null,Q=null,X=null;if(o||r||s||i||l||a){let x=await Promise.resolve().then(()=>(nn(),tn));o&&(p=await x.extractExperienceFromDetails(e,t),console.log(` + Experience: ${p.length} entries`)),r&&(v=await x.extractFeaturedFromDetails(e,t),console.log(` + Featured: ${v.length} entries`)),s&&(w=await x.extractEducationFromDetails(e,t),console.log(` + Education: ${w.length} entries`)),i&&(A=await x.extractSkillsFromDetails(e,t),console.log(` + Skills: ${A.length} entries`)),l&&(Q=await x.extractRecommendationsFromDetails(e,t),console.log(` + Recommendations: ${Q.length} entries`)),a&&(X=await x.extractCertificationsFromDetails(e,t),console.log(` + Certifications: ${X.length} entries`))}return{...f,profileUrl:t,resolvedUrl:m,...p!==null&&{experience:p},...v!==null&&{featured:v},...w!==null&&{education:w},...A!==null&&{skillsFull:A},...Q!==null&&{recommendations:Q},...X!==null&&{certifications:X}}}catch(u){console.error(`Error viewing profile ${t}:`,u.message);try{await e.goto("about:blank",{timeout:1e4}),await k(1e3)}catch{}if(/session expired|bounced to|authwall|checkpoint|redirected to login/i.test(u.message||""))throw u;return null}}We();await we();he();async function un(e,t,n={}){let{maxVisits:o=10,delayBetween:r=4e3,skipRecentDays:s=7,verbose:i=!1,extractExperience:l=!1}=n,a={visited:0,skipped:0,errors:0},u=Date.now()-s*24*60*60*1e3;for(let c of t){if(a.visited>=o)break;if(!c.profileUrl)continue;let m=cn(c.profileUrl);if(m&&new Date(m).getTime()>u){a.skipped++;continue}try{let f=await on(e,c.profileUrl,{extractExperience:l}),p=c.source||"visit";if(f){let w={...f,source:p};l&&Array.isArray(f.experience)&&f.experience.length>0&&(w._lastEnrichedAt=new Date().toISOString()),He(c.profileUrl,w),f.resolvedUrl&&Ve(c.profileUrl,f.resolvedUrl)}an(c.profileUrl,{name:f?.name||c.name||null,source:p}),a.visited++,i?console.log(` [${a.visited}] ${f?.name||c.profileUrl} \u2014 ${f?.title?.slice(0,40)||"?"}`):a.visited%10===0&&console.log(` Visited: ${a.visited}`);let v=Math.random()*4e3;await k(r+v)}catch(f){a.errors++,i&&console.error(` Error visiting ${c.name||c.profileUrl}: ${f.message?.slice(0,60)}`);let p=(f.message||"").toLowerCase();if(p.includes("detached")||p.includes("session closed")||p.includes("target closed")||p.includes("session expired")||p.includes("bounced to")||p.includes("authwall")||p.includes("checkpoint")||p.includes("redirected to login")){console.log(" Fatal browser/session error \u2014 stopping visits");break}}}return a}await we();import"dotenv/config";import*as j from"fs";import*as Y from"path";import{fileURLToPath as Go}from"url";import"dotenv/config";import{createGraphClient as Uo,resolveToken as qo}from"@ilivemylife/graph-sdk";import{config as Bo}from"dotenv";Bo();var fn=qo()||process.env.TOKEN;fn||(console.error(`
|
|
241
241
|
[FATAL] No iLiveMyLife token found.`),console.error(" The bot uses Lifebot AI to decide which jobs to apply for"),console.error(` and how to fill application forms. It cannot work without it.
|
|
242
242
|
`),console.error(" Easiest way:"),console.error(" 1. npm install -g @ilivemylife/graph-sdk"),console.error(` 2. ilml login your@email.com yourpassword
|
|
243
243
|
`),console.error(` Or add ILML_TOKEN=... to .env
|
package/dist/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as e}from"fs";import{fileURLToPath as r}from"url";function i(){return"1.20.
|
|
1
|
+
import{readFileSync as e}from"fs";import{fileURLToPath as r}from"url";function i(){return"1.20.21";try{let n=new URL("./ilml-plugin.json",import.meta.url);return JSON.parse(e(r(n),"utf-8")).version}catch{return"unknown"}}console.log(`ilml-plugin-linkedin ${i()}`);
|
package/dist/visit.mjs
CHANGED
|
@@ -237,7 +237,7 @@ function findEntryContainer(root) {
|
|
|
237
237
|
candidates.sort((a, b) => b.count - a.count || b.totalText - a.totalText);
|
|
238
238
|
return candidates[0]?.el;
|
|
239
239
|
}
|
|
240
|
-
`});var ln=Y(()=>{});import"dotenv/config";import*as N from"fs";import*as j from"path";import{fileURLToPath as Do}from"url";function Po(){N.existsSync(ne)||N.mkdirSync(ne,{recursive:!0})}function Fo(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";N.writeFileSync(o,n);for(let r=0;r<3;r++)try{N.renameSync(o,e);return}catch(s){if(r<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function He(e){try{if(N.existsSync(e))return JSON.parse(N.readFileSync(e,"utf-8"))}catch{}return null}function cn(){ve=null,qo=null,ke=null,Uo=null,te=null}function Mo(){return ve||(ve=He(No)||{},ve)}function Ie(){return ke||(ke=He(Lo)||{},ke)}function fe(){return te||(te=He(an)||{visits:[]},Array.isArray(te.visits)||(te.visits=[]),te)}function un(e,t={}){if(!e)return;Po();let n=fe();n.visits.push({date:new Date().toISOString(),profileUrl:e,name:t.name||null,source:t.source||"manual"});let o=Date.now()-90*24*60*60*1e3;n.visits=n.visits.filter(r=>new Date(r.date).getTime()>o);try{Fo(an,n)}catch(r){console.error(`[!] Failed to save visit-log.json: ${r.message}`)}}function dn(e){let t=fe();for(let n=t.visits.length-1;n>=0;n--)if(t.visits[n].profileUrl===e)return t.visits[n].date;return null}function fn(){let e=Mo(),t=Ie(),n=Object.values(e),o=Object.values(t),r={};for(let u of n)u.company&&(r[u.company]=(r[u.company]||0)+1);let s=Object.entries(r).sort((u,f)=>f[1]-u[1]).slice(0,5).map(([u,f])=>`${u} (${f})`),i={};for(let u of n)for(let f of u.techStack||[])i[f]=(i[f]||0)+1;let a=Object.entries(i).sort((u,f)=>f[1]-u[1]).slice(0,8).map(([u,f])=>`${u} (${f})`),l=fe(),d=new Date().toISOString().slice(0,10),c=l.visits.filter(u=>u.date.startsWith(d)).length,p=new Set(l.visits.map(u=>u.profileUrl)).size;return{totalJobs:n.length,totalRecruiters:o.length,topCompanies:s,topTech:a,totalVisited:p,todayVisited:c,lastScoutDate:n.length>0?n.sort((u,f)=>(f.scoutedAt||"").localeCompare(u.scoutedAt||""))[0]?.scoutedAt:null}}var Oo,jo,ne,No,Lo,an,Js,Ws,ve,qo,ke,Uo,te,Re=Y(()=>{ln();Oo=Do(import.meta.url),jo=j.dirname(j.dirname(Oo)),ne=process.env.DATA_DIR?j.resolve(process.env.DATA_DIR,"market-research"):j.join(jo,"market-research"),No=j.join(ne,"scouted-jobs.json"),Lo=j.join(ne,"discovered-people.json"),an=j.join(ne,"visit-log.json"),Js=j.join(ne,"companies.json"),Ws=j.join(ne,"job-decisions.json");ve=null;qo=null,ke=null;Uo=null,te=null});import so from"puppeteer";import{addExtra as io}from"puppeteer-extra";import lo from"puppeteer-extra-plugin-stealth";he();await Pe();Ee();Ee();import Fe from"fs";import Zn from"path";var eo={headless:!1,defaultViewport:null,protocolTimeout:3e5},ye=new Set,Lt=!1;async function _e(){let e=[...ye].map(t=>{try{return Promise.resolve(t.close()).catch(()=>{})}catch{return Promise.resolve()}});ye.clear(),await Promise.race([Promise.all(e),new Promise(t=>setTimeout(t,5e3))])}function to(){if(Lt)return;Lt=!0;let e=t=>async()=>{await _e(),process.exit(t)};process.once("SIGINT",e(130)),process.once("SIGTERM",e(143)),process.once("SIGHUP",e(129)),process.once("uncaughtException",async t=>{await _e(),console.error(t&&t.stack||t),process.exit(1)}),process.once("unhandledRejection",async t=>{await _e(),console.error(t),process.exit(1)}),process.stdout.on("error",async t=>{t&&t.code==="EPIPE"&&(await _e(),process.exit(0))}),process.on("exit",()=>{for(let t of ye)try{let n=t.process&&t.process();n&&n.kill("SIGKILL")}catch{}})}function Me(e){return Zn.join(e,".ilml-browser-session.json")}function qt(e,t={}){try{Fe.writeFileSync(Me(e),JSON.stringify({ownerPid:process.pid,startedAt:Date.now(),...t}))}catch{}}function no(e,t){try{let n=t.wsEndpoint&&t.wsEndpoint();qt(e,n?{wsEndpoint:n}:{})}catch{}}function oo(e){let t=Ut(e);t&&t.ownerPid===process.pid&&ge(e)}function ge(e){try{Fe.rmSync(Me(e),{force:!0})}catch{}}function Ut(e){try{return JSON.parse(Fe.readFileSync(Me(e),"utf-8"))}catch{return null}}function Pt(e){if(!e||e===process.pid)return!0;try{return process.kill(e,0),!0}catch(t){return!t||t.code!=="ESRCH"}}function Ft(e,t,n){let o,r=new Promise(i=>{o=setTimeout(()=>i(n),t)}),s=Promise.resolve(e).then(i=>(clearTimeout(o),i),i=>{throw clearTimeout(o),i});return Promise.race([s,r])}async function Mt(e,t,{probeTimeoutMs:n=4e3}={}){let o=Ut(t);if(!o)return"free";if(!o.wsEndpoint)return o.ownerPid&&o.ownerPid!==process.pid&&Pt(o.ownerPid)?"live":(ge(t),"free");let r=Symbol("timeout"),s=e.connect({browserWSEndpoint:o.wsEndpoint,defaultViewport:null}).catch(()=>null),i=await Ft(s,n,r);if(i===r)return s.then(l=>{if(l)try{l.disconnect()}catch{}}),"live";if(!i)return ge(t),"free";if(Pt(o.ownerPid)){try{i.disconnect()}catch{}return"live"}if(console.log(" (recovered an orphaned automation browser from a previous run \u2014 closing it)"),await Ft(i.close().then(()=>!0).catch(()=>!1),n,!1))return ge(t),"free";try{i.disconnect()}catch{}return"live"}function ro(e,t){e&&(to(),ye.add(e),e.on("disconnected",()=>{ye.delete(e),t&&ge(t)}))}async function Bt(e,t={}){let n=xt(),o={...eo,...t,userDataDir:n},r=`Could not open the LinkedIn automation Chrome profile at ${n}. Another LinkedIn automation command is already running (or its window is still open) \u2014 wait for it to finish or close that window, then retry.`;if(await Mt(e,n)==="live")throw new Error(r);qt(n);let s;try{s=await e.launch(o)}catch{if(await Mt(e,n)==="live")throw new Error(r);$t(n);try{s=await e.launch(o)}catch(a){throw oo(n),new Error(`Could not open the LinkedIn automation Chrome profile at ${n}. If a LinkedIn automation window is already open, close it and retry. (Chrome: ${a.message})`)}}return ro(s,n),no(n,s),{browser:s,profileDir:n,needsSeed:Et(n)}}async function Vt(e,t){if(!t||!t.needsSeed)return!1;if(!kt())return console.warn(" \u26A0 No saved LinkedIn session found. If this command comes back empty or redirects to login, run: ilml linkedin login"),!1;try{return await e.setCookie(...vt()),_t(t.profileDir),console.log(" (seeded persistent profile from saved cookies \u2014 one-time)"),!0}catch{return!1}}var Ht=io(so);Ht.use(lo());async function Jt(){let{browser:e,profileDir:t,needsSeed:n}=await Bt(Ht),o=await e.newPage();return await o.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})}),await Vt(o,{profileDir:t,needsSeed:n}),console.log(" Browser launched (persistent profile)."),{browser:e,page:o}}he();await $e();async function To(e,t){let n=e.url();if(n.includes("/in/"))return!1;if(n.includes("/login")||n.includes("/authwall")||n.includes("/checkpoint"))throw new Error("Session expired \u2014 redirected to login. Run: ilml linkedin login");console.log(` Off-track: ${n.slice(0,80)}`),console.log(" Navigating back to profile..."),await e.goto(t,{waitUntil:"domcontentloaded",timeout:6e4}),await x(3e3);let o=e.url();return o.includes("/in/")||console.log(` Recovery failed \u2014 still on: ${o.slice(0,80)}`),!0}async function sn(e,t,n={}){let{extractExperience:o=!1,extractFeatured:r=!1,extractEducation:s=!1,extractSkills:i=!1,extractRecommendations:a=!1,extractCertifications:l=!1}=n;try{await ce(e,t,{readySelector:".scaffold-layout__main, main.scaffold-layout__main, .pv-top-card, section.pv-top-card",label:"profile"}),await x(1500),await Tt(e)&&(console.log(" Dismissed Premium overlay"),await x(1500),await To(e,t));let c=0;for(let E=0;E<2;E++){let Q=await e.evaluate(()=>document.body.scrollHeight);for(let W=1;W<=6;W++)await e.evaluate(O=>window.scrollTo(0,O),Q*W/6),await x(900);let X=await e.evaluate(()=>document.body.scrollHeight);if(X===c)break;c=X}await x(800);let p=null;try{let E=e.url();E&&/\/in\/[^/?#]/.test(E)&&(p=E.split("?")[0].replace(/\/+$/,""))}catch{}let u=await e.evaluate(()=>{let E=new Set(["Highlights","About","Activity","Experience","Education","Skills","Recommendations","Languages","Interests","Volunteer experience","Featured","Honors & awards","Honors and awards","Projects","Publications","Patents","Certifications","Licenses & certifications","Courses","Test scores","Organizations"]),Q=new Set(["Ad Options","Don\u2019t want to see this","0 notifications","Explore Premium profiles","You might like","People you may know","Suggested for you","More profiles for you","Promoted"]),X=h=>{let w=(h||"").trim().replace(/^[·•]\s*/,"");return/^(1st|2nd|3rd|she\/her|he\/him|they\/them|their\/them)$/i.test(w)},W={name:null,title:null,location:null,currentCompany:null,currentSchool:null,connectionDegree:null,hasVerifiedBadge:!1,photoUrl:null,connectionsLevel:null,mutualConnectionsCount:null,followers:null,openToWork:!1,hiring:!1,about:null,topSkills:null,availableSections:[]},O=document.querySelector("main");if(!O)return W;let v=[...Array.from(O.querySelectorAll("h1")),...Array.from(O.querySelectorAll("h2"))],I=Array.from(O.querySelectorAll("h2")),le=v.find(h=>{let w=h.textContent&&h.textContent.trim(),S=(w||"").replace(/\s*\(\d+\)$/,"").trim();return w&&w.length>1&&w.length<100&&!E.has(S)&&!Q.has(S)}),ae=le?le.textContent.trim():null,R=le?le.parentElement:null;for(;R&&R!==O&&!Array.from(R.children).filter(w=>w.tagName==="P").some(w=>{let S=w.textContent&&w.textContent.trim();return S&&S.length>5&&!X(S)});)R=R.parentElement;let it=null,lt=null,at=null,je=null;if(R&&R!==O){let h=Array.from(R.children).filter(S=>S.tagName==="P").filter(S=>{let G=S.textContent&&S.textContent.trim();return G&&G.length>5&&!X(G)});if(h[0]&&(it=h[0].textContent.trim()),h[1]){let S=h[1].textContent.trim().split(/\s*[·•]\s*/);lt=S[0]||null,S.length>1&&(at=S.slice(1).join(" \xB7 "))}let w=Array.from(R.children).filter(S=>S.tagName==="DIV");for(let S=w.length-1;S>=0;S--){let G=w[S].textContent&&w[S].textContent.trim();if(G&&!(ae&&G.startsWith(ae))&&(je=G.split(/[·•]/)[0].trim(),je))break}}let ct=null;if(R){let h=R.querySelectorAll("p, span, div");for(let w of h){let S=(w.textContent||"").trim().replace(/^[·•]\s*/,"");if(S==="1st"||S==="2nd"||S==="3rd"){ct=S;break}}}let Dn=!!O.querySelector('svg[data-test-icon="verified"], [aria-label*="Verified" i]'),ut=document.querySelector('main img[width="200"], button img[alt*="photo" i]'),On=ut&&ut.src||null,xe=O.textContent||"",dt=null,ft=xe.match(/(\d+\+?)\s+connections\b/i);ft&&(dt=ft[1]+" connections");let Ae=null,mt=xe.match(/(\d+)\s+mutual connection/i),pt=xe.match(/and\s+(\d+)\s+other mutual/i),jn=xe.match(/([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+and\s+([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+are\s+mutual/);mt?Ae=parseInt(mt[1],10):pt?Ae=2+parseInt(pt[1],10):jn&&(Ae=2);let ht=null,gt=null,yt=I.find(h=>h.textContent&&h.textContent.trim()==="About");if(yt){let h=yt.parentElement;for(;h&&h.tagName!=="SECTION";)h=h.parentElement;if(h){let w=(h.textContent||"").trim();w=w.replace(/^About\s*/,"").trim();let S=w.match(/Top skills\s*(.+?)$/i);if(S){let Ne=S[1].split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim().split(/\s*[·•]\s*/).map(Fn=>Fn.trim()).filter(Boolean);Ne.length>0&&Ne.length<10&&(gt=Ne)}w=w.split(/\n?…\s*more/i)[0].trim(),w=w.split(/\n?\.\.\.\s*more/i)[0].trim(),w=w.split(/\bTop skills\b/i)[0].trim(),w=w.split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim(),ht=w||null}}let wt=null,St=I.find(h=>h.textContent&&h.textContent.trim()==="Activity");if(St){let h=St.parentElement;for(;h&&h.tagName!=="SECTION";)h=h.parentElement;if(h){let w=(h.textContent||"").slice(0,200).match(/([\d,]+)\s+followers/i);w&&(wt=parseInt(w[1].replace(/,/g,""),10))}}let Nn=[...new Set(I.map(h=>(h.textContent||"").trim().replace(/\s*\(\d+\)$/,"").trim()).filter(h=>h&&h.length>1&&h.length<60&&h!==ae).filter(h=>E.has(h)))],me=document.body.textContent||"",Ln=/#OpenToWork\b/i.test(me)||/\bOpen to work\b/.test(me),Pn=/#Hiring\b/i.test(me)||/\bI'?m hiring\b/i.test(me)||/\bWe are hiring\b/i.test(me);return{name:ae,title:it,location:je,currentCompany:lt,currentSchool:at,connectionDegree:ct,hasVerifiedBadge:Dn,photoUrl:On,connectionsLevel:dt,mutualConnectionsCount:Ae,followers:wt,openToWork:Ln,hiring:Pn,about:ht,topSkills:gt,availableSections:Nn}});console.log(`Viewed: ${u.name} - ${u.title}`);let f=null,C=null,b=null,$=null,y=null,g=null;if(o||r||s||i||a||l){let E=await Promise.resolve().then(()=>(rn(),on));o&&(f=await E.extractExperienceFromDetails(e,t),console.log(` + Experience: ${f.length} entries`)),r&&(C=await E.extractFeaturedFromDetails(e,t),console.log(` + Featured: ${C.length} entries`)),s&&(b=await E.extractEducationFromDetails(e,t),console.log(` + Education: ${b.length} entries`)),i&&($=await E.extractSkillsFromDetails(e,t),console.log(` + Skills: ${$.length} entries`)),a&&(y=await E.extractRecommendationsFromDetails(e,t),console.log(` + Recommendations: ${y.length} entries`)),l&&(g=await E.extractCertificationsFromDetails(e,t),console.log(` + Certifications: ${g.length} entries`))}return{...u,profileUrl:t,resolvedUrl:p,...f!==null&&{experience:f},...C!==null&&{featured:C},...b!==null&&{education:b},...$!==null&&{skillsFull:$},...y!==null&&{recommendations:y},...g!==null&&{certifications:g}}}catch(d){console.error(`Error viewing profile ${t}:`,d.message);try{await e.goto("about:blank",{timeout:1e4}),await x(1e3)}catch{}if(/session expired|bounced to|authwall|checkpoint|redirected to login/i.test(d.message||""))throw d;return null}}Re();await $e();he();async function mn(e,t,n={}){let{maxVisits:o=50,delayBetween:r=4e3,skipRecentDays:s=7,verbose:i=!1,extractExperience:a=!1}=n,l={visited:0,skipped:0,errors:0},d=Date.now()-s*24*60*60*1e3;for(let c of t){if(l.visited>=o)break;if(!c.profileUrl)continue;let p=dn(c.profileUrl);if(p&&new Date(p).getTime()>d){l.skipped++;continue}try{let u=await sn(e,c.profileUrl,{extractExperience:a}),f=c.source||"visit";if(u){let b={...u,source:f};a&&Array.isArray(u.experience)&&u.experience.length>0&&(b._lastEnrichedAt=new Date().toISOString()),Be(c.profileUrl,b),u.resolvedUrl&&Ve(c.profileUrl,u.resolvedUrl)}un(c.profileUrl,{name:u?.name||c.name||null,source:f}),l.visited++,i?console.log(` [${l.visited}] ${u?.name||c.profileUrl} \u2014 ${u?.title?.slice(0,40)||"?"}`):l.visited%10===0&&console.log(` Visited: ${l.visited}`);let C=Math.random()*4e3;await x(r+C)}catch(u){l.errors++,i&&console.error(` Error visiting ${c.name||c.profileUrl}: ${u.message?.slice(0,60)}`);let f=(u.message||"").toLowerCase();if(f.includes("detached")||f.includes("session closed")||f.includes("target closed")||f.includes("session expired")||f.includes("bounced to")||f.includes("authwall")||f.includes("checkpoint")||f.includes("redirected to login")){console.log(" Fatal browser/session error \u2014 stopping visits");break}}}return l}function pn(e={}){let{limit:t=150,revisitDays:n=14}=e,o=fe(),r=new Set(o.visits?.map(y=>y.profileUrl)||[]),s=new Map;for(let y of o.visits||[]){let g=s.get(y.profileUrl);(!g||y.date>g)&&s.set(y.profileUrl,y.date)}let i=Date.now()-7*24*60*60*1e3,a=Date.now()-n*24*60*60*1e3;function l(y){let g=s.get(y);return g&&new Date(g).getTime()>i}let d=Ie(),c=Object.entries(d).filter(([y])=>!r.has(y)).map(([y,g])=>({profileUrl:y,name:g.name,source:"new-discovered",priority:1,jobCount:g.jobIds?.length||0})),p=de(),u=Object.entries(p).filter(([y,g])=>g.connectionStatus==="connected"||l(y)?!1:(g.jobCount||g.jobIds?.length||0)>=2).map(([y,g])=>({profileUrl:y,name:g.name,source:"active-recruiter",priority:2,jobCount:g.jobCount||g.jobIds?.length||0})),f=Object.entries(p).filter(([y,g])=>{let B=g.connectionStatus;return!(!B||B==="connected"||B==="pending"||B==="sent_without_note"||B==="sent_with_note"||l(y)||(g.jobCount||g.jobIds?.length||0)>=2)}).map(([y,g])=>({profileUrl:y,name:g.name,source:"unconnected",priority:3,jobCount:g.jobCount||g.jobIds?.length||0})),C=[...s.entries()].filter(([y,g])=>new Date(g).getTime()<a).map(([y,g])=>({profileUrl:y,name:null,source:"revisit",priority:4,lastVisit:g})),b=y=>y.sort(()=>Math.random()-.5);return c.sort((y,g)=>g.jobCount-y.jobCount),u.sort((y,g)=>g.jobCount-y.jobCount),b(f),b(C),[...c,...u,...f,...C].slice(0,t)}function hn(){let e=Ie();return Object.entries(e).map(([t,n])=>({profileUrl:t,name:n.name,source:"discovered"}))}function Je(){let e=de();return Object.entries(e).filter(([,t])=>{let n=t.connectionStatus;return n&&n!=="connected"&&n!=="pending"&&n!=="sent_without_note"&&n!=="sent_with_note"}).map(([t,n])=>({profileUrl:t,name:n.name,source:"unconnected"}))}function We(e=14){let t=fe(),n=Date.now()-e*24*60*60*1e3,o=new Map;for(let r of t.visits){let s=o.get(r.profileUrl);(!s||r.date>s.date)&&o.set(r.profileUrl,r)}return[...o.entries()].filter(([,r])=>new Date(r.date).getTime()<n).map(([r,s])=>({profileUrl:r,name:s.name,source:"revisit"}))}Re();import"dotenv/config";import*as L from"fs";import*as K from"path";import{fileURLToPath as Ko}from"url";import"dotenv/config";import{createGraphClient as Bo,resolveToken as Vo}from"@ilivemylife/graph-sdk";import{config as Ho}from"dotenv";Ho();var gn=Vo()||process.env.TOKEN;gn||(console.error(`
|
|
240
|
+
`});var ln=Y(()=>{});import"dotenv/config";import*as N from"fs";import*as j from"path";import{fileURLToPath as Do}from"url";function Po(){N.existsSync(ne)||N.mkdirSync(ne,{recursive:!0})}function Fo(e,t){let n=JSON.stringify(t,null,2),o=e+".tmp";N.writeFileSync(o,n);for(let r=0;r<3;r++)try{N.renameSync(o,e);return}catch(s){if(r<2&&(s.code==="EPERM"||s.code==="EBUSY")){let i=Date.now();for(;Date.now()-i<200;);continue}throw s}}function He(e){try{if(N.existsSync(e))return JSON.parse(N.readFileSync(e,"utf-8"))}catch{}return null}function cn(){ve=null,qo=null,ke=null,Uo=null,te=null}function Mo(){return ve||(ve=He(No)||{},ve)}function Ie(){return ke||(ke=He(Lo)||{},ke)}function fe(){return te||(te=He(an)||{visits:[]},Array.isArray(te.visits)||(te.visits=[]),te)}function un(e,t={}){if(!e)return;Po();let n=fe();n.visits.push({date:new Date().toISOString(),profileUrl:e,name:t.name||null,source:t.source||"manual"});let o=Date.now()-90*24*60*60*1e3;n.visits=n.visits.filter(r=>new Date(r.date).getTime()>o);try{Fo(an,n)}catch(r){console.error(`[!] Failed to save visit-log.json: ${r.message}`)}}function dn(e){let t=fe();for(let n=t.visits.length-1;n>=0;n--)if(t.visits[n].profileUrl===e)return t.visits[n].date;return null}function fn(){let e=Mo(),t=Ie(),n=Object.values(e),o=Object.values(t),r={};for(let u of n)u.company&&(r[u.company]=(r[u.company]||0)+1);let s=Object.entries(r).sort((u,f)=>f[1]-u[1]).slice(0,5).map(([u,f])=>`${u} (${f})`),i={};for(let u of n)for(let f of u.techStack||[])i[f]=(i[f]||0)+1;let a=Object.entries(i).sort((u,f)=>f[1]-u[1]).slice(0,8).map(([u,f])=>`${u} (${f})`),l=fe(),d=new Date().toISOString().slice(0,10),c=l.visits.filter(u=>u.date.startsWith(d)).length,p=new Set(l.visits.map(u=>u.profileUrl)).size;return{totalJobs:n.length,totalRecruiters:o.length,topCompanies:s,topTech:a,totalVisited:p,todayVisited:c,lastScoutDate:n.length>0?n.sort((u,f)=>(f.scoutedAt||"").localeCompare(u.scoutedAt||""))[0]?.scoutedAt:null}}var Oo,jo,ne,No,Lo,an,Js,Ws,ve,qo,ke,Uo,te,Re=Y(()=>{ln();Oo=Do(import.meta.url),jo=j.dirname(j.dirname(Oo)),ne=process.env.DATA_DIR?j.resolve(process.env.DATA_DIR,"market-research"):j.join(jo,"market-research"),No=j.join(ne,"scouted-jobs.json"),Lo=j.join(ne,"discovered-people.json"),an=j.join(ne,"visit-log.json"),Js=j.join(ne,"companies.json"),Ws=j.join(ne,"job-decisions.json");ve=null;qo=null,ke=null;Uo=null,te=null});import so from"puppeteer";import{addExtra as io}from"puppeteer-extra";import lo from"puppeteer-extra-plugin-stealth";he();await Pe();Ee();Ee();import Fe from"fs";import Zn from"path";var eo={headless:!1,defaultViewport:null,protocolTimeout:3e5},ye=new Set,Lt=!1;async function _e(){let e=[...ye].map(t=>{try{return Promise.resolve(t.close()).catch(()=>{})}catch{return Promise.resolve()}});ye.clear(),await Promise.race([Promise.all(e),new Promise(t=>setTimeout(t,5e3))])}function to(){if(Lt)return;Lt=!0;let e=t=>async()=>{await _e(),process.exit(t)};process.once("SIGINT",e(130)),process.once("SIGTERM",e(143)),process.once("SIGHUP",e(129)),process.once("uncaughtException",async t=>{await _e(),console.error(t&&t.stack||t),process.exit(1)}),process.once("unhandledRejection",async t=>{await _e(),console.error(t),process.exit(1)}),process.stdout.on("error",async t=>{t&&t.code==="EPIPE"&&(await _e(),process.exit(0))}),process.on("exit",()=>{for(let t of ye)try{let n=t.process&&t.process();n&&n.kill("SIGKILL")}catch{}})}function Me(e){return Zn.join(e,".ilml-browser-session.json")}function qt(e,t={}){try{Fe.writeFileSync(Me(e),JSON.stringify({ownerPid:process.pid,startedAt:Date.now(),...t}))}catch{}}function no(e,t){try{let n=t.wsEndpoint&&t.wsEndpoint();qt(e,n?{wsEndpoint:n}:{})}catch{}}function oo(e){let t=Ut(e);t&&t.ownerPid===process.pid&&ge(e)}function ge(e){try{Fe.rmSync(Me(e),{force:!0})}catch{}}function Ut(e){try{return JSON.parse(Fe.readFileSync(Me(e),"utf-8"))}catch{return null}}function Pt(e){if(!e||e===process.pid)return!0;try{return process.kill(e,0),!0}catch(t){return!t||t.code!=="ESRCH"}}function Ft(e,t,n){let o,r=new Promise(i=>{o=setTimeout(()=>i(n),t)}),s=Promise.resolve(e).then(i=>(clearTimeout(o),i),i=>{throw clearTimeout(o),i});return Promise.race([s,r])}async function Mt(e,t,{probeTimeoutMs:n=4e3}={}){let o=Ut(t);if(!o)return"free";if(!o.wsEndpoint)return o.ownerPid&&o.ownerPid!==process.pid&&Pt(o.ownerPid)?"live":(ge(t),"free");let r=Symbol("timeout"),s=e.connect({browserWSEndpoint:o.wsEndpoint,defaultViewport:null}).catch(()=>null),i=await Ft(s,n,r);if(i===r)return s.then(l=>{if(l)try{l.disconnect()}catch{}}),"live";if(!i)return ge(t),"free";if(Pt(o.ownerPid)){try{i.disconnect()}catch{}return"live"}if(console.log(" (recovered an orphaned automation browser from a previous run \u2014 closing it)"),await Ft(i.close().then(()=>!0).catch(()=>!1),n,!1))return ge(t),"free";try{i.disconnect()}catch{}return"live"}function ro(e,t){e&&(to(),ye.add(e),e.on("disconnected",()=>{ye.delete(e),t&&ge(t)}))}async function Bt(e,t={}){let n=xt(),o={...eo,...t,userDataDir:n},r=`Could not open the LinkedIn automation Chrome profile at ${n}. Another LinkedIn automation command is already running (or its window is still open) \u2014 wait for it to finish or close that window, then retry.`;if(await Mt(e,n)==="live")throw new Error(r);qt(n);let s;try{s=await e.launch(o)}catch{if(await Mt(e,n)==="live")throw new Error(r);$t(n);try{s=await e.launch(o)}catch(a){throw oo(n),new Error(`Could not open the LinkedIn automation Chrome profile at ${n}. If a LinkedIn automation window is already open, close it and retry. (Chrome: ${a.message})`)}}return ro(s,n),no(n,s),{browser:s,profileDir:n,needsSeed:Et(n)}}async function Vt(e,t){if(!t||!t.needsSeed)return!1;if(!kt())return console.warn(" \u26A0 No saved LinkedIn session found. If this command comes back empty or redirects to login, run: ilml linkedin login"),!1;try{return await e.setCookie(...vt()),_t(t.profileDir),console.log(" (seeded persistent profile from saved cookies \u2014 one-time)"),!0}catch{return!1}}var Ht=io(so);Ht.use(lo());async function Jt(){let{browser:e,profileDir:t,needsSeed:n}=await Bt(Ht),o=await e.newPage();return await o.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})}),await Vt(o,{profileDir:t,needsSeed:n}),console.log(" Browser launched (persistent profile)."),{browser:e,page:o}}he();await $e();async function To(e,t){let n=e.url();if(n.includes("/in/"))return!1;if(n.includes("/login")||n.includes("/authwall")||n.includes("/checkpoint"))throw new Error("Session expired \u2014 redirected to login. Run: ilml linkedin login");console.log(` Off-track: ${n.slice(0,80)}`),console.log(" Navigating back to profile..."),await e.goto(t,{waitUntil:"domcontentloaded",timeout:6e4}),await x(3e3);let o=e.url();return o.includes("/in/")||console.log(` Recovery failed \u2014 still on: ${o.slice(0,80)}`),!0}async function sn(e,t,n={}){let{extractExperience:o=!1,extractFeatured:r=!1,extractEducation:s=!1,extractSkills:i=!1,extractRecommendations:a=!1,extractCertifications:l=!1}=n;try{await ce(e,t,{readySelector:".scaffold-layout__main, main.scaffold-layout__main, .pv-top-card, section.pv-top-card",label:"profile"}),await x(1500),await Tt(e)&&(console.log(" Dismissed Premium overlay"),await x(1500),await To(e,t));let c=0;for(let E=0;E<2;E++){let Q=await e.evaluate(()=>document.body.scrollHeight);for(let W=1;W<=6;W++)await e.evaluate(O=>window.scrollTo(0,O),Q*W/6),await x(900);let X=await e.evaluate(()=>document.body.scrollHeight);if(X===c)break;c=X}await x(800);let p=null;try{let E=e.url();E&&/\/in\/[^/?#]/.test(E)&&(p=E.split("?")[0].replace(/\/+$/,""))}catch{}let u=await e.evaluate(()=>{let E=new Set(["Highlights","About","Activity","Experience","Education","Skills","Recommendations","Languages","Interests","Volunteer experience","Featured","Honors & awards","Honors and awards","Projects","Publications","Patents","Certifications","Licenses & certifications","Courses","Test scores","Organizations"]),Q=new Set(["Ad Options","Don\u2019t want to see this","0 notifications","Explore Premium profiles","You might like","People you may know","Suggested for you","More profiles for you","Promoted"]),X=h=>{let w=(h||"").trim().replace(/^[·•]\s*/,"");return/^(1st|2nd|3rd|she\/her|he\/him|they\/them|their\/them)$/i.test(w)},W={name:null,title:null,location:null,currentCompany:null,currentSchool:null,connectionDegree:null,hasVerifiedBadge:!1,photoUrl:null,connectionsLevel:null,mutualConnectionsCount:null,followers:null,openToWork:!1,hiring:!1,about:null,topSkills:null,availableSections:[]},O=document.querySelector("main");if(!O)return W;let v=[...Array.from(O.querySelectorAll("h1")),...Array.from(O.querySelectorAll("h2"))],I=Array.from(O.querySelectorAll("h2")),le=v.find(h=>{let w=h.textContent&&h.textContent.trim(),S=(w||"").replace(/\s*\(\d+\)$/,"").trim();return w&&w.length>1&&w.length<100&&!E.has(S)&&!Q.has(S)}),ae=le?le.textContent.trim():null,R=le?le.parentElement:null;for(;R&&R!==O&&!Array.from(R.children).filter(w=>w.tagName==="P").some(w=>{let S=w.textContent&&w.textContent.trim();return S&&S.length>5&&!X(S)});)R=R.parentElement;let it=null,lt=null,at=null,je=null;if(R&&R!==O){let h=Array.from(R.children).filter(S=>S.tagName==="P").filter(S=>{let G=S.textContent&&S.textContent.trim();return G&&G.length>5&&!X(G)});if(h[0]&&(it=h[0].textContent.trim()),h[1]){let S=h[1].textContent.trim().split(/\s*[·•]\s*/);lt=S[0]||null,S.length>1&&(at=S.slice(1).join(" \xB7 "))}let w=Array.from(R.children).filter(S=>S.tagName==="DIV");for(let S=w.length-1;S>=0;S--){let G=w[S].textContent&&w[S].textContent.trim();if(G&&!(ae&&G.startsWith(ae))&&(je=G.split(/[·•]/)[0].trim(),je))break}}let ct=null;if(R){let h=R.querySelectorAll("p, span, div");for(let w of h){let S=(w.textContent||"").trim().replace(/^[·•]\s*/,"");if(S==="1st"||S==="2nd"||S==="3rd"){ct=S;break}}}let Dn=!!O.querySelector('svg[data-test-icon="verified"], [aria-label*="Verified" i]'),ut=document.querySelector('main img[width="200"], button img[alt*="photo" i]'),On=ut&&ut.src||null,xe=O.textContent||"",dt=null,ft=xe.match(/(\d+\+?)\s+connections\b/i);ft&&(dt=ft[1]+" connections");let Ae=null,mt=xe.match(/(\d+)\s+mutual connection/i),pt=xe.match(/and\s+(\d+)\s+other mutual/i),jn=xe.match(/([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+and\s+([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+are\s+mutual/);mt?Ae=parseInt(mt[1],10):pt?Ae=2+parseInt(pt[1],10):jn&&(Ae=2);let ht=null,gt=null,yt=I.find(h=>h.textContent&&h.textContent.trim()==="About");if(yt){let h=yt.parentElement;for(;h&&h.tagName!=="SECTION";)h=h.parentElement;if(h){let w=(h.textContent||"").trim();w=w.replace(/^About\s*/,"").trim();let S=w.match(/Top skills\s*(.+?)$/i);if(S){let Ne=S[1].split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim().split(/\s*[·•]\s*/).map(Fn=>Fn.trim()).filter(Boolean);Ne.length>0&&Ne.length<10&&(gt=Ne)}w=w.split(/\n?…\s*more/i)[0].trim(),w=w.split(/\n?\.\.\.\s*more/i)[0].trim(),w=w.split(/\bTop skills\b/i)[0].trim(),w=w.split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim(),ht=w||null}}let wt=null,St=I.find(h=>h.textContent&&h.textContent.trim()==="Activity");if(St){let h=St.parentElement;for(;h&&h.tagName!=="SECTION";)h=h.parentElement;if(h){let w=(h.textContent||"").slice(0,200).match(/([\d,]+)\s+followers/i);w&&(wt=parseInt(w[1].replace(/,/g,""),10))}}let Nn=[...new Set(I.map(h=>(h.textContent||"").trim().replace(/\s*\(\d+\)$/,"").trim()).filter(h=>h&&h.length>1&&h.length<60&&h!==ae).filter(h=>E.has(h)))],me=document.body.textContent||"",Ln=/#OpenToWork\b/i.test(me)||/\bOpen to work\b/.test(me),Pn=/#Hiring\b/i.test(me)||/\bI'?m hiring\b/i.test(me)||/\bWe are hiring\b/i.test(me);return{name:ae,title:it,location:je,currentCompany:lt,currentSchool:at,connectionDegree:ct,hasVerifiedBadge:Dn,photoUrl:On,connectionsLevel:dt,mutualConnectionsCount:Ae,followers:wt,openToWork:Ln,hiring:Pn,about:ht,topSkills:gt,availableSections:Nn}});console.log(`Viewed: ${u.name} - ${u.title}`);let f=null,C=null,b=null,$=null,y=null,g=null;if(o||r||s||i||a||l){let E=await Promise.resolve().then(()=>(rn(),on));o&&(f=await E.extractExperienceFromDetails(e,t),console.log(` + Experience: ${f.length} entries`)),r&&(C=await E.extractFeaturedFromDetails(e,t),console.log(` + Featured: ${C.length} entries`)),s&&(b=await E.extractEducationFromDetails(e,t),console.log(` + Education: ${b.length} entries`)),i&&($=await E.extractSkillsFromDetails(e,t),console.log(` + Skills: ${$.length} entries`)),a&&(y=await E.extractRecommendationsFromDetails(e,t),console.log(` + Recommendations: ${y.length} entries`)),l&&(g=await E.extractCertificationsFromDetails(e,t),console.log(` + Certifications: ${g.length} entries`))}return{...u,profileUrl:t,resolvedUrl:p,...f!==null&&{experience:f},...C!==null&&{featured:C},...b!==null&&{education:b},...$!==null&&{skillsFull:$},...y!==null&&{recommendations:y},...g!==null&&{certifications:g}}}catch(d){console.error(`Error viewing profile ${t}:`,d.message);try{await e.goto("about:blank",{timeout:1e4}),await x(1e3)}catch{}if(/session expired|bounced to|authwall|checkpoint|redirected to login/i.test(d.message||""))throw d;return null}}Re();await $e();he();async function mn(e,t,n={}){let{maxVisits:o=10,delayBetween:r=4e3,skipRecentDays:s=7,verbose:i=!1,extractExperience:a=!1}=n,l={visited:0,skipped:0,errors:0},d=Date.now()-s*24*60*60*1e3;for(let c of t){if(l.visited>=o)break;if(!c.profileUrl)continue;let p=dn(c.profileUrl);if(p&&new Date(p).getTime()>d){l.skipped++;continue}try{let u=await sn(e,c.profileUrl,{extractExperience:a}),f=c.source||"visit";if(u){let b={...u,source:f};a&&Array.isArray(u.experience)&&u.experience.length>0&&(b._lastEnrichedAt=new Date().toISOString()),Be(c.profileUrl,b),u.resolvedUrl&&Ve(c.profileUrl,u.resolvedUrl)}un(c.profileUrl,{name:u?.name||c.name||null,source:f}),l.visited++,i?console.log(` [${l.visited}] ${u?.name||c.profileUrl} \u2014 ${u?.title?.slice(0,40)||"?"}`):l.visited%10===0&&console.log(` Visited: ${l.visited}`);let C=Math.random()*4e3;await x(r+C)}catch(u){l.errors++,i&&console.error(` Error visiting ${c.name||c.profileUrl}: ${u.message?.slice(0,60)}`);let f=(u.message||"").toLowerCase();if(f.includes("detached")||f.includes("session closed")||f.includes("target closed")||f.includes("session expired")||f.includes("bounced to")||f.includes("authwall")||f.includes("checkpoint")||f.includes("redirected to login")){console.log(" Fatal browser/session error \u2014 stopping visits");break}}}return l}function pn(e={}){let{limit:t=150,revisitDays:n=14}=e,o=fe(),r=new Set(o.visits?.map(y=>y.profileUrl)||[]),s=new Map;for(let y of o.visits||[]){let g=s.get(y.profileUrl);(!g||y.date>g)&&s.set(y.profileUrl,y.date)}let i=Date.now()-7*24*60*60*1e3,a=Date.now()-n*24*60*60*1e3;function l(y){let g=s.get(y);return g&&new Date(g).getTime()>i}let d=Ie(),c=Object.entries(d).filter(([y])=>!r.has(y)).map(([y,g])=>({profileUrl:y,name:g.name,source:"new-discovered",priority:1,jobCount:g.jobIds?.length||0})),p=de(),u=Object.entries(p).filter(([y,g])=>g.connectionStatus==="connected"||l(y)?!1:(g.jobCount||g.jobIds?.length||0)>=2).map(([y,g])=>({profileUrl:y,name:g.name,source:"active-recruiter",priority:2,jobCount:g.jobCount||g.jobIds?.length||0})),f=Object.entries(p).filter(([y,g])=>{let B=g.connectionStatus;return!(!B||B==="connected"||B==="pending"||B==="sent_without_note"||B==="sent_with_note"||l(y)||(g.jobCount||g.jobIds?.length||0)>=2)}).map(([y,g])=>({profileUrl:y,name:g.name,source:"unconnected",priority:3,jobCount:g.jobCount||g.jobIds?.length||0})),C=[...s.entries()].filter(([y,g])=>new Date(g).getTime()<a).map(([y,g])=>({profileUrl:y,name:null,source:"revisit",priority:4,lastVisit:g})),b=y=>y.sort(()=>Math.random()-.5);return c.sort((y,g)=>g.jobCount-y.jobCount),u.sort((y,g)=>g.jobCount-y.jobCount),b(f),b(C),[...c,...u,...f,...C].slice(0,t)}function hn(){let e=Ie();return Object.entries(e).map(([t,n])=>({profileUrl:t,name:n.name,source:"discovered"}))}function Je(){let e=de();return Object.entries(e).filter(([,t])=>{let n=t.connectionStatus;return n&&n!=="connected"&&n!=="pending"&&n!=="sent_without_note"&&n!=="sent_with_note"}).map(([t,n])=>({profileUrl:t,name:n.name,source:"unconnected"}))}function We(e=14){let t=fe(),n=Date.now()-e*24*60*60*1e3,o=new Map;for(let r of t.visits){let s=o.get(r.profileUrl);(!s||r.date>s.date)&&o.set(r.profileUrl,r)}return[...o.entries()].filter(([,r])=>new Date(r.date).getTime()<n).map(([r,s])=>({profileUrl:r,name:s.name,source:"revisit"}))}Re();import"dotenv/config";import*as L from"fs";import*as K from"path";import{fileURLToPath as Ko}from"url";import"dotenv/config";import{createGraphClient as Bo,resolveToken as Vo}from"@ilivemylife/graph-sdk";import{config as Ho}from"dotenv";Ho();var gn=Vo()||process.env.TOKEN;gn||(console.error(`
|
|
241
241
|
[FATAL] No iLiveMyLife token found.`),console.error(" The bot uses Lifebot AI to decide which jobs to apply for"),console.error(` and how to fill application forms. It cannot work without it.
|
|
242
242
|
`),console.error(" Easiest way:"),console.error(" 1. npm install -g @ilivemylife/graph-sdk"),console.error(` 2. ilml login your@email.com yourpassword
|
|
243
243
|
`),console.error(` Or add ILML_TOKEN=... to .env
|
|
@@ -283,7 +283,7 @@ function findEntryContainer(root) {
|
|
|
283
283
|
[FATAL] Migration ${s} applied successfully but schema version file could NOT be updated: ${l.message}`),console.error(` Your data is in the v${r.version} state but .schema-version still shows v${t}.`),console.error(` On next plugin command the runner will try to re-apply ${s}. If that migration is`),console.error(` idempotent (most are), it will succeed harmlessly. If you're unsure, run 'data-cleanup' and contact support.
|
|
284
284
|
`),process.exit(1)}console.log(` \u2713 Migration ${s} complete`)}try{zt()}catch{}try{cn()}catch{}nt=!0,rt(),console.log(`
|
|
285
285
|
\u2713 Schema is now at v${z}
|
|
286
|
-
`)}var jr=[/\.json\.tmp$/,/\.schema-version\.tmp$/,/\.backup\.tmp$/];function Nr(e){let t=e.toLowerCase();return jr.some(n=>n.test(t))}var Lr=6e4;function Pr(){let e=Date.now(),t=[];for(let n of[U,re,se]){if(!m.existsSync(n))continue;let o;try{o=m.readdirSync(n)}catch{continue}for(let r of o){if(!Nr(r))continue;let s=k.join(n,r);try{let i=m.statSync(s);if(!i.isFile()||e-i.mtimeMs<Lr)continue;t.push({dir:n,file:r,fullPath:s})}catch{}}}return t}function Fr({verbose:e=!1}={}){let t=Pr();if(t.length===0)return e&&console.log(" No orphan .tmp files found."),0;let n=0;for(let o of t)try{m.unlinkSync(o.fullPath),n++,e&&console.log(` Removed orphan: ${k.basename(o.dir)}/${o.file}`)}catch(r){console.warn(` [!] Could not remove orphan ${o.fullPath}: ${r.message}`)}return!e&&n>0&&console.log(` Cleaned up ${n} orphan .tmp file(s) from previous run.`),n}function Mr(){let e=[];for(let t of[re,se]){if(!m.existsSync(t))continue;let n=m.readdirSync(t).filter(o=>{let r=o.toLowerCase();return r.endsWith(".json")&&!r.includes(".backup")});for(let o of n){let r=k.join(t,o);try{De(r)}catch(s){e.push({dir:k.basename(t),file:o,err:s.message})}}}return e.length===0?{ok:!0}:{ok:!1,broken:e}}function qr(e){let t=[],n=[];for(let s of[re,se]){if(!m.existsSync(s))continue;let i=m.readdirSync(s).filter(a=>a.endsWith(e));for(let a of i){let l=k.join(s,a),d=a.slice(0,-e.length),c=k.join(s,d);try{d.toLowerCase().endsWith(".json")?De(l):m.accessSync(l,m.constants.R_OK),t.push({backupPath:l,originalPath:c,fileName:a,dir:s})}catch(p){n.push(`${k.basename(s)}/${a}: ${p.message}`)}}}if(n.length>0)return{ok:!1,error:n.join("; "),restored:0};let o=0,r=[];for(let{backupPath:s,originalPath:i,fileName:a,dir:l}of t)try{Tn(s,i),o++}catch(d){r.push(`${k.basename(l)}/${a}: ${d.message}`)}return r.length>0?{ok:!1,error:r.join("; "),restored:o}:{ok:!0,restored:o}}function Tn(e,t){let n=t+".tmp";try{m.copyFileSync(e,n),In(n)}catch(o){try{m.unlinkSync(n)}catch{}throw o}try{m.renameSync(n,t)}catch(o){try{m.unlinkSync(n)}catch{}if(o.code==="EPERM"||o.code==="EBUSY"){let r=new Error(`File locked: ${t} (${o.code}). On Windows this is usually antivirus / Search Indexer / the file open in another app. Close anything that might be holding it and re-run.`);throw r.code=o.code,r}throw o}}await Rn();var Ce=process.argv.slice(2),Oe=Ce.find(e=>e.startsWith("--source="))?.split("=")[1]||"scouted",st=parseInt(Ce.find(e=>e.startsWith("--max="))?.split("=")[1]||"
|
|
286
|
+
`)}var jr=[/\.json\.tmp$/,/\.schema-version\.tmp$/,/\.backup\.tmp$/];function Nr(e){let t=e.toLowerCase();return jr.some(n=>n.test(t))}var Lr=6e4;function Pr(){let e=Date.now(),t=[];for(let n of[U,re,se]){if(!m.existsSync(n))continue;let o;try{o=m.readdirSync(n)}catch{continue}for(let r of o){if(!Nr(r))continue;let s=k.join(n,r);try{let i=m.statSync(s);if(!i.isFile()||e-i.mtimeMs<Lr)continue;t.push({dir:n,file:r,fullPath:s})}catch{}}}return t}function Fr({verbose:e=!1}={}){let t=Pr();if(t.length===0)return e&&console.log(" No orphan .tmp files found."),0;let n=0;for(let o of t)try{m.unlinkSync(o.fullPath),n++,e&&console.log(` Removed orphan: ${k.basename(o.dir)}/${o.file}`)}catch(r){console.warn(` [!] Could not remove orphan ${o.fullPath}: ${r.message}`)}return!e&&n>0&&console.log(` Cleaned up ${n} orphan .tmp file(s) from previous run.`),n}function Mr(){let e=[];for(let t of[re,se]){if(!m.existsSync(t))continue;let n=m.readdirSync(t).filter(o=>{let r=o.toLowerCase();return r.endsWith(".json")&&!r.includes(".backup")});for(let o of n){let r=k.join(t,o);try{De(r)}catch(s){e.push({dir:k.basename(t),file:o,err:s.message})}}}return e.length===0?{ok:!0}:{ok:!1,broken:e}}function qr(e){let t=[],n=[];for(let s of[re,se]){if(!m.existsSync(s))continue;let i=m.readdirSync(s).filter(a=>a.endsWith(e));for(let a of i){let l=k.join(s,a),d=a.slice(0,-e.length),c=k.join(s,d);try{d.toLowerCase().endsWith(".json")?De(l):m.accessSync(l,m.constants.R_OK),t.push({backupPath:l,originalPath:c,fileName:a,dir:s})}catch(p){n.push(`${k.basename(s)}/${a}: ${p.message}`)}}}if(n.length>0)return{ok:!1,error:n.join("; "),restored:0};let o=0,r=[];for(let{backupPath:s,originalPath:i,fileName:a,dir:l}of t)try{Tn(s,i),o++}catch(d){r.push(`${k.basename(l)}/${a}: ${d.message}`)}return r.length>0?{ok:!1,error:r.join("; "),restored:o}:{ok:!0,restored:o}}function Tn(e,t){let n=t+".tmp";try{m.copyFileSync(e,n),In(n)}catch(o){try{m.unlinkSync(n)}catch{}throw o}try{m.renameSync(n,t)}catch(o){try{m.unlinkSync(n)}catch{}if(o.code==="EPERM"||o.code==="EBUSY"){let r=new Error(`File locked: ${t} (${o.code}). On Windows this is usually antivirus / Search Indexer / the file open in another app. Close anything that might be holding it and re-run.`);throw r.code=o.code,r}throw o}}await Rn();var Ce=process.argv.slice(2),Oe=Ce.find(e=>e.startsWith("--source="))?.split("=")[1]||"scouted",st=parseInt(Ce.find(e=>e.startsWith("--max="))?.split("=")[1]||"10"),ie=parseInt(Ce.find(e=>e.startsWith("--revisit="))?.split("=")[1]||"0"),Ur=Ce.includes("--list"),Br=Ce.includes("--verbose");function Vr(){let e=fn();console.log(`
|
|
287
287
|
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`),console.log(" PROFILE VISIT STATS"),console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),console.log(` Unique profiles visited: ${e.totalVisited}`),console.log(` Today: ${e.todayVisited}`),console.log(` Discovered recruiters: ${e.totalRecruiters} (potential visits)`);let t=Je();if(console.log(` Unconnected recruiters: ${t.length}`),ie>0){let n=We(ie);console.log(` Revisit (>${ie}d ago): ${n.length}`)}console.log(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
288
288
|
`)}async function Hr(){if(console.log(`
|
|
289
289
|
LinkedIn Profile Visitor
|
package/ilml-plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linkedin",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.21",
|
|
4
4
|
"description": "Reference ilml plugin: keep a local mirror of your LinkedIn data with AI-assisted workflows",
|
|
5
5
|
"commands": {
|
|
6
6
|
"help": { "run": "node dist/help.mjs", "description": "List all commands with descriptions; or `help <cmd>` for detailed info on one command (usage, flags, ban-risk, when-to-use). Reads ilml-plugin.json directly.", "usage": "ilml linkedin help [<cmd>]", "useWhen": "You forgot what a command does, or you're an AI agent picking the right tool." },
|
|
@@ -140,10 +140,10 @@
|
|
|
140
140
|
"usage": "ilml linkedin visit [--source=<source>] [--max=N] [--list]",
|
|
141
141
|
"flags": {
|
|
142
142
|
"--source=<source>": "Which queue to drain. 'people' (default), 'recruiters', 'discovered'.",
|
|
143
|
-
"--max=N": "Cap visits this run (default
|
|
143
|
+
"--max=N": "Cap visits this run (default 10).",
|
|
144
144
|
"--list": "Stats only, no browser. Safe."
|
|
145
145
|
},
|
|
146
|
-
"banRisk": "
|
|
146
|
+
"banRisk": "HIGH. This command's whole purpose is a profile view with no follow-up action — no message, no connect, no like — which is the exact pattern LinkedIn flags and the reason `enrich-profiles` is rated HIGH. Accounts have been warned for it in practice. Cap = 10/session by default (raise it with --max=N and own the risk), and keep the cadence human and the volume well under it; prefer visiting people you then actually contact.",
|
|
147
147
|
"useWhen": "You want recruiters at target companies to notice you without sending a connection request."
|
|
148
148
|
},
|
|
149
149
|
"viewers": {
|
package/package.json
CHANGED