Files
miti99bot/plans/reports/researcher-260411-0853-grammy-on-cloudflare-workers.md
tiennm99 c4314f21df feat: scaffold plug-n-play telegram bot on cloudflare workers
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/
2026-04-11 09:49:06 +07:00

58 lines
2.8 KiB
Markdown

# Researcher Report: grammY on Cloudflare Workers
**Date:** 2026-04-11
**Scope:** grammY entry point, webhook adapter, secret-token verification, setMyCommands usage.
## Key findings
### Adapter
- Use **`"cloudflare-mod"`** adapter for ES module (fetch handler) Workers. Source: grammY `src/convenience/frameworks.ts`.
- The legacy `"cloudflare"` adapter targets service-worker style Workers. Do NOT use — CF has moved on to module workers.
- Import path (npm, not Deno): `import { Bot, webhookCallback } from "grammy";`
### Minimal fetch handler
```js
import { Bot, webhookCallback } from "grammy";
export default {
async fetch(request, env, ctx) {
const bot = new Bot(env.TELEGRAM_BOT_TOKEN);
// ... register handlers
const handle = webhookCallback(bot, "cloudflare-mod", {
secretToken: env.TELEGRAM_WEBHOOK_SECRET,
});
return handle(request);
},
};
```
### Secret-token verification
- `webhookCallback` accepts `secretToken` in its `WebhookOptions`. When set, grammY validates the incoming `X-Telegram-Bot-Api-Secret-Token` header and rejects mismatches with 401.
- **No need** to manually read the header — delegate to grammY.
- The same secret must be passed to Telegram when calling `setWebhook` (`secret_token` field).
### Bot instantiation cost
- `new Bot()` per request is acceptable for Workers (no persistent state between requests anyway). Global-scope instantiation also works and caches across warm invocations. Prefer **global-scope** for reuse but be aware env bindings are not available at module load — must instantiate lazily inside `fetch`. Recommended pattern: memoize `Bot` in a module-scope variable initialized on first request.
### setMyCommands
- Call via `bot.api.setMyCommands([{ command, description }, ...])`.
- Should be called **on demand**, not on every webhook request (rate-limit risk, latency). Two options:
1. Dedicated admin HTTP route (e.g. `POST /admin/setup`) guarded by a second secret. Runs on demand.
2. One-shot `wrangler` script. Adds tooling complexity.
- **Recommendation:** admin route. Keeps deploy flow in one place (`wrangler deploy` + `curl`). No extra script.
### Init flow
- `bot.init()` is NOT required if you only use `webhookCallback`; grammY handles lazy init.
- For `/admin/setup` that directly calls `bot.api.*`, call `await bot.init()` once to populate `bot.botInfo`.
## Resolved technical answers
| Question | Answer |
|---|---|
| Adapter string | `"cloudflare-mod"` |
| Import | `import { Bot, webhookCallback } from "grammy"` |
| Secret verify | pass `secretToken` in `webhookCallback` options |
| setMyCommands trigger | admin HTTP route guarded by separate secret |
## Unresolved questions
- None blocking. grammY version pin: recommend `^1.30.0` or latest stable at implementation time; phase-01 should `npm view grammy version` to confirm.