package telegram import ( "fmt" "strings" "time" "github.com/mymmrac/telego" ) // MessageContext holds enriched context extracted from a Telegram message. // Ref: TS src/telegram/bot-message-context.ts → buildTelegramMessageContext() type MessageContext struct { ForwardInfo *ForwardInfo ReplyInfo *ReplyInfo LocationInfo *LocationInfo } // ForwardInfo contains metadata about a forwarded message. // Ref: TS normalizeForwardedContext() type ForwardInfo struct { From string // sender name or channel title FromType string // "user", "channel", "supergroup", "hidden" Date time.Time // original message date } // ReplyInfo contains metadata about the message being replied to. // Ref: TS describeReplyTarget() type ReplyInfo struct { Sender string // sender name Body string // quoted message text IsBotReply bool // true if replying to bot's own message MessageID int // id of the replied-to message (edit target) } // LocationInfo contains geographic coordinates. // Ref: TS extractTelegramLocation() type LocationInfo struct { Latitude float64 Longitude float64 } // buildMessageContext extracts forward, reply, and location context from a Telegram message. func buildMessageContext(msg *telego.Message, botUsername string) *MessageContext { ctx := &MessageContext{} ctx.ForwardInfo = extractForwardInfo(msg) ctx.ReplyInfo = extractReplyInfo(msg, botUsername) ctx.LocationInfo = extractLocationInfo(msg) return ctx } // enrichContentWithContext appends forward/reply/location context to the message content. func enrichContentWithContext(content string, msgCtx *MessageContext) string { if msgCtx == nil { return content } var result strings.Builder // Prepend forward context if msgCtx.ForwardInfo != nil { dateStr := msgCtx.ForwardInfo.Date.Format("2006-01-02 15:04") result.WriteString(fmt.Sprintf("[Forwarded from %s at %s]\n", msgCtx.ForwardInfo.From, dateStr)) } result.WriteString(content) // Append reply context. Include the replied-to message id so the agent can // target it with message(action=edit) — e.g. flip a status marker in place. if msgCtx.ReplyInfo != nil && msgCtx.ReplyInfo.Body != "" { result.WriteString(fmt.Sprintf("\n\n[Replying to %s | reply_to_message_id=%d]\n%s\n[/Replying]", msgCtx.ReplyInfo.Sender, msgCtx.ReplyInfo.MessageID, msgCtx.ReplyInfo.Body)) } // Append location if msgCtx.LocationInfo != nil { result.WriteString(fmt.Sprintf("\n\nCoordinates: %.6f, %.6f", msgCtx.LocationInfo.Latitude, msgCtx.LocationInfo.Longitude)) } return result.String() } // extractForwardInfo extracts forwarded message metadata. // Ref: TS normalizeForwardedContext() func extractForwardInfo(msg *telego.Message) *ForwardInfo { if msg.ForwardOrigin == nil { return nil } info := &ForwardInfo{} switch origin := msg.ForwardOrigin.(type) { case *telego.MessageOriginUser: user := origin.SenderUser info.From = buildUserName(&user) info.FromType = "user" info.Date = time.Unix(origin.Date, 0) case *telego.MessageOriginChat: info.From = origin.SenderChat.Title info.FromType = string(origin.SenderChat.Type) info.Date = time.Unix(origin.Date, 0) case *telego.MessageOriginChannel: info.From = origin.Chat.Title info.FromType = "channel" info.Date = time.Unix(origin.Date, 0) case *telego.MessageOriginHiddenUser: info.From = origin.SenderUserName info.FromType = "hidden" info.Date = time.Unix(origin.Date, 0) default: return nil } return info } // extractReplyInfo extracts replied-to message metadata. // Ref: TS describeReplyTarget() func extractReplyInfo(msg *telego.Message, botUsername string) *ReplyInfo { reply := msg.ReplyToMessage if reply == nil { return nil } info := &ReplyInfo{MessageID: reply.MessageID} // Determine sender name if reply.From != nil { info.Sender = buildUserName(reply.From) info.IsBotReply = (reply.From.Username == botUsername) } else { info.Sender = "unknown" } // Extract reply body if reply.Text != "" { info.Body = reply.Text } else if reply.Caption != "" { info.Body = reply.Caption } // Truncate long reply bodies if len(info.Body) > 500 { info.Body = info.Body[:500] + "..." } // Hint for bot replies: the full response is already in session history, // so the LLM doesn't need the full text here — just enough to identify it. if info.IsBotReply && info.Body != "" { info.Body += "\n(This is your previous response — full content is in session history above)" } return info } // extractLocationInfo extracts location data from a message. // Ref: TS extractTelegramLocation() func extractLocationInfo(msg *telego.Message) *LocationInfo { if msg.Location == nil { return nil } return &LocationInfo{ Latitude: msg.Location.Latitude, Longitude: msg.Location.Longitude, } } // buildUserName formats a Telegram user's display name. func buildUserName(user *telego.User) string { if user == nil { return "unknown" } name := user.FirstName if user.LastName != "" { name += " " + user.LastName } return name }