Files
mautrix-telegram/pkg/connector/chatinfo.go
T
2024-07-18 08:50:35 -06:00

295 lines
8.8 KiB
Go

package connector
import (
"context"
"fmt"
"time"
"github.com/gotd/td/tg"
"go.mau.fi/util/ptr"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/database"
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
"go.mau.fi/mautrix-telegram/pkg/connector/media"
)
func (t *TelegramClient) getDMChatInfo(ctx context.Context, userID int64) (*bridgev2.ChatInfo, error) {
chatInfo := bridgev2.ChatInfo{
Type: ptr.Ptr(database.RoomTypeDM),
Members: &bridgev2.ChatMemberList{IsFull: true},
}
// TODO need access hash here
users, err := t.client.API().UsersGetUsers(ctx, []tg.InputUserClass{&tg.InputUser{UserID: userID}})
if err != nil {
return nil, err
} else if len(users) == 0 {
return nil, fmt.Errorf("failed to get user info for user %d", userID)
} else if userInfo, err := t.getUserInfoFromTelegramUser(users[0]); err != nil {
return nil, err
} else if err = t.updateGhostWithUserInfo(ctx, userID, userInfo); err != nil {
return nil, err
} else {
chatInfo.Members.Members = []bridgev2.ChatMember{
{
EventSender: bridgev2.EventSender{
SenderLogin: ids.MakeUserLoginID(userID),
Sender: ids.MakeUserID(userID),
},
UserInfo: userInfo,
},
{
EventSender: bridgev2.EventSender{
IsFromMe: true,
SenderLogin: t.loginID,
Sender: t.userID,
},
},
}
}
return &chatInfo, nil
}
func (t *TelegramClient) getGroupChatInfo(ctx context.Context, fullChat *tg.MessagesChatFull, chatID int64) (*bridgev2.ChatInfo, bool, error) {
if err := t.updateUsersFromResponse(ctx, fullChat); err != nil {
return nil, false, err
}
chatInfo := bridgev2.ChatInfo{
Type: ptr.Ptr(database.RoomTypeGroupDM), // TODO Is this correct for channels?
Members: &bridgev2.ChatMemberList{
IsFull: true,
Members: []bridgev2.ChatMember{
{
EventSender: bridgev2.EventSender{
IsFromMe: true,
SenderLogin: t.loginID,
Sender: t.userID,
},
},
},
},
}
var isBroadcastChannel bool
for _, c := range fullChat.GetChats() {
if c.GetID() == chatID {
switch chat := c.(type) {
case *tg.Chat:
chatInfo.Name = &chat.Title
case *tg.Channel:
chatInfo.Name = &chat.Title
isBroadcastChannel = chat.Broadcast
}
break
}
}
if ttl, ok := fullChat.FullChat.GetTTLPeriod(); ok {
chatInfo.Disappear = &database.DisappearingSetting{
Type: database.DisappearingTypeAfterSend,
Timer: time.Duration(ttl) * time.Second,
}
}
if about := fullChat.FullChat.GetAbout(); about != "" {
chatInfo.Topic = &about
}
return &chatInfo, isBroadcastChannel, nil
}
func (t *TelegramClient) avatarFromPhoto(photo tg.PhotoClass) *bridgev2.Avatar {
if photo == nil || photo.TypeID() != tg.PhotoTypeID {
return nil
}
return &bridgev2.Avatar{
ID: ids.MakeAvatarID(photo.GetID()),
Get: func(ctx context.Context) (data []byte, err error) {
data, _, err = media.NewTransferer(t.client.API()).WithPhoto(photo).Download(ctx)
return
},
}
}
func (t *TelegramClient) filterChannelParticipants(chatParticipants []tg.ChannelParticipantClass, limit int) (members []bridgev2.ChatMember) {
for _, u := range chatParticipants {
userIDable, ok := u.(interface{ GetUserID() int64 })
if !ok {
continue
}
members = append(members, bridgev2.ChatMember{
EventSender: bridgev2.EventSender{
IsFromMe: userIDable.GetUserID() == t.telegramUserID,
SenderLogin: ids.MakeUserLoginID(userIDable.GetUserID()),
Sender: ids.MakeUserID(userIDable.GetUserID()),
},
})
if len(members) >= limit {
break
}
}
return
}
func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.ChatInfo, error) {
// fmt.Printf("get chat info %+v\n", portal)
peerType, id, err := ids.ParsePortalID(portal.ID)
if err != nil {
return nil, err
}
switch peerType {
case ids.PeerTypeUser:
return t.getDMChatInfo(ctx, id)
case ids.PeerTypeChat:
fullChat, err := t.client.API().MessagesGetFullChat(ctx, id)
if err != nil {
return nil, err
}
chatInfo, _, err := t.getGroupChatInfo(ctx, fullChat, id)
if err != nil {
return nil, err
}
chatFull, ok := fullChat.FullChat.(*tg.ChatFull)
if !ok {
return nil, fmt.Errorf("full chat is %T not *tg.ChatFull", fullChat.FullChat)
}
chatInfo.Avatar = t.avatarFromPhoto(chatFull.ChatPhoto)
if chatFull.Participants.TypeID() == tg.ChatParticipantsForbiddenTypeID {
chatInfo.Members.IsFull = false
return chatInfo, nil
}
chatParticipants := chatFull.Participants.(*tg.ChatParticipants)
if !t.main.Config.ShouldBridge(len(chatParticipants.Participants)) {
// TODO change this to a better error whenever that is implemented in mautrix-go
return nil, fmt.Errorf("too many participants (%d) in chat %d", len(chatParticipants.Participants), id)
}
for _, user := range chatParticipants.GetParticipants() {
if user.TypeID() == tg.ChannelParticipantBannedTypeID {
continue
}
chatInfo.Members.Members = append(chatInfo.Members.Members, bridgev2.ChatMember{
EventSender: bridgev2.EventSender{
IsFromMe: user.GetUserID() == t.telegramUserID,
SenderLogin: ids.MakeUserLoginID(user.GetUserID()),
Sender: ids.MakeUserID(user.GetUserID()),
},
})
if len(chatInfo.Members.Members) >= t.main.Config.MemberList.NormalizedMaxInitialSync() {
break
}
}
return chatInfo, nil
case ids.PeerTypeChannel:
accessHash, found, err := t.ScopedStore.GetChannelAccessHash(ctx, t.telegramUserID, id)
if err != nil {
return nil, fmt.Errorf("failed to get channel access hash: %w", err)
} else if !found {
return nil, fmt.Errorf("channel access hash not found for %d", id)
}
inputChannel := &tg.InputChannel{ChannelID: id, AccessHash: accessHash}
fullChat, err := t.client.API().ChannelsGetFullChannel(ctx, inputChannel)
if err != nil {
return nil, err
}
chatInfo, isBroadcastChannel, err := t.getGroupChatInfo(ctx, fullChat, id)
if err != nil {
return nil, err
}
channelFull, ok := fullChat.FullChat.(*tg.ChannelFull)
if !ok {
return nil, fmt.Errorf("full chat is %T not *tg.ChannelFull", fullChat.FullChat)
}
if !t.main.Config.ShouldBridge(channelFull.ParticipantsCount) {
// TODO change this to a better error whenever that is implemented in mautrix-go
return nil, fmt.Errorf("too many participants (%d) in chat %d", channelFull.ParticipantsCount, id)
}
chatInfo.Avatar = t.avatarFromPhoto(channelFull.ChatPhoto)
// TODO save available reactions?
// TODO save reactions limit?
// TODO save emojiset?
chatInfo.Members.IsFull = false
// Just return the current user as a member if we can't view the
// participants or the max initial sync is 0.
if t.main.Config.MemberList.MaxInitialSync == 0 || !channelFull.CanViewParticipants || channelFull.ParticipantsHidden {
return chatInfo, nil
}
// If this is a broadcast channel and we're not syncing broadcast
// channels, just return the chat info without all of the participant
// info.
if isBroadcastChannel && !t.main.Config.MemberList.SyncBroadcastChannels {
return chatInfo, nil
}
limit := t.main.Config.MemberList.NormalizedMaxInitialSync()
if limit <= 200 {
p, err := t.client.API().ChannelsGetParticipants(ctx, &tg.ChannelsGetParticipantsRequest{
Channel: inputChannel,
Filter: &tg.ChannelParticipantsRecent{},
Limit: limit,
})
if err != nil {
return nil, err
}
participants, ok := p.(*tg.ChannelsChannelParticipants)
if !ok {
return nil, fmt.Errorf("returned participants is %T not *tg.ChannelsChannelParticipants", p)
}
chatInfo.Members.IsFull = len(participants.Participants) < limit
if err := t.updateUsersFromResponse(ctx, participants); err != nil {
return nil, err
}
chatInfo.Members.Members = append(chatInfo.Members.Members, t.filterChannelParticipants(participants.Participants, limit)...)
} else {
remaining := t.main.Config.MemberList.NormalizedMaxInitialSync()
var offset int
for remaining > 0 {
p, err := t.client.API().ChannelsGetParticipants(ctx, &tg.ChannelsGetParticipantsRequest{
Channel: inputChannel,
Filter: &tg.ChannelParticipantsSearch{},
Limit: min(remaining, 200),
Offset: offset,
})
if err != nil {
return nil, err
}
participants, ok := p.(*tg.ChannelsChannelParticipants)
if !ok {
return nil, fmt.Errorf("returned participants is %T not *tg.ChannelsChannelParticipants", p)
}
if len(participants.Participants) == 0 {
chatInfo.Members.IsFull = true
break
}
if err := t.updateUsersFromResponse(ctx, participants); err != nil {
return nil, err
}
chatInfo.Members.Members = append(chatInfo.Members.Members, t.filterChannelParticipants(participants.Participants, limit)...)
offset += len(participants.Participants)
remaining -= len(participants.Participants)
}
}
return chatInfo, nil
default:
panic(fmt.Sprintf("unsupported peer type %s", peerType))
}
}