bridgev2/networkinterface: add extra fields to reply metadata to allow unknown cross-room replies
Some checks are pending
Go / Lint (latest) (push) Waiting to run
Go / Build (old, libolm) (push) Waiting to run
Go / Build (latest, libolm) (push) Waiting to run
Go / Build (old, goolm) (push) Waiting to run
Go / Build (latest, goolm) (push) Waiting to run

This commit is contained in:
Tulir Asokan 2025-07-09 16:35:14 +03:00
commit 0777c10028
4 changed files with 48 additions and 18 deletions

View file

@ -91,7 +91,14 @@ func (es EventSender) MarshalZerologObject(evt *zerolog.Event) {
}
type ConvertedMessage struct {
ReplyTo *networkid.MessageOptionalPartID
ReplyTo *networkid.MessageOptionalPartID
// Optional additional info about the reply. This is only used when backfilling messages
// on Beeper, where replies may target messages that haven't been bridged yet.
// Standard Matrix servers can't backwards backfill, so these are never used.
ReplyToRoom networkid.PortalKey
ReplyToUser networkid.UserID
ReplyToLogin networkid.UserLoginID
ThreadRoot *networkid.MessageID
Parts []*ConvertedMessagePart
Disappear database.DisappearingSetting

View file

@ -1942,42 +1942,61 @@ func (portal *Portal) GetIntentFor(ctx context.Context, sender EventSender, sour
func (portal *Portal) getRelationMeta(
ctx context.Context,
currentMsg networkid.MessageID,
replyToPtr *networkid.MessageOptionalPartID,
threadRootPtr *networkid.MessageID,
currentMsgID networkid.MessageID,
currentMsg *ConvertedMessage,
isBatchSend bool,
) (replyTo, threadRoot, prevThreadEvent *database.Message) {
log := zerolog.Ctx(ctx)
var err error
if replyToPtr != nil {
replyTo, err = portal.Bridge.DB.Message.GetFirstOrSpecificPartByID(ctx, portal.Receiver, *replyToPtr)
if currentMsg.ReplyTo != nil {
replyTo, err = portal.Bridge.DB.Message.GetFirstOrSpecificPartByID(ctx, portal.Receiver, *currentMsg.ReplyTo)
if err != nil {
log.Err(err).Msg("Failed to get reply target message from database")
} else if replyTo == nil {
if isBatchSend || portal.Bridge.Config.OutgoingMessageReID {
// This is somewhat evil
// TODO this does not work with cross-room replies
replyTo = &database.Message{
MXID: portal.Bridge.Matrix.GenerateDeterministicEventID(portal.MXID, portal.PortalKey, replyToPtr.MessageID, ptr.Val(replyToPtr.PartID)),
MXID: portal.Bridge.Matrix.GenerateDeterministicEventID(portal.MXID, portal.PortalKey, currentMsg.ReplyTo.MessageID, ptr.Val(currentMsg.ReplyTo.PartID)),
Room: currentMsg.ReplyToRoom,
SenderID: currentMsg.ReplyToUser,
}
if currentMsg.ReplyToLogin != "" && (portal.Receiver == "" || portal.Receiver == currentMsg.ReplyToLogin) {
userLogin, err := portal.Bridge.GetExistingUserLoginByID(ctx, currentMsg.ReplyToLogin)
if err != nil {
log.Err(err).
Str("reply_to_login", string(currentMsg.ReplyToLogin)).
Msg("Failed to get reply target user login")
} else if userLogin != nil {
replyTo.SenderMXID = userLogin.UserMXID
}
} else {
ghost, err := portal.Bridge.GetGhostByID(ctx, currentMsg.ReplyToUser)
if err != nil {
log.Err(err).
Str("reply_to_user_id", string(currentMsg.ReplyToUser)).
Msg("Failed to get reply target ghost")
} else {
replyTo.SenderMXID = ghost.Intent.GetMXID()
}
}
} else {
log.Warn().Any("reply_to", *replyToPtr).Msg("Reply target message not found in database")
log.Warn().Any("reply_to", *currentMsg.ReplyTo).Msg("Reply target message not found in database")
}
}
}
if threadRootPtr != nil && *threadRootPtr != currentMsg {
threadRoot, err = portal.Bridge.DB.Message.GetFirstThreadMessage(ctx, portal.PortalKey, *threadRootPtr)
if currentMsg.ThreadRoot != nil && *currentMsg.ThreadRoot != currentMsgID {
threadRoot, err = portal.Bridge.DB.Message.GetFirstThreadMessage(ctx, portal.PortalKey, *currentMsg.ThreadRoot)
if err != nil {
log.Err(err).Msg("Failed to get thread root message from database")
} else if threadRoot == nil {
if isBatchSend || portal.Bridge.Config.OutgoingMessageReID {
threadRoot = &database.Message{
MXID: portal.Bridge.Matrix.GenerateDeterministicEventID(portal.MXID, portal.PortalKey, *threadRootPtr, ""),
MXID: portal.Bridge.Matrix.GenerateDeterministicEventID(portal.MXID, portal.PortalKey, *currentMsg.ThreadRoot, ""),
}
} else {
log.Warn().Str("thread_root", string(*threadRootPtr)).Msg("Thread root message not found in database")
log.Warn().Str("thread_root", string(*currentMsg.ThreadRoot)).Msg("Thread root message not found in database")
}
} else if prevThreadEvent, err = portal.Bridge.DB.Message.GetLastThreadMessage(ctx, portal.PortalKey, *threadRootPtr); err != nil {
} else if prevThreadEvent, err = portal.Bridge.DB.Message.GetLastThreadMessage(ctx, portal.PortalKey, *currentMsg.ThreadRoot); err != nil {
log.Err(err).Msg("Failed to get last thread message from database")
}
if prevThreadEvent == nil {
@ -2033,7 +2052,9 @@ func (portal *Portal) sendConvertedMessage(
}
}
log := zerolog.Ctx(ctx)
replyTo, threadRoot, prevThreadEvent := portal.getRelationMeta(ctx, id, converted.ReplyTo, converted.ThreadRoot, false)
replyTo, threadRoot, prevThreadEvent := portal.getRelationMeta(
ctx, id, converted, false,
)
output := make([]*database.Message, 0, len(converted.Parts))
allSuccess := true
for i, part := range converted.Parts {

View file

@ -327,7 +327,9 @@ func (portal *Portal) compileBatchMessage(ctx context.Context, source *UserLogin
if !ok {
return
}
replyTo, threadRoot, prevThreadEvent := portal.getRelationMeta(ctx, msg.ID, msg.ReplyTo, msg.ThreadRoot, true)
replyTo, threadRoot, prevThreadEvent := portal.getRelationMeta(
ctx, msg.ID, msg.ConvertedMessage, true,
)
if threadRoot != nil && out.PrevThreadEvents[*msg.ThreadRoot] != "" {
prevThreadEvent.MXID = out.PrevThreadEvents[*msg.ThreadRoot]
}

View file

@ -137,8 +137,8 @@ func (portal *PortalInternals) GetIntentAndUserMXIDFor(ctx context.Context, send
return (*Portal)(portal).getIntentAndUserMXIDFor(ctx, sender, source, otherLogins, evtType)
}
func (portal *PortalInternals) GetRelationMeta(ctx context.Context, currentMsg networkid.MessageID, replyToPtr *networkid.MessageOptionalPartID, threadRootPtr *networkid.MessageID, isBatchSend bool) (replyTo, threadRoot, prevThreadEvent *database.Message) {
return (*Portal)(portal).getRelationMeta(ctx, currentMsg, replyToPtr, threadRootPtr, isBatchSend)
func (portal *PortalInternals) GetRelationMeta(ctx context.Context, currentMsgID networkid.MessageID, currentMsg *ConvertedMessage, isBatchSend bool) (replyTo, threadRoot, prevThreadEvent *database.Message) {
return (*Portal)(portal).getRelationMeta(ctx, currentMsgID, currentMsg, isBatchSend)
}
func (portal *PortalInternals) ApplyRelationMeta(ctx context.Context, content *event.MessageEventContent, replyTo, threadRoot, prevThreadEvent *database.Message) {