From 820951cb6e7a3261703c83f4ef8dea1d12e742a6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 10 Aug 2025 23:47:39 +0300 Subject: [PATCH] Add support for disabling link previews via MSC4095 --- formatter.go | 14 ++++++++++++-- portal.go | 29 ++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/formatter.go b/formatter.go index 6452532..2112b04 100644 --- a/formatter.go +++ b/formatter.go @@ -98,6 +98,7 @@ func (portal *Portal) renderDiscordMarkdownOnlyHTML(text string, allowInlineLink const formatterContextPortalKey = "fi.mau.discord.portal" const formatterContextAllowedMentionsKey = "fi.mau.discord.allowed_mentions" const formatterContextInputAllowedMentionsKey = "fi.mau.discord.input_allowed_mentions" +const formatterContextInputAllowedLinkPreviewsKey = "fi.mau.discord.input_allowed_link_previews" func appendIfNotContains(arr []string, newItem string) []string { for _, item := range arr { @@ -221,16 +222,24 @@ var matrixHTMLParser = &format.HTMLParser{ return fmt.Sprintf("||%s||", text) }, LinkConverter: func(text, href string, ctx format.Context) string { + linkPreviews := ctx.ReturnData[formatterContextInputAllowedLinkPreviewsKey].([]string) + allowPreview := linkPreviews == nil || slices.Contains(linkPreviews, href) if text == href { + if !allowPreview { + return fmt.Sprintf("<%s>", text) + } return text } else if !discordLinkRegexFull.MatchString(href) { return fmt.Sprintf("%s (%s)", escapeDiscordMarkdown(text), escapeDiscordMarkdown(href)) + } else if !allowPreview { + return fmt.Sprintf("[%s](<%s>)", escapeDiscordMarkdown(text), href) + } else { + return fmt.Sprintf("[%s](%s)", escapeDiscordMarkdown(text), href) } - return fmt.Sprintf("[%s](%s)", escapeDiscordMarkdown(text), href) }, } -func (portal *Portal) parseMatrixHTML(content *event.MessageEventContent) (string, *discordgo.MessageAllowedMentions) { +func (portal *Portal) parseMatrixHTML(content *event.MessageEventContent, allowedLinkPreviews []string) (string, *discordgo.MessageAllowedMentions) { allowedMentions := &discordgo.MessageAllowedMentions{ Parse: []discordgo.AllowedMentionType{}, Users: []string{}, @@ -238,6 +247,7 @@ func (portal *Portal) parseMatrixHTML(content *event.MessageEventContent) (strin } if content.Format == event.FormatHTML && len(content.FormattedBody) > 0 { ctx := format.NewContext() + ctx.ReturnData[formatterContextInputAllowedLinkPreviewsKey] = allowedLinkPreviews ctx.ReturnData[formatterContextPortalKey] = portal ctx.ReturnData[formatterContextAllowedMentionsKey] = allowedMentions if content.Mentions != nil { diff --git a/portal.go b/portal.go index d5dc879..439cdfd 100644 --- a/portal.go +++ b/portal.go @@ -1544,7 +1544,8 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) { if editMXID := content.GetRelatesTo().GetReplaceID(); editMXID != "" && content.NewContent != nil { edits := portal.bridge.DB.Message.GetByMXID(portal.Key, editMXID) if edits != nil { - discordContent, allowedMentions := portal.parseMatrixHTML(content.NewContent) + newContentRaw, _ := evt.Content.Raw["m.new_content"].(map[string]any) + discordContent, allowedMentions := portal.parseMatrixHTML(content.NewContent, parseAllowedLinkPreviews(newContentRaw)) var err error var msg *discordgo.Message if !isWebhookSend { @@ -1623,7 +1624,7 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) { } switch content.MsgType { case event.MsgText, event.MsgEmote, event.MsgNotice: - sendReq.Content, sendReq.AllowedMentions = portal.parseMatrixHTML(content) + sendReq.Content, sendReq.AllowedMentions = portal.parseMatrixHTML(content, parseAllowedLinkPreviews(evt.Content.Raw)) if content.MsgType == event.MsgEmote { sendReq.Content = fmt.Sprintf("_%s_", sendReq.Content) } @@ -1636,7 +1637,7 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) { filename := content.Body if content.FileName != "" && content.FileName != content.Body { filename = content.FileName - sendReq.Content, sendReq.AllowedMentions = portal.parseMatrixHTML(content) + sendReq.Content, sendReq.AllowedMentions = portal.parseMatrixHTML(content, parseAllowedLinkPreviews(evt.Content.Raw)) } if evt.Content.Raw["page.codeberg.everypizza.msc4193.spoiler"] == true { @@ -1744,6 +1745,28 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) { } } +func parseAllowedLinkPreviews(raw map[string]any) []string { + if raw == nil { + return nil + } + linkPreviews, ok := raw["com.beeper.linkpreviews"].([]any) + if !ok { + return nil + } + allowedLinkPreviews := make([]string, 0, len(linkPreviews)) + for _, preview := range linkPreviews { + previewMap, ok := preview.(map[string]any) + if !ok { + continue + } + matchedURL, _ := previewMap["matched_url"].(string) + if matchedURL != "" { + allowedLinkPreviews = append(allowedLinkPreviews, matchedURL) + } + } + return allowedLinkPreviews +} + func (portal *Portal) sendDeliveryReceipt(eventID id.EventID) { if portal.bridge.Config.Bridge.DeliveryReceipts { err := portal.bridge.Bot.MarkRead(portal.MXID, eventID)