From 2bd7a38209511238ac0fc9fe7204d486c44d66e5 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Thu, 29 Oct 2026 15:49:01 +0000 Subject: [PATCH 1/2] PLAT-39404 connector: unhide hidden chats when a new message arrives Reddit keeps a chat hidden only until someone sends another message, but the bridge skipped hidden rooms until the chat was reopened natively. Co-Authored-By: Claude Opus 5.5 --- pkg/connector/chatsync.go | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/pkg/connector/chatsync.go b/pkg/connector/chatsync.go index 0e37f9e..117a7a3 100644 --- a/pkg/connector/chatsync.go +++ b/pkg/connector/chatsync.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "slices" "time" "github.com/rs/zerolog" @@ -98,7 +99,7 @@ func (r *RedditClient) runSync(ctx context.Context, done chan struct{}) { continue } - err = r.handleSync(ctx, resp, &roomSnapshots{}) + err = r.handleSync(ctx, resp, &roomSnapshots{}, since != "") if err != nil { log.Error().Err(err).Msg("Failed to process Reddit sync; retaining cursor") r.userLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: "reddit-sync-apply-failed", Message: "Unable to synchronize Reddit chat state. Retrying."}) @@ -132,11 +133,16 @@ func (r *RedditClient) runSync(ctx context.Context, done chan struct{}) { // Unbridgeable native data is skipped so it cannot hold the cursor forever. // Any other failure retries the whole batch. -func (r *RedditClient) handleSync(ctx context.Context, resp *redditchat.SyncResponse, snapshots *roomSnapshots) error { +func (r *RedditClient) handleSync(ctx context.Context, resp *redditchat.SyncResponse, snapshots *roomSnapshots, incremental bool) error { unhidden, err := r.applyHiddenChatData(ctx, resp) if err != nil { return err } + if incremental { + if err = r.unhideChatsWithNewMessages(ctx, resp); err != nil { + return err + } + } for roomID, joined := range resp.Rooms.Join { if r.meta.HiddenRooms[roomID] { continue @@ -177,7 +183,7 @@ func (r *RedditClient) handleSync(ctx context.Context, resp *redditchat.SyncResp if err != nil { return err } - return r.handleSync(ctx, state, snapshots) + return r.handleSync(ctx, state, snapshots, false) } return nil } @@ -228,6 +234,26 @@ func (r *RedditClient) applyHiddenChatData(ctx context.Context, resp *redditchat return unhidden, nil } +// Reddit keeps a hidden chat hidden only until someone sends another message. +func (r *RedditClient) unhideChatsWithNewMessages(ctx context.Context, resp *redditchat.SyncResponse) error { + for _, rooms := range []map[id.RoomID]*mautrix.SyncJoinedRoom{resp.Rooms.Join, resp.Rooms.Peek} { + for roomID, room := range rooms { + if room == nil || !r.meta.HiddenRooms[roomID] || !slices.ContainsFunc(room.Timeline.Events, isMessageEvent) { + continue + } + if err := r.matrix().SetRoomAccountData(ctx, roomID, "com.reddit.hidden_chat", map[string]bool{"hidden": false}); err != nil { + return wrapRedditError(err) + } + delete(r.meta.HiddenRooms, roomID) + } + } + return nil +} + +func isMessageEvent(evt *event.Event) bool { + return evt != nil && evt.Type == event.EventMessage +} + func parseHiddenChat(evt *event.Event) (bool, error) { var content struct { Hidden *bool `json:"hidden"` From ca9266cd785b51d5ffc8e4a1ab5cd8171460119e Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Thu, 29 Oct 2026 16:01:29 +0000 Subject: [PATCH 2/2] PLAT-39404 connector: keep a hide from the same batch and don't stall on a rejected unhide Co-Authored-By: Claude Opus 5.5 --- pkg/connector/chatsync.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/connector/chatsync.go b/pkg/connector/chatsync.go index 117a7a3..b28fd94 100644 --- a/pkg/connector/chatsync.go +++ b/pkg/connector/chatsync.go @@ -238,11 +238,15 @@ func (r *RedditClient) applyHiddenChatData(ctx context.Context, resp *redditchat func (r *RedditClient) unhideChatsWithNewMessages(ctx context.Context, resp *redditchat.SyncResponse) error { for _, rooms := range []map[id.RoomID]*mautrix.SyncJoinedRoom{resp.Rooms.Join, resp.Rooms.Peek} { for roomID, room := range rooms { - if room == nil || !r.meta.HiddenRooms[roomID] || !slices.ContainsFunc(room.Timeline.Events, isMessageEvent) { + // A hide in the same batch may be newer than its messages. + if room == nil || !r.meta.HiddenRooms[roomID] || slices.ContainsFunc(room.AccountData.Events, isHiddenChatEvent) || !slices.ContainsFunc(room.Timeline.Events, isMessageEvent) { continue } - if err := r.matrix().SetRoomAccountData(ctx, roomID, "com.reddit.hidden_chat", map[string]bool{"hidden": false}); err != nil { + err := r.matrix().SetRoomAccountData(ctx, roomID, "com.reddit.hidden_chat", map[string]bool{"hidden": false}) + if err != nil && !isPermanentHTTPError(err) { return wrapRedditError(err) + } else if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Stringer("room_id", roomID).Msg("Failed to unhide Reddit chat natively") } delete(r.meta.HiddenRooms, roomID) } @@ -254,6 +258,10 @@ func isMessageEvent(evt *event.Event) bool { return evt != nil && evt.Type == event.EventMessage } +func isHiddenChatEvent(evt *event.Event) bool { + return evt != nil && evt.Type.Type == "com.reddit.hidden_chat" +} + func parseHiddenChat(evt *event.Event) (bool, error) { var content struct { Hidden *bool `json:"hidden"`