twitter-reader
Fetch Twitter/X post content including long-form Articles with full images and metadata. Use when Claude needs to retrieve tweet/article content, author info, engagement metrics, and embedded media. Supports individual posts and X Articles (long-form content). Automatically downloads all images to local attachments folder and generates complete Markdown with proper image references. Preferred over Jina for X Articles with images.
1How It Works & Mechanism
The preferred path for single-post plain text requires no login and no API key, directly curling the fxtwitter mirror API, with the full text extracted from tweet.text (the documentation notes that the full_text key does not exist) — this path requires zero credentials and zero configuration, replacing Jina as the default since 2026-08-30.
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):
The primary path for X Articles (long-form articles with images) is handled by fetch_article.py: it first uses `uv run --with twitter-cli` to install and execute on the fly the `twitter article` subcommand from the PyPI package twitter-cli, retrieving structured YAML containing author/metrics/articleText.
def run_twitter_cli(url: str) -> dict: """Fetch article data using twitter-cli via uv run.""" cmd = ["uv", "run", "--with", "twitter-cli", "twitter", "article", url]
Body content (including images) is scraped via Jina Reader (r.jina.ai): carrying a Bearer token if JINA_API_KEY is present, or anonymously if not; the body text is then sliced out using the 'Markdown Content:' marker, falling back to articleText returned by twitter-cli if Jina fails.
api_key = os.getenv("JINA_API_KEY", "") jina_url = f"https://r.jina.ai/{url}" cmd = ["curl", "-s", jina_url] if api_key: cmd.extend(["-H", f"Authorization: Bearer {api_key}"])
Image localization is the script's core value-add: regex extracts pbs.twimg.com/media links from scraped Markdown → deduplicates and standardizes them to ?format=jpg&name=large → downloads them individually via curl to attachments/date-author-title/ → regex-replaces remote image URLs in the body text with local relative paths.
pattern = r'https://pbs\.twimg\.com/media/[^\s\)"\']+'
Output convention: YYYY-MM-DD-title.md (including YAML frontmatter: source/author/date/likes/retweets/bookmarks) + an attachment directory of the same name; the date takes the first 10 characters of createdAtLocal, while the title and author are sanitized and truncated to 60 characters via sanitize_filename.
md = f"""--- source: {source_url} author: {author.get("name", "")} date: {date_str} likes: {metrics.get("likes", 0)}
Jina text mode serves as a fallback bypass: fetch_tweet.py (single tweet, strictly requires JINA_API_KEY, HTTPS only) and fetch_tweets.sh (bash batch loop, regex allows http/https) directly curl r.jina.ai and pass through the Bearer token.
for url in "$@"; do if [[ ! "$url" =~ ^https?://(x\.com|twitter\.com)/ ]]; then
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| cli | curl |
| cli | uv (Python 包管理器) |
| cli | python3(含内联 -c 解析) |
| package | twitter-cli(PyPI,uv 现场安装并执行 twitter article 子命令,未 pin 版本) |
| package | pyyaml |
| api | Jina Reader API |
| network | fxtwitter 镜像 API |
| network | Twitter 图片 CDN(图片下载目标) |
| network | x.com / twitter.com(被抓取目标,经 fxtwitter/Jina 代理访问) |
4Recommended Governance & Evidence
- scripts/: Executes local script or command: scripts/fetch_article.py —— `uv run --with pyyaml python scripts/fetch_arti
- scripts/ 或 SKILL.md: Declares dynamic installation of external language package dependencies
- 网络端点: Declares external network access: https://api.fxtwitter.com/<user>/status/<id> (keyless mirror, SKILL.md fxtwitter section)
- 本地工作目录: File read/write and artifact output: fetch_article.py: writes <output_dir>/YYYY-MM-DD-<safe_title>.md and <output_dir>/a
5Second-Pass Verification
- [ok] fxtwitter API(api.fxtwitter.com)调用点真实存在 — SKILL.md fxtwitter 段内联 curl 配方原文命中;文档自述 2026-08-30 实测过 2,324 字符 note_tweet 完整返回。仅验证调用点,端点当前可达性未在线复核
- [ok] Jina r.jina.ai 调用点真实存在 — 三个脚本各自命中:fetch_tweet.py `jina_api_url = f"https://r.jina.ai/{url}"`、fetch_tweets.sh `curl -s "https://r.jina.ai/${url}"`、fetch_article.py run_jina_api;Bearer 头与 JINA_API_KEY 读取逐处对上
- [ok] twitter-cli 依赖真实存在(uv run --with) — fetch_article.py run_twitter_cli 的 argv 原文命中;SKILL.md Quick Start 另以 `uv run --with pyyaml` 启动脚本,与 parse_yaml_output 的 yaml 导入一致
- [ok] pbs.twimg.com 图片下载路径真实存在且仅此 CDN — extract_image_urls 正则仅匹配 pbs\.twimg\.com/media/ 前缀;download_images 用 curl -sL 落盘。未发现其他图片域名
- [ok] JINA_API_KEY 读取点全覆盖、无硬编码密钥 — grep JINA_|sk- 于 twitter-reader/ 命中仅 3 处 env 读取(无字面量);全仓库 CHANGELOG 记录 2026-01-05 前后『Removed hardcoded API keys』。SKILL.md 所述『shared key 402 余额尽』系作者侧市场共享 key,仓库内无对应字符串
- [ok] URL 校验形态:前缀白名单而非权威解析 — fetch_tweet.py/fetch_article.py 用 startswith(("https://x.com/","https://twitter.com/"));fetch_tweets.sh 用正则 ^https?://(x\.com|twitter\.com)/。三处均要求主机后紧跟 /,可挡 https://x.com.evil.com 型主机混淆;bash 版放行 http:// 与文档『HTTPS only』表述存在出入(py 版严格)
- [ok] 无隐藏脚本/无混淆/无隐蔽外发 — 目录仅 SKILL.md + 3 脚本 + .security-scan-passed 标记;脚本全部为列表式 subprocess,无 shell=True/eval/curl 之外的网络动词;.security-scan-passed 为纯文本记录,无执行作用
- [ok] 功能声明与代码能力相符性 — 『长文带图 + 元数据 + 自动下载 + Markdown』在 fetch_article.py 中逐项落实;但核心卖点依赖第三方间歇服务:Jina 匿名被全局 ban 时正文只剩 twitter-cli 的 articleText 兜底(可能不含图),文档已如实标注『Preferred over Jina…』仅针对有图场景——无夸大但有外部依赖边界
6Verdict & Scenarios
6e441b1396143142…