social-fetch
When you or another skill needs to fetch the content of a social media post by URL — tweet, X thread, LinkedIn post, Instagram post, TikTok video, Bluesky post, Reddit thread, Mastodon status, Threads post, Hacker News thread. Returns normalized structured data (author, posted_at
Install
npx skills add https://github.com/coreyhaines31/makerskills/tree/main/skills/social-fetch
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install coreyhaines31-makerskills@llmmart
git clone https://github.com/coreyhaines31/makerskills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole coreyhaines31/makerskills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
/social-fetch — Pull any social post by URL
Normalized fetcher for social posts across platforms. Detects platform from URL, tries strategies in order, returns the same JSON shape regardless of source.
Step 1 — Detect platform
| URL pattern | Platform |
|---|---|
x.com/<user>/status/<id> or twitter.com/<user>/status/<id> |
x (Twitter) |
linkedin.com/posts/<slug> or linkedin.com/feed/update/urn:li:activity:<id> |
|
linkedin.com/in/<handle> (profile, recent activity) |
linkedin-profile |
instagram.com/p/<id> or instagram.com/reel/<id> |
|
tiktok.com/@<user>/video/<id> |
tiktok |
bsky.app/profile/<handle>/post/<rkey> |
bluesky |
reddit.com/r/<sub>/comments/<id>/... |
|
<mastodon-instance>/@<user>/<id> (e.g. mastodon.social, hachyderm.io) |
mastodon |
threads.net/@<user>/post/<id> |
threads |
news.ycombinator.com/item?id=<id> |
hn |
youtube.com/watch?v=<id> or youtu.be/<id> |
→ defer to watch-video |
If the URL doesn't match any pattern, ask the user what platform it is.
Step 2 — Pick strategy chain
Read references/strategies.md for the per-platform strategy chain. Each platform has 2–5 strategies tried in order.
Key principles:
- Free strategies first (direct APIs, agent-browser)
- Paid only as fallback (ScrapeCreators / Apify) — and only if the env key is set
- Bluesky / Mastodon / HN / Reddit are free + reliable (public APIs)
- X / LinkedIn / Instagram / TikTok / Threads need paid or scraping fallback for full data
Step 3 — Execute strategy
For each strategy in the chain:
- Try it
- If success: parse → normalize → return
- If failure (404, 402, auth wall, empty response): note the failure and try the next strategy
After exhausting the chain, return a clear error: which strategies were tried, why each failed, and what's needed to unlock (e.g., "Add $SCRAPECREATORS_API_KEY for X — see references/auth-keys.md").
Step 4 — Normalize output
Return this shape regardless of platform (see references/output-schema.md for the full spec + platform-specific examples):
{
"platform": "x",
"url": "https://x.com/example/status/1234567890",
"fetched_at": "2026-06-17T14:35:00Z",
"raw_source": "scrapecreators",
"author": {
"handle": "@example",
"name": "the user Ganim",
"verified": true
},
"posted_at": "2026-06-17T16:53:00Z",
"text": "The 80/20 of a useful AI second brain: ...",
"media": [],
"engagement": {
"likes": 51,
"reposts": 13,
"replies": 9,
"bookmarks": 7,
"views": 32700
},
"is_thread": true,
"thread": [],
"replies": []
}
Fields with no equivalent on a platform (e.g., bookmarks on Mastodon) get null, not 0. Missing data is different from zero data.
Step 5 — Optional enrichments
Based on flags / asks:
| Flag | Behavior |
|---|---|
--with-replies |
Fetch top-level replies (1 hop). Costs extra API quota. |
--thread |
If the post is part of a thread by the same author, fetch the whole thread. |
--raw |
Include the raw API/scrape response in the output (for debugging) |
--media |
Download media files (images/videos) to ~/Documents/social-fetches/<platform>-<id>/ |
Default: just the post itself, no replies, no media download (just URLs).
Step 6 — Cache (optional)
If ~/Documents/social-fetches/_cache/ exists, cache successful fetches there by {platform}-{id}.json for 24h. Saves API quota when the same post is referenced repeatedly across skills.
Skip cache if --no-cache flag is set or for --with-replies / --thread (likely-stale).
Composes with
deep-research— cite specific posts in research briefs. When research surfaces a relevant tweet/post URL, fetch and include in the brief.jab-hook— pull recent posts from inspiration accounts for deeper format analysis (currently uses agent-browser inline; should call this skill instead).business-brainstorm— pull competitor / operator commentary as evidence during scoring.second-brain— capture a post intoraw/with thetweet-/bookmark-prefix; the structured output makes for cleaner raw files than a screenshot or copy-paste.watch-video— for YouTube URLs (or any video — Loom, Vimeo, Riverside, MP4), route there instead.
Known limits
- X: free strategies return tweet preview only (text, author, basic engagement). Full thread + replies need
$SCRAPECREATORS_API_KEYor$APIFY_API_TOKEN. - LinkedIn: agent-browser works for profile recent-activity (after dismissing the modal). Specific post URLs (
linkedin.com/posts/...) often need paid fallback. - Instagram / TikTok / Threads: heavy anti-bot. Paid fallback strongly recommended.
- Bluesky / Mastodon / HN / Reddit: free + reliable.
- Private / deleted posts: nothing helps. Try Wayback Machine for deleted content.
If a platform consistently fails on free strategies and the user uses it often, prompt to set up the paid key (see references/auth-keys.md).
Notes on quality
- Strategy chain, not single-source. Every platform has a fallback ladder (native oEmbed → agent-browser → SCS API → Apify). If one step fails, degrade gracefully to the next. Never fail hard on the first attempt.
- Structured output over screenshots. Downstream skills (jab-hook, deep-research, second-brain) need JSON with author + text + engagement fields, not an image. Even when the underlying strategy is a screenshot, extract text before returning.
- Cache aggressively, invalidate honestly. 24h TTL on
~/Documents/social-fetches/_cache/prevents API burn when the same post is referenced across multiple skills in a session.--with-replies/--threadskip cache because replies age fast. - Respect paid-key economics. ScrapeCreators / Apify calls cost real money. Prompt before hitting paid strategies if the user hasn't confirmed they want depth. Free strategies first, always.
- Media download is opt-in. Default is post text only;
--mediadownloads images/videos. Silent media downloads eat disk quickly. - Private / deleted content is a hard stop. No strategy chain rescues private accounts or deleted posts. Suggest Wayback Machine for deleted content and stop.
- Rate-limits are per-platform. X free strategies hit rate limits fast; LinkedIn agent-browser burns session fingerprints. Space out calls in loops or the workflow degrades to worse-than-manual.
Files (makerskills)
-
references
-
auth-keys.md 2.8 KB
# Auth keys — unlocking paid fallback strategies The free strategies (Bluesky API, Mastodon API, HN Algolia, Reddit .json, agent-browser) work for many cases. For X / LinkedIn (specific posts) / Instagram / TikTok / Threads, paid keys unlock reliable + complete data. ## Which key unlocks what | Key | Unlocks | Cost (approx, 2026) | |---|---|---| | `$SCRAPECREATORS_API_KEY` | X (tweets + threads + replies), LinkedIn posts, Instagram, TikTok, possibly Threads | Pay-as-you-go, ~$0.005–$0.02/post depending on endpoint | | `$APIFY_API_TOKEN` | Nearly any platform via "Actors" (X, IG, TikTok, LinkedIn, Threads, even niche ones) | Per-actor pricing, often $1–$5 / 1K results | ## Setting up ### ScrapeCreators 1. Sign up at https://scrapecreators.com 2. Get API key from dashboard 3. Add to `~/.zshenv`: ```bash export SCRAPECREATORS_API_KEY="<your-key>" ``` 4. `source ~/.zshenv` or new terminal 5. Verify: `echo $SCRAPECREATORS_API_KEY | head -c 8` ### Apify 1. Sign up at https://apify.com (free tier exists; pay-as-you-go after) 2. Get API token from Settings → Integrations → API 3. Add to `~/.zshenv`: ```bash export APIFY_API_TOKEN="<your-token>" ``` 4. `source ~/.zshenv` 5. Verify: `echo $APIFY_API_TOKEN | head -c 8` ## Free-only mode If neither key is set, `social-fetch` runs in free-only mode: - **Bluesky / Mastodon / HN / Reddit** — fully functional - **X** — preview only (text + basic engagement, no thread/replies) - **LinkedIn profiles** — recent activity feed visible (no specific post fetch) - **Instagram / TikTok / Threads** — Open Graph metadata only (title, description, image — no engagement, no replies) This is fine for most one-off fetches. Paid fallback is mostly useful when: - Building corpus (deep-research running many fetches) - Analyzing thread structure or reply trees on X - Inspiration-account analysis on Instagram / TikTok (where free fails entirely) ## Cost discipline When using paid strategies: - Always check the cache first (`~/Documents/social-fetches/_cache/` if it exists) - Default to 24h cache TTL - Don't auto-enrich with `--with-replies` or `--thread` unless requested — these multiply quota use - For high-volume work (e.g., fetching last 50 posts from an inspiration account), batch via Apify actors (cheaper per item) instead of ScrapeCreators per-call ## When to set up paid Don't set up keys preemptively. Set them up when: - A real workflow (deep-research, inspiration analysis, competitor monitoring) is being blocked - You've done >10 fetches and the free strategies are missing data you need - You're starting on `swipe-save` or another skill where social capture is central If `social-fetch` falls through to "no paid key set" on the same platform 3+ times in a session, it'll surface a one-time prompt. -
output-schema.md 6.4 KB
# Normalized output schema Same shape returned for every platform. Fields with no equivalent on a platform get `null`, not zero or empty string. Missing ≠ zero. ## Schema ```typescript { "platform": "x" | "linkedin" | "linkedin-profile" | "instagram" | "tiktok" | "bluesky" | "reddit" | "mastodon" | "threads" | "hn", "url": string, // canonical URL of the post "fetched_at": ISO8601, "raw_source": // which strategy succeeded "direct-api" | "agent-browser" | "open-graph" | "nitter" | "wayback" | "scrapecreators" | "apify", "author": { "handle": string | null, // @username "name": string | null, // display name "verified": boolean | null, "follower_count": number | null, "profile_url": string | null, "avatar_url": string | null }, "posted_at": ISO8601 | null, "edited_at": ISO8601 | null, "text": string, // post body, plain text. Markdown OK if source supports it. "html": string | null, // original HTML if scraped (Mastodon, etc.) "language": string | null, // ISO 639-1 if detected "media": Array<{ "type": "image" | "video" | "gif" | "audio", "url": string, "alt": string | null, "width": number | null, "height": number | null, "duration_seconds": number | null // video/audio only }>, "engagement": { "likes": number | null, // tweets/posts: likes. Reddit: upvotes. "reposts": number | null, // retweets, shares, reposts, reblogs "replies": number | null, "bookmarks": number | null, "views": number | null, "quotes": number | null // quote-tweets where applicable }, "links": Array<{ // links cited in the post body "url": string, "expanded_url": string, // resolved if t.co or similar shortener "title": string | null }>, "mentions": string[], // @handles mentioned "hashtags": string[], "is_reply": boolean, "reply_to": { // if is_reply = true "url": string, "author_handle": string } | null, "is_thread": boolean, // post is part of a multi-post thread by same author "thread": Array<{ // siblings in the thread (excludes self), in order "url": string, "text": string, "posted_at": ISO8601 }>, "replies": Array<{ // top-level replies (only if --with-replies) "url": string, "author": { "handle": string, "name": string }, "text": string, "posted_at": ISO8601, "engagement": { "likes": number, "replies": number } }>, "raw": object | null // raw API/scrape response (only if --raw) } ``` ## Platform field-coverage matrix What each platform typically provides on a successful free-strategy fetch: | Field | bluesky | mastodon | hn | reddit | x (free) | x (paid) | linkedin (free) | linkedin (paid) | ig (paid) | tiktok (paid) | threads (paid) | |---|---|---|---|---|---|---|---|---|---|---|---| | author.handle | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | author.verified | — | — | — | — | partial | ✅ | — | ✅ | ✅ | ✅ | ✅ | | author.follower_count | ✅ | ✅ | — | — | — | ✅ | — | ✅ | ✅ | ✅ | ✅ | | posted_at | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | partial | ✅ | ✅ | ✅ | ✅ | | text | ✅ | ✅ | ✅ | ✅ | partial | ✅ | partial | ✅ | ✅ | ✅ | ✅ | | media | ✅ | ✅ | — | ✅ | — | ✅ | — | ✅ | ✅ | ✅ | ✅ | | engagement.likes | ✅ | ✅ | ✅ (score) | ✅ | — | ✅ | — | ✅ | ✅ | ✅ | ✅ | | engagement.reposts | ✅ | ✅ | — | — | — | ✅ | — | ✅ | — | ✅ | ✅ | | engagement.views | — | — | — | — | — | ✅ | — | partial | partial | ✅ | partial | | replies | ✅ | ✅ | ✅ | ✅ | — | ✅ | — | partial | — | partial | partial | | thread (own author) | ✅ | ✅ | n/a | n/a | — | ✅ | — | ✅ | — | — | partial | `partial` = sometimes available depending on the post / scraping conditions. ## Example outputs (abbreviated) ### bluesky — full ```json { "platform": "bluesky", "url": "https://bsky.app/profile/jay.bsky.team/post/3kphvqpv6xs2t", "raw_source": "direct-api", "author": { "handle": "@jay.bsky.team", "name": "Jay Graber", "verified": null, "follower_count": 500123 }, "posted_at": "2026-06-15T14:23:00Z", "text": "We're rolling out video posts.", "media": [], "engagement": { "likes": 1245, "reposts": 234, "replies": 89, "bookmarks": null, "views": null } } ``` ### x — partial (free strategy: agent-browser preview) ```json { "platform": "x", "url": "https://x.com/example/status/1234567890", "raw_source": "agent-browser", "author": { "handle": "@example", "name": "Example User", "verified": true, "follower_count": null }, "posted_at": "2026-06-17T16:53:00Z", "text": "The 80/20 of a useful AI second brain: ...", "engagement": { "likes": 51, "reposts": 13, "replies": 9, "bookmarks": 7, "views": 32700 }, "is_thread": true, "thread": [] } ``` Note `thread: []` despite `is_thread: true` — free strategy can detect a thread exists but can't fetch the other posts. Use paid fallback or visit each thread URL directly. ### hn — full ```json { "platform": "hn", "url": "https://news.ycombinator.com/item?id=12345678", "raw_source": "direct-api", "author": { "handle": "pg" }, "posted_at": "2026-06-15T18:00:00Z", "text": "Title of the submission", "links": [{"url": "https://example.com/article", "expanded_url": "https://example.com/article"}], "engagement": { "likes": 234, "replies": 89 }, "replies": [ { "url": "https://news.ycombinator.com/item?id=12345679", "author": { "handle": "user1" }, "text": "Great article because…", "engagement": { "likes": 45, "replies": 3 } } ] } ``` ## Returning to the user When the skill completes, output: 1. **One-line summary** in chat: `<author> · <platform> · <date> · "<first 80 chars>..."` 2. **Path to the JSON output** if `--save` flag set (saved to `~/Documents/social-fetches/<platform>-<id>.json`) 3. **Inline JSON** in chat for the calling skill / the user to use If only partial data was retrievable (free strategy on X/LinkedIn/IG/TikTok), surface it clearly: *"Free strategy returned author + text + engagement. Replies + full thread require `$SCRAPECREATORS_API_KEY`. See references/auth-keys.md to set up."* -
strategies.md 6.7 KB
# Per-platform strategies Each platform has 2–5 strategies tried in order. Free first, paid as fallback (and only if env keys are set). --- ## bluesky **Reliability: high.** Public API, free. ### Strategy 1 — Public AppView API (DEFAULT) URL: `https://bsky.app/profile/<handle>/post/<rkey>` ```bash # Resolve handle → DID DID=$(curl -s "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=<handle>" | jq -r .did) # Build AT-URI and fetch the post + thread URI="at://${DID}/app.bsky.feed.post/<rkey>" curl -s "https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${URI}" | jq . ``` Returns the post + immediate parent + first-level replies in one call. Perfect for `--with-replies`. --- ## mastodon **Reliability: high.** Public API, free, no auth. ### Strategy 1 — Status endpoint URL pattern: `https://<instance>/@<user>/<status-id>` ```bash # Single status curl -s -H "Accept: application/json" "https://<instance>/api/v1/statuses/<status-id>" # With replies curl -s -H "Accept: application/json" "https://<instance>/api/v1/statuses/<status-id>/context" ``` Note: instance and ID parsed from URL. Web URL `https://hachyderm.io/@user/123456` → API `https://hachyderm.io/api/v1/statuses/123456`. --- ## hn **Reliability: high.** Free public Algolia API. ### Strategy 1 — Algolia items API URL pattern: `https://news.ycombinator.com/item?id=<id>` ```bash curl -s "https://hn.algolia.com/api/v1/items/<id>" | jq . ``` Returns the item + full nested comment tree. Comments are recursive — flatten or limit depth per `--with-replies` flag. --- ## reddit **Reliability: medium.** Free .json suffix works but rate-limited per IP (~60 req/min). ### Strategy 1 — Append `.json` to URL URL pattern: `https://www.reddit.com/r/<sub>/comments/<id>/<slug>/` ```bash curl -s -A "Mozilla/5.0 social-fetch/0.1" "<url>.json" | jq . ``` Returns `[post, comments_tree]` as a 2-element array. Set a real User-Agent — Reddit blocks default curl UA. ### Strategy 2 — Wayback Machine fallback If rate-limited or post deleted: ```bash curl -s "https://archive.org/wayback/available?url=<encoded-url>" | jq -r '.archived_snapshots.closest.url' ``` Returns Wayback URL — re-fetch from there. --- ## x (twitter) **Reliability: low without paid keys.** X aggressively blocks scraping. ### Strategy 1 — agent-browser preview (LIMITED) For tweet preview only — body text and basic author info. No engagement counts, no replies, no thread. ```bash agent-browser open "<url>" sleep 3 agent-browser snapshot 2>&1 | head -50 # Parse the StaticText for tweet body ``` Often hits "Sign up to see" modal — dismiss with first interactive button if visible. ### Strategy 2 — Nitter mirror (UNRELIABLE) Nitter instances are frequently rate-limited or down. Try if running: ```bash # Pick a known-working instance (rotate if down) for inst in nitter.net nitter.lacontrevoie.fr nitter.privacydev.net; do CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://${inst}/<user>/status/<id>") if [ "$CODE" = "200" ]; then curl -s "https://${inst}/<user>/status/<id>" break fi done ``` ### Strategy 3 — Wayback Machine ```bash curl -s "https://archive.org/wayback/available?url=https://twitter.com/<user>/status/<id>" | jq -r '.archived_snapshots.closest.url' ``` Older tweets often cached; recent ones rarely. ### Strategy 4 — ScrapeCreators API (PAID, recommended for X) Requires `$SCRAPECREATORS_API_KEY`. If unset, skip and surface a one-time setup prompt. ```bash curl -s "https://api.scrapecreators.com/v1/twitter/tweet?url=<encoded-url>" \ -H "x-api-key: $SCRAPECREATORS_API_KEY" ``` Endpoint exact path may differ — verify in ScrapeCreators docs on first use. ### Strategy 5 — Apify scraper (PAID) Requires `$APIFY_API_TOKEN`. Use the `apify/twitter-scraper` actor or a community equivalent. ```bash curl -X POST "https://api.apify.com/v2/acts/<actor-id>/run-sync-get-dataset-items?token=$APIFY_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"tweetUrls": ["<url>"], "maxItems": 1}' ``` --- ## linkedin **Reliability: medium.** agent-browser works for some content. ### Strategy 1 — agent-browser + dismiss modal ```bash agent-browser open "<url>" sleep 3 # Detect signup modal and dismiss agent-browser snapshot -i 2>&1 | head -20 # If first interactive element is "Dismiss" button: agent-browser click @e1 sleep 2 agent-browser snapshot 2>&1 | head -100 ``` Works well for `linkedin.com/in/<handle>` profile pages (recent activity feed visible). Specific post URLs (`linkedin.com/posts/...`) usually require login — fall through. ### Strategy 2 — ScrapeCreators API (PAID) ```bash curl -s "https://api.scrapecreators.com/v1/linkedin/post?url=<encoded-url>" \ -H "x-api-key: $SCRAPECREATORS_API_KEY" ``` ### Strategy 3 — Apify (PAID) Use `apify/linkedin-profile-scraper` or `apify/linkedin-post-scraper` actor. --- ## instagram **Reliability: low without paid.** Heavy anti-bot. ### Strategy 1 — Open Graph fallback (LIMITED — just description/image) ```bash curl -s -A "Mozilla/5.0" "<url>" | grep -E 'og:(title|description|image)' ``` Returns metadata only. No engagement counts. Often blocked. ### Strategy 2 — ScrapeCreators API (PAID, recommended for IG) ```bash curl -s "https://api.scrapecreators.com/v1/instagram/post?url=<encoded-url>" \ -H "x-api-key: $SCRAPECREATORS_API_KEY" ``` ### Strategy 3 — Apify (PAID) Use `apify/instagram-scraper` actor. --- ## tiktok **Reliability: low without paid.** ### Strategy 1 — Open Graph fallback (LIMITED) ```bash curl -s -A "Mozilla/5.0" "<url>" | grep -E 'og:(title|description|video)' ``` ### Strategy 2 — ScrapeCreators API (PAID) ```bash curl -s "https://api.scrapecreators.com/v1/tiktok/video?url=<encoded-url>" \ -H "x-api-key: $SCRAPECREATORS_API_KEY" ``` ### Strategy 3 — Apify (PAID) Use `apify/tiktok-scraper` actor. --- ## threads **Reliability: low without paid.** Meta's anti-bot, like Instagram. ### Strategy 1 — Open Graph fallback (LIMITED) ```bash curl -s -A "Mozilla/5.0" "<url>" | grep -E 'og:(title|description)' ``` ### Strategy 2 — ScrapeCreators API (PAID, if supported) Check ScrapeCreators docs for Threads endpoint — coverage varies. ### Strategy 3 — Apify (PAID) Use a Threads scraper actor (search Apify marketplace). --- ## Strategy chain summary | Platform | Free strategies | Paid fallback | |---|---|---| | bluesky | Direct API | — | | mastodon | Direct API | — | | hn | Algolia API | — | | reddit | `.json` suffix → Wayback | — | | x | agent-browser → Nitter → Wayback | ScrapeCreators → Apify | | linkedin | agent-browser (modal dismiss) | ScrapeCreators → Apify | | instagram | OG tags | ScrapeCreators → Apify | | tiktok | OG tags | ScrapeCreators → Apify | | threads | OG tags | ScrapeCreators → Apify |
-
-
SKILL.md 7.3 KB
--- name: social-fetch description: "When you or another skill needs to fetch the content of a social media post by URL — tweet, X thread, LinkedIn post, Instagram post, TikTok video, Bluesky post, Reddit thread, Mastodon status, Threads post, Hacker News thread. Returns normalized structured data (author, posted_at, text, engagement counts, media URLs, replies if requested) regardless of platform. Tries strategies in order: direct API (Bluesky, Mastodon, HN, Reddit), agent-browser with modal dismissal (LinkedIn, X preview), Wayback Machine (older posts), paid APIs (ScrapeCreators / Apify — only if env keys present). Triggers on \"/social-fetch <url>,\" \"fetch this tweet,\" \"fetch this post,\" \"what does this LinkedIn say,\" \"read this thread,\" \"pull this post.\" Used by deep-research (citing specific posts), jab-hook (inspiration account analysis), business-brainstorm (competitor / operator commentary)." metadata: version: 0.1.1 --- # /social-fetch — Pull any social post by URL Normalized fetcher for social posts across platforms. Detects platform from URL, tries strategies in order, returns the same JSON shape regardless of source. ## Step 1 — Detect platform | URL pattern | Platform | |---|---| | `x.com/<user>/status/<id>` or `twitter.com/<user>/status/<id>` | **x** (Twitter) | | `linkedin.com/posts/<slug>` or `linkedin.com/feed/update/urn:li:activity:<id>` | **linkedin** | | `linkedin.com/in/<handle>` (profile, recent activity) | **linkedin-profile** | | `instagram.com/p/<id>` or `instagram.com/reel/<id>` | **instagram** | | `tiktok.com/@<user>/video/<id>` | **tiktok** | | `bsky.app/profile/<handle>/post/<rkey>` | **bluesky** | | `reddit.com/r/<sub>/comments/<id>/...` | **reddit** | | `<mastodon-instance>/@<user>/<id>` (e.g. mastodon.social, hachyderm.io) | **mastodon** | | `threads.net/@<user>/post/<id>` | **threads** | | `news.ycombinator.com/item?id=<id>` | **hn** | | `youtube.com/watch?v=<id>` or `youtu.be/<id>` | → defer to `watch-video` | If the URL doesn't match any pattern, ask the user what platform it is. ## Step 2 — Pick strategy chain Read `references/strategies.md` for the per-platform strategy chain. Each platform has 2–5 strategies tried in order. Key principles: - **Free strategies first** (direct APIs, agent-browser) - **Paid only as fallback** (ScrapeCreators / Apify) — and only if the env key is set - **Bluesky / Mastodon / HN / Reddit are free + reliable** (public APIs) - **X / LinkedIn / Instagram / TikTok / Threads** need paid or scraping fallback for full data ## Step 3 — Execute strategy For each strategy in the chain: 1. Try it 2. If success: parse → normalize → return 3. If failure (404, 402, auth wall, empty response): note the failure and try the next strategy After exhausting the chain, return a clear error: which strategies were tried, why each failed, and what's needed to unlock (e.g., "Add `$SCRAPECREATORS_API_KEY` for X — see `references/auth-keys.md`"). ## Step 4 — Normalize output Return this shape regardless of platform (see `references/output-schema.md` for the full spec + platform-specific examples): ```json { "platform": "x", "url": "https://x.com/example/status/1234567890", "fetched_at": "2026-06-17T14:35:00Z", "raw_source": "scrapecreators", "author": { "handle": "@example", "name": "the user Ganim", "verified": true }, "posted_at": "2026-06-17T16:53:00Z", "text": "The 80/20 of a useful AI second brain: ...", "media": [], "engagement": { "likes": 51, "reposts": 13, "replies": 9, "bookmarks": 7, "views": 32700 }, "is_thread": true, "thread": [], "replies": [] } ``` Fields with no equivalent on a platform (e.g., `bookmarks` on Mastodon) get `null`, not `0`. Missing data is different from zero data. ## Step 5 — Optional enrichments Based on flags / asks: | Flag | Behavior | |---|---| | `--with-replies` | Fetch top-level replies (1 hop). Costs extra API quota. | | `--thread` | If the post is part of a thread by the same author, fetch the whole thread. | | `--raw` | Include the raw API/scrape response in the output (for debugging) | | `--media` | Download media files (images/videos) to `~/Documents/social-fetches/<platform>-<id>/` | Default: just the post itself, no replies, no media download (just URLs). ## Step 6 — Cache (optional) If `~/Documents/social-fetches/_cache/` exists, cache successful fetches there by `{platform}-{id}.json` for 24h. Saves API quota when the same post is referenced repeatedly across skills. Skip cache if `--no-cache` flag is set or for `--with-replies` / `--thread` (likely-stale). ## Composes with - `deep-research` — cite specific posts in research briefs. When research surfaces a relevant tweet/post URL, fetch and include in the brief. - `jab-hook` — pull recent posts from inspiration accounts for deeper format analysis (currently uses agent-browser inline; should call this skill instead). - `business-brainstorm` — pull competitor / operator commentary as evidence during scoring. - `second-brain` — capture a post into `raw/` with the `tweet-` / `bookmark-` prefix; the structured output makes for cleaner raw files than a screenshot or copy-paste. - `watch-video` — for YouTube URLs (or any video — Loom, Vimeo, Riverside, MP4), route there instead. ## Known limits - **X**: free strategies return tweet preview only (text, author, basic engagement). Full thread + replies need `$SCRAPECREATORS_API_KEY` or `$APIFY_API_TOKEN`. - **LinkedIn**: agent-browser works for profile recent-activity (after dismissing the modal). Specific post URLs (`linkedin.com/posts/...`) often need paid fallback. - **Instagram / TikTok / Threads**: heavy anti-bot. Paid fallback strongly recommended. - **Bluesky / Mastodon / HN / Reddit**: free + reliable. - **Private / deleted posts**: nothing helps. Try Wayback Machine for deleted content. If a platform consistently fails on free strategies and the user uses it often, prompt to set up the paid key (see `references/auth-keys.md`). ## Notes on quality - **Strategy chain, not single-source.** Every platform has a fallback ladder (native oEmbed → agent-browser → SCS API → Apify). If one step fails, degrade gracefully to the next. Never fail hard on the first attempt. - **Structured output over screenshots.** Downstream skills (jab-hook, deep-research, second-brain) need JSON with author + text + engagement fields, not an image. Even when the underlying strategy is a screenshot, extract text before returning. - **Cache aggressively, invalidate honestly.** 24h TTL on `~/Documents/social-fetches/_cache/` prevents API burn when the same post is referenced across multiple skills in a session. `--with-replies` / `--thread` skip cache because replies age fast. - **Respect paid-key economics.** ScrapeCreators / Apify calls cost real money. Prompt before hitting paid strategies if the user hasn't confirmed they want depth. Free strategies first, always. - **Media download is opt-in.** Default is post text only; `--media` downloads images/videos. Silent media downloads eat disk quickly. - **Private / deleted content is a hard stop.** No strategy chain rescues private accounts or deleted posts. Suggest Wayback Machine for deleted content and stop. - **Rate-limits are per-platform.** X free strategies hit rate limits fast; LinkedIn agent-browser burns session fingerprints. Space out calls in loops or the workflow degrades to worse-than-manual.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.