X

search-tweets

x.com
InstallationInstalls just this skill for your agent
Side effectread
Authsession
Needsnavigate · evaluate
Summary

Search tweets on X/Twitter by keyword/topic (Latest tab), to find intent signals (people asking for a solution, complaining about a competitor, describing a problem).

Params
{"query":"string (search text)"}
Returns
{"count":"int","query":"string","results":"array of { displayName, handle, text, id, url, time }"}
SKILL.md45 lines

Intent

Search tweets on X/Twitter by keyword/topic (Latest tab), to find intent signals (people asking for a solution, complaining about a competitor, describing a problem). PARAM: query (search text, e.g. 'looking for a CRM' or 'alternative to notion'). X's internal API (SearchTimeline GraphQL) is blocked by the anti-bot header x-client-transaction-id, so it navigates to /search?q=...&f=live and parses the DOM (stable data-testid selectors) with scrolling to gather ~30-50 tweets. Returns { count, query, results:[{ displayName, handle, text, id, url, time }] }; if there are no results it returns count:0 / results:[] (not an error). handle = the @ without the at-sign (feeds x-get-profile / replying). NOTE: X virtualizes the timeline (only what's visible is in the DOM); the tool scrolls and dedupes by id, but still brings a subset. Image/video-only tweets may come with empty text. Requires an active X session. If X changes the data-testid, recapture.

Preconditions

  • Logged-in session in the browser you drive (X/Twitter session started (x.com cookies)).
  • If not logged in: navigate to https://x.com and let the user log in, then retry.

Execute

  1. navigate → https://x.com/search?q={{query|encode}}&src=typed_query&f=live
  2. evaluate the Extractor below with params. It does the in-page work and returns the JSON in returns.

Extractor

( async (root, params) => { const sleep = ms => new Promise(r=>setTimeout(r,ms)); const parse1 = a => { const nameEl=a.querySelector('[data-testid="User-Name"]'); const nl = nameEl ? nameEl.innerText.split('\n').filter(Boolean) : []; const handle=(nl.find(s=>s.startsWith('@'))||'').replace('@',''); const displayName=nl[0]||null; const statusLink=Array.from(a.querySelectorAll('a[href*="/status/"]')).map(x=>x.getAttribute('href')).find(h=>//status/\d+/.test(h)); const id=statusLink?(statusLink.match(/status/(\d+)/)||[])[1]:null; const timeEl=a.querySelector('time'); const time=timeEl?timeEl.getAttribute('datetime'):null; let full=a.textContent||''; const grp=a.querySelector('[role="group"]'); if(grp) full=full.split(grp.textContent)[0]; if(timeEl){ const t=timeEl.textContent; const i=full.indexOf(t); if(i>=0) full=full.slice(i+t.length); } const text=full.replace(/^[\s·]+/,'').replace(/^Article/,'').trim(); return { displayName, handle, text: text||null, id, url: statusLink?'https://x.com'+statusLink:null, time }; }; const seen=new Map(); const collect=()=>{ document.querySelectorAll('article[data-testid="tweet"]').forEach(a=>{ const r=parse1(a); if(r.id && !seen.has(r.id)) seen.set(r.id,r); }); }; for(let i=0;i<20 && document.querySelectorAll('article[data-testid="tweet"]').length===0;i++) await sleep(300); collect(); for(let s=0;s<6;s++){ window.scrollTo(0, document.body.scrollHeight); await sleep(900); collect(); } const results=Array.from(seen.values()); return { count: results.length, query: (params&&params.query)||null, results }; } )

Success assertion

{
  "expr": "[data-testid=\"primaryColumn\"]",
  "type": "dom"
}
search-tweets: a X skill for AI browsing agents · browser-memory