From b3f9bfb5b38c65d1a44872d195dba368a9234e25 Mon Sep 17 00:00:00 2001 From: Conan Date: Thu, 20 Nov 2025 01:22:02 +0800 Subject: [PATCH] handlematrix: implement group chat deletes (#126) --- pkg/connector/capabilities.go | 19 ++++++++++-- pkg/connector/chatinfo.go | 8 +++++ pkg/connector/handlematrix.go | 57 +++++++++++++++++++++++++++-------- pkg/connector/metadata.go | 9 +++--- 4 files changed, 73 insertions(+), 20 deletions(-) diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index e8c9a2d5..e674643a 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -28,6 +28,8 @@ import ( "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/event" + + "go.mau.fi/mautrix-telegram/pkg/connector/ids" ) func (tg *TelegramConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities { @@ -58,7 +60,7 @@ func (tg *TelegramConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit } func (tg *TelegramConnector) GetBridgeInfoVersion() (info, capabilities int) { - return 1, 6 + return 1, 7 } // TODO get these from getConfig instead of hardcoding? @@ -208,7 +210,7 @@ func makeTimerList() []jsontime.Milliseconds { var telegramTimers = makeTimerList() func (t *TelegramClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *event.RoomFeatures { - baseID := "fi.mau.telegram.capabilities.2025_09_27" + baseID := "fi.mau.telegram.capabilities.2025_11_12" feat := &event.RoomFeatures{ Formatting: formattingCaps, File: fileCaps, @@ -251,11 +253,22 @@ func (t *TelegramClient) GetCapabilities(ctx context.Context, portal *bridgev2.P feat.File = premiumFileCaps feat.ReactionCount = 3 } - if portal.RoomType == database.RoomTypeDM { + portalMetadata := portal.Metadata.(*PortalMetadata) + peerType, _, _ := ids.ParsePortalID(portal.ID) + + switch portal.RoomType { + case database.RoomTypeDM: baseID += "+dm" feat.DeleteChat = true feat.DeleteChatForEveryone = true + default: + // Group creators can delete the chat for everyone, unless it's a large channel + if peerType == ids.PeerTypeChat || portalMetadata.ParticipantsCount < 1000 { + baseID += "+deletablegroup" + feat.DeleteChatForEveryone = true + } } + feat.ID = baseID return feat } diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 64205a6b..9646fc17 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -125,6 +125,7 @@ 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 participantsCount int for _, c := range fullChat.GetChats() { if c.GetID() == chatID { switch chat := c.(type) { @@ -134,6 +135,10 @@ func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID name = &chat.Title isBroadcastChannel = chat.Broadcast isMegagroup = chat.Megagroup + + if value, ok := chat.GetParticipantsCount(); ok { + participantsCount = value + } } break } @@ -154,6 +159,7 @@ func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID meta := p.Metadata.(*PortalMetadata) _ = updatePortalLastSyncAt(ctx, p) _ = meta.SetIsSuperGroup(isMegagroup) + meta.ParticipantsCount = participantsCount if reactions, ok := fullChat.FullChat.GetAvailableReactions(); ok { switch typedReactions := reactions.(type) { @@ -444,6 +450,7 @@ func (t *TelegramClient) getDMPowerLevels(ghost *bridgev2.Ghost) *bridgev2.Power event.StateRoomAvatar: 0, event.StateTopic: 0, event.StateBeeperDisappearingTimer: 0, + event.BeeperDeleteChat: 0, } return &plo } @@ -500,6 +507,7 @@ func (t *TelegramClient) getPowerLevelOverridesFromBannedRights(entity tg.ChatCl event.StatePowerLevels: 85, event.StateHistoryVisibility: 85, event.StateBeeperDisappearingTimer: 85, + event.BeeperDeleteChat: *creatorPowerLevel, } if dbr.ChangeInfo { diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 5d57ebe7..19d91c09 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -805,22 +805,53 @@ func (t *TelegramClient) HandleMatrixDeleteChat(ctx context.Context, chat *bridg if err != nil { return err } - if chat.Portal.RoomType == database.RoomTypeDM { - if chat.Content.DeleteForEveryone { - _, err := t.client.API().MessagesDeleteHistory(ctx, &tg.MessagesDeleteHistoryRequest{ - Peer: &tg.InputPeerUser{UserID: id}, - Revoke: true, - MaxID: 0, - }) - if err != nil { - return err - } - } - _, err = t.client.API().MessagesDeleteChat(ctx, id) + switch peerType { + case ids.PeerTypeUser: + accessHash, err := t.ScopedStore.GetAccessHash(ctx, peerType, id) if err != nil { return err } - } else { + _, err = t.client.API().MessagesDeleteHistory(ctx, &tg.MessagesDeleteHistoryRequest{ + Peer: &tg.InputPeerUser{UserID: id, AccessHash: accessHash}, + JustClear: !chat.Content.DeleteForEveryone, + Revoke: chat.Content.DeleteForEveryone, + MaxID: 0, + }) + if err != nil { + return err + } + case ids.PeerTypeChat: + if chat.Content.DeleteForEveryone { + result, err := t.client.API().MessagesDeleteChat(ctx, id) + if err != nil { + return err + } + if !result { + return fmt.Errorf("failed to delete chat %d", id) + } + return nil + } else { + return fmt.Errorf("deleting chat only supported for group creators") + } + case ids.PeerTypeChannel: + accessHash, err := t.ScopedStore.GetAccessHash(ctx, peerType, id) + if err != nil { + return err + } + channel := &tg.InputChannel{ + ChannelID: id, + AccessHash: accessHash, + } + if chat.Content.DeleteForEveryone { + _, err := t.client.API().ChannelsDeleteChannel(ctx, channel) + if err != nil { + return err + } + return nil + } else { + return fmt.Errorf("deleting chat only supported for channel creators") + } + default: return fmt.Errorf("deleting chat not supported for peer type %s", peerType) } return nil diff --git a/pkg/connector/metadata.go b/pkg/connector/metadata.go index d1527e59..394115b3 100644 --- a/pkg/connector/metadata.go +++ b/pkg/connector/metadata.go @@ -50,10 +50,11 @@ type GhostMetadata struct { } type PortalMetadata struct { - IsSuperGroup bool `json:"is_supergroup,omitempty"` - ReadUpTo int `json:"read_up_to,omitempty"` - AllowedReactions []string `json:"allowed_reactions"` - LastSync jsontime.Unix `json:"last_sync,omitempty"` + IsSuperGroup bool `json:"is_supergroup,omitempty"` + ReadUpTo int `json:"read_up_to,omitempty"` + AllowedReactions []string `json:"allowed_reactions"` + LastSync jsontime.Unix `json:"last_sync,omitempty"` + ParticipantsCount int `json:"member_count,omitempty"` } func (pm *PortalMetadata) SetIsSuperGroup(isSupergroup bool) (changed bool) {