- Python 59.3%
- JavaScript 30.1%
- CSS 6.5%
- HTML 3.7%
- Nix 0.4%
The Mini App has been recording everything that goes right and discarding everything that goes wrong. logEvent() fires on 20+ success paths into the events table; get_events() has been able to read them back since the table existed but nothing ever called it; every failure went to console.error, which in a Telegram WebView on someone's phone goes nowhere at all. That is why |
||
|---|---|---|
| data | ||
| scripts | ||
| tests | ||
| webapp | ||
| .env.example | ||
| .gitignore | ||
| db.py | ||
| exercise_db.py | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| parser.py | ||
| README.md | ||
| requirements.txt | ||
| server.py | ||
| start.py | ||
BigBiggerBiggestBot 💪
A Telegram Mini App for logging gym workouts. History, stats, notes, planned sessions, edit & delete, JSON/CSV export — all per-user, all in SQLite.
The slash-command bot was removed: the Mini App is the only interface.
A Telegram bot identity (token) is still required so the Mini App can
validate user sessions via initData HMAC.
Workout text format (still supported via "Paste as text" in the Mini App)
Bench press: 4x8x35
Shoulder press (3032): 8x25, 5x35, 6x40
Pull-ups: 3x10
SETSxREPSxWEIGHT— uniform setsREPSxWEIGHT, REPSxWEIGHT, ...— per-set (weight/reps vary)- Omit weight for bodyweight exercises
(machine_id)is optional (gym equipment ID)- Blank line separates superset groups; consecutive lines form a superset
- Both
,and.work as decimal separators
Planned sessions
The Log tab has a "Planned sessions" section. Build a session in the editor, name it, and save it as a reusable plan; tap Load on any plan to pre-fill the editor with its exercises and sets. Loading a plan does not log anything: you adjust the numbers as you actually train, then hit Save Workout as usual.
Plans live in their own tables (plans, plan_groups, plan_exercises) that
mirror the workout schema, so a plan and a workout share the same
superset_groups payload. REST surface: GET/POST /api/plans,
PUT/DELETE /api/plans/{id}.
Run locally
nix run
This launches:
- API + Mini App server (port 8080)
- cloudflared Quick Tunnel for a public HTTPS URL (skipped if
WEBAPP_URLis already set in the environment, e.g. fronted by a reverse proxy)
Put your bot token (from @BotFather) in
~/.secrets/bigbiggerbiggestbot or a .env file:
BOT_TOKEN=123456:your-bot-token-here
nix develop drops you into a dev shell with Python + deps.
Local development (no Telegram, no tunnel)
nix develop --command python scripts/dev.py --seed
Then open http://127.0.0.1:8080 in an ordinary browser. The Mini App
normally needs a real Telegram session to authenticate, which made every UI
change cost a tunnel and a phone; DEV_MODE stands in a fake one instead.
It is an auth bypass, so it is deliberately narrow. server.py accepts an
unsigned request only when all three hold: DEV_MODE=1, the server is bound
to a loopback address, and the peer is loopback. The bind check is the one
that matters in production — this process sits behind the VPS Caddy, which
proxies from 127.0.0.1, so the peer check alone would wave everything
through if DEV_MODE were ever set there. The deployed service binds
0.0.0.0 and so can never satisfy it. On the client side, app.js installs
its stub session only when the page is served from localhost.
Data goes to dev-workouts.db, never workouts.db. --seed fills it with
ten weeks of workouts; --reset empties it first.
Diagnostics
The Mini App reports its own failures. Uncaught errors, unhandled promise
rejections, failed script/stylesheet loads and every catch block go to
POST /api/events as client.error, stamped with the build, platform and
source location. This exists because a dead Mini App once took three commits
to diagnose: the WebView's console is unreachable, so an unreported error is
an invisible one.
Set ADMIN_USER_ID to your Telegram user id and a Diagnostics section
appears in the Settings tab, listing what has been reported. The same data is
at GET /api/admin/events?kind=client.error, which 404s for everyone else.
Unset, the route stays closed.
Tests
nix develop --command pytest tests/ -v
Deployment
One environment, on sunken-ship: fitness-bot.service, working dir
/home/danny/tg_fitness_bot, watching origin/main, served behind a stable
URL via the VPS Caddy at https://bbbot.dannydannydanny.me.
A pull timer fetches every ~15 minutes and restarts the service when main
has new commits, so deploying is just:
git push origin <branch>:main
To deploy immediately instead of waiting for the timer:
sudo systemctl start fitness-bot-pull.service
The pull skips cleanly if the checkout has uncommitted local changes, leaving
any WIP untouched. workouts.db lives next to the code and is gitignored.
fitness-bot.service needs ADMIN_USER_ID in its environment for the
Diagnostics section to appear; without it the app still reports errors, there
is just nowhere in the UI to read them.
The Shipyard staging tenant (B3Bot beta, b3.dannydannydanny.me) was
decommissioned in BIGBOT-42; there is no staging environment now.
Architecture
server.py— aiohttp REST API + static file server for the Mini App; validates TelegraminitDataHMACs against the bot token.db.py— SQLite data layer (workouts, supersets, exercises, plans, feedback, events, settings; soft delete).parser.py— workout text → structured data (used by the Mini App's "Paste as text" path).webapp/— Mini App (HTML/CSS/vanilla JS, Telegram WebApp SDK).start.py— orchestrator: loads token, starts server, optionally starts cloudflared.scripts/dev.py— runs the Mini App locally without Telegram (see above).tests/— pytest suite for parser + db.
License
MIT — see LICENSE.