mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-04-17 17:21:30 +00:00
grammY-based bot with a module plugin system loaded from the MODULES env var. Three command visibility levels (public/protected/private) share a unified command namespace with conflict detection at registry build. - 4 initial modules (util, wordle, loldle, misc); util fully implemented, others are stubs proving the plugin system end-to-end - util: /info (chat/thread/sender ids) + /help (pure renderer over the registry, HTML parse mode, escapes user-influenced strings) - KVStore interface with CFKVStore and a per-module prefixing factory; getJSON/putJSON convenience helpers; other backends drop in via one file - Webhook at POST /webhook with secret-token validation via grammY's webhookCallback; no admin HTTP surface - Post-deploy register script (npm run deploy = wrangler deploy && node --env-file=.env.deploy scripts/register.js) for setWebhook and setMyCommands; --dry-run flag for preview - 56 vitest unit tests across 7 suites covering registry, db wrapper, dispatcher, help renderer, validators, and HTML escaper - biome for lint + format; phased implementation plan under plans/
4.6 KiB
4.6 KiB
Phase 02 — Webhook entrypoint
Context Links
- Plan: plan.md
- Reports: grammY on CF Workers
Overview
- Priority: P1
- Status: pending
- Description: fetch handler with URL routing (
/webhook,GET /health, 404 otherwise), memoizedBotinstance, grammY webhook secret-token validation wired throughwebhookCallback. Webhook + command-menu registration with Telegram is handled OUT OF BAND via a post-deploy node script (phase-07) — the Worker itself exposes no admin surface.
Key Insights
- Use
"cloudflare-mod"adapter (NOT"cloudflare"— that's the legacy service-worker variant). webhookCallback(bot, "cloudflare-mod", { secretToken })delegatesX-Telegram-Bot-Api-Secret-Tokenvalidation to grammY — no manual header parsing.- Bot instance must be memoized at module scope but lazily constructed (env not available at import time).
- No admin HTTP surface on the Worker —
setWebhook+setMyCommandsrun from a local node script at deploy time, not via the Worker.
Requirements
Functional
POST /webhook→ delegate towebhookCallback. Wrong/missing secret → 401 (handled by grammY).GET /→ 200"miti99bot ok"(health check, unauthenticated).- Anything else → 404.
Non-functional
- Single
fetchfunction, <80 LOC. - No top-level await.
- No global state besides memoized Bot.
Architecture
Request
│
▼
fetch(req, env, ctx)
│
├── GET / → 200 "ok"
├── POST /webhook → webhookCallback(bot, "cloudflare-mod", {secretToken})(req)
└── * → 404
getBot(env) lazily constructs and memoizes the Bot, installs dispatcher middleware (from phase-04), and returns the instance.
Related Code Files
Create
src/index.js(fetch handler + URL router)src/bot.js(memoizedgetBot(env)factory — wires grammY middleware from registry/dispatcher)
Modify
- none
Delete
- none
Implementation Steps
- Create
src/index.js:- Import
getBotfrom./bot.js. - Export default object with
async fetch(request, env, ctx). - Parse
new URL(request.url), switch onpathname. - For
POST /webhook:return webhookCallback(getBot(env), "cloudflare-mod", { secretToken: env.TELEGRAM_WEBHOOK_SECRET })(request). - For
GET /: return 200"miti99bot ok". - Default: 404.
- Import
- Create
src/bot.js:- Module-scope
let botInstance = null. export function getBot(env):- If
botInstanceexists, return it. - Construct
new Bot(env.TELEGRAM_BOT_TOKEN). installDispatcher(bot, env)— imported fromsrc/modules/dispatcher.js(phase-04 — stub import now, real impl later).- Assign + return.
- If
- Temporary stub: if
installDispatchernot yet implemented, create a placeholder function insrc/modules/dispatcher.jsthat does nothing so this phase compiles.
- Module-scope
- Env validation: on first
getBotcall, throw ifTELEGRAM_BOT_TOKEN/TELEGRAM_WEBHOOK_SECRET/MODULESmissing. Fail fast is a feature. npm run lint— fix any issues.wrangler dev— hitGET /locally, confirm 200. HitPOST /webhookwithout secret header, confirm 401.
Todo List
src/index.jsfetch handler + URL routersrc/bot.jsmemoized factory- Placeholder
src/modules/dispatcher.jsexportinginstallDispatcher(bot, env)no-op - Env var validation with clear error messages
- Manual smoke test via
wrangler dev
Success Criteria
GET /returns 200"miti99bot ok".POST /webhookwithout header → 401 (via grammY).POST /webhookwith correctX-Telegram-Bot-Api-Secret-Tokenheader and a valid Telegram update JSON body → 200.- Unknown path → 404.
Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Wrong adapter string breaks webhook | Low | High | Pin "cloudflare-mod"; test with wrangler dev + curl |
| Memoized Bot leaks state between deploys | Low | Low | Warm-restart resets module scope; documented behavior |
| Cold-start latency from first Bot() construction | Med | Low | Acceptable for bot use case |
Security Considerations
TELEGRAM_WEBHOOK_SECRETMUST be configured before enabling webhook in Telegram; grammY'ssecretTokenoption gives 401 on mismatch.- Worker has NO admin HTTP surface — no attack surface beyond
/webhook(secret-gated by grammY) and the public health check. - Never log secrets, even on error paths.
Next Steps
- Phase 03 creates the DB abstraction that modules will use in phase 04+.
- Phase 04 replaces the dispatcher stub with real middleware.