Files
miti99bot/src/bot.js
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

57 lines
1.5 KiB
JavaScript

/**
* @file bot — lazy, memoized grammY Bot factory.
*
* The Bot instance is constructed on the first request (env is not available
* at module-load time in CF Workers) and reused on subsequent warm requests.
* Dispatcher middleware is installed exactly once, as part of that same
* lazy init, so the registry is built before any update is routed.
*/
import { Bot } from "grammy";
import { installDispatcher } from "./modules/dispatcher.js";
/** @type {Bot | null} */
let botInstance = null;
/** @type {Promise<Bot> | null} */
let botInitPromise = null;
/**
* Fail fast if any required env var is missing — better a 500 on first webhook
* than a confusing runtime error inside grammY.
*
* @param {any} env
*/
function requireEnv(env) {
const required = ["TELEGRAM_BOT_TOKEN", "TELEGRAM_WEBHOOK_SECRET", "MODULES"];
const missing = required.filter((key) => !env?.[key]);
if (missing.length > 0) {
throw new Error(`missing required env vars: ${missing.join(", ")}`);
}
}
/**
* @param {any} env
* @returns {Promise<Bot>}
*/
export async function getBot(env) {
if (botInstance) return botInstance;
if (botInitPromise) return botInitPromise;
requireEnv(env);
botInitPromise = (async () => {
const bot = new Bot(env.TELEGRAM_BOT_TOKEN);
await installDispatcher(bot, env);
botInstance = bot;
return bot;
})();
try {
return await botInitPromise;
} catch (err) {
// Clear the failed promise so the next request retries init.
botInitPromise = null;
throw err;
}
}