docs: refresh README for web-first state of the world (P3/T24)
The status section reflected the post-Plan-1 world where pairing
went through Telegram. Reality has long since moved on:
- Plans 1, 2, and 3 are all complete.
- The Telegram bot is gone (removed in P3/T4).
- The web app at wabot.04080616.xyz is the primary surface.
- The recurrence picker, multi-message stack, swipe rows, archive
flow, HEIC fallback, and PWA install all shipped.
Other updates:
- Quick-start no longer mentions Telegram tokens — replaced with
the URL the operator opens to use the app.
- Architecture paragraph swapped grammy mention for the LISTEN/
NOTIFY consumer + SSE shape.
- Pointed at the new design spec
(2026-05-09-web-app-design.md) and manual test runbook.
- Layout / scripts tables trimmed: dropped the publish.sh and
link-account.sh stubs that were never implemented.
- "Deferred" section lists honest gaps (media library, E2E tests,
auth, multi-operator).
Test counts called out (249 web + 31 shared + 26 bot = 306) so the
status claim is verifiable in a single grep.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d407fc585e
commit
29535d6bbc
125
README.md
125
README.md
@ -1,41 +1,87 @@
|
|||||||
# cm WhatsApp Reminder Bot
|
# cm WhatsApp Reminder Bot
|
||||||
|
|
||||||
Self-hosted WhatsApp reminder bot. Pairs multiple WhatsApp accounts via Telegram-delivered QR codes and sends scheduled reminders to groups.
|
Self-hosted WhatsApp reminder bot. Pair multiple WhatsApp accounts via
|
||||||
|
a browser-based PWA, schedule recurring reminders to groups, and watch
|
||||||
|
the run history all from a phone home-screen icon.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
**Plan 1 complete.** Foundation, DB schema, and Telegram-driven WhatsApp pairing are working end-to-end. Reminder scheduling, the web dashboard, and production deploy are upcoming plans (`docs/superpowers/plans/`).
|
**Plans 1, 2, and 3 complete.** The web app at `wabot.04080616.xyz` is
|
||||||
|
the primary control surface; the Telegram bot has been removed.
|
||||||
|
|
||||||
What's working today:
|
What's working today:
|
||||||
|
|
||||||
- Single-operator Telegram bot with a whitelist + audit log of every command.
|
- **Self-hosted Next.js 16 PWA** — installable on a phone home screen.
|
||||||
- BotFather-style menu navigation: `/menu` opens a single message that edits in place as you navigate.
|
Mobile-first single-row header with a slide-out drawer; desktop
|
||||||
- Pair a new WhatsApp account with `/menu` → 📡 Pair New → reply with a label. QR is delivered to Telegram and refreshed in place as it expires.
|
sidebar.
|
||||||
- Browse paired accounts with 📒 Accounts. Tap an account → see groups, send a test text message, or unpair.
|
- **Live QR pairing** — server-side Baileys session feeds the QR
|
||||||
- Group sync runs at pairing and on every Baileys `groups.upsert` / `groups.update` event, plus a manual 🔄 Refresh button. Removed groups are pruned automatically.
|
payload directly into the browser via Server-Sent Events. Scan,
|
||||||
- Auto-reconnect on transient drops; restart-survival via Baileys `useMultiFileAuthState` (no QR rescan needed across container restarts as long as WhatsApp hasn't logged the device out).
|
see "✅ Connected" within seconds, auto-redirect.
|
||||||
|
- **Multi-account, multi-group reminders** — 5-step wizard
|
||||||
|
(Account → Message → When → Groups → Review) plus per-section edit
|
||||||
|
pages so you don't have to walk the wizard end-to-end to fix one
|
||||||
|
field. Active recurrence picker covers Daily / Weekly / Monthly /
|
||||||
|
Yearly with multi-rule support and per-rule fire-time pickers; the
|
||||||
|
rendered description reads as plain English ("Every week on Mon,
|
||||||
|
Wed, Fri at 09:00") not raw cron.
|
||||||
|
- **Multi-message stacks** — a reminder can carry multiple ordered
|
||||||
|
parts (text + media), fired in sequence with a 1.5 s gap. Media
|
||||||
|
files swap at any time from the Edit Message page.
|
||||||
|
- **Smart media handling** — per-kind WhatsApp size caps (5 MB image,
|
||||||
|
16 MB video/audio, 100 MB document). HEIC photos and `.mov` videos
|
||||||
|
fall back to the document delivery path so they reach the recipient
|
||||||
|
as a downloadable file instead of failing silently.
|
||||||
|
- **Swipe-to-act rows** — on mobile, swipe a reminder or activity
|
||||||
|
row left for Delete or right for Pause/Restart/Archive. iOS-Mail
|
||||||
|
style.
|
||||||
|
- **Activity tab** — last 200 runs with status filters (Success /
|
||||||
|
Partial / Failed / Skipped) plus an Archived tab. Archive a noisy
|
||||||
|
run to keep the main list readable; restore later. Hard-delete
|
||||||
|
always available. Run history survives a reminder deletion.
|
||||||
|
- **Auto-reconnect on transient drops; restart-survival via Baileys
|
||||||
|
session persistence.** Pair once, the device stays linked across
|
||||||
|
container restarts.
|
||||||
|
- **All actions audited.** Reminder run history queryable from the
|
||||||
|
UI; per-run target results (sent / failed / skipped) preserved
|
||||||
|
even when the underlying group is removed.
|
||||||
|
|
||||||
|
Test count: **249 web + 31 shared + 26 bot = 306** passing.
|
||||||
|
|
||||||
## Host requirements
|
## Host requirements
|
||||||
|
|
||||||
Only Docker. No host Node, pnpm, or any other language toolchain — everything runs in containers via the long-lived `tools` service.
|
Only Docker. No host Node, pnpm, or any other language toolchain —
|
||||||
|
everything runs in containers via the long-lived `tools` sidecar.
|
||||||
|
|
||||||
## Architecture in one paragraph
|
## Architecture in one paragraph
|
||||||
|
|
||||||
Two app containers and one external dependency. `bot` (Node.js) holds the live Baileys WhatsApp sessions, the grammy Telegram bot, and (in plan 2) a pg-boss scheduler. `web` (Next.js, plan 3) is stateless UI + API. `tools` is a long-running Node 22 + pnpm sidecar used for installs/tests/typechecks/migrations so the host doesn't need a Node toolchain. Postgres lives external at `192.168.0.210` in a `wabot` database. All cross-service communication goes through Postgres (`LISTEN/NOTIFY` for events, table writes for state).
|
Two app containers and one external dependency. `bot` (Node.js) holds
|
||||||
|
the live Baileys WhatsApp sessions, the pg-boss scheduler, and a
|
||||||
|
Postgres `LISTEN bot.command` consumer. `web` (Next.js 16 App Router
|
||||||
|
+ React 19) is stateless UI: Server Components for reads, Server
|
||||||
|
Actions for mutations, an SSE endpoint for live updates,
|
||||||
|
`@serwist/next` for the PWA shell. `tools` is a long-running
|
||||||
|
Node 22 + pnpm sidecar used for installs / tests / typechecks /
|
||||||
|
migrations so the host doesn't need a Node toolchain. Postgres lives
|
||||||
|
external at `192.168.0.210` in a `wabot` database. All cross-service
|
||||||
|
communication goes through Postgres (`LISTEN/NOTIFY` for events,
|
||||||
|
table writes for state).
|
||||||
|
|
||||||
Full design spec: [`docs/superpowers/specs/2026-05-03-whatsapp-bot-design.md`](docs/superpowers/specs/2026-05-03-whatsapp-bot-design.md)
|
Full design spec:
|
||||||
|
[`docs/superpowers/specs/2026-05-09-web-app-design.md`](docs/superpowers/specs/2026-05-09-web-app-design.md)
|
||||||
|
|
||||||
## Quick start (dev)
|
## Quick start (dev)
|
||||||
|
|
||||||
Prerequisites: Docker, the `wabot` database + `waBot` role on `192.168.0.210` (with a `pg_hba.conf` line permitting `192.168.0.0/24`), and a Telegram bot token from `@BotFather`.
|
Prerequisites: Docker, the `wabot` database + `waBot` role on
|
||||||
|
`192.168.0.210` (with a `pg_hba.conf` line permitting
|
||||||
|
`192.168.0.0/24`).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Configure env
|
# 1. Configure env
|
||||||
cp envs/.env.example .env.development
|
cp envs/.env.example .env.development
|
||||||
# edit .env.development: real DATABASE_URL, TELEGRAM_BOT_TOKEN, your TG user ID
|
# edit .env.development: real DATABASE_URL, plus the LAN host to expose
|
||||||
scripts/gen_auth_secret.sh --write
|
scripts/gen_auth_secret.sh --write
|
||||||
|
|
||||||
# 2. Bring up the tools container, install deps
|
# 2. Bring up the stack, install deps
|
||||||
NO_SUDO=1 scripts/dev.sh up
|
NO_SUDO=1 scripts/dev.sh up
|
||||||
NO_SUDO=1 scripts/dev.sh pnpm install
|
NO_SUDO=1 scripts/dev.sh pnpm install
|
||||||
|
|
||||||
@ -43,39 +89,62 @@ NO_SUDO=1 scripts/dev.sh pnpm install
|
|||||||
NO_SUDO=1 scripts/db.sh migrate
|
NO_SUDO=1 scripts/db.sh migrate
|
||||||
NO_SUDO=1 scripts/db.sh seed
|
NO_SUDO=1 scripts/db.sh seed
|
||||||
|
|
||||||
# 4. Watch the bot service
|
# 4. Open the web app
|
||||||
NO_SUDO=1 scripts/dev.sh logs bot
|
# Local: http://localhost:9000
|
||||||
|
# LAN: http://<host-ip>:9000 (e.g. http://192.168.0.253:9000)
|
||||||
|
# Public: https://wabot.04080616.xyz (whatever your reverse proxy serves)
|
||||||
```
|
```
|
||||||
|
|
||||||
In Telegram, message your dev bot `/menu`, tap **📡 Pair New**, reply with a label, scan the QR.
|
Pair an account: `/accounts` → "New Account" → enter a label →
|
||||||
|
"Pair WhatsApp" → scan the QR with WhatsApp's "Linked Devices".
|
||||||
|
|
||||||
`NO_SUDO=1` is the right setting if your user is in the `docker` group (the default for this repo). Drop it if you need `sudo docker`.
|
PWA install: phone Chrome → menu → "Install App" / "Add to Home
|
||||||
|
Screen". Launches fullscreen.
|
||||||
|
|
||||||
|
`NO_SUDO=1` is the right setting if your user is in the `docker`
|
||||||
|
group (the default for this repo). Drop it if you need `sudo docker`.
|
||||||
|
|
||||||
|
## Manual test runbook
|
||||||
|
|
||||||
|
End-to-end checks that unit tests can't cover (live Baileys,
|
||||||
|
WhatsApp delivery, swipe gestures):
|
||||||
|
[`docs/superpowers/specs/manual-test-web.md`](docs/superpowers/specs/manual-test-web.md).
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
- `apps/bot/` — Node service: Baileys WhatsApp + grammy Telegram + (later) pg-boss scheduler
|
- `apps/bot/` — Baileys WhatsApp + pg-boss scheduler + LISTEN/NOTIFY
|
||||||
- `apps/web/` — Next.js dashboard (plan 3)
|
command consumer
|
||||||
|
- `apps/web/` — Next.js 16 App Router PWA
|
||||||
- `packages/db/` — Drizzle schema and migrations
|
- `packages/db/` — Drizzle schema and migrations
|
||||||
- `packages/shared/` — cross-app helpers (rrule, media paths, timezones)
|
- `packages/shared/` — cross-app helpers (rrule, media paths,
|
||||||
|
timezones, WhatsApp media classifier)
|
||||||
- `docs/superpowers/specs/` — design specs and manual test runbooks
|
- `docs/superpowers/specs/` — design specs and manual test runbooks
|
||||||
- `docs/superpowers/plans/` — implementation plans
|
- `docs/superpowers/plans/` — implementation plans
|
||||||
- `docker/` — Dockerfiles (`tools.Dockerfile`, `bot.Dockerfile`, `web.Dockerfile` placeholder)
|
- `docker/` — Dockerfiles (`tools.Dockerfile`, `bot.Dockerfile`,
|
||||||
- `scripts/` — `dev.sh`, `db.sh`, `gen_auth_secret.sh`, plus stubs for plans 2/4
|
`web.Dockerfile`)
|
||||||
|
- `scripts/` — `dev.sh`, `db.sh`, `gen_auth_secret.sh`
|
||||||
|
|
||||||
## Scripts
|
## Scripts
|
||||||
|
|
||||||
All `pnpm`/`tsx`/`drizzle-kit` invocations run inside the `tools` container, so no host Node is needed.
|
All `pnpm`/`tsx`/`drizzle-kit` invocations run inside the `tools`
|
||||||
|
container, so no host Node is needed.
|
||||||
|
|
||||||
| Script | Purpose |
|
| Script | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `scripts/dev.sh up\|down\|logs\|status\|build\|exec\|pnpm\|shell\|restart-bot` | Stack lifecycle and tools-container shell |
|
| `scripts/dev.sh up\|down\|logs\|status\|build\|exec\|pnpm\|shell\|restart-bot` | Stack lifecycle and tools-container shell |
|
||||||
| `scripts/db.sh migrate\|generate\|studio\|seed\|reset` | Drizzle migration helper |
|
| `scripts/db.sh migrate\|generate\|studio\|seed\|reset` | Drizzle migration helper |
|
||||||
| `scripts/gen_auth_secret.sh [--write]` | Generate `AUTH_SECRET` (host-only, no Node needed) |
|
| `scripts/gen_auth_secret.sh [--write]` | Generate `AUTH_SECRET` (host-only, no Node needed) |
|
||||||
| `scripts/publish.sh` | Push to Gitea registry — implemented in plan 4 |
|
|
||||||
| `scripts/link-account.sh` | CLI pairing without Telegram — implemented in plan 2 |
|
|
||||||
|
|
||||||
Set `NO_SUDO=1` if your user is in the docker group (recommended).
|
Set `NO_SUDO=1` if your user is in the docker group (recommended).
|
||||||
|
|
||||||
## Next plan
|
## Deferred
|
||||||
|
|
||||||
`docs/superpowers/plans/<next-date>-reminder-scheduling.md` — pg-boss, reminder CRUD via Telegram, fire-reminder handler, sender (text/image/video), retry policy, run history.
|
- **Standalone media library** browser (currently media is uploaded
|
||||||
|
per-reminder).
|
||||||
|
- **E2E browser tests** (Playwright) on the swipe and pairing flows.
|
||||||
|
- **Auth** (passkeys / email-password) — bring back if URL exposure
|
||||||
|
becomes a concern. Today the app trusts whatever's in front of the
|
||||||
|
reverse proxy.
|
||||||
|
- **Multi-operator** — schema supports `operator_id` on every row,
|
||||||
|
but the seed runs as a single operator and there's no /signup or
|
||||||
|
invite flow yet.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user