ilml-plugin-linkedin 1.12.0 → 1.13.0

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.
@@ -1,9 +1,9 @@
1
- import"dotenv/config";import*as p from"fs";import*as g from"path";import{getPluginConfig as F}from"@ilivemylife/graph-sdk";var T=await F();for(let[e,o]of Object.entries(T))process.env[e]===void 0&&(process.env[e]=o);var H=process.env.LINKEDIN_NAME,h=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),Q=Object.freeze(new Set([h.DRAFT,h.CONFIRMED,h.NEEDS_REVIEW])),Y=Object.freeze(new Set([h.DRAFT,h.CONFIRMED,h.REJECTED,h.NEEDS_REVIEW]));import"dotenv/config";import*as l from"fs";import*as i from"path";import{fileURLToPath as x}from"url";var R=x(import.meta.url),_=i.dirname(i.dirname(R)),u=process.env.DATA_DIR?i.resolve(process.env.DATA_DIR,"collected-profiles"):i.join(_,"collected-profiles"),k=i.join(u,"people.json"),X=i.join(u,"jobs.json"),Z=i.join(u,"quota.json"),ee=i.join(u,"conversations.json"),ne=i.join(u,"sync-state.json"),oe=i.join(u,"application-questions.json"),v=i.join(u,"profile-history");var j=null;function N(){l.existsSync(u)||l.mkdirSync(u,{recursive:!0})}function $(e,o){if(!l.existsSync(e))return null;try{let s=JSON.parse(l.readFileSync(e,"utf-8"));try{l.copyFileSync(e,e+".backup")}catch{}return s}catch(s){console.error(`[!] ${o} is corrupted: ${s.message}`),console.error(` File: ${i.resolve(e)}`);let t=e+".backup";if(l.existsSync(t))try{let n=JSON.parse(l.readFileSync(t,"utf-8"));console.error(` Restored from ${t}`);try{l.copyFileSync(t,e)}catch{}return n}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${e}.corrupted`);try{l.copyFileSync(e,e+".corrupted")}catch{}return null}}function S(e){if(!e)return null;let o=e.match(/\/in\/([^/?#]+)/);return o?o[1].toLowerCase():null}function L(e){let o=S(e);return o?i.join(v,`${o}.json`):null}function E(e){let o=L(e);return o?$(o,`profile-history/${S(e)}.json`)||[]:[]}function m(){return j||(N(),j=$(k,"people.json")||{},j)}var b=process.argv.slice(2),M=b.includes("--all"),D=b.find(e=>e.startsWith("--since="))?.split("=")[1]||null,d=b.find(e=>e.startsWith("--field="))?.split("=")[1]||null,I=b.find(e=>!e.startsWith("--"))||null,J=process.env.DATA_DIR?g.resolve(process.env.DATA_DIR,"collected-profiles"):g.join(process.cwd(),"collected-profiles"),y=g.join(J,"profile-history");function O(){return p.existsSync(y)?p.readdirSync(y).filter(e=>e.endsWith(".json")).map(e=>e.replace(/\.json$/,"")):[]}function P(e){let o=m();for(let s of Object.keys(o))if(S(s)===e)return s;return null}function V(e){let o=e.startsWith("http")?S(e):e.toLowerCase();if(!o){console.log("Could not derive slug");return}let s=P(o),t=m(),n=s?t[s]:null,a=s?E(s):(()=>{let r=g.join(y,`${o}.json`);return p.existsSync(r)?JSON.parse(p.readFileSync(r,"utf-8")):[]})();console.log(`
1
+ import"dotenv/config";import*as p from"fs";import*as g from"path";import{getPluginConfig as F}from"@ilivemylife/graph-sdk";var T=await F();for(let[e,o]of Object.entries(T))process.env[e]===void 0&&(process.env[e]=o);var H=process.env.LINKEDIN_NAME,h=Object.freeze({DRAFT:"draft",CONFIRMED:"confirmed",REJECTED:"rejected",NEEDS_REVIEW:"needs_review",SENT:"sent"}),Q=Object.freeze(new Set([h.DRAFT,h.CONFIRMED,h.NEEDS_REVIEW])),Y=Object.freeze(new Set([h.DRAFT,h.CONFIRMED,h.REJECTED,h.NEEDS_REVIEW]));import"dotenv/config";import*as r from"fs";import*as i from"path";import{fileURLToPath as x}from"url";var R=x(import.meta.url),_=i.dirname(i.dirname(R)),u=process.env.DATA_DIR?i.resolve(process.env.DATA_DIR,"collected-profiles"):i.join(_,"collected-profiles"),k=i.join(u,"people.json"),X=i.join(u,"jobs.json"),Z=i.join(u,"quota.json"),ee=i.join(u,"conversations.json"),ne=i.join(u,"sync-state.json"),oe=i.join(u,"application-questions.json"),v=i.join(u,"profile-history");var j=null;function N(){r.existsSync(u)||r.mkdirSync(u,{recursive:!0})}function $(e,o){if(!r.existsSync(e))return null;try{let s=JSON.parse(r.readFileSync(e,"utf-8"));try{r.copyFileSync(e,e+".backup")}catch{}return s}catch(s){console.error(`[!] ${o} is corrupted: ${s.message}`),console.error(` File: ${i.resolve(e)}`);let t=e+".backup";if(r.existsSync(t))try{let n=JSON.parse(r.readFileSync(t,"utf-8"));console.error(` Restored from ${t}`);try{r.copyFileSync(t,e)}catch{}return n}catch{console.error(" Backup also corrupted!")}console.error(` Starting with empty data. Old file preserved as ${e}.corrupted`);try{r.copyFileSync(e,e+".corrupted")}catch{}return null}}function m(e){if(!e)return null;let o=e.match(/\/in\/([^/?#]+)/);return o?o[1].toLowerCase():null}function L(e){let o=m(e);return o?i.join(v,`${o}.json`):null}function E(e){let o=L(e);return o?$(o,`profile-history/${m(e)}.json`)||[]:[]}function S(){return j||(N(),j=$(k,"people.json")||{},j)}var b=process.argv.slice(2),M=b.includes("--all"),D=b.find(e=>e.startsWith("--since="))?.split("=")[1]||null,d=b.find(e=>e.startsWith("--field="))?.split("=")[1]||null,I=b.find(e=>!e.startsWith("--"))||null,J=process.env.DATA_DIR?g.resolve(process.env.DATA_DIR,"collected-profiles"):g.join(process.cwd(),"collected-profiles"),y=g.join(J,"profile-history");function O(){return p.existsSync(y)?p.readdirSync(y).filter(e=>e.endsWith(".json")).map(e=>e.replace(/\.json$/,"")):[]}function P(e){let o=S();for(let s of Object.keys(o))if(m(s)===e)return s;return null}function V(e){let o=e.startsWith("http")?m(e):e.toLowerCase();if(!o){console.log("Could not derive slug");return}let s=P(o),t=S(),n=s?t[s]:null,a=s?E(s):(()=>{let l=g.join(y,`${o}.json`);return p.existsSync(l)?JSON.parse(p.readFileSync(l,"utf-8")):[]})();console.log(`
2
2
  \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(` ${n?.name||"(unknown)"} \u2014 ${o}`),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
3
3
  `),n&&(console.log("CURRENT:"),console.log(` title: ${n.title||"(null)"}`),console.log(` location: ${n.location||"(null)"}`),console.log(` currentCompany: ${n.currentCompany||"(null)"}`),console.log(` connectionDeg: ${n.connectionDegree||"(null)"}`),console.log(` openToWork: ${n.openToWork}`),console.log(` hiring: ${n.hiring}`),console.log(` lastUpdated: ${n.lastUpdated||"(null)"}`),console.log(` lastSnapshotAt: ${n.lastSnapshotAt||"(null)"}`)),console.log(`
4
4
  HISTORY (${a.length} snapshot${a.length===1?"":"s"}):
5
- `);for(let r of a){if(console.log(` ${r.capturedAt} changed: [${(r.changedFields||[]).join(", ")}]`),r.snapshot)for(let c of r.changedFields||[]){let f=r.snapshot[c],A=n?n[c]:"?",w=C=>Array.isArray(C)?C.join(", "):C??"(null)";console.log(` ${c.padEnd(18)} OLD: ${w(f)}`),console.log(` ${" ".repeat(18)} NEW: ${w(A)}`)}console.log()}}function W(){let e=O();console.log(`
5
+ `);for(let l of a){if(console.log(` ${l.capturedAt} changed: [${(l.changedFields||[]).join(", ")}]`),l.snapshot)for(let c of l.changedFields||[]){let f=l.snapshot[c],A=n?n[c]:"?",w=C=>Array.isArray(C)?C.join(", "):C??"(null)";console.log(` ${c.padEnd(18)} OLD: ${w(f)}`),console.log(` ${" ".repeat(18)} NEW: ${w(A)}`)}console.log()}}function W(){let e=O();console.log(`
6
6
  ${e.length} profiles have history files:
7
- `);let o=m(),s=[];for(let t of e){let n=Object.keys(o).find(f=>S(f)===t),a=n?o[n]:null,r=g.join(y,`${t}.json`),c=JSON.parse(p.readFileSync(r,"utf-8"));s.push({slug:t,name:a?.name||"(unknown)",snapshots:c.length,mostRecentChange:c[0]?.capturedAt||null,mostRecentFields:c[0]?.changedFields?.join(", ")||""})}s.sort((t,n)=>(n.mostRecentChange||"").localeCompare(t.mostRecentChange||""));for(let t of s)console.log(` ${t.mostRecentChange?.slice(0,10)||"----------"} ${t.snapshots}\xD7 ${t.name.padEnd(28)} [${t.mostRecentFields}]`)}function U(){let e=O(),o=new Date(D).getTime(),s=m(),t=[];for(let n of e){let a=g.join(y,`${n}.json`),r=JSON.parse(p.readFileSync(a,"utf-8"));for(let c of r){if(new Date(c.capturedAt).getTime()<o||d&&!(c.changedFields||[]).includes(d))continue;let f=Object.keys(s).find(A=>S(A)===n);t.push({date:c.capturedAt,slug:n,name:(f?s[f]:null)?.name||"(unknown)",fields:(c.changedFields||[]).join(", "),...d?{oldValue:c.snapshot?.[d],newValue:(f?s[f]:null)?.[d]}:{}})}}t.sort((n,a)=>a.date.localeCompare(n.date)),console.log(`
7
+ `);let o=S(),s=[];for(let t of e){let n=Object.keys(o).find(f=>m(f)===t),a=n?o[n]:null,l=g.join(y,`${t}.json`),c=JSON.parse(p.readFileSync(l,"utf-8"));s.push({slug:t,name:a?.name||"(unknown)",snapshots:c.length,mostRecentChange:c[0]?.capturedAt||null,mostRecentFields:c[0]?.changedFields?.join(", ")||""})}s.sort((t,n)=>(n.mostRecentChange||"").localeCompare(t.mostRecentChange||""));for(let t of s)console.log(` ${t.mostRecentChange?.slice(0,10)||"----------"} ${t.snapshots}\xD7 ${t.name.padEnd(28)} [${t.mostRecentFields}]`)}function U(){let e=O(),o=new Date(D).getTime(),s=S(),t=[];for(let n of e){let a=g.join(y,`${n}.json`),l=JSON.parse(p.readFileSync(a,"utf-8"));for(let c of l){if(new Date(c.capturedAt).getTime()<o||d&&!(c.changedFields||[]).includes(d))continue;let f=Object.keys(s).find(A=>m(A)===n);t.push({date:c.capturedAt,slug:n,name:(f?s[f]:null)?.name||"(unknown)",fields:(c.changedFields||[]).join(", "),...d?{oldValue:c.snapshot?.[d],newValue:(f?s[f]:null)?.[d]}:{}})}}t.sort((n,a)=>a.date.localeCompare(n.date)),console.log(`
8
8
  ${t.length} change events since ${D}${d?` (field=${d})`:""}:
9
- `);for(let n of t)if(console.log(` ${n.date.slice(0,10)} ${n.name.padEnd(28)} [${n.fields}]`),d){let a=r=>Array.isArray(r)?r.join(", "):r??"(null)";console.log(` OLD: ${a(n.oldValue)}`),console.log(` NEW: ${a(n.newValue)}`)}}I?V(I):M?W():D?U():(console.log("Usage:"),console.log(" node profileHistory.mjs <slug-or-url>"),console.log(" node profileHistory.mjs --all"),console.log(" node profileHistory.mjs --since=YYYY-MM-DD [--field=NAME]"));
9
+ `);for(let n of t)if(console.log(` ${n.date.slice(0,10)} ${n.name.padEnd(28)} [${n.fields}]`),d){let a=l=>Array.isArray(l)?l.join(", "):l??"(null)";console.log(` OLD: ${a(n.oldValue)}`),console.log(` NEW: ${a(n.newValue)}`)}}I?V(I):M?W():D?U():(console.log("Usage:"),console.log(" node profileHistory.mjs <slug-or-url>"),console.log(" node profileHistory.mjs --all"),console.log(" node profileHistory.mjs --since=YYYY-MM-DD [--field=NAME]"));
@@ -0,0 +1,70 @@
1
+ var ee=Object.defineProperty;var te=(t,e)=>()=>(t&&(e=t(t=0)),e);var oe=(t,e)=>{for(var r in e)ee(t,r,{get:e[r],enumerable:!0})};var F={};oe(F,{graph:()=>le});import{createGraphClient as re,resolveToken as ae}from"@ilivemylife/graph-sdk";import{config as ce}from"dotenv";var q,le,W=te(()=>{ce();q=ae()||process.env.TOKEN;q||(console.error(`
2
+ [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.
3
+ `),console.error(" Easiest way:"),console.error(" 1. npm install -g @ilivemylife/graph-sdk"),console.error(` 2. ilml login your@email.com yourpassword
4
+ `),console.error(` Or add ILML_TOKEN=... to .env
5
+ `),process.exit(1));le=re({token:q})});import"dotenv/config";import me from"puppeteer";var v=t=>new Promise(e=>setTimeout(e,t));function U(t){return t?t.split("?")[0].split("#")[0]:""}async function M(t,e,r={}){let x=r.commentPasses??4,i=r.skipComments??!1,d=Number.isFinite(r.commentsMax)?r.commentsMax:1/0;if(await t.goto(e,{waitUntil:"domcontentloaded",timeout:6e4}),await v(4e3),await t.evaluate(()=>/\/login|\/authwall|\/checkpoint|\/uas\//.test(location.pathname)||!!document.querySelector('input[name="session_key"]')))return{authWall:!0};let a=i?2:6;for(let o=0;o<a;o++)await t.evaluate(()=>window.scrollBy(0,1200)),await v(i?500:1e3);if(i)await t.evaluate(()=>{for(let o of document.querySelectorAll("button")){let p=`${o.innerText||""} ${o.getAttribute("aria-label")||""}`;if(/^(see more|…more|show more)$/i.test(p.trim()))try{o.click()}catch{}}}),await v(500);else for(let o=0;o<x;o++){let p=await t.evaluate(()=>{let S=0;for(let c of document.querySelectorAll("button")){let b=`${c.innerText||""} ${c.getAttribute("aria-label")||""}`;if(/see more|…more|show more|load more comments|more comments|previous replies|more replies|показать ещё|ещё|загрузить ещё|показать предыдущие/i.test(b))try{c.click(),S++}catch{}}return S});if(await v(1800),!p)break}let n=await t.evaluate(()=>{let o=(l,u)=>{for(let y of u){let f=l&&l.querySelector(y);if(f&&(f.innerText||"").trim())return f.innerText.trim()}return""},p=(l,u)=>{for(let y of u){let f=l&&l.querySelector(y);if(f&&f.href)return f.href}return""},S=document.querySelector("main")||document.body,c=document.querySelector(".fie-impression-container")||document.querySelector(".feed-shared-update-v2")||S,b={name:o(c,['.update-components-actor__title span[aria-hidden="true"]','.update-components-actor__name span[aria-hidden="true"]',".update-components-actor__title",".update-components-actor__name"]),profileUrl:p(c,['.update-components-actor__container a[href*="/in/"]','.update-components-actor__meta a[href*="/in/"]','a.app-aware-link[href*="/in/"]']),headline:o(c,[".update-components-actor__description"])},O=o(c,[".update-components-update-v2__commentary",".feed-shared-update-v2__description",".update-components-text"]),D=o(c,['.update-components-actor__sub-description span[aria-hidden="true"]',".update-components-actor__sub-description"]),K=o(document,[".social-details-social-counts__reactions-count",".social-details-social-counts__social-proof-fallback-number"]),B=o(document,['button[aria-label*="comment" i] span',".social-details-social-counts__comments"]),V=o(document,['button[aria-label*="repost" i] span',".social-details-social-counts__reposts",".social-details-social-counts__shares"]),N=c.querySelector(".update-components-update-v2__commentary")||c.querySelector(".update-components-text")||c,X=Array.from(N?.querySelectorAll?.('a[href*="/in/"], a[href*="/company/"]')||[]),L=[],E=new Set;for(let l of X){let u=(l.href||"").split("?")[0].replace(/\/$/,"");if(!u||E.has(u))continue;E.add(u);let y=(l.textContent||"").trim(),f=u.includes("/company/")?"company":"person";L.push({kind:f,url:u,label:y||null})}let Y=Array.from(new Set(((O||"")+" "+(N?.textContent||"")).match(/#[\p{L}\p{N}_]{2,40}/gu)||[])),z={hasImage:!!c.querySelector(".update-components-image, img.feed-shared-image__image"),hasVideo:!!c.querySelector("video, .update-components-video, .feed-shared-update-v2__content video"),hasArticle:!!c.querySelector(".update-components-article, .feed-shared-article"),hasDocument:!!c.querySelector(".feed-shared-document, .update-components-document"),hasPoll:!!c.querySelector(".update-components-poll, .feed-shared-poll")},Q=document.querySelectorAll("article.comments-comment-entity, .comments-comment-entity, .comments-comment-item"),I=[];for(let l of Q){let u=o(l,[".comments-comment-meta__description-title",'.comments-post-meta__name-text span[aria-hidden="true"]',".comments-post-meta__name-text"]),y=o(l,[".comments-comment-item__main-content",".comments-comment-entity__content .update-components-text",".comments-comment-entity__content",".update-components-text"]);!u&&!y||I.push({author:{name:u,profileUrl:p(l,['a[href*="/in/"]']),headline:o(l,[".comments-comment-meta__description-subtitle",".comments-post-meta__headline"])},text:y})}let P=new Set,Z=I.filter(l=>{let u=`${l.author.name}|${l.text.slice(0,80)}`;return P.has(u)?!1:(P.add(u),!0)});return{url:location.href,author:b,text:O,postedAt:D,reactions:K,commentCount:B,repostCount:V,hashtags:Y,mentions:L,media:z,comments:Z,fullText:(S.innerText||"").replace(/\n{3,}/g,`
6
+
7
+ `)}});i?(n.comments=[],n.commentsTruncated=!0,n.commentsTruncatedReason="skipComments"):Number.isFinite(d)&&n.comments.length>d?(n.commentsTruncated=!0,n.commentsTruncatedReason=`commentsMax=${d}`,n.commentsTruncatedOriginalCount=n.comments.length,n.comments=n.comments.slice(0,d)):n.commentsTruncated=!1,n.author.profileUrl=U(n.author.profileUrl),n.comments=n.comments.map(o=>({...o,author:{...o.author,profileUrl:U(o.author.profileUrl)}}));let s=n.fullText||"";if(!n.author.name){let o=s.match(/\n\s*Feed post\s*\n+\s*([^\n]+)/i);o&&(n.author.name=o[1].trim())}if(!n.reactions){let o=s.match(/([\d,]+)\s+reactions?/i);o&&(n.reactions=o[1])}if(!n.commentCount){let o=s.match(/([\d,]+)\s+comments?/i);o&&(n.commentCount=o[1])}let h=s,$=h.search(/\n\s*Feed post\s*\n/i);$>=0&&(h=h.slice($).replace(/^\s*Feed post\s*\n/i,"")),h=h.replace(/\n\s*About\s*\n+\s*Accessibility[\s\S]*$/i,"").replace(/\n\s*LinkedIn Corporation ©[\s\S]*$/i,"").trim(),n.cleanText=h;let m=e.match(/(?:activity-|share-)(\d{6,})/);return n.urn=m?m[1]:"",n}import j from"fs";import ne from"path";var se="cookies.json";function ie(){let t=process.env.ILML_SCOPE_DIR;return t?ne.join(t,"plugins-state","linkedin","cookies.json"):se}function R(){let t=ie();if(!j.existsSync(t))throw new Error(`Cookies not found at ${t}. Run: ilml linkedin login`);let e;try{e=j.readFileSync(t,"utf-8")}catch(x){throw new Error(`Failed to read cookies at ${t}: ${x.message}. Run: ilml linkedin login`)}let r;try{r=JSON.parse(e)}catch{throw new Error(`Cookies file at ${t} is corrupted (invalid JSON). Run: ilml linkedin login`)}if(!Array.isArray(r)||r.length===0)throw new Error(`Cookies file at ${t} is empty or not an array. Run: ilml linkedin login`);return r}var w=process.argv.slice(2),ue=w.includes("--help")||w.includes("-h"),H=w.includes("--json"),T=w.find(t=>t.startsWith("--save-to-graph="))?.split("=")[1]||null,C=w.find(t=>t.startsWith("--out="))?.split("=")[1]||null,pe=w.includes("--no-comments"),G=w.find(t=>t.startsWith("--comments-max="))?.split("=")[1],de=G!=null?parseInt(G,10):null,k=w.find(t=>t.startsWith("http")),g=H?(...t)=>console.error(...t):(...t)=>console.log(...t);function J(){console.log(`
8
+ read-post \u2014 read ONE LinkedIn post by URL (analysis-first; saving is opt-in)
9
+
10
+ DEFAULT USE CASE
11
+ You hand the URL to your AI assistant. The assistant calls this command
12
+ with --json and gets back structured post data to discuss / analyse with
13
+ you. Nothing is saved automatically. Saving to your iLiveMyLife graph is
14
+ an EXPLICIT opt-in via --save-to-graph \u2014 separate decision.
15
+
16
+ USAGE
17
+ ilml linkedin read-post <post-url> [--json] [--no-comments] [--comments-max=N]
18
+ [--out=<path>] [--save-to-graph=<nodeId>]
19
+
20
+ ARGUMENTS
21
+ <post-url> Required. A LinkedIn post / activity URL, e.g.
22
+ https://www.linkedin.com/posts/<slug>-activity-<id>-xxxx
23
+ https://www.linkedin.com/feed/update/urn:li:activity:<id>
24
+
25
+ OUTPUT MODE (pick one or combine)
26
+ (default) Human-readable summary printed to stdout.
27
+ --json AI-callable. stdout = pure JSON. Progress \u2192 stderr.
28
+ --out=<path> Write JSON to a file. Good for huge posts \u2014 your AI
29
+ reads selectively from disk without burning context.
30
+
31
+ EXTRACTION SCOPE \u2014 for posts with thousands of comments
32
+ --no-comments Skip comments entirely. Fast path. You still get the
33
+ post text, author, reactions count, repost count,
34
+ comment count, hashtags, mentions, media flags. The
35
+ comments array is [] with commentsTruncated=true.
36
+ --comments-max=N Keep only the first N comments. Helpful middle ground
37
+ \u2014 see top of the discussion without 1000+ rows.
38
+
39
+ OPTIONAL: GRAPH CAPTURE
40
+ --save-to-graph=<nodeId>
41
+ Save a copy as a child of <nodeId> in your graph (ONE
42
+ node per call). Use different parent nodes per topic
43
+ (research, competitor watch, leads, etc.). With --json
44
+ the new node id is included in JSON as 'savedToGraph'.
45
+ This flag is required for any write \u2014 without it,
46
+ nothing is saved.
47
+
48
+ FLAGS
49
+ --help, -h Print this help and exit.
50
+
51
+ WHAT YOU GET ON ONE PAGE LOAD (no extra cost regardless of flags)
52
+ author \u2014 name, headline, profileUrl
53
+ text \u2014 full post body (post "see more" auto-expanded)
54
+ postedAt \u2014 relative time
55
+ reactions \u2014 total count
56
+ commentCount \u2014 total count (whether or not you loaded them)
57
+ repostCount \u2014 reposts / shares count
58
+ hashtags \u2014 # tags found in post text
59
+ mentions \u2014 profile + company links inside the post body
60
+ media \u2014 { hasImage, hasVideo, hasArticle, hasDocument, hasPoll }
61
+ comments \u2014 [] unless skipped/capped \u2014 each: author + text + profileUrl
62
+ urn \u2014 activity / share id
63
+ cleanText / fullText \u2014 text-blob fallbacks if structured selectors miss
64
+
65
+ BAN RISK
66
+ Low. One post view = a single page load = normal human browsing.
67
+ --no-comments is even quieter (skips the load-more-comments scroll loop).
68
+ `)}ue&&(J(),process.exit(0));k||(J(),process.exit(1));/linkedin\.com\/(posts|feed|in)\/|linkedin\.com\/.*(activity|update)/i.test(k)||(console.error(`URL doesn't look like a LinkedIn post: ${k}`),console.error("Expected something like:"),console.error(" https://www.linkedin.com/posts/<slug>-activity-<id>-xxxx"),console.error(" https://www.linkedin.com/feed/update/urn:li:activity:<id>"),process.exit(1));g("");g(` read-post \u2014 ${k}`);g("");var A=null;try{A=await me.launch({headless:!1,defaultViewport:null,protocolTimeout:3e5});let t=await A.newPage();await t.setCookie(...R());let e=await M(t,k,{skipComments:pe,commentsMax:de});e.authWall&&(console.error(" \u26D4 Auth wall \u2014 cookies expired or not logged in."),console.error(" Run 'ilml linkedin login' (or: node src/saveLoginState.mjs) and retry."),process.exit(2));let r=null;if(T){let i=e.author||{},d=e.text||e.cleanText||e.fullText||"",_=(d.replace(/\s+/g," ").trim()||"(empty post)").slice(0,80),a=`${i.name||"unknown"} \u2014 ${_}${_.length===80?"\u2026":""}`,n={url:e.url||k,urn:e.urn||null,author:e.author||null,text:e.text||null,postedAt:e.postedAt||null,hashtags:e.hashtags||[],mentions:e.mentions||[],media:e.media||null,capturedAt:new Date().toISOString()},s=[];i.name&&s.push(`**Author:** ${i.name}${i.headline?` \u2014 ${i.headline}`:""}`),i.profileUrl&&s.push(`**Profile:** ${i.profileUrl}`),e.postedAt&&s.push(`**Posted:** ${e.postedAt}`),s.push(`**Source:** ${k}`),s.push(`**Captured:** ${n.capturedAt.slice(0,10)}`),s.push(""),s.push("---"),s.push(""),s.push(d||"(post text not extracted)"),e.hashtags?.length&&(s.push(""),s.push(`**Hashtags:** ${e.hashtags.join(" ")}`)),e.mentions?.length&&(s.push(""),s.push(`**Mentions:** ${e.mentions.map(m=>`[${m.label||m.kind}](${m.url})`).join(", ")}`));let h=Object.entries(e.media||{}).filter(([,m])=>m).map(([m])=>m.replace(/^has/,"").toLowerCase());h.length&&(s.push(""),s.push(`**Media:** ${h.join(", ")}`)),s.push(""),s.push("---"),s.push(""),s.push("*Live engagement (reactions / comments / reposts) is intentionally NOT saved here \u2014 those drift. Re-run `read-post --json` on the source URL for current numbers.*"),s.push(""),s.push("```json"),s.push(JSON.stringify(n,null,2)),s.push("```");let $=s.join(`
69
+ `);try{let{graph:m}=await Promise.resolve().then(()=>(W(),F)),o=await m.addItem(T,{title:a,description:$,tags:["linkedin-post"]}),p=o?.id||o?.item?.id||null;r={parentId:T,nodeId:p,url:p?`https://app.ilivemylife.io/item/${p}`:null},g(""),g(` \u2713 Saved to graph as child of ${T}`),g(` New node: ${p||"(id not returned by SDK)"}`),p&&g(` URL: https://app.ilivemylife.io/item/${p}`),g("")}catch(m){r={parentId:T,error:m.message?.slice(0,200)},console.error(""),console.error(` \u26A0 Failed to save to graph: ${m.message?.slice(0,200)}`),console.error(" The post was still extracted successfully \u2014 printing below."),console.error("")}}let x={...e,...r!==null&&{savedToGraph:r}};if(C&&((await import("fs")).writeFileSync(C,JSON.stringify(x,null,2)),g(` \u2713 Wrote ${(JSON.stringify(x).length/1024).toFixed(1)}KB to ${C}`)),H)console.log(JSON.stringify(x,null,2));else{let i=e.author||{};console.log(" \u2500\u2500\u2500 POST \u2500\u2500\u2500"),console.log(` Author: ${i.name||"(unknown)"}${i.headline?` \u2014 ${i.headline}`:""}`),i.profileUrl&&console.log(` Profile: ${i.profileUrl}`),e.postedAt&&console.log(` Posted: ${e.postedAt}`);let d=[`${e.reactions||"0"} reactions`,`${e.commentCount||e.comments.length||"0"} comments`,e.repostCount?`${e.repostCount} reposts`:null].filter(Boolean).join(" ");console.log(` Engagement: ${d}`);let _=Object.entries(e.media||{}).filter(([,a])=>a).map(([a])=>a.replace(/^has/,"").toLowerCase());if(_.length&&console.log(` Media: ${_.join(", ")}`),e.hashtags?.length&&console.log(` Hashtags: ${e.hashtags.join(" ")}`),e.mentions?.length&&console.log(` Mentions: ${e.mentions.map(a=>`${a.label||a.url} (${a.kind})`).join(", ")}`),console.log(""),e.commentsTruncated){let a=e.commentsTruncatedReason==="skipComments"?"(comments skipped via --no-comments)":`(showing first ${e.comments.length} of ${e.commentsTruncatedOriginalCount} \u2014 capped via --comments-max)`;console.log(` Note: ${a}`),console.log("")}if(e.comments.length){console.log(e.text||"(post text not parsed \u2014 see cleanText below)"),console.log(""),console.log(` \u2500\u2500\u2500 COMMENTS (${e.comments.length}) \u2500\u2500\u2500`);for(let a of e.comments){let n=a.author||{};console.log(""),console.log(` \u2022 ${n.name||"(unknown)"}${n.headline?` \u2014 ${n.headline}`:""}`),a.text&&console.log(` ${a.text.replace(/\n+/g,`
70
+ `)}`)}}else console.log(e.cleanText||e.fullText||"(no content extracted)");console.log(""),console.log(" (use --json for the full structured object)")}}catch(t){console.error(" Error:",t.message),process.exit(1)}finally{A&&await A.close().catch(()=>{})}