From a38c3e5d0085d747df13f56a9d905f9fde6cd19d Mon Sep 17 00:00:00 2001 From: Adam Van Ymeren Date: Thu, 25 Sep 2025 12:34:41 -0700 Subject: [PATCH] resync: resync portals upon viewing if they haven't been synced in the last 24h (#124) --- pkg/connector/chatinfo.go | 37 +++++++++++++++++++++++------------ pkg/connector/handlematrix.go | 19 ++++++++++++++++++ pkg/connector/metadata.go | 16 ++++++++++++--- pkg/connector/sync.go | 5 ++++- 4 files changed, 61 insertions(+), 16 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 82047a52..26ba1c2e 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -91,14 +91,21 @@ func adminRightsToPowerLevel(rights tg.ChatAdminRights) *int { return otherPowerLevel } -func (t *TelegramClient) getDMChatInfo(userID int64) *bridgev2.ChatInfo { +func (t *TelegramClient) getDMChatInfo(ctx context.Context, userID int64) (*bridgev2.ChatInfo, error) { + ghost, err := t.main.Bridge.GetGhostByID(ctx, ids.MakeUserID(userID)) + if err != nil { + return nil, err + } + chatInfo := bridgev2.ChatInfo{ Type: ptr.Ptr(database.RoomTypeDM), Members: &bridgev2.ChatMemberList{ - IsFull: true, - MemberMap: map[networkid.UserID]bridgev2.ChatMember{}, + IsFull: true, + MemberMap: map[networkid.UserID]bridgev2.ChatMember{}, + PowerLevels: t.getDMPowerLevels(ghost), }, - CanBackfill: true, + 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()} @@ -112,7 +119,7 @@ func (t *TelegramClient) getDMChatInfo(userID int64) *bridgev2.ChatInfo { chatInfo.Name = ptr.Ptr("Telegram Saved Messages") chatInfo.Topic = ptr.Ptr("Your Telegram cloud storage chat") } - return &chatInfo + return &chatInfo, nil } func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID int64) (*bridgev2.ChatInfo, bool, error) { @@ -142,15 +149,14 @@ func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID CanBackfill: true, ExtraUpdates: func(ctx context.Context, p *bridgev2.Portal) bool { meta := p.Metadata.(*PortalMetadata) - changed := meta.SetIsSuperGroup(isMegagroup) + _ = updatePortalLastSyncAt(ctx, p) + _ = meta.SetIsSuperGroup(isMegagroup) if reactions, ok := fullChat.FullChat.GetAvailableReactions(); ok { switch typedReactions := reactions.(type) { case *tg.ChatReactionsAll: - changed = meta.AllowedReactions != nil meta.AllowedReactions = nil case *tg.ChatReactionsNone: - changed = meta.AllowedReactions == nil || len(meta.AllowedReactions) > 0 meta.AllowedReactions = []string{} case *tg.ChatReactionsSome: allowedReactions := make([]string, 0, len(typedReactions.Reactions)) @@ -162,13 +168,12 @@ func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID } slices.Sort(allowedReactions) if !slices.Equal(meta.AllowedReactions, allowedReactions) { - changed = true meta.AllowedReactions = allowedReactions } } } - return changed + return true }, } @@ -243,7 +248,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta switch peerType { case ids.PeerTypeUser: - return t.getDMChatInfo(id), nil + return t.getDMChatInfo(ctx, id) case ids.PeerTypeChat: fullChat, err := APICallWithUpdates(ctx, t, func() (*tg.MessagesChatFull, error) { return t.client.API().MessagesGetFullChat(ctx, id) @@ -261,6 +266,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta return nil, fmt.Errorf("full chat is %T not *tg.ChatFull", fullChat.FullChat) } chatInfo.Avatar = t.avatarFromPhoto(ctx, peerType, id, chatFull.ChatPhoto) + chatInfo.Members.PowerLevels = t.getGroupChatPowerLevels(ctx, fullChat.GetChats()[0]) if chatFull.Participants.TypeID() == tg.ChatParticipantsForbiddenTypeID { chatInfo.Members.IsFull = false @@ -497,8 +503,12 @@ func (t *TelegramClient) getPowerLevelOverridesFromBannedRights(entity tg.ChatCl plo.Events[event.StateRoomName] = *changeInfoPowerLevel plo.Events[event.StateRoomAvatar] = *changeInfoPowerLevel plo.Events[event.StateTopic] = *changeInfoPowerLevel - // TODO is this the correct level? plo.Events[event.StateBeeperDisappearingTimer] = *changeInfoPowerLevel + } else { + plo.Events[event.StateRoomName] = 0 + plo.Events[event.StateRoomAvatar] = 0 + plo.Events[event.StateTopic] = 0 + plo.Events[event.StateBeeperDisappearingTimer] = 0 } if dbr.PinMessages { @@ -509,6 +519,9 @@ func (t *TelegramClient) getPowerLevelOverridesFromBannedRights(entity tg.ChatCl if dbr.SendStickers { plo.Events[event.EventSticker] = *postMessagesPowerLevel + } else { + plo.Events[event.EventSticker] = 0 } + return &plo } diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 96ecd12e..ec75f663 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -42,6 +42,7 @@ import ( "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/bridgev2/simplevent" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" @@ -65,6 +66,7 @@ var ( _ bridgev2.DisappearTimerChangingNetworkAPI = (*TelegramClient)(nil) _ bridgev2.MuteHandlingNetworkAPI = (*TelegramClient)(nil) _ bridgev2.TagHandlingNetworkAPI = (*TelegramClient)(nil) + _ bridgev2.ChatViewingNetworkAPI = (*TelegramClient)(nil) ) func getMediaFilename(content *event.MessageEventContent) (filename string) { @@ -85,6 +87,23 @@ func getMediaFilename(content *event.MessageEventContent) (filename string) { return filename } +func (t *TelegramClient) HandleMatrixViewingChat(ctx context.Context, msg *bridgev2.MatrixViewingChat) error { + if msg.Portal == nil { + return nil + } + meta := msg.Portal.Metadata.(*PortalMetadata) + if meta.LastSync.Add(24 * time.Hour).Before(time.Now()) { + t.userLogin.QueueRemoteEvent(&simplevent.ChatResync{ + EventMeta: simplevent.EventMeta{ + Type: bridgev2.RemoteEventChatResync, + PortalKey: msg.Portal.PortalKey, + }, + GetChatInfoFunc: t.GetChatInfo, + }) + } + return nil +} + func (t *TelegramClient) transferMediaToTelegram(ctx context.Context, content *event.MessageEventContent, sticker bool) (tg.InputMediaClass, error) { var upload tg.InputFileClass var forceDocument bool diff --git a/pkg/connector/metadata.go b/pkg/connector/metadata.go index 55246d71..d1527e59 100644 --- a/pkg/connector/metadata.go +++ b/pkg/connector/metadata.go @@ -19,10 +19,13 @@ package connector import ( "context" + "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/id" + "go.mau.fi/util/jsontime" + "go.mau.fi/mautrix-telegram/pkg/gotd/crypto" "go.mau.fi/mautrix-telegram/pkg/gotd/session" ) @@ -47,9 +50,10 @@ type GhostMetadata struct { } type PortalMetadata struct { - IsSuperGroup bool `json:"is_supergroup,omitempty"` - ReadUpTo int `json:"read_up_to,omitempty"` - AllowedReactions []string `json:"allowed_reactions"` + 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"` } func (pm *PortalMetadata) SetIsSuperGroup(isSupergroup bool) (changed bool) { @@ -118,3 +122,9 @@ func (s *UserLoginSession) Save(ctx context.Context, data *session.Data) error { // TODO save UserLogin to database? return nil } + +func updatePortalLastSyncAt(_ context.Context, portal *bridgev2.Portal) bool { + meta := portal.Metadata.(*PortalMetadata) + meta.LastSync = jsontime.UnixNow() + return true +} diff --git a/pkg/connector/sync.go b/pkg/connector/sync.go index eb64dc37..256a6039 100644 --- a/pkg/connector/sync.go +++ b/pkg/connector/sync.go @@ -134,7 +134,10 @@ func (t *TelegramClient) handleDialogs(ctx context.Context, dialogs tg.ModifiedM log.Debug().Int64("user_id", peer.UserID).Msg("Not syncing portal because user is deleted") continue } - chatInfo = t.getDMChatInfo(peer.UserID) + chatInfo, err = t.getDMChatInfo(ctx, peer.UserID) + if err != nil { + return fmt.Errorf("failed to get dm info for %d: %w", peer.UserID, err) + } case *tg.PeerChat: chat := chats[peer.ChatID] if chat.TypeID() == tg.ChatForbiddenTypeID {