Claude Skill

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

LLM Mart · 0 points · 7 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download coreyhaines31-makerskills-skills_social-fetch-1868b81.zip · 9 KB
Part of coreyhaines31/makerskills — 18 skills

Install

skills CLI npx skills add https://github.com/coreyhaines31/makerskills/tree/main/skills/social-fetch
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install coreyhaines31-makerskills@llmmart
Git 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
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):

{
  "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.
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.

No comments yet.

Reviews (0)

No reviews yet.

Related