package webhook import ( "encoding/json" "io" "net/http" "github.com/gin-gonic/gin" channelprovider "github.com/gochat/gochat/internal/channel/provider" telegramchannel "github.com/gochat/gochat/internal/channel/telegram" "github.com/gochat/gochat/internal/model" applogger "github.com/gochat/gochat/pkg/logger" ) // TelegramWebhookHandler processes incoming Telegram webhook HTTP requests via Gin. // Reference: Chatwoot routes Telegram webhooks at: // post '/webhooks/telegram/:bot_token' => 'telegram_bots#process_message' // // The handler: // 1. Receives raw Update JSON from Telegram's webhook POST // 2. Validates the update structure // 3. Routes to appropriate processing (message, edited_message, callback_query) // 4. Returns 200 OK to Telegram (required to prevent retries) // // Telegram webhook requirements: // - Must respond within 10 seconds // - Must return 200 OK even on processing errors (async processing recommended) // - Updates are sent as JSON POST to the webhook URL // TelegramWebhookHandler processes Telegram webhook requests via Gin. type TelegramWebhookHandler struct { provider *channelprovider.TelegramProvider telWebhook *telegramchannel.WebhookHandler // inboxRepo would be injected here for inbox lookup in production } // NewTelegramWebhookHandler creates a Telegram webhook handler for Gin integration. func NewTelegramWebhookHandler( provider *channelprovider.TelegramProvider, telWebhook *telegramchannel.WebhookHandler, ) *TelegramWebhookHandler { return &TelegramWebhookHandler{ provider: provider, telWebhook: telWebhook, } } // HandleTelegramWebhook processes an incoming Telegram webhook Gin request. // URL pattern: /webhooks/telegram/:bot_token // Method: POST // Content-Type: application/json // // Reference: Chatwoot: Telegram webhook receives Update payloads // at /webhooks/telegram/{bot_token} // // Flow: // 1. Parse bot_token from URL path → lookup Inbox + ChannelTelegram // 2. Read Update JSON from request body // 3. Dispatch to appropriate processing pipeline // 4. Return 200 OK immediately (Telegram requires fast response) func (h *TelegramWebhookHandler) HandleTelegramWebhook(c *gin.Context) { botToken := c.Param("bot_token") if botToken == "" { applogger.L().Warn("Telegram webhook: missing bot_token in path") // Still return 200 to prevent Telegram retries c.JSON(http.StatusOK, gin.H{"status": "ignored"}) return } // Read request body body, err := io.ReadAll(c.Request.Body) if err != nil { applogger.L().Errorf("Telegram webhook: failed to read body: %v", err) c.JSON(http.StatusOK, gin.H{"status": "ignored"}) return } defer c.Request.Body.Close() applogger.L().Infof("Telegram webhook received for bot_token prefix: %s", maskBotToken(botToken)) // Parse the update to extract update_id for logging var update telegramchannel.TelegramUpdate if err := json.Unmarshal(body, &update); err != nil { applogger.L().Errorf("Telegram webhook: failed to parse update: %v", err) c.JSON(http.StatusOK, gin.H{"status": "ignored"}) return } // Look up Inbox for this bot_token // In production: query GORM for Inbox with channel_type=telegram matching the bot_token inbox, err := h.lookupInbox(botToken) if err != nil { applogger.L().Errorf("Telegram webhook: inbox lookup failed: %v", err) c.JSON(http.StatusOK, gin.H{"status": "ignored"}) return } // Parse raw payload for provider processing var payload map[string]interface{} if err := json.Unmarshal(body, &payload); err != nil { applogger.L().Errorf("Telegram webhook: failed to parse payload: %v", err) c.JSON(http.StatusOK, gin.H{"status": "ignored"}) return } // Process the incoming message via the provider pipeline // Reference: Chatwoot processes updates via IncomingMessageService incomingMsg, err := h.provider.ProcessIncomingMessage(c.Request.Context(), inbox, payload) if err != nil { applogger.L().Errorf("Telegram webhook: message processing failed (update_id=%d): %v", update.UpdateID, err) } else if incomingMsg != nil { applogger.L().Infof("Telegram webhook: message processed (inbox_id=%d, source_id=%s)", incomingMsg.InboxID, incomingMsg.SourceID) // TODO: Push to message broker/dispatcher for persistence + notification } // Always return 200 OK to Telegram — it retries if not 200 c.JSON(http.StatusOK, gin.H{"status": "ok"}) } // lookupInbox finds the Inbox associated with a Telegram bot token. // In production: queries GORM for Inbox with channel_type=telegram matching the bot_token. // Reference: Chatwoot: Inbox.find_by(channel: telegram, bot_token: token) func (h *TelegramWebhookHandler) lookupInbox(botToken string) (*model.Inbox, error) { // TODO: Implement with GORM repository // Placeholder — production would use: // var ch channelmodel.ChannelTelegram // db.Where("bot_token = ?", botToken).First(&ch) // var inbox model.Inbox // db.Where("id = ?", ch.InboxID).First(&inbox) // // For now, return a placeholder inbox inbox := &model.Inbox{ ChannelType: "telegram", } return inbox, nil } // maskBotToken masks a bot token for safe logging (show first 8 chars only). func maskBotToken(token string) string { if len(token) > 8 { return token[:8] + "..." } return token }