ilml-plugin-linkedin 1.11.0 → 1.11.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 +4 -0
- package/dist/syncAll.mjs +1 -1
- package/ilml-plugin.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.11.1
|
|
4
|
+
- **`sync-all` now backfills profile URLs for card-only stub conversations.** When the inbox scan saw a conversation card but never successfully opened its thread (e.g. an earlier sync failed mid-read, or the thread was created from outbound activity alone), the entry was stuck with 0 stored messages and no `profileUrl` — and the previous "re-read to capture URL" check required `messages.length > 0`, so these stubs were never picked up again. The condition now triggers re-read on any sign of activity (stored messages OR sidebar preview), so empty-but-recently-active stubs are queued for thread-open on the next sync. Net effect on long-running databases: dozens of conversations that had only sidebar metadata get their profile URL and message body filled in over the next few sync passes. Backwards-compatible — only adds re-read attempts that previously weren't made.
|
|
5
|
+
- No schema migration; existing data files unchanged.
|
|
6
|
+
|
|
3
7
|
## 1.11.0
|
|
4
8
|
- **`sync-all` no longer gives up when a single thread misbehaves.** LinkedIn's inbox occasionally reacts to an automated search input by navigating the page away from `/messaging/` mid-sync. In v1.10.0 that single navigation killed the entire remaining queue — the run would abort on, say, item 1 of 67 and silently leave 66 unread. The thread loop now treats both thrown exceptions and verified-false returns the same way: log it, hand the thread to the sidebar fallback below, and keep moving. A bad thread costs that one slot, not the whole sync.
|
|
5
9
|
- **`sync-all` now reaches conversations buried deep in the inbox.** LinkedIn only renders ~20 sidebar cards at a time and lazy-loads the rest on scroll. Older threads — including any conversation that hadn't received a recent message — sat outside that initial window, so the existing "click the card in the sidebar" path couldn't find them. When that fails, the sync now navigates back to `/messaging/`, scrolls the sidebar from the top, and reopens the thread once its card has been loaded. The happy path (top-of-inbox conversations found by direct click) is unchanged — only previously-failing threads pay the extra scrolling time.
|
package/dist/syncAll.mjs
CHANGED
|
@@ -256,7 +256,7 @@ ${r.join(`
|
|
|
256
256
|
\u2713 Schema is now at v${Na}
|
|
257
257
|
`)}var mRe=[/\.json\.tmp$/,/\.schema-version\.tmp$/,/\.backup\.tmp$/];function SRe(t){let e=t.toLowerCase();return mRe.some(A=>A.test(e))}var bRe=6e4;function FRe(){let t=Date.now(),e=[];for(let A of[ms,Zg,zg]){if(!je.existsSync(A))continue;let r;try{r=je.readdirSync(A)}catch{continue}for(let i of r){if(!SRe(i))continue;let n=GA.join(A,i);try{let s=je.statSync(n);if(!s.isFile()||t-s.mtimeMs<bRe)continue;e.push({dir:A,file:i,fullPath:n})}catch{}}}return e}function vRe({verbose:t=!1}={}){let e=FRe();if(e.length===0)return t&&console.log(" No orphan .tmp files found."),0;let A=0;for(let r of e)try{je.unlinkSync(r.fullPath),A++,t&&console.log(` Removed orphan: ${GA.basename(r.dir)}/${r.file}`)}catch(i){console.warn(` [!] Could not remove orphan ${r.fullPath}: ${i.message}`)}return!t&&A>0&&console.log(` Cleaned up ${A} orphan .tmp file(s) from previous run.`),A}function kRe(){let t=[];for(let e of[Zg,zg]){if(!je.existsSync(e))continue;let A=je.readdirSync(e).filter(r=>{let i=r.toLowerCase();return i.endsWith(".json")&&!i.includes(".backup")});for(let r of A){let i=GA.join(e,r);try{LD(i)}catch(n){t.push({dir:GA.basename(e),file:r,err:n.message})}}}return t.length===0?{ok:!0}:{ok:!1,broken:t}}function RRe(t){let e=[],A=[];for(let n of[Zg,zg]){if(!je.existsSync(n))continue;let s=je.readdirSync(n).filter(o=>o.endsWith(t));for(let o of s){let g=GA.join(n,o),u=o.slice(0,-t.length),a=GA.join(n,u);try{u.toLowerCase().endsWith(".json")?LD(g):je.accessSync(g,je.constants.R_OK),e.push({backupPath:g,originalPath:a,fileName:o,dir:n})}catch(I){A.push(`${GA.basename(n)}/${o}: ${I.message}`)}}}if(A.length>0)return{ok:!1,error:A.join("; "),restored:0};let r=0,i=[];for(let{backupPath:n,originalPath:s,fileName:o,dir:g}of e)try{J9(n,s),r++}catch(u){i.push(`${GA.basename(g)}/${o}: ${u.message}`)}return i.length>0?{ok:!1,error:i.join("; "),restored:r}:{ok:!0,restored:r}}function J9(t,e){let A=e+".tmp";try{je.copyFileSync(t,A),O9(A)}catch(r){try{je.unlinkSync(A)}catch{}throw r}try{je.renameSync(A,e)}catch(r){try{je.unlinkSync(A)}catch{}if(r.code==="EPERM"||r.code==="EBUSY"){let i=new Error(`File locked: ${e} (${r.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 i.code=r.code,i}throw r}}async function MRe(t,e,A="op"){let r,i=new Promise((n,s)=>{r=setTimeout(()=>s(new Error(`${A} timeout after ${e}ms`)),e)});try{return await Promise.race([t,i])}finally{clearTimeout(r)}}function $g(t){if(!t)return!1;let e=t.constructor?.name||"";if(e==="ProtocolError"||e==="TargetCloseError")return!0;let A=t.message||"";return/Runtime\.callFunctionOn timed out/i.test(A)||/Target closed/i.test(A)||/Session closed/i.test(A)||/Connection closed/i.test(A)||/Most likely the page has been closed/i.test(A)||/Navigating frame was detached/i.test(A)||/Protocol error.*timed out/i.test(A)}async function j9(t,e,{returnUrl:A,gotoTimeoutMs:r=6e4,cookiesProvider:i=null,setupPage:n=null}={}){try{await MRe(e.close({runBeforeUnload:!1}),5e3,"oldPage.close")}catch{}let s=await t.newPage();if(n)try{await n(s)}catch{}if(i)try{let o=i();Array.isArray(o)&&o.length>0&&await s.setCookie(...o)}catch{}if(A){await s.goto(A,{waitUntil:"domcontentloaded",timeout:r});let o=s.url();if(/\/login|\/checkpoint|\/uas\/login|\/authwall/.test(o))throw new Error(`Session expired after recovery \u2014 landed on ${o}. Run: ilml linkedin login`)}return s}await H9();var X9="https://www.linkedin.com/messaging/",_Re=10,v1=3,NRe=async t=>{await t.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>{}})})},LRe=()=>kD(),eu={pageRecoveries:0};async function xD(t,e){if(eu.pageRecoveries>=_Re)throw new Error(`CDP unrecoverable: ${eu.pageRecoveries} page recoveries already attempted. Last: ${e}`);console.log(`
|
|
258
258
|
\u{1F504} CDP dead \u2014 recovering page (#${eu.pageRecoveries+1}). Reason: ${e}`),t.page=await j9(t.browser,t.page,{returnUrl:X9,cookiesProvider:LRe,setupPage:NRe}),eu.pageRecoveries++,await _A(3e3);try{await t.page.waitForSelector(".msg-conversation-listitem",{timeout:15e3})}catch{}console.log(`\u2713 Page recovered (total: ${eu.pageRecoveries}).
|
|
259
|
-
`)}function Wl(t){return(t.messages||[]).find(e=>oI.has(e.status))?.status}var uo=process.argv.slice(2),k1=uo.includes("--full"),on=uo.includes("--dry-run"),Y9=uo.includes("--report"),R1=uo.includes("--verbose"),q9=uo.includes("--backfill"),W9=uo.includes("--enrich"),V9=uo.includes("--today"),M1=uo.includes("--read-unread"),ZE=parseInt(uo.find(t=>t.startsWith("--max="))?.split("=")[1]||"50");function xRe(t,e){if(!e)return"new";if(t.unread)return"unread";if(e.messages?.length
|
|
259
|
+
`)}function Wl(t){return(t.messages||[]).find(e=>oI.has(e.status))?.status}var uo=process.argv.slice(2),k1=uo.includes("--full"),on=uo.includes("--dry-run"),Y9=uo.includes("--report"),R1=uo.includes("--verbose"),q9=uo.includes("--backfill"),W9=uo.includes("--enrich"),V9=uo.includes("--today"),M1=uo.includes("--read-unread"),ZE=parseInt(uo.find(t=>t.startsWith("--max="))?.split("=")[1]||"50");function xRe(t,e){if(!e)return"new";if(t.unread)return"unread";if(!e.profileUrl&&(e.messages?.length||e.lastMessagePreview))return"missing_url";let A=(e.lastMessagePreview||"").substring(0,50),r=(t.lastMessagePreview||"").substring(0,50);return r&&A&&r!==A?"preview_changed":t.lastMessageBy&&t.lastMessageBy!==e.lastMessageBy?"sender_changed":null}async function URe(t,e){console.log(`
|
|
260
260
|
\u2550\u2550 PHASE 1: Inbox Scan \u2550\u2550
|
|
261
261
|
`);let A=lw(),r=A.lastIncrementalSync?new Date(A.lastIncrementalSync):null,i=new Map,n=new Map,s=new Map,o=0,g=0,u=0,a=0,I=0,c=-1,l=0;for(;;){g++;let h=await y9(t),d=0;for(let f of h){if(!f.name||i.has(f.name))continue;i.set(f.name,f),d++,f.unread&&s.set(f.name,f);let w=e[f.name],S=xRe(f,w);S&&(!M1&&f.unread?o++:n.set(f.name,{card:f,reason:S})),w&&(S||(w.lastMessageBy=f.lastMessageBy,w.lastMessagePreview=f.lastMessagePreview||w.lastMessagePreview,w.lastMessageTime=f.lastMessageTime||w.lastMessageTime),w.unread=f.unread,f.lastMessageBy==="them"&&f.unread&&(w.conversationStatus="unanswered_by_us"))}if(R1?console.log(` Round #${g}: +${d} new | Total: ${i.size} | Need reading: ${n.size}`):process.stdout.write(`\r Scanned: ${i.size} conversations (page #${g}, ${n.size} need reading)`),d===0){if(I++,I>=3){console.log(`
|
|
262
262
|
3 rounds with no new items \u2014 end of visible list.`);break}}else I=0;if(!k1){if(n.size===a){if(u++,u>=3){console.log(`
|
package/ilml-plugin.json
CHANGED
package/package.json
CHANGED