Files
Cyrene_v2/plugins/channels/telegram/updates.go
T

224 lines
6.8 KiB
Go

package telegram
import (
"strconv"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
)
// telegramUpdate is the top-level object Telegram sends for every event.
type telegramUpdate struct {
UpdateID int64 `json:"update_id"`
Message *telegramMessage `json:"message"`
EditedMessage *telegramMessage `json:"edited_message"`
ChannelPost *telegramMessage `json:"channel_post"`
EditedChannelPost *telegramMessage `json:"edited_channel_post"`
}
// message returns the concrete chat message carried by the update, or nil.
func (u telegramUpdate) message() *telegramMessage {
switch {
case u.Message != nil:
return u.Message
case u.EditedMessage != nil:
return u.EditedMessage
case u.ChannelPost != nil:
return u.ChannelPost
case u.EditedChannelPost != nil:
return u.EditedChannelPost
default:
return nil
}
}
// telegramMessage is a chat message as delivered by the Bot API.
type telegramMessage struct {
MessageID int64 `json:"message_id"`
From *telegramUser `json:"from"`
Chat telegramChat `json:"chat"`
Date int64 `json:"date"`
Text string `json:"text"`
Caption string `json:"caption"`
Photo []telegramPhotoSize `json:"photo"`
Voice *telegramAudio `json:"voice"`
Audio *telegramAudio `json:"audio"`
Document *telegramDocument `json:"document"`
Video *telegramVideo `json:"video"`
ReplyToMessage *telegramMessage `json:"reply_to_message"`
MessageThreadID int64 `json:"message_thread_id"`
}
// telegramUser is the sender of a message.
type telegramUser struct {
ID int64 `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Username string `json:"username"`
}
// telegramChat is the conversation a message belongs to.
type telegramChat struct {
ID int64 `json:"id"`
Type string `json:"type"` // private | group | supergroup | channel
Title string `json:"title"`
}
// telegramPhotoSize is one entry of a photo attachment.
type telegramPhotoSize struct {
FileID string `json:"file_id"`
FileSize int64 `json:"file_size"`
}
// telegramAudio describes a voice or audio attachment.
type telegramAudio struct {
FileID string `json:"file_id"`
FileSize int64 `json:"file_size"`
MIMEType string `json:"mime_type"`
}
// telegramDocument describes a document attachment.
type telegramDocument struct {
FileID string `json:"file_id"`
FileSize int64 `json:"file_size"`
FileName string `json:"file_name"`
MIMEType string `json:"mime_type"`
}
// telegramVideo describes a video attachment.
type telegramVideo struct {
FileID string `json:"file_id"`
FileSize int64 `json:"file_size"`
MIMEType string `json:"mime_type"`
}
// toUnifiedMessage converts an update to the normalized model. It returns ok
// false for updates that carry no deliverable chat message.
func (a *Adapter) toUnifiedMessage(update telegramUpdate) (adapter.UnifiedMessage, bool) {
m := update.message()
if m == nil {
return adapter.UnifiedMessage{}, false
}
msg := adapter.UnifiedMessage{
Platform: a.Name(),
ChannelID: strconv.FormatInt(m.Chat.ID, 10),
ChannelType: chatType(m.Chat.Type),
MessageID: strconv.FormatInt(m.MessageID, 10),
Timestamp: time.Unix(m.Date, 0),
}
if m.Chat.Type == "group" || m.Chat.Type == "supergroup" || m.Chat.Type == "channel" {
msg.GroupID = strconv.FormatInt(m.Chat.ID, 10)
msg.GroupName = m.Chat.Title
}
if m.MessageThreadID != 0 {
msg.ThreadID = strconv.FormatInt(m.MessageThreadID, 10)
}
content, contentType, media := extractPayload(m)
msg.Content = content
msg.ContentType = contentType
msg.Media = media
if m.From != nil {
msg.SenderID = strconv.FormatInt(m.From.ID, 10)
msg.SenderName = displayName(m.From)
msg.Sender = adapter.Actor{
Role: adapter.RoleUnknown,
UserID: msg.SenderID,
DisplayName: msg.SenderName,
GroupID: msg.GroupID,
GroupName: msg.GroupName,
}.Resolve()
}
if m.ReplyToMessage != nil {
msg.ReplyToID = strconv.FormatInt(m.ReplyToMessage.MessageID, 10)
msg.ReplyToText = m.ReplyToMessage.Text
if msg.ReplyToText == "" {
msg.ReplyToText = m.ReplyToMessage.Caption
}
}
a.recordChat(m.Chat, m.From)
return msg, true
}
// extractPayload picks the primary content and attachment list from a message.
func extractPayload(m *telegramMessage) (string, adapter.ContentType, []adapter.Media) {
media := make([]adapter.Media, 0, 2)
content := m.Caption
if m.Text != "" {
content = m.Text
}
ct := adapter.ContentText
if len(m.Photo) > 0 {
p := m.Photo[len(m.Photo)-1]
media = append(media, adapter.Media{Kind: adapter.MediaImage, URL: p.FileID, Size: p.FileSize, MIMEType: "image/jpeg"})
ct = adapter.ContentImage
}
if m.Voice != nil {
media = append(media, adapter.Media{Kind: adapter.MediaVoice, URL: m.Voice.FileID, Size: m.Voice.FileSize, MIMEType: m.Voice.MIMEType})
ct = adapter.ContentVoice
} else if m.Audio != nil {
media = append(media, adapter.Media{Kind: adapter.MediaVoice, URL: m.Audio.FileID, Size: m.Audio.FileSize, MIMEType: m.Audio.MIMEType})
ct = adapter.ContentVoice
}
if m.Document != nil {
media = append(media, adapter.Media{Kind: adapter.MediaFile, URL: m.Document.FileID, Size: m.Document.FileSize, Name: m.Document.FileName, MIMEType: m.Document.MIMEType})
ct = adapter.ContentFile
}
if m.Video != nil {
media = append(media, adapter.Media{Kind: adapter.MediaVideo, URL: m.Video.FileID, Size: m.Video.FileSize, MIMEType: m.Video.MIMEType})
ct = adapter.ContentFile
}
return content, ct, media
}
// chatType maps a Telegram chat type to the adapter's ChannelType.
func chatType(t string) adapter.ChannelType {
if t == "group" || t == "supergroup" || t == "channel" {
return adapter.ChannelGroup
}
return adapter.ChannelDirect
}
// displayName renders a sender's best human-readable name without leaking
// anything sensitive.
func displayName(u *telegramUser) string {
if u.FirstName != "" && u.LastName != "" {
return u.FirstName + " " + u.LastName
}
if u.FirstName != "" {
return u.FirstName
}
if u.Username != "" {
return u.Username
}
return strconv.FormatInt(u.ID, 10)
}
// recordChat caches a conversation so ListChannels can report it.
func (a *Adapter) recordChat(c telegramChat, from *telegramUser) {
info := adapter.ChannelInfo{
ChannelID: strconv.FormatInt(c.ID, 10),
ChannelType: chatType(c.Type),
ChannelName: c.Title,
}
if info.ChannelType == adapter.ChannelGroup {
info.GroupID = info.ChannelID
info.GroupName = c.Title
} else if from != nil {
info.ChannelName = displayName(from)
}
if info.ChannelName == "" {
info.ChannelName = info.ChannelID
}
a.mu.Lock()
a.chats[info.ChannelID] = info
a.mu.Unlock()
}