diff --git a/attachments.go b/attachments.go index 4d248bd..7c34768 100644 --- a/attachments.go +++ b/attachments.go @@ -10,6 +10,7 @@ import ( "time" "github.com/bwmarrin/discordgo" + "github.com/gabriel-vasile/mimetype" "maunium.net/go/mautrix" "maunium.net/go/mautrix/appservice" @@ -75,6 +76,9 @@ func (br *DiscordBridge) uploadMatrixAttachment(intent *appservice.IntentAPI, da dbFile.URL = url dbFile.ID = attachmentID dbFile.Size = len(data) + if mime == "" { + mime = mimetype.Detect(data).String() + } if strings.HasPrefix(mime, "image/") { cfg, _, _ := image.DecodeConfig(bytes.NewReader(data)) dbFile.Width = cfg.Width diff --git a/config/bridge.go b/config/bridge.go index fb5cf33..797eedc 100644 --- a/config/bridge.go +++ b/config/bridge.go @@ -42,6 +42,7 @@ type BridgeConfig struct { MessageErrorNotices bool `yaml:"message_error_notices"` RestrictedRooms bool `yaml:"restricted_rooms"` AutojoinThreadOnOpen bool `yaml:"autojoin_thread_on_open"` + EmbedFieldsAsTables bool `yaml:"embed_fields_as_tables"` MuteChannelsOnCreate bool `yaml:"mute_channels_on_create"` SyncDirectChatList bool `yaml:"sync_direct_chat_list"` ResendBridgeInfo bool `yaml:"resend_bridge_info"` diff --git a/config/upgrade.go b/config/upgrade.go index 99b892e..625e4f7 100644 --- a/config/upgrade.go +++ b/config/upgrade.go @@ -37,6 +37,7 @@ func DoUpgrade(helper *up.Helper) { helper.Copy(up.Bool, "bridge", "message_error_notices") helper.Copy(up.Bool, "bridge", "restricted_rooms") helper.Copy(up.Bool, "bridge", "autojoin_thread_on_open") + helper.Copy(up.Bool, "bridge", "embed_fields_as_tables") helper.Copy(up.Bool, "bridge", "mute_channels_on_create") helper.Copy(up.Bool, "bridge", "sync_direct_chat_list") helper.Copy(up.Bool, "bridge", "resend_bridge_info") diff --git a/example-config.yaml b/example-config.yaml index 52fb084..4f2dd41 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -115,6 +115,9 @@ bridge: # Should the bridge automatically join the user to threads on Discord when the thread is opened on Matrix? # This only works with clients that support thread read receipts (MSC3771 added in Matrix v1.4). autojoin_thread_on_open: true + # Should inline fields in Discord embeds be bridged as HTML tables to Matrix? + # Tables aren't supported in all clients, but are the only way to emulate the Discord inline field UI. + embed_fields_as_tables: true # Should guild channels be muted when the portal is created? This only meant for single-user instances, # it won't mute it for all users if there are multiple Matrix users in the same Discord guild. mute_channels_on_create: false diff --git a/formatter.go b/formatter.go index 2f2cfd6..4069972 100644 --- a/formatter.go +++ b/formatter.go @@ -34,6 +34,10 @@ var discordExtensions = goldmark.WithExtensions(mdext.SimpleSpoiler, mdext.Disco var escapeFixer = regexp.MustCompile(`\\(__[^_]|\*\*[^*])`) func (portal *Portal) renderDiscordMarkdown(text string) event.MessageEventContent { + return format.HTMLToContent(portal.renderDiscordMarkdownOnlyHTML(text)) +} + +func (portal *Portal) renderDiscordMarkdownOnlyHTML(text string) string { text = escapeFixer.ReplaceAllStringFunc(text, func(s string) string { return s[:2] + `\` + s[2:] }) @@ -45,7 +49,13 @@ func (portal *Portal) renderDiscordMarkdown(text string) event.MessageEventConte format.Extensions, format.HTMLOptions, discordExtensions, goldmark.WithExtensions(&DiscordTag{portal}), ) - return format.RenderMarkdownCustom(text, mdRenderer) + + var buf strings.Builder + err := mdRenderer.Convert([]byte(text), &buf) + if err != nil { + panic(fmt.Errorf("markdown parser errored: %w", err)) + } + return format.UnwrapSingleParagraph(buf.String()) } const formatterContextUserKey = "fi.mau.discord.user" diff --git a/go.mod b/go.mod index e1bca7d..cd0a3b3 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/bwmarrin/discordgo v0.26.1 + github.com/gabriel-vasile/mimetype v1.4.1 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.7 diff --git a/go.sum b/go.sum index b0ba0f5..7ff060f 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q= +github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= @@ -49,15 +51,20 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/portal.go b/portal.go index 2c9825c..e907956 100644 --- a/portal.go +++ b/portal.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "html" "reflect" "strconv" "strings" @@ -12,6 +13,7 @@ import ( "maunium.net/go/mautrix/bridge/status" "maunium.net/go/mautrix/crypto/attachment" + "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/util/variationselector" "github.com/bwmarrin/discordgo" @@ -654,6 +656,139 @@ func (portal *Portal) handleDiscordAttachment(intent *appservice.IntentAPI, att return portal.handleDiscordFile("attachment", intent, att.ID, att.URL, content, ts, threadRelation) } +const ( + embedHTMLWrapper = `
%s` + embedHTMLWrapperColor = `
%s` + embedHTMLAuthorWithImage = `` + embedHTMLAuthorPlain = `` + embedHTMLAuthorLink = `%s` + embedHTMLTitleWithLink = `` + embedHTMLTitlePlain = `` + embedHTMLDescription = `` + embedHTMLFieldName = `