From 9a8ce9b17e560433731eb5efa3cee7ced0b93605 Mon Sep 17 00:00:00 2001
From: vpzomtrrfrt <colin@vpzom.click>
Date: Sun, 9 Jan 2022 14:46:59 -0800
Subject: [PATCH] Reply support for Matrix (#1664)

* Post replies to matrix

* Handle replies from matrix

* Include protocol in canonical ID return

* fmt
---
 bridge/matrix/matrix.go | 79 +++++++++++++++++++++++++++++++++++++++++
 gateway/gateway.go      |  8 ++---
 2 files changed, 83 insertions(+), 4 deletions(-)

diff --git a/bridge/matrix/matrix.go b/bridge/matrix/matrix.go
index e89002b2..739a7923 100644
--- a/bridge/matrix/matrix.go
+++ b/bridge/matrix/matrix.go
@@ -65,6 +65,19 @@ type EditedMessage struct {
 	matrix.TextMessage
 }
 
+type InReplyToRelationContent struct {
+	EventID string `json:"event_id"`
+}
+
+type InReplyToRelation struct {
+	InReplyTo InReplyToRelationContent `json:"m.in_reply_to"`
+}
+
+type ReplyMessage struct {
+	RelatedTo InReplyToRelation `json:"m.relates_to"`
+	matrix.TextMessage
+}
+
 func New(cfg *bridge.Config) bridge.Bridger {
 	b := &Bmatrix{Config: cfg}
 	b.RoomMap = make(map[string]string)
@@ -275,6 +288,38 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) {
 		return resp.EventID, err
 	}
 
+	if msg.ParentValid() {
+		m := ReplyMessage{
+			TextMessage: matrix.TextMessage{
+				MsgType:       "m.text",
+				Body:          username.plain + msg.Text,
+				FormattedBody: username.formatted + helper.ParseMarkdown(msg.Text),
+			},
+		}
+
+		m.RelatedTo = InReplyToRelation{
+			InReplyTo: InReplyToRelationContent{
+				EventID: msg.ParentID,
+			},
+		}
+
+		var (
+			resp *matrix.RespSendEvent
+			err  error
+		)
+
+		err = b.retry(func() error {
+			resp, err = b.mc.SendMessageEvent(channel, "m.room.message", m)
+
+			return err
+		})
+		if err != nil {
+			return "", err
+		}
+
+		return resp.EventID, err
+	}
+
 	// Post normal message with HTML support (eg riot.im)
 	var (
 		resp *matrix.RespSendEvent
@@ -341,6 +386,35 @@ func (b *Bmatrix) handleEdit(ev *matrix.Event, rmsg config.Message) bool {
 	return true
 }
 
+func (b *Bmatrix) handleReply(ev *matrix.Event, rmsg config.Message) bool {
+	relationInterface, present := ev.Content["m.relates_to"]
+	if !present {
+		return false
+	}
+
+	var relation InReplyToRelation
+	if err := interface2Struct(relationInterface, &relation); err != nil {
+		// probably fine
+		return false
+	}
+
+	body := rmsg.Text
+	for strings.HasPrefix(body, "> ") {
+		lineIdx := strings.IndexRune(body, '\n')
+		if lineIdx == -1 {
+			body = ""
+		} else {
+			body = body[(lineIdx + 1):]
+		}
+	}
+
+	rmsg.Text = body
+	rmsg.ParentID = relation.InReplyTo.EventID
+	b.Remote <- rmsg
+
+	return true
+}
+
 func (b *Bmatrix) handleMemberChange(ev *matrix.Event) {
 	// Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information
 	if ev.Content["membership"] == "join" {
@@ -403,6 +477,11 @@ func (b *Bmatrix) handleEvent(ev *matrix.Event) {
 			return
 		}
 
+		// Is it a reply?
+		if b.handleReply(ev, rmsg) {
+			return
+		}
+
 		// Do we have attachments
 		if b.containsAttachment(ev.Content) {
 			err := b.handleDownloadFile(&rmsg, ev.Content)
diff --git a/gateway/gateway.go b/gateway/gateway.go
index 1c6c21c3..85f5a187 100644
--- a/gateway/gateway.go
+++ b/gateway/gateway.go
@@ -66,7 +66,7 @@ func New(rootLogger *logrus.Logger, cfg *config.Gateway, r *Router) *Gateway {
 func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
 	ID := protocol + " " + mID
 	if gw.Messages.Contains(ID) {
-		return mID
+		return ID
 	}
 
 	// If not keyed, iterate through cache for downstream, and infer upstream.
@@ -75,7 +75,7 @@ func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
 		ids := v.([]*BrMsgID)
 		for _, downstreamMsgObj := range ids {
 			if ID == downstreamMsgObj.ID {
-				return strings.Replace(mid.(string), protocol+" ", "", 1)
+				return mid.(string)
 			}
 		}
 	}
@@ -454,9 +454,9 @@ func (gw *Gateway) SendMessage(
 		msg.Channel = rmsg.Channel
 	}
 
-	msg.ParentID = gw.getDestMsgID(rmsg.Protocol+" "+canonicalParentMsgID, dest, channel)
+	msg.ParentID = gw.getDestMsgID(canonicalParentMsgID, dest, channel)
 	if msg.ParentID == "" {
-		msg.ParentID = canonicalParentMsgID
+		msg.ParentID = strings.Replace(canonicalParentMsgID, dest.Protocol+" ", "", 1)
 	}
 
 	// if the parentID is still empty and we have a parentID set in the original message