diff --git a/bridgev2/matrix/connector.go b/bridgev2/matrix/connector.go index 3e05837f..dbddaff2 100644 --- a/bridgev2/matrix/connector.go +++ b/bridgev2/matrix/connector.go @@ -145,6 +145,7 @@ func (br *Connector) Init(bridge *bridgev2.Bridge) { br.EventProcessor.On(event.StateMember, br.handleRoomEvent) br.EventProcessor.On(event.StatePowerLevels, br.handleRoomEvent) br.EventProcessor.On(event.StateRoomName, br.handleRoomEvent) + br.EventProcessor.On(event.BeeperSendState, br.handleRoomEvent) br.EventProcessor.On(event.StateRoomAvatar, br.handleRoomEvent) br.EventProcessor.On(event.StateTopic, br.handleRoomEvent) br.EventProcessor.On(event.StateTombstone, br.handleRoomEvent) diff --git a/bridgev2/messagestatus.go b/bridgev2/messagestatus.go index 7118649d..df0c9e4d 100644 --- a/bridgev2/messagestatus.go +++ b/bridgev2/messagestatus.go @@ -20,6 +20,7 @@ import ( type MessageStatusEventInfo struct { RoomID id.RoomID + TransactionID string SourceEventID id.EventID NewEventID id.EventID EventType event.Type @@ -41,6 +42,7 @@ func StatusEventInfoFromEvent(evt *event.Event) *MessageStatusEventInfo { return &MessageStatusEventInfo{ RoomID: evt.RoomID, + TransactionID: evt.Unsigned.TransactionID, SourceEventID: evt.ID, EventType: evt.Type, MessageType: evt.Content.AsMessage().MsgType, @@ -182,9 +184,10 @@ func (ms *MessageStatus) ToMSSEvent(evt *MessageStatusEventInfo) *event.BeeperMe Type: event.RelReference, EventID: evt.SourceEventID, }, - Status: ms.Status, - Reason: ms.ErrorReason, - Message: ms.Message, + TargetTxnID: evt.TransactionID, + Status: ms.Status, + Reason: ms.ErrorReason, + Message: ms.Message, } if ms.InternalError != nil { content.InternalError = ms.InternalError.Error() diff --git a/bridgev2/portal.go b/bridgev2/portal.go index b664c8f6..0fae1724 100644 --- a/bridgev2/portal.go +++ b/bridgev2/portal.go @@ -512,6 +512,13 @@ func (portal *Portal) handleSingleEvent(ctx context.Context, rawEvt any, doneCal }() switch evt := rawEvt.(type) { case *portalMatrixEvent: + isStateRequest := evt.evt.Type == event.BeeperSendState + if isStateRequest { + if err := portal.unwrapBeeperSendState(ctx, evt.evt); err != nil { + portal.sendErrorStatus(ctx, evt.evt, err) + return + } + } res = portal.handleMatrixEvent(ctx, evt.sender, evt.evt) if res.SendMSS { if res.Error != nil { @@ -520,9 +527,21 @@ func (portal *Portal) handleSingleEvent(ctx context.Context, rawEvt any, doneCal portal.sendSuccessStatus(ctx, evt.evt, 0, "") } } - if res.Error != nil && evt.evt.StateKey != nil { + if !isStateRequest && res.Error != nil && evt.evt.StateKey != nil { portal.revertRoomMeta(ctx, evt.evt) } + if isStateRequest && res.Success { + portal.sendRoomMeta( + ctx, + evt.sender.DoublePuppet(ctx), + time.UnixMilli(evt.evt.Timestamp), + evt.evt.Type, + evt.evt.GetStateKey(), + evt.evt.Content.Parsed, + false, + evt.evt.Content.Raw, + ) + } case *portalRemoteEvent: res = portal.handleRemoteEvent(ctx, evt.source, evt.evtType, evt.evt) case *portalCreateEvent: @@ -534,6 +553,29 @@ func (portal *Portal) handleSingleEvent(ctx context.Context, rawEvt any, doneCal } } +func (portal *Portal) unwrapBeeperSendState(ctx context.Context, evt *event.Event) error { + content, ok := evt.Content.Parsed.(*event.BeeperSendStateEventContent) + if !ok { + return fmt.Errorf("%w: %T", ErrUnexpectedParsedContentType, evt.Content.Parsed) + } + evt.Content = content.Content + evt.StateKey = &content.StateKey + evt.Type = event.Type{Type: content.Type, Class: event.StateEventType} + _ = evt.Content.ParseRaw(evt.Type) + mx, ok := portal.Bridge.Matrix.(MatrixConnectorWithArbitraryRoomState) + if !ok { + return fmt.Errorf("matrix connector doesn't support fetching state") + } + prevEvt, err := mx.GetStateEvent(ctx, portal.MXID, evt.Type, evt.GetStateKey()) + if err != nil { + return fmt.Errorf("failed to get prev event: %w", err) + } else if prevEvt != nil { + evt.Unsigned.PrevContent = &prevEvt.Content + evt.Unsigned.PrevSender = prevEvt.Sender + } + return nil +} + func (portal *Portal) FindPreferredLogin(ctx context.Context, user *User, allowRelay bool) (*UserLogin, *database.UserPortal, error) { if portal.Receiver != "" { login, err := portal.Bridge.GetExistingUserLoginByID(ctx, portal.Receiver) diff --git a/event/beeper.go b/event/beeper.go index 95b4a571..94892de7 100644 --- a/event/beeper.go +++ b/event/beeper.go @@ -53,6 +53,8 @@ type BeeperMessageStatusEventContent struct { LastRetry id.EventID `json:"last_retry,omitempty"` + TargetTxnID string `json:"relates_to_txn_id,omitempty"` + MutateEventKey string `json:"mutate_event_key,omitempty"` // Indicates the set of users to whom the event was delivered. If nil, then @@ -90,6 +92,12 @@ type BeeperChatDeleteEventContent struct { DeleteForEveryone bool `json:"delete_for_everyone,omitempty"` } +type BeeperSendStateEventContent struct { + Type string `json:"type"` + StateKey string `json:"state_key"` + Content Content `json:"content"` +} + type IntOrString int func (ios *IntOrString) UnmarshalJSON(data []byte) error { diff --git a/event/content.go b/event/content.go index c0ff51ad..73fb0db5 100644 --- a/event/content.go +++ b/event/content.go @@ -64,6 +64,7 @@ var TypeMap = map[Type]reflect.Type{ BeeperMessageStatus: reflect.TypeOf(BeeperMessageStatusEventContent{}), BeeperTranscription: reflect.TypeOf(BeeperTranscriptionEventContent{}), BeeperDeleteChat: reflect.TypeOf(BeeperChatDeleteEventContent{}), + BeeperSendState: reflect.TypeOf(BeeperSendStateEventContent{}), AccountDataRoomTags: reflect.TypeOf(TagEventContent{}), AccountDataDirectChats: reflect.TypeOf(DirectChatsEventContent{}), diff --git a/event/type.go b/event/type.go index 56ea82f6..4fca07ea 100644 --- a/event/type.go +++ b/event/type.go @@ -237,6 +237,7 @@ var ( BeeperMessageStatus = Type{"com.beeper.message_send_status", MessageEventType} BeeperTranscription = Type{"com.beeper.transcription", MessageEventType} BeeperDeleteChat = Type{"com.beeper.delete_chat", MessageEventType} + BeeperSendState = Type{"com.beeper.send_state", MessageEventType} EventUnstablePollStart = Type{Type: "org.matrix.msc3381.poll.start", Class: MessageEventType} EventUnstablePollResponse = Type{Type: "org.matrix.msc3381.poll.response", Class: MessageEventType}