2 次代码提交 fd3d9509b3 ... 0af5af914a

作者 SHA1 备注 提交日期
  kotoyuuko 0af5af914a feat: 支持优惠码选择 3 周之前
  kotoyuuko d20815a69b feat: update base url to cloudflare domain 3 周之前
共有 3 个文件被更改,包括 161 次插入41 次删除
  1. 2 2
      src/client/cosmoe.ts
  2. 150 38
      src/command/handlers/bookEvent.ts
  3. 9 1
      src/command/index.ts

+ 2 - 2
src/client/cosmoe.ts

@@ -3,7 +3,7 @@
 /**
  * Cosmoe API Client
  * API Documentation: https://cos.world/api/v1/api_docs.html
- * Base URL: http://cos.moe/api/v1/
+ * Base URL: http://cos.cx/api/v1/
  */
 
 interface ApiResponse<T = any> {
@@ -111,7 +111,7 @@ interface BookingRequest {
 }
 
 export class CosmoeClient {
-  private baseUrl = "https://cos.world/api/v1";
+  private baseUrl = "https://cos.cx/api/v1";
   private userId?: number;
   private token?: string;
 

+ 150 - 38
src/command/handlers/bookEvent.ts

@@ -1,4 +1,4 @@
-import { Context } from "grammy";
+import { Context, InlineKeyboard } from "grammy";
 import { CosmoeClient } from "../../client/cosmoe";
 
 export interface Env {
@@ -63,52 +63,164 @@ export async function handleBookEvent(ctx: Context, env: Env): Promise<void> {
       }
       
       // Check for available coupons for this event
-      let couponCode: string | undefined;
+      let coupons: any[] = [];
       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}`);
+          coupons = couponsResult.data;
         }
       } 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 = `成功预约活动 *${event.name}*,时间段 *${selectedSlot.range}*`;
-        successMessage += `\n*最终价格*  ¥${finalPrice}`;
-        if (bookingId) {
-          successMessage += `\n*预约编号*  ${bookingId}`;
-        }
-        if (couponCode) {
-          successMessage += ` \n*使用优惠码*  ${couponCode}`;
-        }
-        await ctx.reply(successMessage, { parse_mode: "Markdown" });
-      } else {
-        let errorMessage = `预约失败: ${bookingResult.msg || '未知错误'}`;
-        await ctx.reply(errorMessage);
+
+      // If multiple coupons available, show selection keyboard
+      if (coupons.length > 1) {
+        const keyboard = new InlineKeyboard();
+        coupons.forEach((coupon, index) => {
+          const discountText = coupon.type === 'percentage'
+            ? `${coupon.value}%`
+            : `¥${coupon.value}`;
+          keyboard.text(
+            `${coupon.code} (${discountText})`,
+            `select_coupon_${eventId}_${slotIndex}_${coupon.code}`
+          );
+          if (index < coupons.length - 1) {
+            keyboard.row();
+          }
+        });
+        // Add option to book without coupon
+        keyboard.row();
+        keyboard.text('不使用优惠码', `select_coupon_${eventId}_${slotIndex}_none`);
+
+        await ctx.reply(
+          `请选择要使用的优惠码:\n\n` +
+          `*活动*  ${event.name}\n` +
+          `*时间段*  ${selectedSlot.range}\n` +
+          `*原价*  ¥${selectedSlot.price}`,
+          {
+            parse_mode: "Markdown",
+            reply_markup: keyboard,
+          }
+        );
+        return;
       }
+
+      // If only one coupon, use it automatically; if none, proceed without
+      const couponCode = coupons.length === 1 ? coupons[0].code : undefined;
+
+      // Proceed with booking
+      await proceedWithBooking(ctx, cosmoeClient, event, selectedSlot, couponCode);
     }
   } catch (error) {
-      console.error("Error handling booking request:", error);
-      await ctx.reply("处理预约请求时出错,请稍后再试")
+    console.error("Error handling booking request:", error);
+    await ctx.reply("处理预约请求时出错,请稍后再试");
+  }
+}
+
+// Helper function to proceed with booking
+async function proceedWithBooking(
+  ctx: Context,
+  cosmoeClient: CosmoeClient,
+  event: any,
+  selectedSlot: any,
+  couponCode: string | undefined
+): Promise<void> {
+  const bookingRequest: any = {
+    event_id: event.id,
+    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 = `成功预约活动 *${event.name}*,时间段 *${selectedSlot.range}*`;
+    successMessage += `\n*最终价格*  ¥${finalPrice}`;
+    if (bookingId) {
+      successMessage += `\n*预约编号*  ${bookingId}`;
+    }
+    if (couponCode) {
+      successMessage += ` \n*使用优惠码*  ${couponCode}`;
+    }
+    await ctx.reply(successMessage, { parse_mode: "Markdown" });
+  } else {
+    let errorMessage = `预约失败: ${bookingResult.msg || '未知错误'}`;
+    await ctx.reply(errorMessage);
+  }
+}
+
+// Handler for coupon selection callback
+export async function handleCouponSelection(
+  ctx: Context,
+  env: Env,
+  action: string
+): Promise<void> {
+  try {
+    // Parse action: select_coupon_{event_id}_{slot_index}_{coupon_code}
+    const parts = action.split('_');
+    if (parts.length < 5 || parts[0] !== 'select' || parts[1] !== 'coupon') {
+      await ctx.editMessageText("无效的优惠码选择");
+      return;
+    }
+
+    const eventId = parts[2];
+    const slotIndex = parseInt(parts[3]);
+    const couponCode = parts[4] === 'none' ? undefined : parts[4];
+
+    // Get user credentials from KV storage
+    const telegramUserId = ctx.from?.id.toString();
+    if (!telegramUserId) {
+      await ctx.editMessageText("无法获取你的 Telegram 用户身份");
+      return;
+    }
+
+    const storedCredentials = await env.COSMOE_CREDENTIALS.get(telegramUserId);
+    if (!storedCredentials) {
+      await ctx.editMessageText("请先使用 /login 命令登录后再进行预约");
+      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.editMessageText(`获取活动详情时出错: ${eventResult.msg || '未知错误'}`);
+      return;
+    }
+
+    const event = eventResult.data;
+    const sortedSlots = [...event.slots].sort((a, b) => a.range.localeCompare(b.range));
+
+    if (slotIndex < 0 || slotIndex >= sortedSlots.length) {
+      await ctx.editMessageText(`无效的时间段编号`);
+      return;
+    }
+
+    const selectedSlot = sortedSlots[slotIndex];
+
+    // Update message to show selection
+    let selectionMessage = `已选择: ${couponCode ? `优惠码 ${couponCode}` : '不使用优惠码'}\n\n`;
+    selectionMessage += `*活动*  ${event.name}\n`;
+    selectionMessage += `*时间段*  ${selectedSlot.range}`;
+    await ctx.editMessageText(selectionMessage, { parse_mode: "Markdown" });
+
+    // Proceed with booking
+    await proceedWithBooking(ctx, cosmoeClient, event, selectedSlot, couponCode);
+    await ctx.answerCallbackQuery();
+  } catch (error) {
+    console.error("Error handling coupon selection:", error);
+    await ctx.editMessageText("处理优惠码选择时出错,请稍后再试");
   }
 }

+ 9 - 1
src/command/index.ts

@@ -5,7 +5,7 @@ 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 { handleBookEvent, handleCouponSelection } from "./handlers/bookEvent";
 import { handleHistoryCommand } from "./handlers/history";
 import { handleCancelCommand, handleCancelConfirmation } from "./handlers/cancel";
 import { handleLogoutCommand } from "./handlers/logout";
@@ -96,6 +96,14 @@ export function setupCommands(bot: Bot<ConversationFlavor<Context>>, env: Env) {
     }
   });
 
+  // Handle callback queries for coupon selection
+  bot.callbackQuery(/select_coupon_\d+_\d+_\w+/, async (ctx) => {
+    if (ctx.callbackQuery.data) {
+      await handleCouponSelection(ctx, env, ctx.callbackQuery.data);
+      await ctx.answerCallbackQuery();
+    }
+  });
+
   bot.command("logout", async (ctx: Context) => {
     await handleLogoutCommand(ctx, env);
   });