A self-hosted fan-site that archives the finished daily MonkeyType
Indonesian leaderboard (time 15) every day, and produces transparent
recaps (monthly / semester / yearly / all-time), plus opt-in player profiles
that mirror MonkeyType data and show each player's keyboard.
MonkeyType only serves today and yesterday for non-English boards and keeps no long-term history. This project is the missing memory: capture yesterday daily and you become the community's only historical record.
- No rating system. We deliberately do NOT use Glicko/ELO. The goal is an archive + recaps, not PvP ranking. Typing already has an absolute score (WPM), so a rating system adds opaque complexity and fairness disputes without value.
- Recaps are transparent statistics. Each period yields several INDEPENDENT
category winners, every value a plain fact from the archive:
peak_wpm— highest single WPM (raw skill).avg_wpm— highest average WPM, min. 5 days (consistency).most_active— most days appeared (dedication).podium— most top-3 finishes, tiebreak top-10 (board dominance). "Fairness" is automatic and self-filtering: a one-day fluke can hold the peak record (it really was the fastest run) but cannot win the categories that require repeated presence.
- Capture everything, key on uid. Yesterday can exceed 250 players; we paginate and store all of them. uid is stable; names change (verified: one uid's leaderboard name differed from its profile name) and are tracked separately.
- Raw archive is immutable; recaps computed on demand.
daily_resultsis the source of truth and is never mutated. Recaps read from it directly, so there's no derived state to rebuild. - Profiles are opt-in + ownership-verified. A player registers, places a token in their MonkeyType bio, and we verify before mirroring their bio/keyboard/typingStats. We never auto-import non-registered players.
mtid/
db.py schema + connection (SQLite, WAL)
collector.py API client + paginated capture + idempotent store
recap.py statistical recaps (month/semester/year/all-time)
crown.py record per-category champions for a period
profile.py opt-in verified profiles (token-in-bio), weekly refresh
run_daily.py cron/systemd entrypoint: collect + weekly profile refresh
api_server.py stdlib read-only JSON API
web/index.html recap page (period selector + category boards)
web/player.html per-player profile (WPM chart, PBs, keyboard, MT stats)
DEPLOY.md VPS setup (systemd timer, Caddy, backups)
python3 -m mtid.db # init schema
python3 run_daily.py # collect yesterday into the archive
python3 api_server.py # serve API on :8787
python3 -m mtid.crown month 2026 5 # crown a period's category championsSee DEPLOY.md for VPS/systemd/Caddy.
The React frontend is served by Vite and talks to the Python API through the
relative /api/* paths. Vite proxies those paths to http://127.0.0.1:8787.
pnpm install
python3 -m mtid.db
python3 api_server.py
pnpm devOpen the Vite URL, usually http://127.0.0.1:5173. For production:
pnpm buildServe the generated dist/ directory with Caddy and keep proxying /api/* to
the Python API, as shown in DEPLOY.md.
Verified players' keyboard name comes from their MonkeyType bio/keyboard
field. The displayed image is an optional keyboard_image_url you control
(a keyboard image DB you maintain, or player upload) — nothing is auto-scraped.
Player profiles are opt-in. On a player page, click generate token, add the
token to that MonkeyType account's bio, then click verify. The backend fetches
/users/<uid>/profile?isUid=true, checks the token, and stores bio, keyboard,
completed tests, started tests, typing time, XP, streaks, personal bests,
all-time leaderboard data, and activity for display.
/keyboards is a public copy/paste board for keyboard snippets/configs. Anyone
can submit a keyboard name, image URL, entry type, switch details, mods, notes,
layout text, generated code, and config JSON to copy into MonkeyType. The API
endpoints are GET /api/keyboards and POST /api/keyboards.