diff --git a/bridgev2/networkinterface.go b/bridgev2/networkinterface.go index 8293be51..fa87086a 100644 --- a/bridgev2/networkinterface.go +++ b/bridgev2/networkinterface.go @@ -346,6 +346,10 @@ type NetworkGeneralCapabilities struct { // Should the bridge re-request user info on incoming messages even if the ghost already has info? // By default, info is only requested for ghosts with no name, and other updating is left to events. AggressiveUpdateInfo bool + // Should the bridge call HandleMatrixReadReceipt with fake data when receiving a new message? + // This should be enabled if the network requires each message to be marked as read independently, + // and doesn't automatically do it when sending a message. + ImplicitReadReceipts bool // If the bridge uses the pending message mechanism ([MatrixMessage.AddPendingToSave]) // to handle asynchronous message responses, this field can be set to enable // automatic timeout errors in case the asynchronous response never arrives. @@ -1361,6 +1365,8 @@ type MatrixReadReceipt struct { LastRead time.Time // The receipt metadata. Receipt event.ReadReceipt + // Whether the receipt is implicit, i.e. triggered by an incoming timeline event rather than an explicit receipt. + Implicit bool } type MatrixTyping struct { diff --git a/bridgev2/portal.go b/bridgev2/portal.go index bbe41e02..8924566f 100644 --- a/bridgev2/portal.go +++ b/bridgev2/portal.go @@ -587,7 +587,7 @@ func (portal *Portal) handleMatrixEvent(ctx context.Context, sender *User, evt * // Tombstones aren't bridged so they don't need a login return portal.handleMatrixTombstone(ctx, evt) } - login, _, err := portal.FindPreferredLogin(ctx, sender, true) + login, userPortal, err := portal.FindPreferredLogin(ctx, sender, true) if err != nil { log.Err(err).Msg("Failed to get user login to handle Matrix event") if errors.Is(err, ErrNotLoggedIn) { @@ -646,6 +646,21 @@ func (portal *Portal) handleMatrixEvent(ctx context.Context, sender *User, evt * } // Copy logger because many of the handlers will use UpdateContext ctx = log.With().Str("login_id", string(login.ID)).Logger().WithContext(ctx) + + if origSender == nil && portal.Bridge.Network.GetCapabilities().ImplicitReadReceipts { + rrLog := log.With().Str("subaction", "implicit read receipt").Logger() + rrCtx := rrLog.WithContext(ctx) + rrLog.Debug().Msg("Sending implicit read receipt for event") + evtTS := time.UnixMilli(evt.Timestamp) + portal.callReadReceiptHandler(rrCtx, login, nil, &MatrixReadReceipt{ + Portal: portal, + EventID: evt.ID, + Implicit: true, + ReadUpTo: evtTS, + Receipt: event.ReadReceipt{Timestamp: evtTS}, + }, userPortal) + } + switch evt.Type { case event.EventMessage, event.EventSticker, event.EventUnstablePollStart, event.EventUnstablePollResponse: return portal.handleMatrixMessage(ctx, login, origSender, evt) @@ -735,15 +750,10 @@ func (portal *Portal) handleMatrixReadReceipt(ctx context.Context, user *User, e EventID: eventID, Receipt: receipt, } - if userPortal == nil { - userPortal = database.UserPortalFor(login.UserLogin, portal.PortalKey) - } else { - evt.LastRead = userPortal.LastRead - userPortal = userPortal.CopyWithoutValues() - } evt.ExactMessage, err = portal.Bridge.DB.Message.GetPartByMXID(ctx, eventID) if err != nil { log.Err(err).Msg("Failed to get exact message from database") + evt.ReadUpTo = receipt.Timestamp } else if evt.ExactMessage != nil { log.UpdateContext(func(c zerolog.Context) zerolog.Context { return c.Str("exact_message_id", string(evt.ExactMessage.ID)).Time("exact_message_ts", evt.ExactMessage.Timestamp) @@ -752,19 +762,38 @@ func (portal *Portal) handleMatrixReadReceipt(ctx context.Context, user *User, e } else { evt.ReadUpTo = receipt.Timestamp } - err = rrClient.HandleMatrixReadReceipt(ctx, evt) + portal.callReadReceiptHandler(ctx, login, rrClient, evt, userPortal) +} + +func (portal *Portal) callReadReceiptHandler( + ctx context.Context, + login *UserLogin, + rrClient ReadReceiptHandlingNetworkAPI, + evt *MatrixReadReceipt, + userPortal *database.UserPortal, +) { + if rrClient == nil { + var ok bool + rrClient, ok = login.Client.(ReadReceiptHandlingNetworkAPI) + if !ok { + return + } + } + if userPortal == nil { + userPortal = database.UserPortalFor(login.UserLogin, portal.PortalKey) + } else { + evt.LastRead = userPortal.LastRead + userPortal = userPortal.CopyWithoutValues() + } + err := rrClient.HandleMatrixReadReceipt(ctx, evt) if err != nil { - log.Err(err).Msg("Failed to handle read receipt") + zerolog.Ctx(ctx).Err(err).Msg("Failed to handle read receipt") return } - if evt.ExactMessage != nil { - userPortal.LastRead = evt.ExactMessage.Timestamp - } else { - userPortal.LastRead = receipt.Timestamp - } + userPortal.LastRead = evt.ReadUpTo err = portal.Bridge.DB.UserPortal.Put(ctx, userPortal) if err != nil { - log.Err(err).Msg("Failed to save user portal metadata") + zerolog.Ctx(ctx).Err(err).Msg("Failed to save user portal metadata") } portal.Bridge.DisappearLoop.StartAll(ctx, portal.MXID) }