ilml-plugin-linkedin 1.13.0 → 1.13.1
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 +7 -1
- package/dist/enrichProfile.mjs +30 -5
- package/dist/enrichProfiles.mjs +41 -16
- package/dist/funnelRunner.mjs +40 -15
- package/dist/readPost.mjs +12 -6
- package/dist/run.mjs +39 -14
- package/dist/syncAll.mjs +57 -32
- package/dist/verifyAbout.mjs +28 -3
- package/dist/visit.mjs +36 -11
- package/ilml-plugin.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 1.13.
|
|
3
|
+
## 1.13.1
|
|
4
|
+
- **`enrich-profile --full` no longer pollutes profiles that lack Featured / Education / Recommendations sections.** When a profile doesn't have these sections (e.g. CEO profiles that publish almost nothing), LinkedIn was redirecting the `/details/<section>/` subpage to its "Other profiles you may know" widget; the parser was hijacked and saved 4-5 well-known names (Sundar Pichai, Bill Gates, Satya Nadella, …) into your contacts DB as fake entries. The widget is now rejected on three independent signals: container header ("More profiles for you"), per-entry "degree-only" marker (every child is just `<Name> · 3rd`), and per-section safety filters. Existing affected records are not auto-cleaned — re-run `enrich-profile <url> --full` to overwrite.
|
|
5
|
+
- **`read-post` now reliably extracts post text on all post variants.** Some posts return empty `text` because LinkedIn's `.update-components-update-v2__commentary` class doesn't apply universally. There's now a fallback parsing the stable `cleanText` blob: finds the timestamp marker (`9h •`, `2d •`), takes everything between it and the reaction-count block / "Most relevant" header. Same fallback also fills `postedAt` when the DOM selector misses it.
|
|
6
|
+
- **`read-post` comments — cleanText fallback when DOM selectors miss.** Same drift issue for `.comments-comment-entity`. Comments now have a fallback parser that walks the cleanText section after "Most relevant", chunking on `<N> reactions` line terminators. Each block extracts author name + headline + comment text. `commentsFromCleanTextFallback: true` is set in the JSON output when fallback was used. Respects `--comments-max=N`.
|
|
7
|
+
- **`mentions[]` de-noise.** Previously included every `/in/<slug>` link on the page — sidebar widgets, comments, "more from author". Now filtered to mentions whose visible label appears in the actual post text. `mentionsFilteredFromDOM` records how many were dropped.
|
|
8
|
+
|
|
9
|
+
## 1.13.0
|
|
4
10
|
|
|
5
11
|
**1. Read full LinkedIn profile in one command.**
|
|
6
12
|
```
|
package/dist/enrichProfile.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var Xe=Object.defineProperty;var Ae=(t,e)=>()=>(t&&(e=t(t=0)),e);var et=(t,e)=>{for(var n in e)Xe(t,n,{get:e[n],enumerable:!0})};async function w(t){let e=Math.floor(Math.random()*2001),n=t+e;return new Promise(o=>setTimeout(o,n))}async function $e(t){return await t.evaluate(()=>{let e=document.querySelectorAll("div.artdeco-modal--is-open");for(let o of e){let i=o.innerText||"",s=i.includes("Premium")||i.includes("premium")||i.includes("Upgrade")||i.includes("upgrade"),f=i.includes("Try for")||i.includes("free trial")||i.includes("/month")||i.includes("per month")||i.includes("Start my")||i.includes("Subscribe"),d=i.includes("Get hired faster")||i.includes("Get hired 2x");if(s||f||d){let D=o.querySelector('button[aria-label="Dismiss"]')||o.querySelector("button.artdeco-modal__dismiss")||o.querySelector("button[data-test-modal-close-btn]");if(D)return D.click(),{dismissed:!0,matched:i.substring(0,80)};let $=o.querySelectorAll("button");for(let a of $){let S=a.innerText?.trim().toLowerCase()||"",p=a.getAttribute("aria-label")?.toLowerCase()||"";if(S==="not now"||S==="no thanks"||S==="dismiss"||p.includes("dismiss"))return a.click(),{dismissed:!0,matched:i.substring(0,80)}}}}let n=document.querySelector(".premium-upsell-link")||document.querySelector("[data-test-premium-upsell]")||document.querySelector(".premium-hub-upsell");if(n){let o=n.closest('[class*="modal"]')?.querySelector('button[aria-label="Dismiss"]')||n.closest('[class*="banner"]')?.querySelector('button[aria-label="Close"]');if(o)return o.click(),{dismissed:!0,matched:"premium-banner"}}return!1})}var re=Ae(()=>{});var
|
|
1
|
+
var Xe=Object.defineProperty;var Ae=(t,e)=>()=>(t&&(e=t(t=0)),e);var et=(t,e)=>{for(var n in e)Xe(t,n,{get:e[n],enumerable:!0})};async function w(t){let e=Math.floor(Math.random()*2001),n=t+e;return new Promise(o=>setTimeout(o,n))}async function $e(t){return await t.evaluate(()=>{let e=document.querySelectorAll("div.artdeco-modal--is-open");for(let o of e){let i=o.innerText||"",s=i.includes("Premium")||i.includes("premium")||i.includes("Upgrade")||i.includes("upgrade"),f=i.includes("Try for")||i.includes("free trial")||i.includes("/month")||i.includes("per month")||i.includes("Start my")||i.includes("Subscribe"),d=i.includes("Get hired faster")||i.includes("Get hired 2x");if(s||f||d){let D=o.querySelector('button[aria-label="Dismiss"]')||o.querySelector("button.artdeco-modal__dismiss")||o.querySelector("button[data-test-modal-close-btn]");if(D)return D.click(),{dismissed:!0,matched:i.substring(0,80)};let $=o.querySelectorAll("button");for(let a of $){let S=a.innerText?.trim().toLowerCase()||"",p=a.getAttribute("aria-label")?.toLowerCase()||"";if(S==="not now"||S==="no thanks"||S==="dismiss"||p.includes("dismiss"))return a.click(),{dismissed:!0,matched:i.substring(0,80)}}}}let n=document.querySelector(".premium-upsell-link")||document.querySelector("[data-test-premium-upsell]")||document.querySelector(".premium-hub-upsell");if(n){let o=n.closest('[class*="modal"]')?.querySelector('button[aria-label="Dismiss"]')||n.closest('[class*="banner"]')?.querySelector('button[aria-label="Close"]');if(o)return o.click(),{dismissed:!0,matched:"premium-banner"}}return!1})}var re=Ae(()=>{});var Ne={};et(Ne,{extractCertificationsFromDetails:()=>bt,extractEducationFromDetails:()=>gt,extractExperienceFromDetails:()=>ht,extractFeaturedFromDetails:()=>wt,extractRecommendationsFromDetails:()=>St,extractSkillsFromDetails:()=>yt});async function ht(t,e){let n=e.replace(/\/+$/,"")+"/details/experience/";try{await t.goto(n,{waitUntil:"domcontentloaded",timeout:6e4}),await w(3e3);let o=t.url();if(/\/(login|authwall|checkpoint)/i.test(o))throw new Error(`Session expired \u2014 bounced to ${o} when fetching /details/experience/`);for(let s=0;s<5;s++)await t.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),await w(900);await w(1e3);let i=0;for(;i<5&&await t.evaluate(()=>{for(let f of document.querySelectorAll("button"))if(/^Load more$/i.test((f.textContent||"").trim()))return f.click(),!0;return!1});)i++,await w(1500),await t.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),await w(700);return await t.evaluate(()=>{let s=document.querySelector("main");if(!s)return[];function f(a){let S=[];for(let p of a.querySelectorAll("div")){let h=Array.from(p.children).filter(v=>v.tagName==="DIV");if(h.length<3)continue;let y=h.filter(v=>(v.textContent||"").trim().length>50);y.length<3||(p.textContent||"").length<500||S.push({el:p,count:y.length,totalText:(p.textContent||"").length})}return S.sort((p,h)=>h.count-p.count||p.totalText-h.totalText),S[0]?.el}let d=f(s);if(!d)return[];let D=Array.from(d.children).filter(a=>a.tagName==="DIV").filter(a=>(a.textContent||"").trim().length>50);function $(a){let S=a[0]||null,p=null,h=null;if(a[1]){let c=a[1].split("\xB7").map(k=>k.trim());p=c[0]||null,c[1]&&(h=c[1])}let y=a.findIndex(c=>/(\d{4}|present)/i.test(c)&&/(-|–|—)/i.test(c)&&c.length<80),v=null,L=null;if(y>=0){let c=a[y].split("\xB7").map(k=>k.trim());v=c[0]||null,c[1]&&(L=c[1])}let T=null,ne=-1;if(y>=0&&a[y+1]){let c=a[y+1],k=/^[>*\-•]|^>/.test(c),P=/https?:\/\//.test(c),x=/\d{4}/.test(c),V=/,/.test(c)||/\bArea\b|\bRegion\b|\bProvince\b|\bCountry\b|, [A-Z]{2}\b/.test(c);!k&&!P&&!x&&c.length<100&&V&&(T=c,ne=y+1)}let b=null,j=-1,I=a[a.length-1];if(I&&/and\s+\+\d+\s+skills?$/i.test(I)){j=a.length-1;let c=I.match(/^(.+?)\s+and\s+\+(\d+)\s+skills?$/i);c&&(b={listed:c[1].split(",").map(k=>k.trim()),additional:parseInt(c[2],10)})}let F=new Set([0,1,y,ne,j].filter(c=>c>=0)),C=a.filter((c,k)=>!F.has(k)),oe=C.length>0?C.join(`
|
|
2
2
|
|
|
3
|
-
`):null;return{title:S,company:p,employmentType:h,dates:v,duration:
|
|
3
|
+
`):null;return{title:S,company:p,employmentType:h,dates:v,duration:L,location:T,description:oe,skills:b,_raw:a}}return D.map(a=>{let p=(a.innerText||"").trim().split(`
|
|
4
4
|
`).map(h=>h.trim()).filter(h=>h&&h.length<5e3);return $(p)}).filter(a=>a.title)})}catch(o){if(/session expired|bounced to|authwall|checkpoint/i.test(o.message||""))throw o;return console.error(`extractExperienceFromDetails failed for ${e}: ${o.message?.slice(0,120)}`),[]}}async function B(t,e,n){let o=e.replace(/\/+$/,"")+`/details/${n}/`;await t.goto(o,{waitUntil:"domcontentloaded",timeout:6e4}),await w(3e3);let i=t.url();if(/\/(login|authwall|checkpoint)/i.test(i))throw new Error(`Session expired \u2014 bounced to ${i} when fetching /details/${n}/`);if(!i.toLowerCase().includes(`/details/${n.toLowerCase()}/`))throw new Error(`Section /details/${n}/ not available on profile (landed on ${i})`);for(let f=0;f<4;f++)await t.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),await w(700);await w(800);let s=0;for(;s<5&&await t.evaluate(()=>{for(let d of document.querySelectorAll("button"))if(/^Load more$/i.test((d.textContent||"").trim()))return d.click(),!0;return!1});)s++,await w(1300),await t.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),await w(600)}async function U(t,e,n,o){try{return await o()}catch(i){let s=i.message||"";if(/session expired|bounced to|authwall|checkpoint/i.test(s))throw i;return/not available on profile/i.test(s)?(console.log(` (${e}: section not on profile \u2014 skipped)`),[]):(console.error(`${n} failed for ${t}: ${s.slice(0,120)}`),[])}}async function wt(t,e){return U(e,"featured","extractFeaturedFromDetails",async()=>(await B(t,e,"featured"),await t.evaluate(`(() => {
|
|
5
5
|
${H}
|
|
6
6
|
|
|
@@ -37,6 +37,9 @@ var Xe=Object.defineProperty;var Ae=(t,e)=>()=>(t&&(e=t(t=0)),e);var et=(t,e)=>{
|
|
|
37
37
|
// Reject "People You May Know" sidebar cards: signature is
|
|
38
38
|
// exactly [title, "View"] with no url/caption beyond "View".
|
|
39
39
|
if (e._raw.length === 2 && e._raw[1] === 'View') return false;
|
|
40
|
+
// Reject "Other profiles you may know" widget cards: caption is
|
|
41
|
+
// just a connection-degree marker (e.g. "\xB7 3rd", "\xB7 2nd").
|
|
42
|
+
if (e.caption && /^\xB7\\s*\\d+(?:st|nd|rd|th)\\+?$/i.test(e.caption.trim())) return false;
|
|
40
43
|
return true;
|
|
41
44
|
});
|
|
42
45
|
})()`)))}async function gt(t,e){return U(e,"education","extractEducationFromDetails",async()=>(await B(t,e,"education"),await t.evaluate(`(() => {
|
|
@@ -71,6 +74,9 @@ var Xe=Object.defineProperty;var Ae=(t,e)=>()=>(t&&(e=t(t=0)),e);var et=(t,e)=>{
|
|
|
71
74
|
if (!e.school) return false;
|
|
72
75
|
// Reject "People You May Know" sidebar cards: [title, "View"].
|
|
73
76
|
if (e._raw.length === 2 && e._raw[1] === 'View') return false;
|
|
77
|
+
// Reject related-profile widget: "degree" parsed as the
|
|
78
|
+
// connection-degree marker ("\xB7 3rd") instead of a real degree.
|
|
79
|
+
if (e.degree && /^\xB7\\s*\\d+(?:st|nd|rd|th)\\+?$/i.test(e.degree.trim())) return false;
|
|
74
80
|
return true;
|
|
75
81
|
});
|
|
76
82
|
})()`)))}async function yt(t,e){return U(e,"skills","extractSkillsFromDetails",async()=>(await B(t,e,"skills"),await t.evaluate(`(() => {
|
|
@@ -161,9 +167,12 @@ var Xe=Object.defineProperty;var Ae=(t,e)=>()=>(t&&(e=t(t=0)),e);var et=(t,e)=>{
|
|
|
161
167
|
if (!e.recommender || !e.text) return false;
|
|
162
168
|
// Reject "People You May Know" sidebar cards: [title, "View"].
|
|
163
169
|
if (e._raw.length === 2 && e._raw[1] === 'View') return false;
|
|
170
|
+
// Reject related-profile widget: recommenderTitle is the
|
|
171
|
+
// connection-degree marker, not a real job title.
|
|
172
|
+
if (e.recommenderTitle && /^\xB7\\s*\\d+(?:st|nd|rd|th)\\+?$/i.test(e.recommenderTitle.trim())) return false;
|
|
164
173
|
return true;
|
|
165
174
|
});
|
|
166
|
-
})()`)))}var H,
|
|
175
|
+
})()`)))}var H,je=Ae(()=>{re();H=`
|
|
167
176
|
function findEntryContainer(root) {
|
|
168
177
|
const candidates = [];
|
|
169
178
|
for (const div of root.querySelectorAll('div')) {
|
|
@@ -183,6 +192,20 @@ function findEntryContainer(root) {
|
|
|
183
192
|
});
|
|
184
193
|
if (allLookLikePYMK) continue;
|
|
185
194
|
|
|
195
|
+
// Reject "Other profiles you may know" / related-profile widget:
|
|
196
|
+
// LinkedIn shows this when /details/<section>/ has no real data
|
|
197
|
+
// (e.g. profile has no Featured at all). Every entry is a short
|
|
198
|
+
// [name, "\xB7 Nth"] card with a connection-degree marker as the only
|
|
199
|
+
// metadata. Real Featured / Education / Recommendations entries
|
|
200
|
+
// NEVER consist solely of name + degree.
|
|
201
|
+
const DEGREE_RE = /^\xB7\\s*\\d+(?:st|nd|rd|th)\\+?$/i;
|
|
202
|
+
const allLookLikeRelatedProfiles = substantial.every(c => {
|
|
203
|
+
const lines = (c.innerText || '').trim().split('\\n').map(l => l.trim()).filter(Boolean);
|
|
204
|
+
if (lines.length > 4) return false;
|
|
205
|
+
return lines.some(l => DEGREE_RE.test(l));
|
|
206
|
+
});
|
|
207
|
+
if (allLookLikeRelatedProfiles) continue;
|
|
208
|
+
|
|
186
209
|
// Reject LinkedIn footer/legal blocks: when a /details/<section>/ page
|
|
187
210
|
// renders empty, LinkedIn falls back to "Questions? Visit our Help
|
|
188
211
|
// Center", "Select language" (35+ languages), "Recommendation
|
|
@@ -198,6 +221,8 @@ function findEntryContainer(root) {
|
|
|
198
221
|
'Who your viewers also viewed', // "Private to you" PYMK block on empty pages
|
|
199
222
|
'People also viewed',
|
|
200
223
|
'People you may know',
|
|
224
|
+
'More profiles for you', // LinkedIn related-profiles widget shown
|
|
225
|
+
'Other profiles you may know', // when /details/<section>/ is empty
|
|
201
226
|
];
|
|
202
227
|
if (FOOTER_MARKERS.some(m => containerText.includes(m))) continue;
|
|
203
228
|
|
|
@@ -210,7 +235,7 @@ function findEntryContainer(root) {
|
|
|
210
235
|
candidates.sort((a, b) => b.count - a.count || b.totalText - a.totalText);
|
|
211
236
|
return candidates[0]?.el;
|
|
212
237
|
}
|
|
213
|
-
`});import"dotenv/config";import Et from"puppeteer";re();import{getPluginConfig as tt}from"@ilivemylife/graph-sdk";var nt=await tt();for(let[t,e]of Object.entries(nt))process.env[t]===void 0&&(process.env[t]=e);var Dt=process.env.LINKEDIN_NAME,T=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),Mt=Object.freeze(new Set([T.DRAFT,T.CONFIRMED,T.NEEDS_REVIEW])),Ft=Object.freeze(new Set([T.DRAFT,T.CONFIRMED,T.REJECTED,T.NEEDS_REVIEW]));import"dotenv/config";import*as m from"fs";import*as g from"path";import{fileURLToPath as ot}from"url";var it=ot(import.meta.url),st=g.dirname(g.dirname(it)),A=process.env.DATA_DIR?g.resolve(process.env.DATA_DIR,"collected-profiles"):g.join(st,"collected-profiles"),le=g.join(A,"people.json"),Pt=g.join(A,"jobs.json"),jt=g.join(A,"quota.json"),Ot=g.join(A,"conversations.json"),qt=g.join(A,"sync-state.json"),Bt=g.join(A,"application-questions.json"),ae=g.join(A,"profile-history");var q=null;function Me(){m.existsSync(A)||m.mkdirSync(A,{recursive:!0})}function Fe(t,e){let n=JSON.stringify(e,null,2),o=t+".tmp";m.writeFileSync(o,n);for(let i=0;i<3;i++)try{m.renameSync(o,t);return}catch(s){if(i<2&&(s.code==="EPERM"||s.code==="EBUSY")){let f=Date.now();for(;Date.now()-f<200;);continue}throw s}}function Le(t,e){if(!m.existsSync(t))return null;try{let n=JSON.parse(m.readFileSync(t,"utf-8"));try{m.copyFileSync(t,t+".backup")}catch{}return n}catch(n){console.error(`[!] ${e} is corrupted: ${n.message}`),console.error(` File: ${g.resolve(t)}`);let o=t+".backup";if(m.existsSync(o))try{let i=JSON.parse(m.readFileSync(o,"utf-8"));console.error(` Restored from ${o}`);try{m.copyFileSync(o,t)}catch{}return i}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${t}.corrupted`);try{m.copyFileSync(t,t+".corrupted")}catch{}return null}}function rt(){return new Date().toISOString().split("T")[0]}var lt=["name","title","location","company","connectionDegree","connectionsCount","mutualConnections","photoUrl","hasVerifiedBadge","about","currentCompany","currentSchool","connectionsLevel","mutualConnectionsCount","followers","openToWork","hiring","topSkills","availableSections","experience","featuredLinks","_lastEnrichedAt","featured","education","skillsFull","recommendations","certifications"],at=["name","title","location","currentCompany","currentSchool","connectionDegree","hasVerifiedBadge","openToWork","hiring","about","topSkills"];function ce(t){if(!t)return null;let e=t.match(/\/in\/([^/?#]+)/);return e?e[1].toLowerCase():null}function ct(){m.existsSync(ae)||m.mkdirSync(ae,{recursive:!0})}function Te(t){let e=ce(t);return e?g.join(ae,`${e}.json`):null}function ut(t){let e=Te(t);return e?Le(e,`profile-history/${ce(t)}.json`)||[]:[]}function dt(t,e){let n=Te(t);if(!n)return;ct();let o=ut(t);o.unshift(e);try{Fe(n,o)}catch(i){console.error(`[!] Failed to append profile-history for ${ce(t)}: ${i.message}`)}}function _e(t){return!!(t==null||Array.isArray(t)&&t.length===0||typeof t=="string"&&!t.trim())}function De(t){return Array.isArray(t)?[...t].map(e=>String(e).trim()).sort().join("||"):typeof t=="string"?t.replace(/\s+/g," ").trim():t}function ft(t,e){if(!t)return[];let n=[];for(let o of at){let i=t[o],s=e[o];_e(i)||_e(s)||De(i)!==De(s)&&n.push(o)}return n}function mt(t){let e=new Set(["seenCount","sources","firstSeen","lastSeen","lastUpdated","encounters","jobIds","lastFullVisitAt","lastSnapshotAt","messageSent","connectionStatus","connectionDate","connectionMethod"]),n={};for(let[o,i]of Object.entries(t))e.has(o)||(n[o]=i);return n}function Ne(){return q||(Me(),q=Le(le,"people.json")||{},q)}function pt(t){Me(),q=t;try{Fe(le,t)}catch(e){console.error(`[!] Failed to save people.json: ${e.message}`),console.error(` Path: ${g.resolve(le)}`)}}function ue(t,e={}){if(!t)return null;let n=Ne(),o=rt(),i=new Date().toISOString(),s=n[t];if(s){let f=ft(s,e);f.length>0&&(dt(t,{capturedAt:i,previousLastUpdated:s.lastUpdated||null,changedFields:f,snapshot:mt(s)}),s.lastSnapshotAt=i),s.seenCount=(s.seenCount||1)+1,s.lastSeen=o,s.lastUpdated=i,e.source&&!s.sources?.includes(e.source)&&(s.sources=s.sources||[],s.sources.push(e.source));for(let d of lt)if(e[d]!=null){if(Array.isArray(e[d])&&e[d].length===0&&Array.isArray(s[d])&&s[d].length>0)continue;s[d]=e[d]}Array.isArray(e.availableSections)&&(s.lastFullVisitAt=i)}else n[t]={name:e.name||null,title:e.title||null,location:e.location||null,company:e.company||null,about:e.about||null,connectionDegree:e.connectionDegree||null,connectionsCount:e.connectionsCount||null,mutualConnections:e.mutualConnections||null,photoUrl:e.photoUrl||null,hasVerifiedBadge:e.hasVerifiedBadge||!1,currentCompany:e.currentCompany||null,currentSchool:e.currentSchool||null,connectionsLevel:e.connectionsLevel||null,mutualConnectionsCount:e.mutualConnectionsCount||null,followers:e.followers||null,openToWork:e.openToWork||!1,hiring:e.hiring||!1,topSkills:e.topSkills||null,availableSections:e.availableSections||null,experience:e.experience||null,featuredLinks:e.featuredLinks||null,_lastEnrichedAt:e._lastEnrichedAt||null,featured:e.featured||null,education:e.education||null,skillsFull:e.skillsFull||null,recommendations:e.recommendations||null,certifications:e.certifications||null,firstSeen:o,lastSeen:o,seenCount:1,sources:e.source?[e.source]:[],connectionStatus:null,connectionDate:null,connectionMethod:null,messageSent:null,jobIds:[],lastUpdated:i,lastSnapshotAt:null,lastFullVisitAt:Array.isArray(e.availableSections)?i:null};return pt(n),n[t]}async function kt(t,e){let n=t.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: npm run login");console.log(` Off-track: ${n.slice(0,80)}`),console.log(" Navigating back to profile..."),await t.goto(e,{waitUntil:"domcontentloaded",timeout:6e4}),await w(3e3);let o=t.url();return o.includes("/in/")||console.log(` Recovery failed \u2014 still on: ${o.slice(0,80)}`),!0}async function je(t,e,n={}){let{extractExperience:o=!1,extractFeatured:i=!1,extractEducation:s=!1,extractSkills:f=!1,extractRecommendations:d=!1,extractCertifications:D=!1}=n;try{await t.goto(e,{waitUntil:"domcontentloaded",timeout:6e4}),await w(3e3),await $e(t)&&(console.log(" Dismissed Premium overlay"),await w(1500),await kt(t,e));let a=0;for(let b=0;b<2;b++){let P=await t.evaluate(()=>document.body.scrollHeight);for(let L=1;L<=6;L++)await t.evaluate(C=>window.scrollTo(0,C),P*L/6),await w(900);let I=await t.evaluate(()=>document.body.scrollHeight);if(I===a)break;a=I}await w(800);let S=await t.evaluate(()=>{let b=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"]),P=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"]),I=r=>{let l=(r||"").trim().replace(/^[·•]\s*/,"");return/^(1st|2nd|3rd|she\/her|he\/him|they\/them|their\/them)$/i.test(l)},L={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:[]},C=document.querySelector("main");if(!C)return L;let oe=[...Array.from(C.querySelectorAll("h1")),...Array.from(C.querySelectorAll("h2"))],c=Array.from(C.querySelectorAll("h2")),k=oe.find(r=>{let l=r.textContent&&r.textContent.trim(),u=(l||"").replace(/\s*\(\d+\)$/,"").trim();return l&&l.length>1&&l.length<100&&!b.has(u)&&!P.has(u)}),j=k?k.textContent.trim():null,x=k?k.parentElement:null;for(;x&&x!==C&&!Array.from(x.children).filter(l=>l.tagName==="P").some(l=>{let u=l.textContent&&l.textContent.trim();return u&&u.length>5&&!I(u)});)x=x.parentElement;let V=null,pe=null,he=null,ie=null;if(x&&x!==C){let r=Array.from(x.children).filter(u=>u.tagName==="P").filter(u=>{let _=u.textContent&&u.textContent.trim();return _&&_.length>5&&!I(_)});if(r[0]&&(V=r[0].textContent.trim()),r[1]){let u=r[1].textContent.trim().split(/\s*[·•]\s*/);pe=u[0]||null,u.length>1&&(he=u.slice(1).join(" \xB7 "))}let l=Array.from(x.children).filter(u=>u.tagName==="DIV");for(let u=l.length-1;u>=0;u--){let _=l[u].textContent&&l[u].textContent.trim();if(_&&!(j&&_.startsWith(j))&&(ie=_.split(/[·•]/)[0].trim(),ie))break}}let we=null;if(x){let r=x.querySelectorAll("p, span, div");for(let l of r){let u=(l.textContent||"").trim().replace(/^[·•]\s*/,"");if(u==="1st"||u==="2nd"||u==="3rd"){we=u;break}}}let Je=!!C.querySelector('svg[data-test-icon="verified"], [aria-label*="Verified" i]'),ge=document.querySelector('main img[width="200"], button img[alt*="photo" i]'),Ke=ge&&ge.src||null,W=C.textContent||"",ye=null,be=W.match(/(\d+\+?)\s+connections\b/i);be&&(ye=be[1]+" connections");let J=null,Se=W.match(/(\d+)\s+mutual connection/i),ke=W.match(/and\s+(\d+)\s+other mutual/i),Ye=W.match(/([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+and\s+([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+are\s+mutual/);Se?J=parseInt(Se[1],10):ke?J=2+parseInt(ke[1],10):Ye&&(J=2);let Ce=null,xe=null,ve=c.find(r=>r.textContent&&r.textContent.trim()==="About");if(ve){let r=ve.parentElement;for(;r&&r.tagName!=="SECTION";)r=r.parentElement;if(r){let l=(r.textContent||"").trim();l=l.replace(/^About\s*/,"").trim();let u=l.match(/Top skills\s*(.+?)$/i);if(u){let se=u[1].split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim().split(/\s*[·•]\s*/).map(Ze=>Ze.trim()).filter(Boolean);se.length>0&&se.length<10&&(xe=se)}l=l.split(/\n?…\s*more/i)[0].trim(),l=l.split(/\n?\.\.\.\s*more/i)[0].trim(),l=l.split(/\bTop skills\b/i)[0].trim(),l=l.split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim(),Ce=l||null}}let Ee=null,Ie=c.find(r=>r.textContent&&r.textContent.trim()==="Activity");if(Ie){let r=Ie.parentElement;for(;r&&r.tagName!=="SECTION";)r=r.parentElement;if(r){let l=(r.textContent||"").slice(0,200).match(/([\d,]+)\s+followers/i);l&&(Ee=parseInt(l[1].replace(/,/g,""),10))}}let Qe=[...new Set(c.map(r=>(r.textContent||"").trim().replace(/\s*\(\d+\)$/,"").trim()).filter(r=>r&&r.length>1&&r.length<60&&r!==j).filter(r=>b.has(r)))],O=document.body.textContent||"",ze=/#OpenToWork\b/i.test(O)||/\bOpen to work\b/.test(O),Ge=/#Hiring\b/i.test(O)||/\bI'?m hiring\b/i.test(O)||/\bWe are hiring\b/i.test(O);return{name:j,title:V,location:ie,currentCompany:pe,currentSchool:he,connectionDegree:we,hasVerifiedBadge:Je,photoUrl:Ke,connectionsLevel:ye,mutualConnectionsCount:J,followers:Ee,openToWork:ze,hiring:Ge,about:Ce,topSkills:xe,availableSections:Qe}});console.log(`Viewed: ${S.name} - ${S.title}`);let p=null,h=null,y=null,v=null,M=null,F=null;if(o||i||s||f||d||D){let b=await Promise.resolve().then(()=>(Pe(),Re));o&&(p=await b.extractExperienceFromDetails(t,e),console.log(` + Experience: ${p.length} entries`)),i&&(h=await b.extractFeaturedFromDetails(t,e),console.log(` + Featured: ${h.length} entries`)),s&&(y=await b.extractEducationFromDetails(t,e),console.log(` + Education: ${y.length} entries`)),f&&(v=await b.extractSkillsFromDetails(t,e),console.log(` + Skills: ${v.length} entries`)),d&&(M=await b.extractRecommendationsFromDetails(t,e),console.log(` + Recommendations: ${M.length} entries`)),D&&(F=await b.extractCertificationsFromDetails(t,e),console.log(` + Certifications: ${F.length} entries`))}return{...S,profileUrl:e,...p!==null&&{experience:p},...h!==null&&{featured:h},...y!==null&&{education:y},...v!==null&&{skillsFull:v},...M!==null&&{recommendations:M},...F!==null&&{certifications:F}}}catch($){console.error(`Error viewing profile ${e}:`,$.message);try{await t.goto("about:blank",{timeout:1e4}),await w(1e3)}catch{}if(/session expired|bounced to|authwall|checkpoint|redirected to login/i.test($.message||""))throw $;return null}}import Oe from"fs";import Ct from"path";var xt="cookies.json";function vt(){let t=process.env.ILML_SCOPE_DIR;return t?Ct.join(t,"plugins-state","linkedin","cookies.json"):xt}function qe(){let t=vt();if(!Oe.existsSync(t))throw new Error(`Cookies not found at ${t}. Run: ilml linkedin login`);let e;try{e=Oe.readFileSync(t,"utf-8")}catch(o){throw new Error(`Failed to read cookies at ${t}: ${o.message}. Run: ilml linkedin login`)}let n;try{n=JSON.parse(e)}catch{throw new Error(`Cookies file at ${t} is corrupted (invalid JSON). Run: ilml linkedin login`)}if(!Array.isArray(n)||n.length===0)throw new Error(`Cookies file at ${t} is empty or not an array. Run: ilml linkedin login`);return n}function Be(t){return t?t.replace(/^https?:\/\/(www\.|m\.)?linkedin\.com/i,"https://linkedin.com").split("?")[0].split("#")[0].replace(/\/+$/,"").toLowerCase():""}function He(t){return(t||"").replace(/ /g," ").replace(/\s+/g," ").trim().toLowerCase()}var E=process.argv.slice(2),It=E.includes("--help")||E.includes("-h"),N=E.find(t=>t.startsWith("http")),R=E.includes("--full"),Y=R||E.includes("--with-experience"),Q=R||E.includes("--with-featured"),z=R||E.includes("--with-education"),G=R||E.includes("--with-skills"),Z=R||E.includes("--with-recommendations"),X=R||E.includes("--with-certifications"),Ue=[["experience",Y],["featured",Q],["education",z],["skills",G],["recommendations",Z],["certifications",X]],Ve=Ue.filter(([,t])=>t).length,fe=1+Ve;function We(){console.log(`
|
|
238
|
+
`});import"dotenv/config";import Et from"puppeteer";re();import{getPluginConfig as tt}from"@ilivemylife/graph-sdk";var nt=await tt();for(let[t,e]of Object.entries(nt))process.env[t]===void 0&&(process.env[t]=e);var Dt=process.env.LINKEDIN_NAME,M=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),Lt=Object.freeze(new Set([M.DRAFT,M.CONFIRMED,M.NEEDS_REVIEW])),Tt=Object.freeze(new Set([M.DRAFT,M.CONFIRMED,M.REJECTED,M.NEEDS_REVIEW]));import"dotenv/config";import*as m from"fs";import*as g from"path";import{fileURLToPath as ot}from"url";var it=ot(import.meta.url),st=g.dirname(g.dirname(it)),A=process.env.DATA_DIR?g.resolve(process.env.DATA_DIR,"collected-profiles"):g.join(st,"collected-profiles"),le=g.join(A,"people.json"),jt=g.join(A,"jobs.json"),Pt=g.join(A,"quota.json"),Ot=g.join(A,"conversations.json"),qt=g.join(A,"sync-state.json"),Bt=g.join(A,"application-questions.json"),ae=g.join(A,"profile-history");var q=null;function Le(){m.existsSync(A)||m.mkdirSync(A,{recursive:!0})}function Te(t,e){let n=JSON.stringify(e,null,2),o=t+".tmp";m.writeFileSync(o,n);for(let i=0;i<3;i++)try{m.renameSync(o,t);return}catch(s){if(i<2&&(s.code==="EPERM"||s.code==="EBUSY")){let f=Date.now();for(;Date.now()-f<200;);continue}throw s}}function Fe(t,e){if(!m.existsSync(t))return null;try{let n=JSON.parse(m.readFileSync(t,"utf-8"));try{m.copyFileSync(t,t+".backup")}catch{}return n}catch(n){console.error(`[!] ${e} is corrupted: ${n.message}`),console.error(` File: ${g.resolve(t)}`);let o=t+".backup";if(m.existsSync(o))try{let i=JSON.parse(m.readFileSync(o,"utf-8"));console.error(` Restored from ${o}`);try{m.copyFileSync(o,t)}catch{}return i}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${t}.corrupted`);try{m.copyFileSync(t,t+".corrupted")}catch{}return null}}function rt(){return new Date().toISOString().split("T")[0]}var lt=["name","title","location","company","connectionDegree","connectionsCount","mutualConnections","photoUrl","hasVerifiedBadge","about","currentCompany","currentSchool","connectionsLevel","mutualConnectionsCount","followers","openToWork","hiring","topSkills","availableSections","experience","featuredLinks","_lastEnrichedAt","featured","education","skillsFull","recommendations","certifications"],at=["name","title","location","currentCompany","currentSchool","connectionDegree","hasVerifiedBadge","openToWork","hiring","about","topSkills"];function ce(t){if(!t)return null;let e=t.match(/\/in\/([^/?#]+)/);return e?e[1].toLowerCase():null}function ct(){m.existsSync(ae)||m.mkdirSync(ae,{recursive:!0})}function Me(t){let e=ce(t);return e?g.join(ae,`${e}.json`):null}function ut(t){let e=Me(t);return e?Fe(e,`profile-history/${ce(t)}.json`)||[]:[]}function dt(t,e){let n=Me(t);if(!n)return;ct();let o=ut(t);o.unshift(e);try{Te(n,o)}catch(i){console.error(`[!] Failed to append profile-history for ${ce(t)}: ${i.message}`)}}function _e(t){return!!(t==null||Array.isArray(t)&&t.length===0||typeof t=="string"&&!t.trim())}function De(t){return Array.isArray(t)?[...t].map(e=>String(e).trim()).sort().join("||"):typeof t=="string"?t.replace(/\s+/g," ").trim():t}function ft(t,e){if(!t)return[];let n=[];for(let o of at){let i=t[o],s=e[o];_e(i)||_e(s)||De(i)!==De(s)&&n.push(o)}return n}function mt(t){let e=new Set(["seenCount","sources","firstSeen","lastSeen","lastUpdated","encounters","jobIds","lastFullVisitAt","lastSnapshotAt","messageSent","connectionStatus","connectionDate","connectionMethod"]),n={};for(let[o,i]of Object.entries(t))e.has(o)||(n[o]=i);return n}function Re(){return q||(Le(),q=Fe(le,"people.json")||{},q)}function pt(t){Le(),q=t;try{Te(le,t)}catch(e){console.error(`[!] Failed to save people.json: ${e.message}`),console.error(` Path: ${g.resolve(le)}`)}}function ue(t,e={}){if(!t)return null;let n=Re(),o=rt(),i=new Date().toISOString(),s=n[t];if(s){let f=ft(s,e);f.length>0&&(dt(t,{capturedAt:i,previousLastUpdated:s.lastUpdated||null,changedFields:f,snapshot:mt(s)}),s.lastSnapshotAt=i),s.seenCount=(s.seenCount||1)+1,s.lastSeen=o,s.lastUpdated=i,e.source&&!s.sources?.includes(e.source)&&(s.sources=s.sources||[],s.sources.push(e.source));for(let d of lt)if(e[d]!=null){if(Array.isArray(e[d])&&e[d].length===0&&Array.isArray(s[d])&&s[d].length>0)continue;s[d]=e[d]}Array.isArray(e.availableSections)&&(s.lastFullVisitAt=i)}else n[t]={name:e.name||null,title:e.title||null,location:e.location||null,company:e.company||null,about:e.about||null,connectionDegree:e.connectionDegree||null,connectionsCount:e.connectionsCount||null,mutualConnections:e.mutualConnections||null,photoUrl:e.photoUrl||null,hasVerifiedBadge:e.hasVerifiedBadge||!1,currentCompany:e.currentCompany||null,currentSchool:e.currentSchool||null,connectionsLevel:e.connectionsLevel||null,mutualConnectionsCount:e.mutualConnectionsCount||null,followers:e.followers||null,openToWork:e.openToWork||!1,hiring:e.hiring||!1,topSkills:e.topSkills||null,availableSections:e.availableSections||null,experience:e.experience||null,featuredLinks:e.featuredLinks||null,_lastEnrichedAt:e._lastEnrichedAt||null,featured:e.featured||null,education:e.education||null,skillsFull:e.skillsFull||null,recommendations:e.recommendations||null,certifications:e.certifications||null,firstSeen:o,lastSeen:o,seenCount:1,sources:e.source?[e.source]:[],connectionStatus:null,connectionDate:null,connectionMethod:null,messageSent:null,jobIds:[],lastUpdated:i,lastSnapshotAt:null,lastFullVisitAt:Array.isArray(e.availableSections)?i:null};return pt(n),n[t]}async function kt(t,e){let n=t.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: npm run login");console.log(` Off-track: ${n.slice(0,80)}`),console.log(" Navigating back to profile..."),await t.goto(e,{waitUntil:"domcontentloaded",timeout:6e4}),await w(3e3);let o=t.url();return o.includes("/in/")||console.log(` Recovery failed \u2014 still on: ${o.slice(0,80)}`),!0}async function Pe(t,e,n={}){let{extractExperience:o=!1,extractFeatured:i=!1,extractEducation:s=!1,extractSkills:f=!1,extractRecommendations:d=!1,extractCertifications:D=!1}=n;try{await t.goto(e,{waitUntil:"domcontentloaded",timeout:6e4}),await w(3e3),await $e(t)&&(console.log(" Dismissed Premium overlay"),await w(1500),await kt(t,e));let a=0;for(let b=0;b<2;b++){let j=await t.evaluate(()=>document.body.scrollHeight);for(let F=1;F<=6;F++)await t.evaluate(C=>window.scrollTo(0,C),j*F/6),await w(900);let I=await t.evaluate(()=>document.body.scrollHeight);if(I===a)break;a=I}await w(800);let S=await t.evaluate(()=>{let b=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"]),j=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"]),I=r=>{let l=(r||"").trim().replace(/^[·•]\s*/,"");return/^(1st|2nd|3rd|she\/her|he\/him|they\/them|their\/them)$/i.test(l)},F={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:[]},C=document.querySelector("main");if(!C)return F;let oe=[...Array.from(C.querySelectorAll("h1")),...Array.from(C.querySelectorAll("h2"))],c=Array.from(C.querySelectorAll("h2")),k=oe.find(r=>{let l=r.textContent&&r.textContent.trim(),u=(l||"").replace(/\s*\(\d+\)$/,"").trim();return l&&l.length>1&&l.length<100&&!b.has(u)&&!j.has(u)}),P=k?k.textContent.trim():null,x=k?k.parentElement:null;for(;x&&x!==C&&!Array.from(x.children).filter(l=>l.tagName==="P").some(l=>{let u=l.textContent&&l.textContent.trim();return u&&u.length>5&&!I(u)});)x=x.parentElement;let V=null,pe=null,he=null,ie=null;if(x&&x!==C){let r=Array.from(x.children).filter(u=>u.tagName==="P").filter(u=>{let _=u.textContent&&u.textContent.trim();return _&&_.length>5&&!I(_)});if(r[0]&&(V=r[0].textContent.trim()),r[1]){let u=r[1].textContent.trim().split(/\s*[·•]\s*/);pe=u[0]||null,u.length>1&&(he=u.slice(1).join(" \xB7 "))}let l=Array.from(x.children).filter(u=>u.tagName==="DIV");for(let u=l.length-1;u>=0;u--){let _=l[u].textContent&&l[u].textContent.trim();if(_&&!(P&&_.startsWith(P))&&(ie=_.split(/[·•]/)[0].trim(),ie))break}}let we=null;if(x){let r=x.querySelectorAll("p, span, div");for(let l of r){let u=(l.textContent||"").trim().replace(/^[·•]\s*/,"");if(u==="1st"||u==="2nd"||u==="3rd"){we=u;break}}}let Je=!!C.querySelector('svg[data-test-icon="verified"], [aria-label*="Verified" i]'),ge=document.querySelector('main img[width="200"], button img[alt*="photo" i]'),Ke=ge&&ge.src||null,W=C.textContent||"",ye=null,be=W.match(/(\d+\+?)\s+connections\b/i);be&&(ye=be[1]+" connections");let J=null,Se=W.match(/(\d+)\s+mutual connection/i),ke=W.match(/and\s+(\d+)\s+other mutual/i),Ye=W.match(/([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+and\s+([A-Z][\w'-]+(?:\s+[A-Z][\w'-]+)?)\s+are\s+mutual/);Se?J=parseInt(Se[1],10):ke?J=2+parseInt(ke[1],10):Ye&&(J=2);let Ce=null,xe=null,ve=c.find(r=>r.textContent&&r.textContent.trim()==="About");if(ve){let r=ve.parentElement;for(;r&&r.tagName!=="SECTION";)r=r.parentElement;if(r){let l=(r.textContent||"").trim();l=l.replace(/^About\s*/,"").trim();let u=l.match(/Top skills\s*(.+?)$/i);if(u){let se=u[1].split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim().split(/\s*[·•]\s*/).map(Ze=>Ze.trim()).filter(Boolean);se.length>0&&se.length<10&&(xe=se)}l=l.split(/\n?…\s*more/i)[0].trim(),l=l.split(/\n?\.\.\.\s*more/i)[0].trim(),l=l.split(/\bTop skills\b/i)[0].trim(),l=l.split(/\b(?:see (?:more|less)|show (?:more|less))\b/i)[0].trim(),Ce=l||null}}let Ee=null,Ie=c.find(r=>r.textContent&&r.textContent.trim()==="Activity");if(Ie){let r=Ie.parentElement;for(;r&&r.tagName!=="SECTION";)r=r.parentElement;if(r){let l=(r.textContent||"").slice(0,200).match(/([\d,]+)\s+followers/i);l&&(Ee=parseInt(l[1].replace(/,/g,""),10))}}let Ge=[...new Set(c.map(r=>(r.textContent||"").trim().replace(/\s*\(\d+\)$/,"").trim()).filter(r=>r&&r.length>1&&r.length<60&&r!==P).filter(r=>b.has(r)))],O=document.body.textContent||"",Qe=/#OpenToWork\b/i.test(O)||/\bOpen to work\b/.test(O),ze=/#Hiring\b/i.test(O)||/\bI'?m hiring\b/i.test(O)||/\bWe are hiring\b/i.test(O);return{name:P,title:V,location:ie,currentCompany:pe,currentSchool:he,connectionDegree:we,hasVerifiedBadge:Je,photoUrl:Ke,connectionsLevel:ye,mutualConnectionsCount:J,followers:Ee,openToWork:Qe,hiring:ze,about:Ce,topSkills:xe,availableSections:Ge}});console.log(`Viewed: ${S.name} - ${S.title}`);let p=null,h=null,y=null,v=null,L=null,T=null;if(o||i||s||f||d||D){let b=await Promise.resolve().then(()=>(je(),Ne));o&&(p=await b.extractExperienceFromDetails(t,e),console.log(` + Experience: ${p.length} entries`)),i&&(h=await b.extractFeaturedFromDetails(t,e),console.log(` + Featured: ${h.length} entries`)),s&&(y=await b.extractEducationFromDetails(t,e),console.log(` + Education: ${y.length} entries`)),f&&(v=await b.extractSkillsFromDetails(t,e),console.log(` + Skills: ${v.length} entries`)),d&&(L=await b.extractRecommendationsFromDetails(t,e),console.log(` + Recommendations: ${L.length} entries`)),D&&(T=await b.extractCertificationsFromDetails(t,e),console.log(` + Certifications: ${T.length} entries`))}return{...S,profileUrl:e,...p!==null&&{experience:p},...h!==null&&{featured:h},...y!==null&&{education:y},...v!==null&&{skillsFull:v},...L!==null&&{recommendations:L},...T!==null&&{certifications:T}}}catch($){console.error(`Error viewing profile ${e}:`,$.message);try{await t.goto("about:blank",{timeout:1e4}),await w(1e3)}catch{}if(/session expired|bounced to|authwall|checkpoint|redirected to login/i.test($.message||""))throw $;return null}}import Oe from"fs";import Ct from"path";var xt="cookies.json";function vt(){let t=process.env.ILML_SCOPE_DIR;return t?Ct.join(t,"plugins-state","linkedin","cookies.json"):xt}function qe(){let t=vt();if(!Oe.existsSync(t))throw new Error(`Cookies not found at ${t}. Run: ilml linkedin login`);let e;try{e=Oe.readFileSync(t,"utf-8")}catch(o){throw new Error(`Failed to read cookies at ${t}: ${o.message}. Run: ilml linkedin login`)}let n;try{n=JSON.parse(e)}catch{throw new Error(`Cookies file at ${t} is corrupted (invalid JSON). Run: ilml linkedin login`)}if(!Array.isArray(n)||n.length===0)throw new Error(`Cookies file at ${t} is empty or not an array. Run: ilml linkedin login`);return n}function Be(t){return t?t.replace(/^https?:\/\/(www\.|m\.)?linkedin\.com/i,"https://linkedin.com").split("?")[0].split("#")[0].replace(/\/+$/,"").toLowerCase():""}function He(t){return(t||"").replace(/ /g," ").replace(/\s+/g," ").trim().toLowerCase()}var E=process.argv.slice(2),It=E.includes("--help")||E.includes("-h"),R=E.find(t=>t.startsWith("http")),N=E.includes("--full"),Y=N||E.includes("--with-experience"),G=N||E.includes("--with-featured"),Q=N||E.includes("--with-education"),z=N||E.includes("--with-skills"),Z=N||E.includes("--with-recommendations"),X=N||E.includes("--with-certifications"),Ue=[["experience",Y],["featured",G],["education",Q],["skills",z],["recommendations",Z],["certifications",X]],Ve=Ue.filter(([,t])=>t).length,fe=1+Ve;function We(){console.log(`
|
|
214
239
|
enrich-profile \u2014 visit ONE LinkedIn profile and save what we learn
|
|
215
240
|
|
|
216
241
|
USAGE
|
|
@@ -261,4 +286,4 @@ WHEN TO USE
|
|
|
261
286
|
- Audit your own LinkedIn profile (--full prints everything to stdout)
|
|
262
287
|
- Deep-dive on one specific contact before reaching out
|
|
263
288
|
- Verify what we know about a recruiter / lead
|
|
264
|
-
`)}It&&(We(),process.exit(0));
|
|
289
|
+
`)}It&&(We(),process.exit(0));R||(We(),process.exit(1));/linkedin\.com\/in\//i.test(R)||(console.error(`URL doesn't look like a LinkedIn profile: ${R}`),console.error("Expected format: https://www.linkedin.com/in/<slug>/"),process.exit(1));var me=Be(process.env.MY_LINKEDIN_URL||""),te=He(process.env.LINKEDIN_NAME||"");!me&&!te&&(console.error(""),console.error(" \u26D4 Refusing to run: cannot self-detect without configuration."),console.error(""),console.error(" Set EITHER (preferably both) of:"),console.error(" MY_LINKEDIN_URL=https://www.linkedin.com/in/<your-slug>"),console.error(' LINKEDIN_NAME="Your Full Name"'),console.error(""),console.error(" in .env (dev) or via `ilml plugin config linkedin set <KEY> <VALUE>` (prod)."),console.error(" This prevents your own profile from accidentally landing in people.json."),console.error(""),process.exit(1));var ee=me&&Be(R)===me,de=Ue.filter(([,t])=>t).map(([t])=>t);console.log("");console.log(` enrichProfile \u2014 ${R}`);N?console.log(` Mode: --full (${fe} page loads: main + ${de.join(" + ")})`):de.length>0?console.log(` Mode: rich (${fe} loads: main + ${de.join(" + ")})`):console.log(" Mode: basic (1 load \u2014 main page only)");console.log(ee?" Target: SELF (URL match) \u2014 will print to stdout, not saved to people.json":te?` Target: TBD \u2014 name-match against "${process.env.LINKEDIN_NAME}" after fetch`:" Target: will save to people.json (no MY_LINKEDIN_URL or LINKEDIN_NAME set for self-check)");Ve>0&&(console.log(""),console.log(` \u26A0 ${fe} page loads on the SAME profile = normal "deep read" pattern.`),console.log(" This is intentionally single-URL \u2014 DO NOT batch. For batch enrichment"),console.log(" use `enrichProfiles.mjs --with-experience` (gated, capped)."));console.log("");var K=null;try{K=await Et.launch({headless:!1,defaultViewport:null,protocolTimeout:3e5});let t=await K.newPage();await t.setCookie(...qe());let e=await Pe(t,R,{extractExperience:Y,extractFeatured:G,extractEducation:Q,extractSkills:z,extractRecommendations:Z,extractCertifications:X});if((!e||!e.name)&&(console.error(" viewProfile returned no name \u2014 profile not loaded or DOM changed"),process.exit(1)),!ee&&te&&e.name&&He(e.name)===te&&(ee=!0,console.log(" \u2191 Detected self via LINKEDIN_NAME match \u2014 will NOT save to people.json")),console.log(""),console.log(" \u2500\u2500\u2500 extracted \u2500\u2500\u2500"),console.log(` name: ${e.name}`),console.log(` title: ${e.title||"(none)"}`),console.log(` location: ${e.location||"(none)"}`),console.log(` about: ${e.about?`${e.about.length} chars`:"(none)"}`),console.log(` topSkills: ${e.topSkills?e.topSkills.join(", "):"(none)"}`),Y&&console.log(` experience: ${(e.experience||[]).length} entries`),G&&console.log(` featured: ${(e.featured||[]).length} entries`),Q&&console.log(` education: ${(e.education||[]).length} entries`),z&&console.log(` skillsFull: ${(e.skillsFull||[]).length} entries`),Z&&console.log(` recommendations: ${(e.recommendations||[]).length} entries`),X&&console.log(` certifications: ${(e.certifications||[]).length} entries`),console.log(""),ee)console.log(" \u2500\u2500\u2500 full data (self \u2014 not persisted) \u2500\u2500\u2500"),console.log(JSON.stringify(e,null,2));else{let n={name:e.name,title:e.title,location:e.location,about:e.about,currentCompany:e.currentCompany,currentSchool:e.currentSchool,topSkills:e.topSkills,availableSections:e.availableSections,connectionsLevel:e.connectionsLevel,mutualConnectionsCount:e.mutualConnectionsCount,followers:e.followers,openToWork:e.openToWork,hiring:e.hiring,hasVerifiedBadge:e.hasVerifiedBadge,photoUrl:e.photoUrl,source:"enrich-profile-single"},o=[["experience",Y,e.experience],["featured",G,e.featured],["education",Q,e.education],["skillsFull",z,e.skillsFull],["recommendations",Z,e.recommendations],["certifications",X,e.certifications]],i=!1;for(let[s,f,d]of o)f&&(Array.isArray(d)&&d.length>0?(n[s]=d,i=!0):console.log(` \u26A0 --with-${s==="skillsFull"?"skills":s} returned 0 entries \u2014 NOT writing ${s} (existing data preserved)`));i&&(n._lastEnrichedAt=new Date().toISOString()),ue(R,n),console.log(" \u2713 Saved to people.json")}}catch(t){console.error(" Error:",t.message),process.exit(1)}finally{K&&await K.close().catch(()=>{})}
|