Add support for reactions from Matrix

This commit is contained in:
Tulir Asokan 2023-07-15 01:06:49 +03:00
parent 376f908a03
commit b5257f53d3
3 changed files with 85 additions and 5 deletions

View file

@ -4,7 +4,7 @@
* [x] Plain text * [x] Plain text
* [ ] Media/files * [ ] Media/files
* [x] Replies (RCS) * [x] Replies (RCS)
* [ ] Reactions (RCS) * [x] Reactions (RCS)
* [ ] Typing notifications (RCS) * [ ] Typing notifications (RCS)
* [ ] Read receipts (RCS) * [ ] Read receipts (RCS)
* Google Messages → Matrix * Google Messages → Matrix
@ -12,9 +12,9 @@
* [x] Plain text * [x] Plain text
* [x] Media/files * [x] Media/files
* [x] Replies (RCS) * [x] Replies (RCS)
* [ ] Reactions * [x] Reactions (RCS)
* [ ] Typing notifications * [ ] Typing notifications (RCS)
* [ ] Read receipts * [ ] Read receipts (RCS)
* Misc * Misc
* [x] Automatic portal creation * [x] Automatic portal creation
* [x] After login * [x] After login

View file

@ -39,6 +39,7 @@ var (
errUnexpectedParsedContentType = errors.New("unexpected parsed content type") errUnexpectedParsedContentType = errors.New("unexpected parsed content type")
errUnknownMsgType = errors.New("unknown msgtype") errUnknownMsgType = errors.New("unknown msgtype")
errMediaUnsupportedType = errors.New("unsupported media type") errMediaUnsupportedType = errors.New("unsupported media type")
errTargetNotFound = errors.New("target event not found")
errMessageTakingLong = errors.New("bridging the message is taking longer than usual") errMessageTakingLong = errors.New("bridging the message is taking longer than usual")
) )
@ -73,6 +74,8 @@ func errorToStatusReason(err error) (reason event.MessageStatusReason, status ev
return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, err.Error() return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, err.Error()
case errors.Is(err, context.DeadlineExceeded): case errors.Is(err, context.DeadlineExceeded):
return event.MessageStatusTooOld, event.MessageStatusRetriable, false, true, "handling the message took too long and was cancelled" return event.MessageStatusTooOld, event.MessageStatusRetriable, false, true, "handling the message took too long and was cancelled"
case errors.Is(err, errTargetNotFound):
return event.MessageStatusGenericError, event.MessageStatusFail, true, false, ""
case errors.As(err, &ose): case errors.As(err, &ose):
return event.MessageStatusNetworkError, event.MessageStatusFail, true, true, ose.HumanError() return event.MessageStatusNetworkError, event.MessageStatusFail, true, true, ose.HumanError()
default: default:
@ -187,6 +190,9 @@ func (portal *Portal) sendMessageMetrics(evt *event.Event, err error, part strin
portal.log.Debugfln("Handled Matrix %s %s", msgType, evtDescription) portal.log.Debugfln("Handled Matrix %s %s", msgType, evtDescription)
portal.sendDeliveryReceipt(evt.ID) portal.sendDeliveryReceipt(evt.ID)
portal.bridge.SendMessageSuccessCheckpoint(evt, status.MsgStepRemote, ms.getRetryNum()) portal.bridge.SendMessageSuccessCheckpoint(evt, status.MsgStepRemote, ms.getRetryNum())
if msgType != "message" {
portal.sendStatusEvent(origEvtID, evt.ID, nil)
}
if prevNotice := ms.popNoticeID(); prevNotice != "" { if prevNotice := ms.popNoticeID(); prevNotice != "" {
_, _ = portal.MainIntent().RedactEvent(portal.MXID, prevNotice, mautrix.ReqRedact{ _, _ = portal.MainIntent().RedactEvent(portal.MXID, prevNotice, mautrix.ReqRedact{
Reason: "error resolved", Reason: "error resolved",

View file

@ -1174,7 +1174,10 @@ func (portal *Portal) uploadMedia(intent *appservice.IntentAPI, data []byte, con
func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event, timings messageTimings) { func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event, timings messageTimings) {
ms := metricSender{portal: portal, timings: &timings} ms := metricSender{portal: portal, timings: &timings}
log := portal.zlog.With().Str("event_id", evt.ID.String()).Logger() log := portal.zlog.With().
Str("event_id", evt.ID.String()).
Str("action", "handle matrix message").
Logger()
ctx := log.WithContext(context.TODO()) ctx := log.WithContext(context.TODO())
log.Debug().Dur("age", timings.totalReceive).Msg("Handling Matrix message") log.Debug().Dur("age", timings.totalReceive).Msg("Handling Matrix message")
@ -1225,7 +1228,78 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event, timing
} }
func (portal *Portal) HandleMatrixReaction(sender *User, evt *event.Event) { func (portal *Portal) HandleMatrixReaction(sender *User, evt *event.Event) {
err := portal.handleMatrixReaction(sender, evt)
go portal.sendMessageMetrics(evt, err, "Error sending", nil)
}
func (portal *Portal) handleMatrixReaction(sender *User, evt *event.Event) error {
content, ok := evt.Content.Parsed.(*event.ReactionEventContent)
if !ok {
return fmt.Errorf("unexpected parsed content type %T", evt.Content.Parsed)
}
log := portal.zlog.With().
Str("event_id", evt.ID.String()).
Str("target_event_id", content.RelatesTo.EventID.String()).
Str("action", "handle matrix reaction").
Logger()
ctx := log.WithContext(context.Background())
log.Debug().Msg("Handling Matrix reaction")
msg, err := portal.bridge.DB.Message.GetByMXID(ctx, content.RelatesTo.EventID)
if err != nil {
log.Err(err).Msg("Failed to get reaction target event")
return fmt.Errorf("failed to get event from database")
} else if msg == nil {
return errTargetNotFound
}
existingReaction, err := portal.bridge.DB.Reaction.GetByID(ctx, portal.Key, msg.ID, portal.SelfUserID)
if err != nil {
log.Err(err).Msg("Failed to get existing reaction")
return fmt.Errorf("failed to get existing reaction from database")
}
emoji := variationselector.Remove(content.RelatesTo.Key)
action := binary.Reaction_ADD
if existingReaction != nil {
action = binary.Reaction_SWITCH
}
resp, err := sender.Client.Messages.React(&binary.SendReactionPayload{
MessageID: msg.ID,
ReactionData: &binary.ReactionData{
Unicode: emoji,
Type: binary.UnicodeToEmojiType(emoji),
},
Action: action,
})
if err != nil {
return fmt.Errorf("failed to send reaction: %w", err)
} else if !resp.Success {
return fmt.Errorf("got non-success response")
}
if existingReaction == nil {
existingReaction = portal.bridge.DB.Reaction.New()
existingReaction.Chat = portal.Key
existingReaction.MessageID = msg.ID
existingReaction.Sender = portal.SelfUserID
} else if sender.DoublePuppetIntent != nil {
_, err = sender.DoublePuppetIntent.RedactEvent(portal.MXID, existingReaction.MXID)
if err != nil {
log.Err(err).Msg("Failed to redact old reaction with double puppet after new Matrix reaction")
}
} else {
_, err = portal.MainIntent().RedactEvent(portal.MXID, existingReaction.MXID)
if err != nil {
log.Err(err).Msg("Failed to redact old reaction with main intent after new Matrix reaction")
}
}
existingReaction.Reaction = emoji
existingReaction.MXID = evt.ID
err = existingReaction.Insert(ctx)
if err != nil {
log.Err(err).Msg("Failed to save reaction from Matrix to database")
}
return nil
} }
func (portal *Portal) Delete() { func (portal *Portal) Delete() {