English | 中文
Ever run into this on X: the same original post keeps showing up because different followings retweet or quote it, and your timeline feels full of duplicates? Or you just cannot tell at a glance which posts are getting the most activity among people you follow. XReporter is built for exactly that workflow. It is a CLI-first pipeline that collects activity from a target X user's followings, normalizes it into SQLite, and generates a static HTML report. The idea is simple: merge and de-duplicate first, then group by original post and sort by activity count. You can scan hotspot posts quickly and drill into each following user's exact related actions (retweet, quote, reply) 🔍
Current version: 0.1.0 (MVI)
Note: This project has been primarily operated and implemented by Codex.
- Official X API can be expensive at scale (pricing and quota can become a bottleneck).
- Keep collection reproducible: each run is tracked (
runs,run_activities,run_warnings). - Keep reruns safe: upsert/idempotent persistence avoids duplicated core records.
- Keep review efficient: HTML report has warning, grouped, user-grouped, and timeline views.
- Keep operations practical:
official/socialdataprovider switch + fixture mode for offline testing.
- 3-Minute Quick Start
- Example Reports
- How The Report Is Organized
- Technical Route At A Glance
- CLI Reference
- Configuration
- Logging
- Repository Map
- Development And Testing
- Troubleshooting
- Contributing
conda env create -f environment.yml
conda activate XReporter
pip install -e .[dev]# official provider
export X_BEARER_TOKEN="<your_token>"
# socialdata provider
export SOCIALDATA_API_KEY="<your_socialdata_api_key>"Secrets are never persisted into project config files.
xreporter config init --username target_user --lang auto
# default provider for new config: officialxreporter collect --last 24h
xreporter render --latestxreporter doctor- English sample: example/run_1_en.html
- Chinese sample: example/run_1_zh.html
- Source run:
betterestli,--last 12h,run_id=1
Generated report is static HTML and includes:
- Warning section (provider/user/API path/raw error).
- Grouped-by-original section (post/retweet/quote/reply).
- Grouped-by-user section (all activities per actor).
- Chronological full timeline.
Interaction and ordering rules:
- Grouped-by-original, grouped-by-user, and timeline sections are collapsible (default collapsed).
- Each grouped item card is also collapsible (default collapsed) to handle long content.
- Timeline is sorted newest to oldest.
- Grouped-by-original items are sorted by action count (desc), then latest action time (desc).
- Grouped-by-user items are sorted by action count (desc), then latest action time (desc).
- Report language follows config (
en/zh;autoresolves by locale with English fallback).
CLI (Typer + Rich)
-> Config + i18n + logging bootstrap
-> CollectorService
-> provider adapter (XApiClient / SocialDataApiClient / FixtureXApiClient)
-> normalizer
-> SQLiteStorage
-> HTML renderer
Detailed route: doc/tech_route.md | 中文
xreporter config init --username <name> [--lang auto|en|zh] [--db-path <path>] [--report-dir <path>] [--following-cap <int>] [--include-replies/--no-include-replies] [--api-provider official|socialdata]xreporter config showxreporter collect [--username <name>] [--last 12h|24h | --since <ISO8601> --until <ISO8601>] [--following-cap <int>] [--include-replies/--no-include-replies] [--api-concurrency <int>] [--resume-run-id <id>]xreporter render [--run-id <id> | --latest] [--output <html_path>]xreporter doctor
# 1) init once
xreporter config init --username jack --lang auto --following-cap 200
# 2) collect one window
xreporter collect --last 24h --api-concurrency 4
# 3) render latest run
xreporter render --latest
# 4) or render a specific run
xreporter render --run-id 3 --output ./reports/manual_run_3.html
# 5) resume an interrupted/failed run
xreporter collect --resume-run-id 3 --api-concurrency 4Default config path:
~/.xreporter/config.toml
Config fields:
username(string)language(auto|en|zh)db_path(string)report_dir(string)following_cap_default(int, default200)include_replies_default(bool, defaulttrue)api_provider(official|socialdata; missing legacy field defaults toofficial)
- Default log file:
~/.xreporter/logs/xreporter.log - If
XREPORTER_HOMEis set, log path becomes$XREPORTER_HOME/logs/xreporter.log. - Log includes command lifecycle, run-level collection progress, API request/retry/fallback status, and storage commit markers.
- Retried API calls are both logged and printed to terminal (safe summary only, no secrets).
- Optional env vars:
XREPORTER_LOG_LEVEL(DEBUG|INFO|WARNING|ERROR, defaultINFO)XREPORTER_LOG_STDERR(1|true|yes|on) to mirror logs to stderr
official- Best aligned with canonical X API schema.
- Requires
X_BEARER_TOKEN. - Can face strong cost/rate-limit pressure depending on access tier.
socialdata- Requires
SOCIALDATA_API_KEY. - Adapter aligns to documented endpoints/params and avoids unsupported filters.
- Referenced tweet backfill uses batch endpoint (
tweets-by-ids) to reduce request count. - Timeline
403privacy responses are recorded as warnings and skipped.
- Requires
- Timeline page cap (anti-waste)
- Per-following timeline collection is capped at 5 pages by default (both
officialandsocialdataproviders). - This is currently a code-level parameter (not a CLI flag).
- To modify it, edit:
src/xreporter/x_api.py->XApiClient.__init__(..., max_timeline_pages=5)src/xreporter/x_api.py->SocialDataApiClient.__init__(..., max_timeline_pages=5)
- Per-following timeline collection is capped at 5 pages by default (both
fixture- Set
XREPORTER_FIXTURE_FILEto run offline demos/tests without real API calls.
- Set
Core SQLite tables:
users,tweets,tweet_links,activitiesruns,run_activities,run_warnings
src/xreporter/
cli.py # command interface and orchestration
config.py # config load/save/default paths
i18n.py # language resolution and message catalog
logging_utils.py # runtime logging setup (file rotation + level control)
models.py # typed data contracts
normalizer.py # payload -> normalized batch
service.py # collect workflow and warning handling
storage.py # SQLite schema, upsert, run metadata
render.py # static HTML generation
time_range.py # last/since/until parsing
x_api.py # official/socialdata/fixture clients
tests/
doc/
conda activate XReporter
pytestCoverage focus:
- unit: time range parsing, i18n fallback, activity classification, SQLite idempotency
- integration: pagination, retry on
429/5xx, unresolved referenced tweet fetch - e2e: fixture
collect -> render, rerun idempotency, bilingual CLI behavior
- Confirm provider in config:
xreporter config show - For
official, checkX_BEARER_TOKEN - For
socialdata, checkSOCIALDATA_API_KEY
--latestmay point to a failed run with0activities.- Use
--run-idto render a known run with data:
xreporter render --run-id <id> --output ./reports/run_<id>.html- Check
~/.xreporter/logs/xreporter.log(or$XREPORTER_HOME/logs/xreporter.log). - Set
XREPORTER_LOG_LEVEL=DEBUGto capture per-request retry/fallback details.
- New pagination safeguards stop repeated cursor/token loops automatically and write warning logs.
- Per-following timeline pagination also has a hard cap of
5pages by default (seemax_timeline_pagesinsrc/xreporter/x_api.py). - Increase API parallelism when your quota allows:
xreporter collect --last 24h --api-concurrency 8
- If runtime is still long, reduce collection scope temporarily:
xreporter collect --last 12h --following-cap 100 --no-include-replies
- Resume the same run without reprocessing completed followings:
xreporter collect --resume-run-id <id> --api-concurrency 4
- Set explicit language in config (
enorzh) instead ofauto.
Issues and PRs are welcome. Suggested contribution flow:
- Create/focus an issue with expected behavior and scope.
- Keep modules aligned with repository boundaries (
x_api.py,normalizer.py,storage.py,render.py,cli.py). - Add tests for new behavior.
- Keep English and Chinese docs in sync (
*_cn.md).
- Technical route: doc/tech_route.md / doc/tech_route_cn.md
- Progress log: doc/progress.md / doc/progress_cn.md
- Agent conventions: AGENTS.md / AGENTS_cn.md

