From b7e5078053f2af6c014d3ffd78e99efaf5945572 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 3 Dec 2025 17:44:18 +0200 Subject: [PATCH] chatinfo: ensure own member is always added --- pkg/connector/chatinfo.go | 115 +++++++++++++++++++++----------------- pkg/connector/ids/ids.go | 9 +++ 2 files changed, 74 insertions(+), 50 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 2ddebd46..d5f7a727 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -20,6 +20,7 @@ import ( "context" "crypto/sha256" "fmt" + "iter" "slices" "time" @@ -106,8 +107,8 @@ func (t *TelegramClient) getDMChatInfo(ctx context.Context, userID int64) (*brid CanBackfill: true, ExtraUpdates: updatePortalLastSyncAt, } - chatInfo.Members.MemberMap[ids.MakeUserID(userID)] = bridgev2.ChatMember{EventSender: t.senderForUserID(userID)} - chatInfo.Members.MemberMap[t.userID] = bridgev2.ChatMember{EventSender: t.mySender()} + chatInfo.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: t.mySender()}) + chatInfo.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: t.senderForUserID(userID)}) if userID == t.telegramUserID { chatInfo.Avatar = &bridgev2.Avatar{ ID: networkid.AvatarID(t.main.Config.SavedMessagesAvatar), @@ -123,17 +124,20 @@ func (t *TelegramClient) getDMChatInfo(ctx context.Context, userID int64) (*brid func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID int64) (*bridgev2.ChatInfo, bool, error) { var name *string - var isBroadcastChannel, isMegagroup bool + var isBroadcastChannel, isMegagroup, left, found bool var participantsCount int for _, c := range fullChat.GetChats() { if c.GetID() == chatID { + found = true switch chat := c.(type) { case *tg.Chat: name = &chat.Title + left = chat.Left case *tg.Channel: name = &chat.Title isBroadcastChannel = chat.Broadcast isMegagroup = chat.Megagroup + left = chat.Left if value, ok := chat.GetParticipantsCount(); ok { participantsCount = value @@ -142,13 +146,16 @@ func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID break } } + if !found { + return nil, false, fmt.Errorf("chat ID %d not found in full chat", chatID) + } chatInfo := bridgev2.ChatInfo{ Name: name, Type: ptr.Ptr(database.RoomTypeDefault), Members: &bridgev2.ChatMemberList{ IsFull: true, - MemberMap: map[networkid.UserID]bridgev2.ChatMember{}, + MemberMap: bridgev2.ChatMemberMap{}, ExcludeChangesFromTimeline: true, }, @@ -184,6 +191,9 @@ func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID return true }, } + if !left { + chatInfo.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: t.mySender()}) + } if ttl, ok := fullChat.FullChat.GetTTLPeriod(); ok { chatInfo.Disappear = &database.DisappearingSetting{ @@ -215,35 +225,43 @@ func (t *TelegramClient) avatarFromPhoto(ctx context.Context, peerType ids.PeerT return avatar } -func (t *TelegramClient) filterChannelParticipants(participants []tg.ChannelParticipantClass, limit int) (members []bridgev2.ChatMember) { - for _, u := range participants { - var userID int64 - var powerLevel *int - switch participant := u.(type) { - case *tg.ChannelParticipant: - userID = participant.GetUserID() - case *tg.ChannelParticipantSelf: - userID = participant.GetUserID() - case *tg.ChannelParticipantCreator: - userID = participant.GetUserID() - powerLevel = creatorPowerLevel - case *tg.ChannelParticipantAdmin: - userID = participant.GetUserID() - powerLevel = adminRightsToPowerLevel(participant.AdminRights) - default: - continue - } +func (t *TelegramClient) filterChannelParticipants(participants []tg.ChannelParticipantClass, limit int) iter.Seq[bridgev2.ChatMember] { + return func(yield func(bridgev2.ChatMember) bool) { + for i, u := range participants { + var member bridgev2.ChatMember + switch participant := u.(type) { + case *tg.ChannelParticipant: + member.EventSender = t.senderForUserID(participant.GetUserID()) + case *tg.ChannelParticipantSelf: + member.EventSender = t.senderForUserID(participant.GetUserID()) + case *tg.ChannelParticipantCreator: + member.EventSender = t.senderForUserID(participant.GetUserID()) + member.PowerLevel = creatorPowerLevel + case *tg.ChannelParticipantAdmin: + member.EventSender = t.senderForUserID(participant.GetUserID()) + member.PowerLevel = adminRightsToPowerLevel(participant.AdminRights) + case *tg.ChannelParticipantBanned: + member.Membership = event.MembershipBan + member.PrevMembership = event.MembershipJoin + member.EventSender = t.getPeerSender(participant.GetPeer()) + member.MemberSender = t.senderForUserID(participant.GetKickedBy()) + case *tg.ChannelParticipantLeft: + member.Membership = event.MembershipLeave + member.PrevMembership = event.MembershipJoin + member.EventSender = t.getPeerSender(participant.GetPeer()) + default: + // TODO warning log? + continue + } + if i >= limit && member.Membership == "" && !member.EventSender.IsFromMe { + continue + } - members = append(members, bridgev2.ChatMember{ - EventSender: t.senderForUserID(userID), - PowerLevel: powerLevel, - }) - - if len(members) >= limit { - break + if !yield(member) { + return + } } } - return } func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.ChatInfo, error) { @@ -254,6 +272,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta return nil, err } + memberSyncLimit := t.main.Config.MemberList.NormalizedMaxInitialSync() switch peerType { case ids.PeerTypeUser: return t.getDMChatInfo(ctx, id) @@ -288,24 +307,22 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta } for _, user := range chatParticipants.GetParticipants() { - if user.TypeID() == tg.ChannelParticipantBannedTypeID { - continue - } - var powerLevel *int switch user.(type) { case *tg.ChatParticipantCreator: powerLevel = creatorPowerLevel case *tg.ChatParticipantAdmin: powerLevel = modPowerLevel + default: + powerLevel = ptr.Ptr(0) } - chatInfo.Members.MemberMap[ids.MakeUserID(user.GetUserID())] = bridgev2.ChatMember{ + chatInfo.Members.MemberMap.Set(bridgev2.ChatMember{ EventSender: t.senderForUserID(user.GetUserID()), PowerLevel: powerLevel, - } + }) - if len(chatInfo.Members.MemberMap) >= t.main.Config.MemberList.NormalizedMaxInitialSync() { + if len(chatInfo.Members.MemberMap) >= memberSyncLimit { break } } @@ -348,11 +365,10 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta chatInfo.Members.PowerLevels = t.getGroupChatPowerLevels(ctx, fullChat.GetChats()[0]) if !portal.Metadata.(*PortalMetadata).IsSuperGroup { // Add the channel user - sender := ids.MakeChannelUserID(id) - chatInfo.Members.MemberMap[sender] = bridgev2.ChatMember{ - EventSender: bridgev2.EventSender{Sender: sender}, + chatInfo.Members.MemberMap.Set(bridgev2.ChatMember{ + EventSender: bridgev2.EventSender{Sender: ids.MakeChannelUserID(id)}, PowerLevel: superadminPowerLevel, - } + }) } // Just return the current user as a member if we can't view the @@ -368,13 +384,12 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta return chatInfo, nil } - limit := t.main.Config.MemberList.NormalizedMaxInitialSync() - if limit <= 200 { + if memberSyncLimit <= 200 { participants, err := APICallWithUpdates(ctx, t, func() (*tg.ChannelsChannelParticipants, error) { p, err := t.client.API().ChannelsGetParticipants(ctx, &tg.ChannelsGetParticipantsRequest{ Channel: inputChannel, Filter: &tg.ChannelParticipantsRecent{}, - Limit: limit, + Limit: memberSyncLimit, }) if err != nil { return nil, err @@ -389,12 +404,12 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta if err != nil { return nil, err } - chatInfo.Members.IsFull = len(participants.Participants) < limit - for _, participant := range t.filterChannelParticipants(participants.Participants, limit) { - chatInfo.Members.MemberMap[participant.Sender] = participant + chatInfo.Members.IsFull = len(participants.Participants) < memberSyncLimit + for participant := range t.filterChannelParticipants(participants.Participants, memberSyncLimit) { + chatInfo.Members.MemberMap.Set(participant) } } else { - remaining := t.main.Config.MemberList.NormalizedMaxInitialSync() + remaining := memberSyncLimit var offset int for remaining > 0 { participants, err := APICallWithUpdates(ctx, t, func() (*tg.ChannelsChannelParticipants, error) { @@ -422,8 +437,8 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta break } - for _, participant := range t.filterChannelParticipants(participants.Participants, limit) { - chatInfo.Members.MemberMap[participant.Sender] = participant + for participant := range t.filterChannelParticipants(participants.Participants, remaining) { + chatInfo.Members.MemberMap.Set(participant) } offset += len(participants.Participants) diff --git a/pkg/connector/ids/ids.go b/pkg/connector/ids/ids.go index 09a2d51a..6a91f1f5 100644 --- a/pkg/connector/ids/ids.go +++ b/pkg/connector/ids/ids.go @@ -27,10 +27,16 @@ import ( ) func MakeUserID(userID int64) networkid.UserID { + if userID == 0 { + return "" + } return networkid.UserID(strconv.FormatInt(userID, 10)) } func MakeChannelUserID(channelID int64) networkid.UserID { + if channelID == 0 { + return "" + } return networkid.UserID("channel-" + strconv.FormatInt(channelID, 10)) } @@ -50,6 +56,9 @@ func ParseUserLoginID(userID networkid.UserLoginID) (int64, error) { } func MakeUserLoginID(userID int64) networkid.UserLoginID { + if userID == 0 { + return "" + } return networkid.UserLoginID(strconv.FormatInt(userID, 10)) }