224 lines
6.8 KiB
Go
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()
|
|
}
|