import { Bot, Context } from "grammy"; import { conversations, createConversation, ConversationFlavor, VersionedStateStorage } from "@grammyjs/conversations"; import { KvAdapter } from "@grammyjs/storage-cloudflare"; import { handleStartCommand } from "./handlers/start"; import { handleInteractiveLogin } from "./handlers/login"; import { handleEventsCommand } from "./handlers/events"; import { handleEventDetails } from "./handlers/eventDetails"; import { handleBookEvent } from "./handlers/bookEvent"; import { handleHistoryCommand } from "./handlers/history"; import { handleCancelCommand, handleCancelConfirmation } from "./handlers/cancel"; import { handleLogoutCommand } from "./handlers/logout"; export interface Env { BOT_INFO: string; BOT_TOKEN: string; COSMOE_CREDENTIALS: KVNamespace; COSMOE_STORAGE: KVNamespace; } export function setupCommands(bot: Bot>, env: Env) { // Create a KV adapter for conversation storage using COSMOE_STORAGE namespace const kvAdapter = new KvAdapter(env.COSMOE_STORAGE); // Define conversation storage using the KV adapter const conversationStorage = { read: async (key: string) => { try { const value = await kvAdapter.read(key); return value ? JSON.parse(value as string) : undefined; } catch (error) { console.error('Error reading conversation from KV:', error); return undefined; } }, write: async (key: string, value: any) => { try { await kvAdapter.write(key, JSON.stringify(value)); } catch (error) { console.error('Error writing conversation to KV:', error); } }, delete: async (key: string) => { try { await kvAdapter.delete(key); } catch (error) { console.error('Error deleting conversation from KV:', error); } }, }; // Install the conversations plugin with KV storage bot.use(conversations({ storage: conversationStorage })); // Create the login conversation, with environment bound to the handler bot.use(createConversation(async (conversation, ctx) => { await handleInteractiveLogin(conversation, ctx, env); }, "login")); bot.command("start", async (ctx: Context) => { await handleStartCommand(ctx); }); // Use the interactive conversation for login bot.command("login", async (ctx) => { await ctx.conversation.enter("login"); }); bot.command("events", async (ctx: Context) => { await handleEventsCommand(ctx); }); // Handle /event_123 format using hears bot.hears(/^\/event_(.+)$/, async (ctx) => { await handleEventDetails(ctx); }); // Handle /book_{event_id}_{slot_id} format bot.hears(/^\/book_(\d+)_(\d+)$/, async (ctx) => { await handleBookEvent(ctx, env); }); bot.command("history", async (ctx: Context) => { await handleHistoryCommand(ctx, env); }); // Handle /cancel_{booking_id} format bot.hears(/^\/cancel_(\d+)$/, async (ctx) => { await handleCancelCommand(ctx, env); }); // Handle callback queries for cancel confirmation bot.callbackQuery(/confirm_cancel_\d+|cancel_action/, async (ctx) => { if (ctx.callbackQuery.data) { await handleCancelConfirmation(ctx, env, ctx.callbackQuery.data); await ctx.answerCallbackQuery(); } }); bot.command("logout", async (ctx: Context) => { await handleLogoutCommand(ctx, env); }); }