From 094bc9bd77202de2a6e798acca67c1cfe1e9a0a6 Mon Sep 17 00:00:00 2001 From: Skip R Date: Tue, 3 Feb 2026 21:55:58 -0800 Subject: [PATCH] connector: support transaction IDs --- pkg/connector/connector.go | 13 +++++++++++-- pkg/connector/handlediscord.go | 10 +++++++++- pkg/discordid/id.go | 11 +++++++++++ pkg/msgconv/from-matrix.go | 16 +++++----------- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index d866e49..25070b7 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -20,7 +20,11 @@ import ( "context" "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" + "go.mau.fi/mautrix-discord/pkg/discordid" "go.mau.fi/mautrix-discord/pkg/msgconv" ) @@ -31,8 +35,9 @@ type DiscordConnector struct { } var ( - _ bridgev2.NetworkConnector = (*DiscordConnector)(nil) - _ bridgev2.MaxFileSizeingNetwork = (*DiscordConnector)(nil) + _ bridgev2.NetworkConnector = (*DiscordConnector)(nil) + _ bridgev2.MaxFileSizeingNetwork = (*DiscordConnector)(nil) + _ bridgev2.TransactionIDGeneratingNetwork = (*DiscordConnector)(nil) ) func (d *DiscordConnector) Init(bridge *bridgev2.Bridge) { @@ -59,3 +64,7 @@ func (d *DiscordConnector) GetName() bridgev2.BridgeName { DefaultPort: 29334, } } + +func (d *DiscordConnector) GenerateTransactionID(_ id.UserID, _ id.RoomID, _ event.Type) networkid.RawTransactionID { + return networkid.RawTransactionID(discordid.GenerateNonce()) +} diff --git a/pkg/connector/handlediscord.go b/pkg/connector/handlediscord.go index 2826d64..9516779 100644 --- a/pkg/connector/handlediscord.go +++ b/pkg/connector/handlediscord.go @@ -59,11 +59,19 @@ type DiscordMessage struct { } var ( - _ bridgev2.RemoteMessage = (*DiscordMessage)(nil) + _ bridgev2.RemoteMessage = (*DiscordMessage)(nil) + _ bridgev2.RemoteMessageWithTransactionID = (*DiscordMessage)(nil) // _ bridgev2.RemoteEdit = (*DiscordMessage)(nil) // _ bridgev2.RemoteMessageRemove = (*DiscordMessage)(nil) ) +func (m *DiscordMessage) GetTransactionID() networkid.TransactionID { + if m.Data.Nonce == "" { + return "" + } + return networkid.TransactionID(m.Data.Nonce) +} + func (m *DiscordMessage) ConvertMessage(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI) (*bridgev2.ConvertedMessage, error) { return m.Client.connector.MsgConv.ToMatrix(ctx, portal, intent, m.Client.UserLogin, m.Client.Session, m.Data), nil } diff --git a/pkg/discordid/id.go b/pkg/discordid/id.go index 42d4321..346604a 100644 --- a/pkg/discordid/id.go +++ b/pkg/discordid/id.go @@ -17,10 +17,21 @@ package discordid import ( + "strconv" + "time" + "github.com/bwmarrin/discordgo" "maunium.net/go/mautrix/bridgev2/networkid" ) +const DiscordEpochMillis = 1420070400000 + +// GenerateNonce creates a Discord-style snowflake nonce for message idempotency. +func GenerateNonce() string { + snowflake := (time.Now().UnixMilli() - DiscordEpochMillis) << 22 + return strconv.FormatInt(snowflake, 10) +} + func MakeUserID(userID string) networkid.UserID { return networkid.UserID(userID) } diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 8e328f8..d717377 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -22,8 +22,6 @@ import ( "fmt" "io" "net/http" - "strconv" - "time" "github.com/bwmarrin/discordgo" "github.com/rs/zerolog" @@ -35,14 +33,6 @@ import ( "go.mau.fi/mautrix-discord/pkg/discordid" ) -const discordEpochMillis = 1420070400000 - -func generateMessageNonce() string { - snowflake := (time.Now().UnixMilli() - discordEpochMillis) << 22 - // Nonce snowflakes don't have internal IDs or increments - return strconv.FormatInt(snowflake, 10) -} - func parseAllowedLinkPreviews(raw map[string]any) []string { if raw == nil { return nil @@ -102,7 +92,11 @@ func (mc *MessageConverter) ToDiscord( ctx = context.WithValue(ctx, contextKeyPortal, msg.Portal) ctx = context.WithValue(ctx, contextKeyDiscordClient, session) var req discordgo.MessageSend - req.Nonce = generateMessageNonce() + if msg.InputTransactionID != "" { + req.Nonce = string(msg.InputTransactionID) + } else { + req.Nonce = discordid.GenerateNonce() + } log := zerolog.Ctx(ctx) if msg.ReplyTo != nil {