ilml-plugin-linkedin 1.11.3 → 1.11.4

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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.11.4
4
+ - **`sync-all` now catches new activity that LinkedIn's sidebar hides in preview text.** When LinkedIn shows a thread's subject line (or any other static text) as the sidebar preview — common for InMail / starred / sponsored conversations — the preview never changes when new messages arrive, so the previous "preview_changed" check missed those threads forever. The sync now also re-reads a thread when the sidebar's `lastMessageTime` is newer than our own `lastSynced` for that thread, which catches activity regardless of what the card text shows. Self-correcting: once Phase 2 re-reads and updates `lastSynced`, the check stays quiet until the next real activity.
5
+ - No schema migration; existing data files unchanged.
6
+
3
7
  ## 1.11.3
4
8
  - **Fixes plugin ignoring your configured `DATA_DIR` in CLI mode.** `ilml linkedin today` and other read-only commands were silently reading from an empty folder inside the plugin install dir instead of the `DATA_DIR` you set via `ilml plugin config`. Reports showed zeros even on databases with thousands of conversations. No data loss — just the wrong folder was being read.
5
9
 
package/dist/syncAll.mjs CHANGED
@@ -51,7 +51,7 @@ ${n.join(`
51
51
  \u2713 Schema is now at v${ee}
52
52
  `)}var hs=[/\.json\.tmp$/,/\.schema-version\.tmp$/,/\.backup\.tmp$/];function ys(e){let t=e.toLowerCase();return hs.some(o=>o.test(t))}var Ss=6e4;function ws(){let e=Date.now(),t=[];for(let o of[H,ie,ae]){if(!b.existsSync(o))continue;let n;try{n=b.readdirSync(o)}catch{continue}for(let s of n){if(!ys(s))continue;let r=_.join(o,s);try{let i=b.statSync(r);if(!i.isFile()||e-i.mtimeMs<Ss)continue;t.push({dir:o,file:s,fullPath:r})}catch{}}}return t}function bs({verbose:e=!1}={}){let t=ws();if(t.length===0)return e&&console.log(" No orphan .tmp files found."),0;let o=0;for(let n of t)try{b.unlinkSync(n.fullPath),o++,e&&console.log(` Removed orphan: ${_.basename(n.dir)}/${n.file}`)}catch(s){console.warn(` [!] Could not remove orphan ${n.fullPath}: ${s.message}`)}return!e&&o>0&&console.log(` Cleaned up ${o} orphan .tmp file(s) from previous run.`),o}function vs(){let e=[];for(let t of[ie,ae]){if(!b.existsSync(t))continue;let o=b.readdirSync(t).filter(n=>{let s=n.toLowerCase();return s.endsWith(".json")&&!s.includes(".backup")});for(let n of o){let s=_.join(t,n);try{Be(s)}catch(r){e.push({dir:_.basename(t),file:n,err:r.message})}}}return e.length===0?{ok:!0}:{ok:!1,broken:e}}function $s(e){let t=[],o=[];for(let r of[ie,ae]){if(!b.existsSync(r))continue;let i=b.readdirSync(r).filter(a=>a.endsWith(e));for(let a of i){let l=_.join(r,a),c=a.slice(0,-e.length),f=_.join(r,c);try{c.toLowerCase().endsWith(".json")?Be(l):b.accessSync(l,b.constants.R_OK),t.push({backupPath:l,originalPath:f,fileName:a,dir:r})}catch(d){o.push(`${_.basename(r)}/${a}: ${d.message}`)}}}if(o.length>0)return{ok:!1,error:o.join("; "),restored:0};let n=0,s=[];for(let{backupPath:r,originalPath:i,fileName:a,dir:l}of t)try{wo(r,i),n++}catch(c){s.push(`${_.basename(l)}/${a}: ${c.message}`)}return s.length>0?{ok:!1,error:s.join("; "),restored:n}:{ok:!0,restored:n}}function wo(e,t){let o=t+".tmp";try{b.copyFileSync(e,o),yo(o)}catch(n){try{b.unlinkSync(o)}catch{}throw n}try{b.renameSync(o,t)}catch(n){try{b.unlinkSync(o)}catch{}if(n.code==="EPERM"||n.code==="EBUSY"){let s=new Error(`File locked: ${t} (${n.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 s.code=n.code,s}throw n}}async function Cs(e,t,o="op"){let n,s=new Promise((r,i)=>{n=setTimeout(()=>i(new Error(`${o} timeout after ${t}ms`)),t)});try{return await Promise.race([e,s])}finally{clearTimeout(n)}}function le(e){if(!e)return!1;let t=e.constructor?.name||"";if(t==="ProtocolError"||t==="TargetCloseError")return!0;let o=e.message||"";return/Runtime\.callFunctionOn timed out/i.test(o)||/Target closed/i.test(o)||/Session closed/i.test(o)||/Connection closed/i.test(o)||/Most likely the page has been closed/i.test(o)||/Navigating frame was detached/i.test(o)||/Protocol error.*timed out/i.test(o)}async function bo(e,t,{returnUrl:o,gotoTimeoutMs:n=6e4,cookiesProvider:s=null,setupPage:r=null}={}){try{await Cs(t.close({runBeforeUnload:!1}),5e3,"oldPage.close")}catch{}let i=await e.newPage();if(r)try{await r(i)}catch{}if(s)try{let a=s();Array.isArray(a)&&a.length>0&&await i.setCookie(...a)}catch{}if(o){await i.goto(o,{waitUntil:"domcontentloaded",timeout:n});let a=i.url();if(/\/login|\/checkpoint|\/uas\/login|\/authwall/.test(a))throw new Error(`Session expired after recovery \u2014 landed on ${a}. Run: ilml linkedin login`)}return i}await So();var To="https://www.linkedin.com/messaging/",ks=10,wt=3,Ts=async e=>{await e.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})})},As=()=>Ne(),ce={pageRecoveries:0};async function qe(e,t){if(ce.pageRecoveries>=ks)throw new Error(`CDP unrecoverable: ${ce.pageRecoveries} page recoveries already attempted. Last: ${t}`);console.log(`
53
53
  \u{1F504} CDP dead \u2014 recovering page (#${ce.pageRecoveries+1}). Reason: ${t}`),e.page=await bo(e.browser,e.page,{returnUrl:To,cookiesProvider:As,setupPage:Ts}),ce.pageRecoveries++,await E(3e3);try{await e.page.waitForSelector(".msg-conversation-listitem",{timeout:15e3})}catch{}console.log(`\u2713 Page recovered (total: ${ce.pageRecoveries}).
54
- `)}function he(e){return(e.messages||[]).find(t=>ve.has(t.status))?.status}var G=process.argv.slice(2),bt=G.includes("--full"),L=G.includes("--dry-run"),vo=G.includes("--report"),vt=G.includes("--verbose"),$o=G.includes("--backfill"),Co=G.includes("--enrich"),ko=G.includes("--today"),$t=G.includes("--read-unread"),De=parseInt(G.find(e=>e.startsWith("--max="))?.split("=")[1]||"50");function Es(e,t){if(!t)return"new";if(e.unread)return"unread";if(!t.profileUrl&&(t.messages?.length||t.lastMessagePreview))return"missing_url";let o=(t.lastMessagePreview||"").substring(0,50),n=(e.lastMessagePreview||"").substring(0,50);return n&&o&&n!==o?"preview_changed":e.lastMessageBy&&e.lastMessageBy!==t.lastMessageBy?"sender_changed":null}async function _s(e,t){console.log(`
54
+ `)}function he(e){return(e.messages||[]).find(t=>ve.has(t.status))?.status}var G=process.argv.slice(2),bt=G.includes("--full"),L=G.includes("--dry-run"),vo=G.includes("--report"),vt=G.includes("--verbose"),$o=G.includes("--backfill"),Co=G.includes("--enrich"),ko=G.includes("--today"),$t=G.includes("--read-unread"),De=parseInt(G.find(e=>e.startsWith("--max="))?.split("=")[1]||"50");function Es(e,t){if(!t)return"new";if(e.unread)return"unread";if(!t.profileUrl&&(t.messages?.length||t.lastMessagePreview))return"missing_url";let o=(t.lastMessagePreview||"").substring(0,50),n=(e.lastMessagePreview||"").substring(0,50);if(n&&o&&n!==o)return"preview_changed";if(e.lastMessageBy&&e.lastMessageBy!==t.lastMessageBy)return"sender_changed";if(t.lastSynced&&e.lastMessageTime){let s=new Date(e.lastMessageTime).getTime(),r=new Date(t.lastSynced).getTime();if(Number.isFinite(s)&&Number.isFinite(r)&&s>r)return"activity_after_last_read"}return null}async function _s(e,t){console.log(`
55
55
  \u2550\u2550 PHASE 1: Inbox Scan \u2550\u2550
56
56
  `);let o=Ke(),n=o.lastIncrementalSync?new Date(o.lastIncrementalSync):null,s=new Map,r=new Map,i=new Map,a=0,l=0,c=0,f=0,d=0,u=-1,m=0;for(;;){l++;let $=await Xt(e),C=0;for(let p of $){if(!p.name||s.has(p.name))continue;s.set(p.name,p),C++,p.unread&&i.set(p.name,p);let w=t[p.name],k=Es(p,w);k&&(!$t&&p.unread?a++:r.set(p.name,{card:p,reason:k})),w&&(k||(w.lastMessageBy=p.lastMessageBy,w.lastMessagePreview=p.lastMessagePreview||w.lastMessagePreview,w.lastMessageTime=p.lastMessageTime||w.lastMessageTime),w.unread=p.unread,p.lastMessageBy==="them"&&p.unread&&(w.conversationStatus="unanswered_by_us"))}if(vt?console.log(` Round #${l}: +${C} new | Total: ${s.size} | Need reading: ${r.size}`):process.stdout.write(`\r Scanned: ${s.size} conversations (page #${l}, ${r.size} need reading)`),C===0){if(d++,d>=3){console.log(`
57
57
  3 rounds with no new items \u2014 end of visible list.`);break}}else d=0;if(!bt){if(r.size===f){if(c++,c>=3){console.log(`
package/ilml-plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linkedin",
3
- "version": "1.11.3",
3
+ "version": "1.11.4",
4
4
  "description": "Reference ilml plugin: keep a local mirror of your LinkedIn data with AI-assisted workflows",
5
5
  "commands": {
6
6
  "apply": { "run": "node dist/run.mjs" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilml-plugin-linkedin",
3
- "version": "1.11.3",
3
+ "version": "1.11.4",
4
4
  "description": "Reference ilml plugin: keep a local mirror of your LinkedIn data and run AI-assisted workflows under your direction.",
5
5
  "type": "module",
6
6
  "files": [