Read the logged-in user's LinkedIn feed/home and return the current timeline posts.
{"count":"number (optional; maximum number of posts to return. If omitted, returns all in the feed)"}{"count":"int (number of posts returned)","posts":"array of { author:string|null (name, person or company), authorUrl:string|null (absolute profile/page URL), authorSlug:string|null (in/company/school slug), text:string|null (text), urn:string (urn:li:activity:...), url:string (permalink /feed/update/<urn>/), reactions:int, comments:int, shares:int, time:string|null (relative age e.g. \"2 h\"), isPromoted:bool (true if it is a Promoted ad), reason:string|null (reason it appears when the network surfaces it, e.g. \"X recommends this\") }"}Read the logged-in user's LinkedIn feed/home and return the current timeline posts. Read-only, no required params (optional count to limit). Does an in-page fetch of the SSR HTML of /feed/ with the session cookies (credentials:include) and extracts the posts, with a fallback to the rendered DOM. For each post: author (person or company), authorUrl, authorSlug, text, urn (urn:li:activity), url permalink, reactions, comments, shares, time (relative age), isPromoted (flags Promoted/ads so they do not pollute) and reason (e.g. "X recommends this" / "X shared this" when the network surfaces the content).
params. It does the in-page work and returns the JSON in returns.( async (root, params) => { const doc = root.ownerDocument || root || document; const max = (params && params.count) ? parseInt(params.count, 10) : null; const dec = (s) => { if (s == null) return s; return String(s) .replace(/\u002d/gi, '-').replace(/\u0026/gi, '&').replace(/\n/g, '\n') .replace(/\"/g, '"').replace(/\//g, '/').replace(/\\/g, '\') .replace(/&/g, '&').replace(/'|'/g, "'").replace(/"/g, '"') .replace(/</g, '<').replace(/>/g, '>').replace(/ | /g, ' ') .replace(/\s+/g, ' ').trim(); }; const toInt = (v) => { if (v == null) return 0; const n = parseInt(String(v).replace(/[^\d]/g, ''), 10); return isNaN(n) ? 0 : n; };
// Fetch the home feed SSR HTML using the page's own session cookies. let html = ''; try { const r = await fetch('https://www.linkedin.com/feed/', { credentials: 'include', headers: { 'accept': 'text/html' } }); if (r.status === 401 || r.status === 403) throw new Error('re-auth: HTTP ' + r.status); html = await r.text(); } catch (e) { if (String(e.message).indexOf('re-auth') === 0) throw e; html = ''; }
const posts = []; const seen = new Set();
if (html) { const urnRe = /urn:li:activity:(\d+)/g; let m; const order = []; const idxByUrn = {}; while ((m = urnRe.exec(html))) { const urn = 'urn:li:activity:' + m[1]; if (!(urn in idxByUrn)) { idxByUrn[urn] = m.index; order.push(urn); } }
for (const urn of order) {
if (max && posts.length >= max) break;
if (seen.has(urn)) continue;
seen.add(urn);
const at = idxByUrn[urn];
const win = html.slice(Math.max(0, at - 4500), at + 4500);
let author = null, authorSlug = null, authorUrl = null, text = null;
let reactions = 0, comments = 0, shares = 0, time = null, reason = null, isPromoted = false;
const slugM = win.match(/"publicIdentifier":"([^"]+)"/) ||
win.match(/\/(?:in|company|school)\/([A-Za-z0-9\-_%]+)/);
if (slugM) authorSlug = decodeURIComponent(slugM[1].split(/[/?#]/)[0]);
const aUrlM = win.match(/"navigationUrl":"(https:\\?\/\\?\/www\.linkedin\.com\\?\/(?:in|company|school)\\?\/[^"]+)"/);
if (aUrlM) authorUrl = dec(aUrlM[1]).split('?')[0];
else if (authorSlug) {
authorUrl = 'https://www.linkedin.com/' + (/"companyName"|\/company\//.test(win) ? 'company' : 'in') + '/' + authorSlug;
}
const nameM = win.match(/"actorName"[^"]*"text":"([^"]+)"/) ||
win.match(/"name":"([^"]+)"[^}]*"navigationUrl":"https:\\?\/\\?\/www\.linkedin\.com\\?\/(?:in|company|school)\\?\//) ||
win.match(/"title":\{"text":"([^"]+)"/);
if (nameM) author = dec(nameM[1]);
const txtM = win.match(/"commentary":\{[^}]*"text":\{"text":"((?:[^"\\]|\\.)*)"/) ||
win.match(/"summary":\{"text":"((?:[^"\\]|\\.)*)"/) ||
win.match(/"text":"((?:[^"\\]|\\.)*)"/);
if (txtM) text = dec(txtM[1]);
const reM = win.match(/"numLikes":(\d+)/) || win.match(/"reactionCount":(\d+)/) || win.match(/"numReactions":(\d+)/);
if (reM) reactions = toInt(reM[1]);
const coM = win.match(/"numComments":(\d+)/) || win.match(/"commentsCount":(\d+)/);
if (coM) comments = toInt(coM[1]);
const shM = win.match(/"numShares":(\d+)/) || win.match(/"shareCount":(\d+)/) || win.match(/"reposts?":(\d+)/);
if (shM) shares = toInt(shM[1]);
const tmM = win.match(/"actorSubDescription"[^"]*"text":"([^"]+)"/) ||
win.match(/"timeText":\{"text":"([^"]+)"/) ||
win.match(/"createdTimeText":"([^"]+)"/);
if (tmM) { let tt = dec(tmM[1]); const tk = tt.match(/^(\d+\s*(?:s|seg|min|h|d|sem|sm|mes|a|w|mo|y)\w*\.?)/i); time = tk ? tk[1] : (tt.split('•')[0] || tt).trim(); }
const rsM = win.match(/"header"[^}]*"text":\{"text":"([^"]+)"/) ||
win.match(/"updateMetadata"[^}]*"actionText"[^"]*"text":"([^"]+)"/) ||
win.match(/"text":"([^"]*(?:recomienda esto|ha compartido esto|ha comentado|likes this|shared this|recommends this|commented on this)[^"]*)"/i);
if (rsM) reason = dec(rsM[1]);
if (/Promocionad|"adContext"|sponsoredContent|"sponsored":true|Patrocinad|"promoted":true/i.test(win)) isPromoted = true;
posts.push({
author: author || null,
authorUrl: authorUrl || null,
authorSlug: authorSlug || null,
text: text || null,
urn: urn,
url: 'https://www.linkedin.com/feed/update/' + urn + '/',
reactions: reactions,
comments: comments,
shares: shares,
time: time || null,
isPromoted: isPromoted,
reason: reason || null
});
}
}
const clean = (s) => (s == null ? '' : String(s)).replace(/ /g, ' ').replace(/\s+/g, ' ').trim(); const liveCards = doc.querySelectorAll('div.feed-shared-update-v2, div[data-urn*="urn:li:activity"], [data-id*="urn:li:activity"]'); liveCards.forEach((card) => { if (max && posts.length >= max) return; let urn = (card.getAttribute && (card.getAttribute('data-urn') || card.getAttribute('data-id'))) || ''; if (!/urn:li:activity/.test(urn)) { const inner = card.querySelector('[data-urn*="urn:li:activity"], [data-id*="urn:li:activity"], a[href*="urn:li:activity"]'); if (inner) urn = inner.getAttribute('data-urn') || inner.getAttribute('data-id') || inner.getAttribute('href') || ''; } const um = urn.match(/urn:li:activity:\d+/); if (!um) return; urn = um[0]; if (seen.has(urn)) return; seen.add(urn);
const c = card.closest('div.feed-shared-update-v2') || card;
const nameEl = c.querySelector('.update-components-actor__title span[aria-hidden="true"], .update-components-actor__title, .update-components-actor__name');
const aEl = c.querySelector('a.update-components-actor__meta-link, .update-components-actor__container a[href*="/in/"], .update-components-actor__container a[href*="/company/"], .update-components-actor a[href]');
let authorUrl = aEl ? (aEl.getAttribute('href') || '') : '';
if (authorUrl && authorUrl.indexOf('/') === 0) authorUrl = 'https://www.linkedin.com' + authorUrl;
authorUrl = authorUrl ? authorUrl.split('?')[0] : null;
let authorSlug = null;
if (authorUrl) { const sm = authorUrl.match(/\/(?:in|company|school)\/([^\/?#]+)/); if (sm) authorSlug = sm[1]; }
const txEl = c.querySelector('.update-components-text .break-words, .update-components-text, .feed-shared-inline-show-more-text, .update-components-update-v2__commentary');
const hdrEl = c.querySelector('.update-components-header__text-view, .update-components-header');
const subEl = c.querySelector('.update-components-actor__description, .update-components-actor__sub-description');
const sub = subEl ? clean(subEl.innerText || subEl.textContent) : '';
const reason = hdrEl ? (clean(hdrEl.innerText || hdrEl.textContent) || null) : null;
const isPromoted = /promocionad|promoted|sponsored|patrocinad/i.test((reason || '') + ' ' + sub);
posts.push({
author: nameEl ? (clean(nameEl.innerText || nameEl.textContent) || null) : null,
authorUrl: authorUrl,
authorSlug: authorSlug,
text: txEl ? (clean(txEl.innerText || txEl.textContent) || null) : null,
urn: urn,
url: 'https://www.linkedin.com/feed/update/' + urn + '/',
reactions: 0,
comments: 0,
shares: 0,
time: sub ? (sub.replace(/\s*•.*$/, '').trim() || null) : null,
isPromoted: isPromoted,
reason: reason
});
});
return { count: posts.length, posts: posts }; } )
{
"type": "json",
"jsonPath": "posts.0.urn"
}