Mirror of GitHub (de-platform roadmap)
  • Python 59.3%
  • JavaScript 30.1%
  • CSS 6.5%
  • HTML 3.7%
  • Nix 0.4%
Find a file
Danny 0c4b90db58 feat: client error reporting and a local dev loop (BIGBOT-47) 🔦
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 3efa8e0 -> 8c25ffb -> 2589bff took three swings at one dead-shell bug.

Errors now use the pipe the success paths already use:

- window.onerror, unhandledrejection, and failed script/stylesheet loads,
  registered before anything else in app.js so they survive the failure that
  actually bit us: a shell too old to have i18n.js.
- the 11 silent console.error sites route through logError(), as does the
  "shell still stale after reload" case, which was the original symptom.
- reports carry build, platform and source location, are deduplicated per
  fault and capped per page load, and can never throw.
- GET /api/admin/events reads them back, surfaced as a Diagnostics section in
  Settings for ADMIN_USER_ID. Unset, the route 404s.

And because none of this was inspectable without a tunnel and a phone,
scripts/dev.py runs the whole app in an ordinary browser. The bypass needs
DEV_MODE=1, a loopback *bind*, and a loopback peer, the bind check being the
one that matters, since Caddy makes every proxied request look local.

19 new tests, including that the bind guard actually closes the door.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 22:48:24 +02:00
data feat: bundle Free-Exercise-DB + name matcher (step 1) 2026-05-24 11:11:34 +02:00
scripts feat: client error reporting and a local dev loop (BIGBOT-47) 🔦 2026-09-09 22:48:24 +02:00
tests feat: client error reporting and a local dev loop (BIGBOT-47) 🔦 2026-09-09 22:48:24 +02:00
webapp feat: client error reporting and a local dev loop (BIGBOT-47) 🔦 2026-09-09 22:48:24 +02:00
.env.example feat(tg-fitness-bot): initial Telegram workout tracker bot 2026-03-24 15:50:05 +01:00
.gitignore feat: client error reporting and a local dev loop (BIGBOT-47) 🔦 2026-09-09 22:48:24 +02:00
db.py feat: about, release notes, per-exercise history and trends 📈 2026-08-22 12:57:45 +02:00
exercise_db.py feat: exercise_aliases table + lookup_exercise() alias-aware wrapper 2026-06-01 10:49:24 +03:00
flake.lock feat(tg-fitness-bot): initial Telegram workout tracker bot 2026-03-24 15:50:05 +01:00
flake.nix refactor: drop the slash-command bot — Mini App is the only interface 2026-05-10 13:40:28 +02:00
LICENSE docs: add LICENSE (MIT) and README 2026-04-17 14:21:12 +02:00
parser.py feat(tg-fitness-bot): multi-set format, delete, export, SQL stats 2026-04-07 22:46:10 +02:00
README.md feat: client error reporting and a local dev loop (BIGBOT-47) 🔦 2026-09-09 22:48:24 +02:00
requirements.txt refactor: drop the slash-command bot — Mini App is the only interface 2026-05-10 13:40:28 +02:00
server.py feat: client error reporting and a local dev loop (BIGBOT-47) 🔦 2026-09-09 22:48:24 +02:00
start.py fix(start.py): line-buffer stdout so journals show progress promptly 2026-05-10 13:42:51 +02:00

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 sets
  • REPSxWEIGHT, 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_URL is 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 Telegram initData HMACs 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.