LinkedIn

react

linkedin.com
InstallationInstalls just this skill for your agent
Side effectwrite
Authsession
Needsnavigate · evaluate
Summary

React to a LinkedIn post (by its urn:li:activity or permalink url) from the logged-in account, with the given reaction type (default: Celebrate).

Params
{"urn":"string: urn:li:activity:... of the post (or the full permalink url)","reaction":"string: reaction type. Friendly (celebrate/like/support/love/insightful/funny) or API code (PRAISE/LIKE/APPRECIATION/EMPATHY/INTEREST/ENTERTAINMENT). Default celebrate."}
Returns
{"status":"int (HTTP of the create, 200 = ok)","reacted":"bool","threadUrn":"string (resolved thread that was reacted on)","resourceKey":"string (urn of the fsd_reaction created)","reactionType":"string (API code applied)"}
SKILL.md53 lines

Intent

React to a LinkedIn post (by its urn:li:activity or permalink url) from the logged-in account, with the given reaction type (default: Celebrate). WRITE-REVERSIBLE (it can be removed). Accepts friendly names: 'celebrate'->PRAISE, 'like'->LIKE, 'support'->APPRECIATION, 'love'->EMPATHY, 'insightful'->INTEREST, 'funny'->ENTERTAINMENT (or pass the API code directly). Robust path via the Voyager GraphQL API: the recipe navigates to the permalink /feed/update/<urn>/ (classic authenticated voyager-web page) and the extractor does an in-page fetch with session cookies. (1) Resolves the threadUrn (the socialDetail ugcPost/share, different from the activity urn) by reading the permalink HTML (looks for 'fsd_socialDetail:(<threadUrn>', with a fallback to the first urn:li:ugcPost/share/activity). (2) Best-effort DELETE of the previous reaction (queryId voyagerSocialDashReactions...f68b48ae) to allow changing it / idempotency. (3) CREATE POST /voyager/api/graphql?action=execute&queryId=voyagerSocialDashReactions.b731222600772fd42464c0fe19bd722b with body {variables:{entity:{reactionType},threadUrn},queryId,includeWebMetadata:true}. Headers: csrf-token=JSESSIONID cookie, x-restli-protocol-version 2.0.0, accept application/vnd.linkedin.normalized+json+2.1, content-type application/json. Success = HTTP 200 with createSocialDashReactions/resourceKey. PARAMS: urn (urn:li:activity:... or the permalink) and reaction (friendly name or code, default celebrate); optional threadUrn to force the thread. Returns { reacted, status, threadUrn, reactionType, resourceKey }. Requires a session. NOTE: the queryId hashes ROTATE on every LinkedIn deploy; if it returns HTTP 400/404 recapture the create/delete hashes from the reaction button.

Preconditions

  • Logged-in session in the browser you drive (Logged-in LinkedIn session (www.linkedin.com cookies, including JSESSIONID)).
  • If not logged in: navigate to https://linkedin.com and let the user log in, then retry.

Execute

  1. navigate → https://www.linkedin.com/feed/update/{{urn}}/
  2. evaluate the Extractor below with params. It does the in-page work and returns the JSON in returns.

Extractor

( async (root, params) => { const CREATE_QID = 'voyagerSocialDashReactions.b731222600772fd42464c0fe19bd722b'; const DELETE_QID = 'voyagerSocialDashReactions.f68b48ae5bc0085d7a45c7003b772a39'; const MAP = { like:'LIKE', celebrate:'PRAISE', praise:'PRAISE', support:'APPRECIATION', appreciation:'APPRECIATION', love:'EMPATHY', empathy:'EMPATHY', insightful:'INTEREST', interest:'INTEREST', funny:'ENTERTAINMENT', entertainment:'ENTERTAINMENT' }; const raw = String(params.reaction || params.reactionType || 'celebrate').trim().toLowerCase().split(' ').join(''); const reactionType = MAP[raw] || raw.toUpperCase(); if (['LIKE','PRAISE','APPRECIATION','EMPATHY','INTEREST','ENTERTAINMENT'].indexOf(reactionType) < 0) throw new Error('not-applicable: unknown reaction type: ' + raw); const ck = document.cookie || ''; let csrf = ''; { const parts = ck.split(';'); for (const p of parts) { const s = p.trim(); if (s.indexOf('JSESSIONID=') === 0) { csrf = s.substring(11).split('"').join(''); break; } } } if (!csrf) throw new Error('re-auth: no JSESSIONID cookie (LinkedIn session not started)'); let threadUrn = String(params.threadUrn || '').trim(); if (!threadUrn) { const html = document.documentElement.innerHTML; const KEY = 'fsd_socialDetail:('; let i = html.indexOf(KEY); if (i >= 0) { let j = i + KEY.length; let end = j; while (end < html.length && html[end] !== ',' && html[end] !== ')') end++; threadUrn = html.substring(j, end); } if (!threadUrn) { for (const t of ['urn:li:ugcPost:', 'urn:li:share:', 'urn:li:activity:']) { const k = html.indexOf(t); if (k >= 0) { let e = k + t.length; while (e < html.length && html[e] >= '0' && html[e] <= '9') e++; threadUrn = html.substring(k, e); break; } } } if (!threadUrn) { const u = String(params.urn || location.href); const t = 'urn:li:activity:'; const k = u.indexOf(t); if (k >= 0) { let e = k + t.length; while (e < u.length && u[e] >= '0' && u[e] <= '9') e++; threadUrn = u.substring(k, e); } } } if (!threadUrn) throw new Error('tool-broken: could not resolve the threadUrn of the post'); const H = { 'csrf-token': csrf, 'x-restli-protocol-version': '2.0.0', 'accept': 'application/vnd.linkedin.normalized+json+2.1', 'content-type': 'application/json; charset=UTF-8' }; const ep = (qid) => 'https://www.linkedin.com/voyager/api/graphql?action=execute&queryId=' + qid; try { await fetch(ep(DELETE_QID), { method:'POST', credentials:'include', headers:H, body: JSON.stringify({ variables:{ threadUrn }, queryId: DELETE_QID, includeWebMetadata:true }) }); } catch (e) {} const res = await fetch(ep(CREATE_QID), { method:'POST', credentials:'include', headers:H, body: JSON.stringify({ variables:{ entity:{ reactionType }, threadUrn }, queryId: CREATE_QID, includeWebMetadata:true }) }); if (res.status === 401 || res.status === 403) throw new Error('re-auth: HTTP ' + res.status + ' while reacting'); const txt = await res.text(); if (!res.ok) throw new Error('tool-broken: reaction HTTP ' + res.status + ' ' + txt.substring(0,180)); let resourceKey = ''; { const K = '"resourceKey":"'; const a = txt.indexOf(K); if (a >= 0) { const b = txt.indexOf('"', a + K.length); resourceKey = txt.substring(a + K.length, b); } } const reacted = txt.indexOf('createSocialDashReactions') >= 0 || resourceKey.length > 0; return { reacted: reacted, status: res.status, threadUrn: threadUrn, reactionType: reactionType, resourceKey: resourceKey }; } )

Success assertion

{
  "type": "json",
  "jsonPath": "reacted"
}