Files
miti99bot/src/index.js
T
tiennm99 a2f67a7758 fix: project-wide review — trading safety, loldle drift guard, doc refresh
Code fixes:
- trading/handlers + stats-handler: guard ctx.from?.id to prevent
  cross-user state corruption when channel posts or inline queries lack
  a sender
- trading/prices + trading/symbols: encodeURIComponent on ticker before
  interpolating into TCBS API URLs
- trading/stats-handler: parallelize per-stock price fetches with
  Promise.allSettled so N-stock portfolios don't stack serial latency
- loldle/handlers: guard target champion lookup against champions.json
  refresh drift — start a fresh round or fall back to the stored id
- wordle + loldle: explicitly initialize giveup:false in startFreshGame
  for stable state shape
- wordle/lookup: fix stale JSDoc that claimed null return
- biome: ignore auto-generated champions.json / champions-data.js /
  words-data.js
- Apply formatter to src/index.js, loldle/handlers.js imports, and
  loldle/compare.test.js (previously red)

Docs refresh:
- README: 105+ tests -> 200+; wordle/loldle described as real modules
- architecture: module tree updated, test count 105 -> 200, runtime
  ~500ms -> ~2s, stub list narrowed to misc only
- codebase-summary: module table rewritten (wordle/loldle now Complete
  with real command lists and KV schema); test coverage table updated
- loldle/README: full rewrite matching the current implementation
  (was describing the original stub)
- New docs/development-roadmap.md tracking upcoming features
  (daily-mode for wordle + loldle, crypto/gold/forex trading, shared
  picker util, handler-level tests, coverage reporting, staging env)

Tests: 200/200 passing. Lint: clean.
2026-04-20 22:08:58 +07:00

104 lines
2.8 KiB
JavaScript

/**
* @file fetch entry point for the Cloudflare Worker.
*
* Routes:
* GET / — "miti99bot ok" health check (unauthenticated).
* POST /webhook — Telegram webhook. grammY validates the
* X-Telegram-Bot-Api-Secret-Token header against
* env.TELEGRAM_WEBHOOK_SECRET and replies 401 on mismatch.
* * — 404.
*
* There is NO admin HTTP surface. `setWebhook` + `setMyCommands` run at
* deploy time from `scripts/register.js`, not from the Worker.
*/
import { webhookCallback } from "grammy";
import { getBot, getRegistry } from "./bot.js";
import { dispatchScheduled } from "./modules/cron-dispatcher.js";
/** @type {ReturnType<typeof webhookCallback> | null} */
let cachedWebhookHandler = null;
/**
* @param {any} env
*/
async function getWebhookHandler(env) {
if (cachedWebhookHandler) return cachedWebhookHandler;
const bot = await getBot(env);
cachedWebhookHandler = webhookCallback(bot, "cloudflare-mod", {
secretToken: env.TELEGRAM_WEBHOOK_SECRET,
});
return cachedWebhookHandler;
}
export default {
/**
* Cloudflare Cron Trigger handler.
* Dispatches the scheduled event to all module cron handlers whose
* schedule matches event.cron.
*
* @param {any} event — ScheduledEvent ({ cron: string, scheduledTime: number })
* @param {any} env
* @param {{ waitUntil: (p: Promise<any>) => void }} ctx
*/
async scheduled(event, env, ctx) {
try {
const registry = await getRegistry(env);
dispatchScheduled(event, env, ctx, registry);
} catch (err) {
console.error("scheduled handler failed", err);
}
},
/**
* @param {Request} request
* @param {any} env
* @param {any} _ctx
*/
async fetch(request, env, _ctx) {
const start = Date.now();
const { pathname } = new URL(request.url);
const method = request.method;
const response = await route(request, env, pathname);
// Structured request log for Workers Observability dashboard.
console.log(
JSON.stringify({
msg: "req",
method,
path: pathname,
status: response.status,
ms: Date.now() - start,
}),
);
return response;
},
};
/**
* @param {Request} request
* @param {any} env
* @param {string} pathname
*/
async function route(request, env, pathname) {
if (request.method === "GET" && pathname === "/") {
return new Response("miti99bot ok", {
status: 200,
headers: { "content-type": "text/plain" },
});
}
if (request.method === "POST" && pathname === "/webhook") {
try {
const handler = await getWebhookHandler(env);
return await handler(request);
} catch (err) {
console.error("webhook handler failed", err);
return new Response("internal error", { status: 500 });
}
}
return new Response("not found", { status: 404 });
}