A TypeScript WhatsApp bot for personal daily tracking and reminders.
Features · Quick Start · Commands · Configuration · Project Structure · Docs
Log lift and cardio sessions with compact syntax, track streaks, and get daily group leaderboards.
#workout lift push up 20reps 4sets 10kg
#workout cardio run 30min 5km
#workout list
- Explicit modes: lift and cardio
- Configurable streak threshold (
MIN_WORKOUTS_FOR_STREAK) - Paginated history with mode badges (
[lift],[cardio]) - Daily digest leaderboard in group chat
- Monthly recap: on day 1 of each month, sends the previous month's Workout and Quran leaderboards to
DIGEST_GROUP_ID - Undo last workout log within 5 minutes (
#workout undo) - Leaderboard preview on demand (
#workout leaderboard)
Track daily pages, manage bookmarks, and get nightly group reminders.
#quran read 3
#quran mark 145
#quran list
- Auto-accumulate multiple logs per day
- Auto-advance bookmark after each read (skip with
--no-mark) - Khatam detection — resets bookmark when passing page 604
- Streak tracking and nightly reminder in group
- Undo today's read within 5 minutes (
#quran undo) - Leaderboard preview on demand (
#quran leaderboard)
Fetch daily prayer times with location-aware caching.
#sholat --today
#sholat --today --location bandung
- DB-cached schedules (fetched once per location per day)
- Self-healing location catalog refresh on stale data
Set reminders with natural date/time input, delivered back to the source chat.
#remind tomorrow 9am Review proposal
#remind 2026-03-10 10:30 Submit report
#remind list
- Flexible parsing:
today,tomorrow,YYYY-MM-DD+9am,HH:MM,14 - Delivered to the same chat (group or direct) where it was created
- Safety limits: 200 char max text, 50 active reminders per user
- Undo last reminder within a short window (
#remind undo)
| Layer | Technology |
|---|---|
| Runtime | Node.js 20+ · TypeScript 5.x |
| whatsapp-web.js | |
| Database | PostgreSQL · Drizzle ORM |
| Testing | Vitest |
| Linting | ESLint · Prettier |
| Package Manager | pnpm |
- Node.js 20+
- pnpm
- PostgreSQL instance (local or remote)
- Chromium dependencies (OS-dependent, only if running outside Docker)
# 1. Install dependencies
pnpm install
# 2. Configure environment
cp .env.example .env
# Edit .env — fill DATABASE_URL and ALLOWED_NUMBERS at minimum
# 3. Build and start
pnpm build
pnpm startOn first run, scan the QR code shown in terminal to authenticate your WhatsApp session.
docker compose up --builddocker-compose.yml mounts .wwebjs_auth/ for session persistence and data/ for local storage.
| Command | Description |
|---|---|
#workout lift push up 20reps 4sets 10kg |
Log a lift session (weight optional) |
#workout lift pull up 8rep 5set |
Log bodyweight lift |
#workout cardio run 30min 5km |
Log cardio (distance optional) |
#workout cardio brisk walk 1hour |
Log cardio with hour unit |
#workout list |
View paginated history |
#workout list 2 |
View page 2 |
#workout undo |
Undo your most recent log (within 5 minutes) |
#workout leaderboard |
Show today's group leaderboard |
#workout help |
Show command help |
Format rules: Reps accept rep/reps, sets accept set/sets, weight uses kg only, duration uses min/hour, distance uses km.
| Command | Description |
|---|---|
#quran read 3 |
Log 3 pages read today |
#quran read 3 --no-mark |
Log without advancing bookmark |
#quran mark 145 |
Set bookmark to page 145 |
#quran mark |
Check current bookmark |
#quran list |
View paginated reading history |
#quran undo |
Undo today's most recent read (within 5 minutes) |
#quran leaderboard |
Show today's group leaderboard |
#quran help |
Show command help |
| Command | Description |
|---|---|
#sholat / #sholat --today |
Today's schedule (default location) |
#sholat --today --location bandung |
Specify location |
#sholat help |
Show command help |
| Command | Description |
|---|---|
#remind 2026-03-10 10:30 Review proposal |
Set reminder with specific date |
#remind today 9am Join standup |
Set reminder for today |
#remind tomorrow 8:15 Prepare update |
Set reminder for tomorrow |
#remind list |
View active reminders |
#remind undo |
Undo last reminder (within a short window) |
#remind help |
Show command help |
See .env.example for the full template.
Required variables
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
— | PostgreSQL connection URL |
ALLOWED_NUMBERS |
"" |
Comma-separated phone allowlist (digits only, e.g. 6281234567890) |
DIGEST_GROUP_ID |
"" |
Target group for scheduled digests. Scheduler disabled if empty. |
DEBUG |
false |
Enable debug logs (true/1) |
Scheduling
| Variable | Default | Description |
|---|---|---|
USER_TIMEZONE_OFFSET_MINUTES |
420 |
UTC offset in minutes (UTC+7 = 420) |
DAILY_DIGEST_HOUR |
8 |
Workout digest hour (24h, user timezone) |
DAILY_DIGEST_MINUTE |
0 |
Workout digest minute |
QURAN_REMINDER_HOUR |
22 |
Quran reminder hour (24h, user timezone) |
QURAN_REMINDER_MINUTE |
0 |
Quran reminder minute |
MONTHLY_DIGEST_HOUR |
8 |
Monthly recap hour (day 1, 24h, user timezone) |
MONTHLY_DIGEST_MINUTE |
0 |
Monthly recap minute |
Feature behavior
| Variable | Default | Description |
|---|---|---|
MIN_WORKOUTS_FOR_STREAK |
3 |
Workouts/day to count as a streak day |
WORKOUT_LIST_LIMIT |
10 |
Rows per page for #workout list |
QURAN_LIST_LIMIT |
10 |
Rows per page for #quran list |
REMIND_LIST_LIMIT |
10 |
Rows per page for #remind list |
QURAN_RAMADHAN_COUNT_ENABLED |
false |
Show Ramadhan total in #quran list |
QURAN_RAMADHAN_START_DATE |
— | Ramadhan start (YYYY-MM-DD, inclusive) |
QURAN_RAMADHAN_END_DATE |
— | Ramadhan end (YYYY-MM-DD, inclusive) |
SHOLAT_DEFAULT_LOCATION |
KAB. BOGOR |
Default prayer schedule location |
SHOLAT_TIMEZONE |
Asia/Jakarta |
IANA timezone for sholat date calc |
Deployment
| Variable | Default | Description |
|---|---|---|
PUPPETEER_EXECUTABLE_PATH |
— | Override Chromium binary path |
RAILWAY_VOLUME_MOUNT_PATH |
— | Override WA auth storage path |
Timezone and date logic
Timestamps are stored in UTC. User-local day boundaries are calculated using USER_TIMEZONE_OFFSET_MINUTES.
Example: With USER_TIMEZONE_OFFSET_MINUTES=420 (UTC+7), a log at 2026-02-21T17:30:00Z becomes local 2026-02-22 00:30 and belongs to Feb 22.
Scheduled jobs use the same offset, so digest/reminder timing is always consistent with user-local time. Reminder input (#remind) is interpreted in this timezone, then stored as UTC.
Ramadhan counter: When QURAN_RAMADHAN_COUNT_ENABLED=true, #quran list shows a Ramadhan total line. Date range is inclusive on both ends, using user-local day comparison.
src/
├── index.ts # Composition root — wires all dependencies
├── config/env.ts # Centralized env parsing
├── app/ # Application layer (router, handler, auth, scheduler)
├── adapters/whatsapp/ # WhatsApp adapter (ports, gateway, ID helpers)
├── modules/
│ ├── workouts/ # Workout tracking module
│ ├── quran/ # Quran reading module
│ ├── sholat/ # Prayer schedule module
│ ├── remind/ # Reminder module
│ └── users/ # User identity management
├── db/ # Drizzle connection, migrations, schema aggregator
└── shared/ # Shared utilities (Result type)
Each module follows a consistent internal structure:
modules/<name>/
├── index.ts # Registration factory (module's public API)
├── <name>Parser.ts # Pure input parsing → Result<T>
├── <name>Service.ts # Business logic (depends on repository interface)
├── <name>Controller.ts # Thin handler: invocation → service → presenter
├── <name>Presenter.ts # Pure string formatting
└── infra/
├── <name>Repository.ts # Interface (contract)
├── drizzle<Name>Repository.ts # Implementation (Drizzle queries)
└── schema.ts # Table definition
See the Architecture Guide for a full walkthrough of how the layers connect and how to add a new module.
pnpm dev # TypeScript watch mode
pnpm build # Compile + copy migrations
pnpm verify # Type-check + lint (tsc --noEmit && eslint)
pnpm test # Run unit tests
pnpm lint:fix # Auto-fix lint issues
pnpm format # Format with Prettier- Group chats: bot responds only when mentioned or message starts with
#. - Auth: only phone numbers in
ALLOWED_NUMBERScan execute commands. - Identity: user IDs are normalized before persistence to handle WA ID format variations.
- Remind scheduler: runs independently after WA client is ready, polls every 30s.
- Digest/Quran scheduler: runs only when
DIGEST_GROUP_IDis configured.
- Keep
.envout of version control (already in.gitignore). - Restrict access via
ALLOWED_NUMBERS— no allowlist means no one can use the bot. - Persist
.wwebjs_auth/anddata/in production environments. - Do not share terminal logs publicly (may contain operational details).
Bot does not respond
- Check sender number is in
ALLOWED_NUMBERS - In groups, ensure message starts with
#or bot is mentioned - Set
DEBUG=trueand inspect logs
Scheduler not running
- Digest/Quran jobs: verify
DIGEST_GROUP_IDis set and timezone/hour/minute values are correct - Reminders: ensure WA client reached
readystate; check DB for pending reminders (sent_at IS NULL,scheduled_at <= now)
Authentication issues
- Ensure auth path is writable (
.wwebjs_auth/orRAILWAY_VOLUME_MOUNT_PATH) - Delete auth folder and rescan QR if session is corrupted
| Document | Description |
|---|---|
| Architecture Guide | How the codebase is structured, key patterns, and step-by-step guide for adding new features |
MIT
