import { Context } from "grammy"; import { CosmoeClient } from "../../client/cosmoe"; export interface Env { BOT_INFO: string; BOT_TOKEN: string; COSMOE_CREDENTIALS: KVNamespace; COSMOE_STORAGE: KVNamespace; } export async function handleBookEvent(ctx: Context, env: Env): Promise { try { if (!ctx.message || !ctx.message.text) { return; } const match = /^\/book_(\d+)_(\d+)$/.exec(ctx.message.text); if (match && match[1] && match[2]) { const eventId = match[1]; const slotIndex = parseInt(match[2]); // Get user credentials from KV storage const telegramUserId = ctx.from?.id.toString(); if (!telegramUserId) { await ctx.reply("Could not identify your Telegram user ID."); return; } const storedCredentials = await env.COSMOE_CREDENTIALS.get(telegramUserId); if (!storedCredentials) { await ctx.reply("You need to login first using /login username password before booking events."); return; } const credentials = JSON.parse(storedCredentials); // Initialize Cosmoe API client and set credentials const cosmoeClient = new CosmoeClient(); cosmoeClient.setCredentials(credentials.user_id, credentials.token); // Fetch event details to get time slots const eventResult = await cosmoeClient.getEventDetail(eventId); if (eventResult.code !== 200) { await ctx.reply(`Event not found: ${eventResult.msg || 'Unknown error'}`); return; } const event = eventResult.data; // Sort slots by range for consistent ordering const sortedSlots = [...event.slots].sort((a, b) => a.range.localeCompare(b.range)); if (slotIndex < 0 || slotIndex >= sortedSlots.length) { await ctx.reply(`Invalid slot number. Valid range is 0-${sortedSlots.length - 1}.`); return; } const selectedSlot = sortedSlots[slotIndex]; if (selectedSlot.remaining <= 0) { await ctx.reply(`Sorry, the selected slot (${selectedSlot.range}) is fully booked. No spots left.`); return; } // Check for available coupons for this event let couponCode: string | undefined; try { const couponsResult = await cosmoeClient.getAvailableCoupons(eventId); if (couponsResult.code === 200 && couponsResult.data && couponsResult.data.length > 0) { // Use the first available coupon (you could implement logic to select the best one) const availableCoupon = couponsResult.data[0]; couponCode = availableCoupon.code; console.log(`Using coupon ${couponCode} for event ${eventId}`); } } catch (error) { console.warn('Failed to fetch coupons, proceeding without coupon:', error); } // Attempt to book the event with coupon if available const bookingRequest: any = { event_id: eventId, time_slot: selectedSlot.range, }; if (couponCode) { bookingRequest.coupon_code = couponCode; } const bookingResult = await cosmoeClient.bookEvent(bookingRequest); if (bookingResult.code === 200) { // Extract final price and booking ID from the response data if available const finalPrice = bookingResult.data?.final_price || selectedSlot.price; const bookingId = bookingResult.data?.id; let successMessage = `Successfully booked event ${event.name} for slot ${selectedSlot.range}!`; successMessage += `\nFinal Price: ¥${finalPrice}`; if (bookingId) { successMessage += `\nBooking ID: ${bookingId}`; } if (couponCode) { successMessage += ` \n\(Used coupon: ${couponCode}\)`; } await ctx.reply(successMessage); } else { let errorMessage = `Booking failed: ${bookingResult.msg || 'Unknown error'}`; if (couponCode) { // Retry without coupon in case the coupon caused the failure const retryResult = await cosmoeClient.bookEvent({ event_id: eventId, time_slot: selectedSlot.range, }); if (retryResult.code === 200) { await ctx.reply(`Successfully booked event ${event.name} for slot ${selectedSlot.range}! \(Original attempt failed with coupon, succeeded without coupon\)`); } else { await ctx.reply(errorMessage); } } else { await ctx.reply(errorMessage); } } } } catch (error) { console.error("Error handling booking request:", error); await ctx.reply("An error occurred while processing your booking request. Please try again."); } }