Browse Source

Use convert replies to embeds when sending via webhook

Fixes #68
Tulir Asokan 2 years ago
parent
commit
35f534affa
1 changed files with 76 additions and 10 deletions
  1. 76 10
      portal.go

+ 76 - 10
portal.go

@@ -1305,6 +1305,61 @@ func (portal *Portal) getRelayUserMeta(sender *User) (name, avatarURL string) {
 	return
 }
 
+const replyEmbedMaxLines = 1
+const replyEmbedMaxChars = 72
+
+func cutBody(body string) string {
+	lines := strings.Split(strings.TrimSpace(body), "\n")
+	var output string
+	for i, line := range lines {
+		if i >= replyEmbedMaxLines {
+			output += " […]"
+			break
+		}
+		if i > 0 {
+			output += "\n"
+		}
+		output += line
+		if len(output) > replyEmbedMaxChars {
+			output = output[:replyEmbedMaxChars] + "…"
+			break
+		}
+	}
+	return output
+}
+
+func (portal *Portal) convertReplyMessageToEmbed(eventID id.EventID, url string) (*discordgo.MessageEmbed, error) {
+	evt, err := portal.MainIntent().GetEvent(portal.MXID, eventID)
+	if err != nil {
+		return nil, fmt.Errorf("failed to fetch event: %w", err)
+	}
+	err = evt.Content.ParseRaw(evt.Type)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse event content: %w", err)
+	}
+	content, ok := evt.Content.Parsed.(*event.MessageEventContent)
+	if !ok {
+		return nil, fmt.Errorf("unsupported event type %s / %T", evt.Type.String(), evt.Content.Parsed)
+	}
+	content.RemoveReplyFallback()
+	var targetUser string
+
+	puppet := portal.bridge.GetPuppetByMXID(evt.Sender)
+	if puppet != nil {
+		targetUser = fmt.Sprintf("<@%s>", puppet.ID)
+	} else if user := portal.bridge.GetUserByMXID(evt.Sender); user != nil && user.DiscordID != "" {
+		targetUser = fmt.Sprintf("<@%s>", user.DiscordID)
+	} else if member := portal.bridge.StateStore.GetMember(portal.MXID, evt.Sender); member != nil && member.Displayname != "" {
+		targetUser = member.Displayname
+	} else {
+		targetUser = evt.Sender.String()
+	}
+	body := escapeDiscordMarkdown(cutBody(content.Body))
+	body = fmt.Sprintf("**[Replying to](%s) %s**\n%s", url, targetUser, body)
+	embed := &discordgo.MessageEmbed{Description: body}
+	return embed, nil
+}
+
 func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
 	if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver {
 		go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring")
@@ -1323,6 +1378,7 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
 		go portal.sendMessageMetrics(evt, errUserNotLoggedIn, "Ignoring")
 		return
 	}
+	isWebhookSend := sess == nil
 	var threadID string
 
 	if editMXID := content.GetRelatesTo().GetReplaceID(); editMXID != "" && content.NewContent != nil {
@@ -1330,7 +1386,7 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
 		if edits != nil {
 			discordContent, allowedMentions := portal.parseMatrixHTML(content.NewContent)
 			var err error
-			if sess != nil {
+			if !isWebhookSend {
 				// TODO save edit in message table
 				_, err = sess.ChannelMessageEdit(edits.DiscordProtoChannelID(), edits.DiscordID, discordContent)
 			} else {
@@ -1349,7 +1405,7 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
 		if existingThread != nil {
 			threadID = existingThread.ID
 		} else {
-			if sess == nil {
+			if isWebhookSend {
 				// TODO start thread with bot?
 				go portal.sendMessageMetrics(evt, errCantStartThread, "Dropping")
 				return
@@ -1378,17 +1434,27 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
 		}
 	}
 
-	switch content.MsgType {
-	case event.MsgText, event.MsgEmote, event.MsgNotice:
-		if replyToMXID := content.RelatesTo.GetNonFallbackReplyTo(); replyToMXID != "" {
-			replyTo := portal.bridge.DB.Message.GetByMXID(portal.Key, replyToMXID)
-			if replyTo != nil && replyTo.ThreadID == threadID {
+	if replyToMXID := content.RelatesTo.GetNonFallbackReplyTo(); replyToMXID != "" {
+		replyTo := portal.bridge.DB.Message.GetByMXID(portal.Key, replyToMXID)
+		if replyTo != nil && replyTo.ThreadID == threadID {
+			if isWebhookSend {
+				messageURL := fmt.Sprintf("https://discord.com/channels/%s/%s/%s", portal.GuildID, channelID, replyTo.DiscordID)
+				embed, err := portal.convertReplyMessageToEmbed(replyTo.MXID, messageURL)
+				if err != nil {
+					portal.log.Warn().Err(err).Msg("Failed to convert reply message to embed for webhook send")
+				} else if embed != nil {
+					sendReq.Embeds = []*discordgo.MessageEmbed{embed}
+				}
+			} else {
 				sendReq.Reference = &discordgo.MessageReference{
 					ChannelID: channelID,
 					MessageID: replyTo.DiscordID,
 				}
 			}
 		}
+	}
+	switch content.MsgType {
+	case event.MsgText, event.MsgEmote, event.MsgNotice:
 		sendReq.Content, sendReq.AllowedMentions = portal.parseMatrixHTML(content)
 	case event.MsgAudio, event.MsgFile, event.MsgImage, event.MsgVideo:
 		data, err := downloadMatrixAttachment(portal.MainIntent(), content)
@@ -1402,7 +1468,7 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
 			sendReq.Content, sendReq.AllowedMentions = portal.parseMatrixHTML(content)
 		}
 
-		if sess != nil && sess.IsUser {
+		if !isWebhookSend && sess.IsUser {
 			att := &discordgo.MessageAttachment{
 				ID:          "0",
 				Filename:    filename,
@@ -1438,7 +1504,7 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
 		go portal.sendMessageMetrics(evt, fmt.Errorf("%w %q", errUnknownMsgType, content.MsgType), "Ignoring")
 		return
 	}
-	if sess != nil {
+	if !isWebhookSend {
 		// AllowedMentions must not be set for real users, and it's also not that useful for personal bots.
 		// It's only important for relaying, where the webhook may have higher permissions than the user on Matrix.
 		sendReq.AllowedMentions = nil
@@ -1455,7 +1521,7 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
 	sendReq.Nonce = generateNonce()
 	var msg *discordgo.Message
 	var err error
-	if sess != nil {
+	if !isWebhookSend {
 		msg, err = sess.ChannelMessageSendComplex(channelID, &sendReq)
 	} else {
 		username, avatarURL := portal.getRelayUserMeta(sender)