package telegram import ( "bytes" "context" "encoding/json" "fmt" "net/http" "time" "git.yeij.top/AskaEth/Cyrene/platform-bridge/internal/bridge" ) // Adapter implements PlatformAdapter for Telegram Bot API. type Adapter struct { token string webhookURL string client *http.Client connected bool } func NewAdapter(token, webhookURL string) *Adapter { return &Adapter{ token: token, webhookURL: webhookURL, client: &http.Client{Timeout: 10 * time.Second}, } } func (a *Adapter) PlatformName() string { return "telegram" } func (a *Adapter) Capabilities() bridge.PlatformCapabilities { return bridge.PlatformCapabilities{ MaxMessageLength: 4096, SupportsMarkdown: true, SupportsImage: true, SupportsVoice: true, SupportsEmoji: true, SupportsReaction: true, SupportsTypingHint: true, RecommendBurstMax: 3, } } func (a *Adapter) Connect(ctx context.Context) error { if a.token == "" { return fmt.Errorf("telegram bot token not configured") } if a.webhookURL == "" { // Polling mode not implemented; webhook required. return fmt.Errorf("telegram webhook URL not configured") } // Set webhook. url := fmt.Sprintf("https://api.telegram.org/bot%s/setWebhook?url=%s/api/v1/webhook/telegram", a.token, a.webhookURL) resp, err := a.client.Post(url, "application/json", nil) if err != nil { return fmt.Errorf("failed to set telegram webhook: %w", err) } defer resp.Body.Close() a.connected = true fmt.Println("[telegram] webhook set, bot ready") return nil } func (a *Adapter) Disconnect(ctx context.Context) error { if a.token != "" { url := fmt.Sprintf("https://api.telegram.org/bot%s/deleteWebhook", a.token) a.client.Post(url, "application/json", nil) } a.connected = false return nil } func (a *Adapter) IsConnected() bool { return a.connected } func (a *Adapter) HealthCheck() error { if a.token == "" { return fmt.Errorf("telegram token not configured") } url := fmt.Sprintf("https://api.telegram.org/bot%s/getMe", a.token) resp, err := a.client.Get(url) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 200 { return fmt.Errorf("telegram API returned %d", resp.StatusCode) } return nil } // TelegramUpdate is the webhook payload from Telegram. type TelegramUpdate struct { UpdateID int64 `json:"update_id"` Message *TelegramMessage `json:"message"` Callback *TelegramCallback `json:"callback_query"` } // TelegramMessage represents a Telegram message. type TelegramMessage struct { MessageID int64 `json:"message_id"` From TelegramUser `json:"from"` Chat TelegramChat `json:"chat"` Text string `json:"text"` Date int64 `json:"date"` ReplyTo *TelegramMessage `json:"reply_to_message"` Entities []TelegramEntity `json:"entities"` } // TelegramUser represents a Telegram user. type TelegramUser struct { ID int64 `json:"id"` Username string `json:"username"` FirstName string `json:"first_name"` LastName string `json:"last_name"` } // TelegramChat represents a Telegram chat. type TelegramChat struct { ID int64 `json:"id"` Type string `json:"type"` // "private", "group", "supergroup", "channel" } // TelegramEntity represents a text entity in a Telegram message. type TelegramEntity struct { Type string `json:"type"` // "mention", "bot_command", etc. Offset int `json:"offset"` Length int `json:"length"` } // TelegramCallback represents a callback query. type TelegramCallback struct { ID string `json:"id"` From TelegramUser `json:"from"` Message *TelegramMessage `json:"message"` Data string `json:"data"` } // ToUnified converts a Telegram update to UnifiedMessage. // Accepts both *TelegramUpdate and map[string]interface{} (from webhook). func (a *Adapter) ToUnified(rawMessage interface{}) (*bridge.UnifiedMessage, error) { var msg *TelegramMessage switch v := rawMessage.(type) { case *TelegramUpdate: msg = v.Message case map[string]interface{}: // Parse from raw webhook payload. data, _ := json.Marshal(v) var update TelegramUpdate if err := json.Unmarshal(data, &update); err != nil { return nil, fmt.Errorf("parse telegram update: %w", err) } msg = update.Message default: return nil, fmt.Errorf("expected *TelegramUpdate or map, got %T", rawMessage) } if msg == nil { return nil, fmt.Errorf("no message in update") } channelType := "direct" channelID := fmt.Sprintf("%d", msg.Chat.ID) if msg.Chat.Type == "group" || msg.Chat.Type == "supergroup" { channelType = "group" } else if msg.Chat.Type == "channel" { channelType = "channel" } senderName := msg.From.FirstName if msg.From.LastName != "" { senderName += " " + msg.From.LastName } if msg.From.Username != "" { senderName = msg.From.Username } replyTo := "" if msg.ReplyTo != nil { replyTo = fmt.Sprintf("%d", msg.ReplyTo.MessageID) } return &bridge.UnifiedMessage{ SenderID: fmt.Sprintf("%d", msg.From.ID), SenderName: senderName, Platform: "telegram", ChannelID: channelID, ChannelType: channelType, Content: msg.Text, ContentType: "text", MessageID: fmt.Sprintf("%d", msg.MessageID), ReplyTo: replyTo, RawData: rawMessage, Timestamp: time.Unix(msg.Date, 0), }, nil } // FromUnified converts a unified response to Telegram messages. func (a *Adapter) FromUnified(response *bridge.UnifiedResponse) ([]bridge.PlatformMessage, error) { var msgs []bridge.PlatformMessage for _, rm := range response.Messages { content := rm.Content format := rm.FormatMode if format == "" { format = "markdown" } msgs = append(msgs, bridge.PlatformMessage{ Content: content, FormatMode: format, ReplyTo: response.ReplyTo, }) } return msgs, nil } // SendMessage sends a message via Telegram Bot API. func (a *Adapter) SendMessage(chatID int64, text, parseMode, replyTo string) error { body := map[string]interface{}{ "chat_id": chatID, "text": text, } if parseMode != "" { body["parse_mode"] = parseMode } if replyTo != "" { body["reply_to_message_id"] = replyTo } data, _ := json.Marshal(body) url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", a.token) resp, err := a.client.Post(url, "application/json", bytes.NewReader(data)) if err != nil { return err } defer resp.Body.Close() return nil } // SendChatAction sends a "typing..." indicator via Telegram Bot API. func (a *Adapter) SendChatAction(chatID int64, action string) error { body, _ := json.Marshal(map[string]interface{}{ "chat_id": chatID, "action": action, // "typing", "upload_photo", etc. }) url := fmt.Sprintf("https://api.telegram.org/bot%s/sendChatAction", a.token) resp, err := a.client.Post(url, "application/json", bytes.NewReader(body)) if err != nil { return err } defer resp.Body.Close() return nil }