twitter-reader
daymade/claude-code-skills
Fetch Twitter/X posts and articles with full media support, automatic image download, and Markdown generation.
What is twitter-reader?
Fetches Twitter/X post and article content including text, author, engagement metrics, and embedded media. Downloads images locally and generates complete Markdown with image references. Use this when retrieving tweet text, X Articles with images, or pulling post metadata for Markdown/PKM records.
- Fetch full post text and article content from Twitter/X
- Download all embedded images locally with organized folder structure
- Generate complete Markdown files with local image references and YAML frontmatter
- Extract structured metadata (author, date, likes, retweets, bookmarks)
- Handle X Articles (long-form content) with automatic image embedding
- Support batch fetching of multiple tweets via shell script
How to install twitter-reader
npx skills add https://github.com/daymade/claude-code-skills --skill twitter-reader- uv (Python package manager)
- JINA_API_KEY environment variable (for text-only mode; optional for full article mode)
- Get Jina API key from https://jina.ai/
How to use twitter-reader
- 1.For full articles with images: run `uv run --with pyyaml python scripts/fetch_article.py <article_url> [output_dir]`
- 2.For simple text-only posts: set JINA_API_KEY and use `curl https://r.jina.ai/https://x.com/USER/status/ID`
- 3.For batch fetching: use `scripts/fetch_tweets.sh url1 url2 url3`
- 4.Check output folder for generated Markdown file and attachments subfolder with downloaded images
- 5.Note: Jina API access to x.com can be intermittently blocked; full article mode has fallback handling
Use cases
- Save X Articles with all images to a personal knowledge management system
- Archive tweets with full media for offline reference or research
- Extract engagement metrics and metadata for content analysis
- Create Markdown records of Twitter threads with embedded images
- Batch download multiple tweet threads for documentation
- Knowledge workers and researchers archiving online content
- Content creators tracking engagement metrics
- PKM (personal knowledge management) users
- Developers building Twitter/X integration tools
- Anyone needing offline access to X content with images
twitter-reader FAQ
fetch_article.py is full-featured: it downloads all images, generates complete Markdown with local image references, and includes YAML frontmatter. The Jina API approach is text-only and requires an API key. Use fetch_article.py for articles with images; use Jina for quick text extraction.
Anonymous access to x.com via r.jina.ai gets blocked for hours when abused. The API returns HTTP 200 with a JSON error envelope instead of failing visibly. fetch_article.py detects this by checking for the 'Markdown Content:' marker and warns on stderr while keeping the twitter-cli text.
twitter-cli has a 500-item ceiling enforced in its code (`_ABSOLUTE_MAX_COUNT = 500`). This is a tool limit, not a platform limit. To go deeper, call X's GraphQL endpoint directly with the Bookmarks operation and page until the platform stops returning a cursor.
Supports https://x.com/USER/status/ID (posts), https://x.com/USER/article/ID (long-form articles), and legacy https://twitter.com/USER/status/ID format.
Images are saved to `attachments/YYYY-MM-DD-AUTHOR-TITLE/` within your output directory, and the generated Markdown includes local references to these files.
Full instructions (SKILL.md)
Source of truth, from daymade/claude-code-skills.
name: twitter-reader description: >- Fetches Twitter/X post and Article content — text, author, engagement metrics and embedded media — downloading images locally and generating complete Markdown with image references. Use when retrieving a tweet's text or an X Article with images, or pulling post metadata for a Markdown/PKM record. Preferred over Jina alone for X Articles with images.
Twitter Reader
Fetch Twitter/X post and article content with full media support.
Reading a single post's text: fxtwitter first (2026-08-30)
For plain post text, prefer the fxtwitter mirror API — login-free, key-free,
works direct, and returns the full note_tweet body in tweet.text (the
full_text key does not exist; a 2,324-char long-form announcement came back
complete):
curl -sS --max-time 20 "https://api.fxtwitter.com/<user>/status/<id>" \
| python3 -c "import json,sys; t=json.load(sys.stdin)['tweet']; print(t['created_at']); print(t['text'])"
replies is a count, not the reply thread. For X Articles with images, use
fetch_article.py below — fxtwitter does not carry article bodies.
X Articles with images: fetch_article.py
uv run --with pyyaml python scripts/fetch_article.py <article_url> [output_dir]
Example:
uv run --with pyyaml python scripts/fetch_article.py \
https://x.com/HiTw93/status/2040047268221608281 \
./Clippings
This will:
- Fetch structured data via
twitter-cli(likes, retweets, bookmarks) - Fetch content with images via
jina.aiAPI - Download all images to
attachments/YYYY-MM-DD-AUTHOR-TITLE/ - Generate complete Markdown with embedded image references
- Include YAML frontmatter with metadata
Metadata and plain article text come from twitter-cli; the image URLs ride in
the markdown Jina's reader returns. When Jina refuses (see the section below),
the script warns on stderr, keeps the twitter-cli text, and reports Images: 0.
The Markdown is still correct; it simply has no pictures. Images: 0 on an
article that visibly contains images is the signature of a Jina refusal.
Example Output
Fetching: https://x.com/HiTw93/status/2040047268221608281
--------------------------------------------------
Getting metadata...
Title: 你不知道的大模型训练:原理、路径与新实践
Author: Tw93
Likes: 1648
Getting content and images...
Images: 15
Downloading 15 images...
✓ 01-image.jpg
✓ 02-image.jpg
...
✓ Saved: ./Clippings/2026-04-03-文章标题.md
✓ Images: ./Clippings/attachments/2026-04-03-HiTw93-.../ (15 downloaded)
Jina's reader is intermittent, and its refusals look like success
Anonymous r.jina.ai access to x.com gets blocked for hours at a time when
third-party callers abuse the domain. The block hits every anonymous caller and
then expires on its own. Both states showed up minutes apart on 2026-09-12.
A refusal does not look like a failure. curl exits 0 and the body is a JSON
envelope:
{"data":null,"code":403,"name":"AbuseAlleviationError","status":40305,
"message":"Anonymous access to domain x.com blocked until <date> ..."}
A lapsed or unfunded key returns the same shape with "code":402, "name":"InsufficientBalanceError". curl --fail catches neither: this endpoint
answered HTTP 200 with the envelope in the body. The one signal that holds is
the Markdown Content: marker that Jina's reader puts in front of the article
body: no marker, no article. fetch_article.py tests for that marker before
accepting a response, which keeps a refusal envelope out of the generated
Markdown.
scripts/fetch_tweets.sh and scripts/fetch_tweet.py both require
JINA_API_KEY and have no second source to fall back to, so they stop working
whenever that key lapses. Both check the same marker and fail loudly on a
refusal — the Python one raises without writing the output file, the shell one
reports the URL on stderr and exits non-zero — rather than handing back an
envelope dressed up as a post. No key ships with this repository.
For simple text-only fetching:
# Single tweet
curl "https://r.jina.ai/https://x.com/USER/status/TWEET_ID" \
-H "Authorization: Bearer ${JINA_API_KEY}"
# Batch fetching
scripts/fetch_tweets.sh url1 url2 url3
Features
Full Article Mode (fetch_article.py)
- ✅ Structured metadata (author, date, engagement metrics)
- ✅ Automatic image download (all embedded media)
- ✅ Complete Markdown with local image references
- ✅ YAML frontmatter for PKM systems
- ✅ Handles X Articles (long-form content)
Simple Mode (Jina API)
- Text-only content
- Intermittent availability (see the section above); both scripts require
JINA_API_KEYand have no fallback - Usable for quick text extraction while Jina is answering
Prerequisites
For Full Article Mode
uv(Python package manager)- No additional setup (twitter-cli auto-installed)
For Simple Mode (Jina)
export JINA_API_KEY="your_api_key_here"
# Get from https://jina.ai/
Output Structure
output_dir/
├── YYYY-MM-DD-article-title.md # Main Markdown file
└── attachments/
└── YYYY-MM-DD-author-title/
├── 01-image.jpg
├── 02-image.jpg
└── ...
What Gets Returned
Full Article Mode
- YAML Frontmatter: source, author, date, likes, retweets, bookmarks
- Markdown Content: Full article text with local image references
- Attachments: All downloaded images in dedicated folder
Simple Mode
- Title: Post author and content preview
- URL Source: Original tweet link
- Published Time: GMT timestamp
- Markdown Content: Text with remote media URLs
URL Formats Supported
https://x.com/USER/status/ID(posts)https://x.com/USER/article/ID(long-form articles)https://twitter.com/USER/status/ID(legacy)
Scripts
fetch_article.py
Full-featured article fetcher with image download:
uv run --with pyyaml python scripts/fetch_article.py <url> [output_dir]
fetch_tweet.py
Simple text-only fetcher using Jina API:
python scripts/fetch_tweet.py <tweet_url> [output_file]
fetch_tweets.sh
Batch fetch multiple tweets (Jina API):
scripts/fetch_tweets.sh <url1> <url2> ...
twitter-cli's 500-item ceiling belongs to the tool, not to X
This skill reads metadata through twitter-cli, so the tool's own limits apply.
twitter bookmarks and the other timeline commands stop at 500 items however
the config is written. That ceiling is a module constant in the client:
_ABSOLUTE_MAX_COUNT = 500 in twitter_cli/client.py, applied as
min(maxCount, 500) when the client is constructed. Both lines read from
version 0.8.5. X's own bookmark timeline goes far deeper than that.
Two consequences are easy to get wrong:
- A run that stops at exactly 500 stopped on its own counter and never reached the point of reading the next cursor. "The platform gave no further page" and "my loop finished" are different claims, and only the first one is evidence. A round number is a reason to look closer, not a boundary.
maxCounthas to sit under therateLimit:section of the config. Put it underfetch:and the client ignores it without any message, falling back to 200.
Going deeper means calling the GraphQL endpoint directly: the Bookmarks
operation with the client's own feature flags, parsed by
twitter_cli.parser.parse_timeline_response, paging until the platform stops
returning a cursor or returns the same cursor twice. Stop on the platform's
signal rather than on a count.
Migration from Jina API
Old workflow:
curl "https://r.jina.ai/https://x.com/..."
# Manual image extraction and download
New workflow:
uv run --with pyyaml python scripts/fetch_article.py <url>
# Automatic image download, complete Markdown
Related skills
More from daymade/claude-code-skills and the wider catalog.

ui-designer
Extract design systems from UI images and generate implementation-ready design prompts.

video-comparer
This skill should be used when comparing two videos to analyze compression results or quality differences. Generates interactive HTML reports with quality metrics (PSNR, SSIM) and frame-by-frame visual comparisons. Triggers when users mention "compare videos", "video quality", "compression analysis", "before/after compression", or request quality assessment of compressed videos.

youtube-downloader
Download YouTube videos and HLS streams with yt-dlp and ffmpeg, handling authentication and protected content.

capture-screen
Programmatic screenshot capture on macOS. Find window IDs with Swift CGWindowListCopyWindowInfo, control application windows via AppleScript (zoom, scroll, select), and capture with screencapture. Use when automating screenshots, capturing application windows for documentation, or building multi-shot visual workflows.

camel-matrix
Generate Apache Camel Spring Boot compatibility matrices with version range support.

cc-best-practices
Master Claude Code with context management, verification strategies, and the explore-plan-implement workflow.