mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-04-17 13:21:31 +00:00
docs: add CLAUDE.md and project documentation
Add CLAUDE.md for AI assistant context. Create four new docs: deployment-guide.md (full deploy flow + secret rotation + rollback), code-standards.md (formatting, naming, module conventions, testing), codebase-summary.md (tech stack, modules, data flows, external APIs), development-roadmap.md (completed phases + planned work).
This commit is contained in:
69
CLAUDE.md
Normal file
69
CLAUDE.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # local dev server (wrangler dev) at http://localhost:8787
|
||||||
|
npm run lint # biome check src tests scripts
|
||||||
|
npm run format # biome format --write
|
||||||
|
npm test # vitest run (all tests)
|
||||||
|
npx vitest run tests/modules/trading/format.test.js # single test file
|
||||||
|
npx vitest run -t "formats with dot" # single test by name
|
||||||
|
npm run deploy # wrangler deploy + register webhook/commands with Telegram
|
||||||
|
npm run register:dry # preview setWebhook + setMyCommands payloads without calling Telegram
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
grammY Telegram bot on Cloudflare Workers. Modules are plug-n-play: each module is a folder under `src/modules/` that exports `{ name, commands[], init? }`. A single `MODULES` env var controls which modules are loaded.
|
||||||
|
|
||||||
|
**Request flow:** `POST /webhook` → grammY validates secret header → `getBot(env)` (memoized per warm instance) → `installDispatcher` builds registry on first call → `bot.command(name, handler)` for every command → handler runs.
|
||||||
|
|
||||||
|
**Key abstractions:**
|
||||||
|
- `src/modules/registry.js` — loads modules from static import map (`src/modules/index.js`), validates commands, detects name conflicts across all visibility levels, builds four maps (public/protected/private/all). Memoized via `getCurrentRegistry()`.
|
||||||
|
- `src/db/create-store.js` — wraps Cloudflare KV with auto-prefixed keys per module (`moduleName:key`). Modules never touch `env.KV` directly.
|
||||||
|
- `scripts/register.js` — post-deploy script that imports the same registry to derive public commands, then calls Telegram `setWebhook` + `setMyCommands`. Uses `stub-kv.js` to satisfy KV binding without real IO.
|
||||||
|
|
||||||
|
**Three command visibilities:** public (in Telegram `/` menu + `/help`), protected (in `/help` only), private (hidden easter eggs). All three are registered via `bot.command()` — visibility controls discoverability, not access.
|
||||||
|
|
||||||
|
## Adding a Module
|
||||||
|
|
||||||
|
1. Create `src/modules/<name>/index.js` with default export `{ name, commands, init? }`
|
||||||
|
2. Add one line to `src/modules/index.js` static import map
|
||||||
|
3. Add `<name>` to `MODULES` in `wrangler.toml` `[vars]`
|
||||||
|
4. Full guide: `docs/adding-a-module.md`
|
||||||
|
|
||||||
|
## Module Contract
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
name: "mymod", // must match folder + import map key
|
||||||
|
init: async ({ db, env }) => { ... }, // optional — db is prefixed KVStore
|
||||||
|
commands: [{
|
||||||
|
name: "mycmd", // ^[a-z0-9_]{1,32}$, no leading slash
|
||||||
|
visibility: "public", // "public" | "protected" | "private"
|
||||||
|
description: "Does a thing", // required for all visibilities
|
||||||
|
handler: async (ctx) => { ... }, // grammY context
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Command names must be globally unique across ALL modules and visibilities. Conflicts throw at load time.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Pure-logic unit tests only — no workerd, no Telegram fixtures. Tests use fakes from `tests/fakes/` (fake-kv-namespace, fake-bot, fake-modules) injected via parameters, not `vi.mock`.
|
||||||
|
|
||||||
|
For modules that call `fetch` (like trading/prices), stub `global.fetch` with `vi.fn()` in tests.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
Biome enforces: 2-space indent, double quotes, semicolons, trailing commas, 100-char line width, sorted imports. Run `npm run format` before committing. Keep files under 200 lines — split into focused submodules when approaching the limit.
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
- Secrets (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_WEBHOOK_SECRET`): set via `wrangler secret put`, mirrored in `.env.deploy` (gitignored) for register script
|
||||||
|
- `.dev.vars`: local dev secrets (gitignored), copy from `.dev.vars.example`
|
||||||
|
- Node >=20.6 required (for `--env-file` flag)
|
||||||
90
docs/code-standards.md
Normal file
90
docs/code-standards.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Code Standards
|
||||||
|
|
||||||
|
## Language & Runtime
|
||||||
|
|
||||||
|
- **JavaScript** (ES modules, `"type": "module"` in package.json)
|
||||||
|
- **No TypeScript** — JSDoc typedefs for type contracts (see `kv-store-interface.js`, `registry.js`)
|
||||||
|
- **Cloudflare Workers runtime** — Web APIs only, no Node.js built-ins, no `nodejs_compat`
|
||||||
|
- **grammY** for Telegram bot framework
|
||||||
|
|
||||||
|
## Formatting (Biome)
|
||||||
|
|
||||||
|
Enforced by `npm run lint` / `npm run format`:
|
||||||
|
|
||||||
|
- 2-space indent
|
||||||
|
- Double quotes
|
||||||
|
- Semicolons: always
|
||||||
|
- Trailing commas: all
|
||||||
|
- Line width: 100 characters
|
||||||
|
- Imports: auto-sorted by Biome
|
||||||
|
|
||||||
|
Run `npm run format` before committing.
|
||||||
|
|
||||||
|
## File Organization
|
||||||
|
|
||||||
|
- **Max 200 lines per code file.** Split into focused submodules when approaching the limit.
|
||||||
|
- Module code lives in `src/modules/<name>/` — one folder per module.
|
||||||
|
- Shared utilities in `src/util/`.
|
||||||
|
- DB layer in `src/db/`.
|
||||||
|
- Tests mirror source structure: `tests/modules/<name>/`, `tests/db/`, `tests/util/`.
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
|
||||||
|
- **Files:** lowercase, hyphens for multi-word (`stats-handler.js`, `fake-kv-namespace.js`)
|
||||||
|
- **Directories:** lowercase, single word preferred (`trading/`, `util/`)
|
||||||
|
- **Functions/variables:** camelCase
|
||||||
|
- **Constants:** UPPER_SNAKE_CASE for frozen config objects (`SYMBOLS`, `CURRENCIES`)
|
||||||
|
- **Command names:** lowercase + digits + underscore, 1-32 chars, no leading slash
|
||||||
|
|
||||||
|
## Module Conventions
|
||||||
|
|
||||||
|
Every module default export must have:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
name: "modname", // === folder name === import map key
|
||||||
|
commands: [...], // validated at load time
|
||||||
|
init: async ({ db, env }) => { ... }, // optional
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- Store module-level `db` reference in a closure variable, set during `init`
|
||||||
|
- Never access `env.KV` directly — always use the prefixed `db` from `init`
|
||||||
|
- Handlers receive grammY `ctx` — use `ctx.match` for command arguments, `ctx.from.id` for user identity
|
||||||
|
- Reply with `ctx.reply(text)` — plain text or Telegram HTML
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- **Load-time failures** (bad module, command conflicts, missing env): throw immediately — fail loud at deploy, not at runtime.
|
||||||
|
- **Handler-level errors** (API failures, bad user input): catch and reply with user-friendly message. Never crash the handler — grammY logs unhandled rejections but the user sees nothing.
|
||||||
|
- **KV failures**: best-effort writes (wrap in try/catch), guard reads with `?.` and null coalescing.
|
||||||
|
- `getJSON` swallows corrupt JSON and returns null — modules must handle null gracefully.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- **Framework:** Vitest
|
||||||
|
- **Style:** Pure-logic unit tests. No workerd, no Telegram integration, no network calls.
|
||||||
|
- **Fakes:** `tests/fakes/` provides `fake-kv-namespace.js`, `fake-bot.js`, `fake-modules.js`. Inject via parameters, not `vi.mock`.
|
||||||
|
- **External APIs:** Stub `global.fetch` with `vi.fn()` returning canned responses.
|
||||||
|
- **Coverage:** `npx vitest run --coverage` (v8 provider, text + HTML output).
|
||||||
|
|
||||||
|
## Commit Messages
|
||||||
|
|
||||||
|
Conventional commits:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: add paper trading module
|
||||||
|
fix: handle null price in sell handler
|
||||||
|
docs: update architecture for trading module
|
||||||
|
refactor: extract stats handler to separate file
|
||||||
|
test: add portfolio edge case tests
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Secrets live in Cloudflare Workers secrets (runtime) and `.env.deploy` (local, gitignored). Never commit secrets.
|
||||||
|
- `.dev.vars` is gitignored — local dev only.
|
||||||
|
- grammY validates webhook secret on every update. No manual header parsing.
|
||||||
|
- Module KV prefixing is a code-review boundary, not a cryptographic one.
|
||||||
|
- Private commands are discoverability control, not access control.
|
||||||
|
- HTML output in `/help` uses `escapeHtml` to prevent injection.
|
||||||
79
docs/codebase-summary.md
Normal file
79
docs/codebase-summary.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Codebase Summary
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Telegram bot on Cloudflare Workers with a plug-n-play module system. grammY handles Telegram API; modules register commands with three visibility levels. Data stored in Cloudflare KV behind a prefixed `KVStore` interface.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|-------|-----------|
|
||||||
|
| Runtime | Cloudflare Workers (V8 isolates) |
|
||||||
|
| Bot framework | grammY 1.x |
|
||||||
|
| Storage | Cloudflare KV |
|
||||||
|
| Linter/Formatter | Biome |
|
||||||
|
| Tests | Vitest |
|
||||||
|
| Deploy | Wrangler CLI |
|
||||||
|
|
||||||
|
## Active Modules
|
||||||
|
|
||||||
|
| Module | Status | Commands | Description |
|
||||||
|
|--------|--------|----------|-------------|
|
||||||
|
| `util` | Complete | `/info`, `/help` | Bot info and command help renderer |
|
||||||
|
| `trading` | Complete | `/trade_topup`, `/trade_buy`, `/trade_sell`, `/trade_convert`, `/trade_stats` | Paper trading with crypto, VN stocks, forex, gold |
|
||||||
|
| `misc` | Stub | `/ping`, `/mstats`, `/fortytwo` | Health check + DB demo |
|
||||||
|
| `wordle` | Stub | `/wordle`, `/wstats`, `/konami` | Placeholder for word game |
|
||||||
|
| `loldle` | Stub | `/loldle`, `/ggwp` | Placeholder for LoL game |
|
||||||
|
|
||||||
|
## Key Data Flows
|
||||||
|
|
||||||
|
### Command Processing
|
||||||
|
```
|
||||||
|
Telegram update → POST /webhook → grammY secret validation
|
||||||
|
→ getBot(env) → dispatcher routes /cmd → module handler
|
||||||
|
→ handler reads/writes KV via db.getJSON/putJSON
|
||||||
|
→ ctx.reply() → response to Telegram
|
||||||
|
```
|
||||||
|
|
||||||
|
### Trading Module Price Fetch
|
||||||
|
```
|
||||||
|
User sends /trade_buy → handler calls getPrice(db, symbol)
|
||||||
|
→ getPrices(db) checks KV cache (key: "prices:latest")
|
||||||
|
→ if stale (>60s): fetch CoinGecko + TCBS + ER-API in parallel
|
||||||
|
→ merge results, cache in KV → return price in VND
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy Pipeline
|
||||||
|
```
|
||||||
|
npm run deploy → wrangler deploy (upload to CF)
|
||||||
|
→ scripts/register.js → buildRegistry with stub KV
|
||||||
|
→ POST setWebhook + POST setMyCommands to Telegram API
|
||||||
|
```
|
||||||
|
|
||||||
|
## External Dependencies
|
||||||
|
|
||||||
|
| Dependency | Purpose | Version |
|
||||||
|
|-----------|---------|---------|
|
||||||
|
| `grammy` | Telegram Bot API framework | ^1.30.0 |
|
||||||
|
| `@biomejs/biome` | Linting + formatting (dev) | ^1.9.0 |
|
||||||
|
| `vitest` | Test runner (dev) | ^2.1.0 |
|
||||||
|
| `wrangler` | Cloudflare Workers CLI (dev) | ^3.90.0 |
|
||||||
|
|
||||||
|
## External APIs (Trading Module)
|
||||||
|
|
||||||
|
| API | Purpose | Auth | Rate Limit |
|
||||||
|
|-----|---------|------|-----------|
|
||||||
|
| CoinGecko `/api/v3/simple/price` | Crypto + gold prices in VND | None | 30 calls/min (free) |
|
||||||
|
| TCBS `/stock-insight/v1/stock/bars-long-term` | Vietnam stock close prices | None | Unofficial |
|
||||||
|
| open.er-api.com `/v6/latest/USD` | USD/VND forex rate | None | 1,500/month (free) |
|
||||||
|
|
||||||
|
## Test Coverage
|
||||||
|
|
||||||
|
110 tests across 11 test files:
|
||||||
|
|
||||||
|
| Area | Tests | What's Covered |
|
||||||
|
|------|-------|---------------|
|
||||||
|
| DB layer | 19 | KV store, prefixing, JSON helpers, pagination |
|
||||||
|
| Module framework | 33 | Registry, dispatcher, validators, help renderer |
|
||||||
|
| Utilities | 4 | HTML escaping |
|
||||||
|
| Trading module | 54 | Symbols, formatters, portfolio CRUD, all 5 command handlers |
|
||||||
123
docs/deployment-guide.md
Normal file
123
docs/deployment-guide.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Deployment Guide
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js ≥ 20.6
|
||||||
|
- Cloudflare account with Workers + KV enabled
|
||||||
|
- Telegram bot token from [@BotFather](https://t.me/BotFather)
|
||||||
|
- `wrangler` CLI authenticated: `npx wrangler login`
|
||||||
|
|
||||||
|
## Environment Setup
|
||||||
|
|
||||||
|
### 1. Cloudflare KV Namespaces
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx wrangler kv namespace create miti99bot-kv
|
||||||
|
npx wrangler kv namespace create miti99bot-kv --preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Paste returned IDs into `wrangler.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[kv_namespaces]]
|
||||||
|
binding = "KV"
|
||||||
|
id = "<production-id>"
|
||||||
|
preview_id = "<preview-id>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Worker Secrets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx wrangler secret put TELEGRAM_BOT_TOKEN
|
||||||
|
npx wrangler secret put TELEGRAM_WEBHOOK_SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
`TELEGRAM_WEBHOOK_SECRET` — any high-entropy string (e.g. `openssl rand -hex 32`). grammY validates it on every webhook update via `X-Telegram-Bot-Api-Secret-Token`.
|
||||||
|
|
||||||
|
### 3. Local Dev Config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .dev.vars.example .dev.vars # for wrangler dev
|
||||||
|
cp .env.deploy.example .env.deploy # for register script
|
||||||
|
```
|
||||||
|
|
||||||
|
Both are gitignored. Fill in matching token + secret values.
|
||||||
|
|
||||||
|
`.env.deploy` also needs:
|
||||||
|
- `WORKER_URL` — the `*.workers.dev` URL (known after first deploy)
|
||||||
|
- `MODULES` — comma-separated, must match `wrangler.toml` `[vars].MODULES`
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
### First Time
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx wrangler deploy # learn the *.workers.dev URL
|
||||||
|
# paste URL into .env.deploy as WORKER_URL
|
||||||
|
npm run register:dry # preview payloads
|
||||||
|
npm run deploy # deploy + register webhook + commands
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subsequent Deploys
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
This runs `wrangler deploy` then `scripts/register.js` (setWebhook + setMyCommands).
|
||||||
|
|
||||||
|
### What the Register Script Does
|
||||||
|
|
||||||
|
`scripts/register.js` imports the same registry the Worker uses, builds it with a stub KV to derive public commands, then calls two Telegram APIs:
|
||||||
|
|
||||||
|
1. `setWebhook` — points Telegram at `WORKER_URL/webhook` with the secret token
|
||||||
|
2. `setMyCommands` — pushes public command list to Telegram's `/` menu
|
||||||
|
|
||||||
|
### Dry Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run register:dry
|
||||||
|
```
|
||||||
|
|
||||||
|
Prints both payloads (webhook secret redacted) without calling Telegram.
|
||||||
|
|
||||||
|
## Secret Rotation
|
||||||
|
|
||||||
|
1. Generate new secret: `openssl rand -hex 32`
|
||||||
|
2. Update Cloudflare: `npx wrangler secret put TELEGRAM_WEBHOOK_SECRET`
|
||||||
|
3. Update `.env.deploy` with same value
|
||||||
|
4. Redeploy: `npm run deploy` (register step re-calls setWebhook with new secret)
|
||||||
|
|
||||||
|
Both values MUST match — mismatch causes 401 on every webhook.
|
||||||
|
|
||||||
|
## Adding/Removing Modules
|
||||||
|
|
||||||
|
When changing the active module list:
|
||||||
|
|
||||||
|
1. Update `MODULES` in `wrangler.toml` `[vars]`
|
||||||
|
2. Update `MODULES` in `.env.deploy`
|
||||||
|
3. If adding: ensure module exists in `src/modules/index.js` import map
|
||||||
|
4. `npm run deploy`
|
||||||
|
|
||||||
|
Removing a module from `MODULES` makes it inert — its KV data remains but nothing loads it.
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
Cloudflare Workers supports instant rollback via the dashboard or:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx wrangler rollback
|
||||||
|
```
|
||||||
|
|
||||||
|
To disable a specific module without rollback, remove its name from `MODULES` and redeploy.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Cause | Fix |
|
||||||
|
|---------|-------|-----|
|
||||||
|
| 401 on all webhooks | Secret mismatch between CF secret and `.env.deploy` | Re-set both to same value, redeploy |
|
||||||
|
| Bot doesn't respond to commands | `MODULES` missing the module name | Add to both `wrangler.toml` and `.env.deploy` |
|
||||||
|
| `command conflict` at deploy | Two modules register same command name | Rename one command |
|
||||||
|
| `missing env: X` from register | `.env.deploy` incomplete | Add missing variable |
|
||||||
|
| `--env-file` not recognized | Node < 20.6 | Upgrade Node |
|
||||||
|
| `/help` missing a module | Module has no public or protected commands | Add at least one non-private command |
|
||||||
44
docs/development-roadmap.md
Normal file
44
docs/development-roadmap.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Development Roadmap
|
||||||
|
|
||||||
|
## Current State: v0.1.0
|
||||||
|
|
||||||
|
Core framework complete. One fully implemented module (trading), three stubs (wordle, loldle, misc). 110 passing tests. Deployed to Cloudflare Workers.
|
||||||
|
|
||||||
|
## Completed
|
||||||
|
|
||||||
|
### Phase 1: Bot Framework (v0.1.0)
|
||||||
|
- [x] Cloudflare Workers entry point with webhook validation
|
||||||
|
- [x] grammY bot factory with memoized cold-start handling
|
||||||
|
- [x] Plug-n-play module system with static import map
|
||||||
|
- [x] Three-tier command visibility (public/protected/private)
|
||||||
|
- [x] Unified conflict detection across all modules
|
||||||
|
- [x] KVStore interface with auto-prefixed per-module namespacing
|
||||||
|
- [x] Post-deploy register script (setWebhook + setMyCommands)
|
||||||
|
- [x] `/info` and `/help` commands
|
||||||
|
- [x] 56 unit tests covering all framework seams
|
||||||
|
|
||||||
|
### Phase 2: Trading Module (v0.1.0)
|
||||||
|
- [x] Paper trading with 5 commands (topup/buy/sell/convert/stats)
|
||||||
|
- [x] Real-time prices: CoinGecko (crypto+gold), TCBS (VN stocks), ER-API (forex)
|
||||||
|
- [x] 60-second price caching in KV with stale fallback
|
||||||
|
- [x] Per-user portfolio storage with VND as base currency
|
||||||
|
- [x] P&L tracking (total invested vs current portfolio value)
|
||||||
|
- [x] 54 unit tests for symbols, formatters, portfolio CRUD, handlers
|
||||||
|
|
||||||
|
## Planned
|
||||||
|
|
||||||
|
### Phase 3: Game Modules
|
||||||
|
- [ ] Wordle implementation (currently stub)
|
||||||
|
- [ ] Loldle implementation (currently stub)
|
||||||
|
- [ ] Game state persistence in KV
|
||||||
|
|
||||||
|
### Phase 4: Infrastructure
|
||||||
|
- [ ] CI pipeline (GitHub Actions: lint + test on PR)
|
||||||
|
- [ ] KV namespace IDs in wrangler.toml (currently REPLACE_ME placeholders)
|
||||||
|
- [ ] Per-module rate limiting consideration
|
||||||
|
|
||||||
|
### Future Considerations
|
||||||
|
- Internationalization (per-module if needed)
|
||||||
|
- More trading assets (expand symbol registry)
|
||||||
|
- Trading history / transaction log
|
||||||
|
- Group chat features
|
||||||
Reference in New Issue
Block a user