From 3ae88caa8045a6d36da5afd46423038b3d7591fc Mon Sep 17 00:00:00 2001 From: Conan Date: Thu, 7 Aug 2025 03:09:24 +0800 Subject: [PATCH] connector: handle supergroup upgrades (#118) Co-authored-by: Tulir Asokan --- pkg/connector/config.go | 3 ++ pkg/connector/example-config.yaml | 3 ++ pkg/connector/telegram.go | 64 ++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/pkg/connector/config.go b/pkg/connector/config.go index e437aef0..51ee666a 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -82,6 +82,8 @@ type TelegramConfig struct { AlwaysCustomEmojiReaction bool `yaml:"always_custom_emoji_reaction"` SavedMessagesAvatar id.ContentURIString `yaml:"saved_message_avatar"` + + AlwaysTombstoneOnSupergroupMigration bool `yaml:"always_tombstone_on_supergroup_migration"` } func (c TelegramConfig) ShouldBridge(participantCount int) bool { @@ -120,6 +122,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Bool, "sync", "direct_chats") helper.Copy(up.Bool, "always_custom_emoji_reaction") helper.Copy(up.Str, "saved_message_avatar") + helper.Copy(up.Bool, "always_tombstone_on_supergroup_migration") } func (tg *TelegramConnector) GetConfig() (example string, data any, upgrader up.Upgrader) { diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index e880825b..de9f1d21 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -88,3 +88,6 @@ always_custom_emoji_reaction: false # The avatar to use for the Telegram Saved Messages chat saved_message_avatar: mxc://nevarro.space/ZzzyOSDZfyGjKRRbyDPTweAA + +# Create a new room and tombstone the old one when upgrading rooms +always_tombstone_on_supergroup_migration: false diff --git a/pkg/connector/telegram.go b/pkg/connector/telegram.go index 63cf84de..005c9831 100644 --- a/pkg/connector/telegram.go +++ b/pkg/connector/telegram.go @@ -534,8 +534,39 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, entities tg.Ent return err } - // case *tg.MessageActionChatMigrateTo: + case *tg.MessageActionChatMigrateTo: + log.Debug().Int64("channel_id", action.ChannelID).Msg("MessageActionChatMigrateTo") + newPortalKey := t.makePortalKeyFromID(ids.PeerTypeChannel, action.ChannelID) + if err := t.migrateChat(ctx, eventMeta.PortalKey, newPortalKey); err != nil { + log.Err(err).Msg("Failed to migrate chat to channel") + return err + } + res := t.main.Bridge.QueueRemoteEvent(t.userLogin, &simplevent.Message[any]{ + EventMeta: eventMeta. + WithPortalKey(newPortalKey). + WithStreamOrder(0). + WithType(bridgev2.RemoteEventMessage), + ID: ids.GetMessageIDFromMessage(msg), + ConvertMessageFunc: func(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, data any) (*bridgev2.ConvertedMessage, error) { + return &bridgev2.ConvertedMessage{ + Parts: []*bridgev2.ConvertedMessagePart{ + { + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: "Upgraded this group to a supergroup", + }, + }, + }, + }, nil + }, + }) + if err := resultToError(res); err != nil { + return err + } + // case *tg.MessageActionChannelMigrateFrom: + // case *tg.MessageActionPinMessage: // case *tg.MessageActionHistoryClear: // case *tg.MessageActionGameScore: @@ -579,6 +610,37 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, entities tg.Ent return nil } +func (t *TelegramClient) migrateChat(ctx context.Context, oldPortalKey, newPortalKey networkid.PortalKey) error { + if t.main.Config.AlwaysTombstoneOnSupergroupMigration { + newPortal, err := t.main.Bridge.GetPortalByKey(ctx, newPortalKey) + if err != nil { + return fmt.Errorf("failed to get new portal for chat migration: %w", err) + } + info, err := t.GetChatInfo(ctx, newPortal) + if err != nil { + return fmt.Errorf("failed to get chat info for new portal: %w", err) + } + err = newPortal.CreateMatrixRoom(ctx, t.userLogin, info) + if err != nil { + return fmt.Errorf("failed to create Matrix room for new portal: %w", err) + } + } + + result, portal, err := t.main.Bridge.ReIDPortal(ctx, oldPortalKey, newPortalKey) + if err != nil { + return fmt.Errorf("failed to re-ID portal: %w", err) + } else if result == bridgev2.ReIDResultSourceReIDd || result == bridgev2.ReIDResultTargetDeletedAndSourceReIDd { + // If the source portal is re-ID'd, we need to sync metadata and participants. + // If the source is deleted, then it doesn't matter, any existing target will already be correct + info, err := t.GetChatInfo(ctx, portal) + if err != nil { + return fmt.Errorf("failed to get chat info for new portal: %w", err) + } + portal.UpdateInfo(ctx, info, t.userLogin, nil, time.Time{}) + } + return nil +} + func (t *TelegramClient) getEventSender(msg interface { GetOut() bool GetFromID() (tg.PeerClass, bool)