diff --git a/pkg/attachment/attachment.go b/pkg/attachment/attachment.go
deleted file mode 100644
index 5890029..0000000
--- a/pkg/attachment/attachment.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// mautrix-discord - A Matrix-Discord puppeting bridge.
-// Copyright (C) 2026 Tulir Asokan
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package attachment
-
-import (
- "maunium.net/go/mautrix/event"
- "maunium.net/go/mautrix/id"
-)
-
-// TODO(skip): These types are only in a leaf package to avoid import cycles.
-// Perhaps figure out a better way to structure this so that this package is unnecessary.
-
-type AttachmentReupload struct {
- DownloadingURL string
- FileName string
- MimeType string
-}
-
-type ReuploadedAttachment struct {
- AttachmentReupload
- DownloadedSize int
- MXC id.ContentURIString
- // This can be nil if the room isn't encrypted.
- EncryptedFile *event.EncryptedFileInfo
-}
diff --git a/pkg/connector/attachments.go b/pkg/connector/attachments.go
deleted file mode 100644
index c552deb..0000000
--- a/pkg/connector/attachments.go
+++ /dev/null
@@ -1,104 +0,0 @@
-// mautrix-discord - A Matrix-Discord puppeting bridge.
-// Copyright (C) 2026 Tulir Asokan
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package connector
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "net/http"
- "net/url"
- "path"
- "strconv"
-
- "github.com/bwmarrin/discordgo"
- "maunium.net/go/mautrix/bridgev2"
-
- "go.mau.fi/mautrix-discord/pkg/attachment"
-)
-
-func downloadDiscordAttachment(cli *http.Client, url string, maxSize int64) ([]byte, error) {
- req, err := http.NewRequest(http.MethodGet, url, nil)
- if err != nil {
- return nil, err
- }
- for key, value := range discordgo.DroidDownloadHeaders {
- req.Header.Set(key, value)
- }
-
- resp, err := cli.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- if resp.StatusCode > 300 {
- data, _ := io.ReadAll(resp.Body)
- return nil, fmt.Errorf("unexpected status %d downloading %s: %s", resp.StatusCode, url, data)
- }
- if resp.Header.Get("Content-Length") != "" {
- length, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
- if err != nil {
- return nil, fmt.Errorf("failed to parse content length: %w", err)
- } else if length > maxSize {
- return nil, fmt.Errorf("attachment too large (%d > %d)", length, maxSize)
- }
- return io.ReadAll(resp.Body)
- } else {
- var mbe *http.MaxBytesError
- data, err := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxSize))
- if err != nil && errors.As(err, &mbe) {
- return nil, fmt.Errorf("attachment too large (over %d)", maxSize)
- }
- return data, err
- }
-}
-
-func (d *DiscordConnector) ReuploadMedia(ctx context.Context, intent bridgev2.MatrixAPI, portal *bridgev2.Portal, upload attachment.AttachmentReupload) (*attachment.ReuploadedAttachment, error) {
- // TODO(skip): Do we need to check if we've already downloaded this media before?
- // TODO(skip): Read a maximum size from the config.
- data, err := downloadDiscordAttachment(http.DefaultClient, upload.DownloadingURL, 1_024*1_024*50)
- if err != nil {
- return nil, fmt.Errorf("couldn't download attachment for reupload: %w", err)
- }
-
- if upload.FileName == "" {
- url, err := url.Parse(upload.DownloadingURL)
- if err != nil {
- return nil, fmt.Errorf("couldn't parse URL to download for media reupload: %w", err)
- }
- fileName := path.Base(url.Path)
- upload.FileName = fileName
- }
-
- if upload.MimeType == "" {
- mime := http.DetectContentType(data)
- upload.MimeType = mime
- }
-
- mxc, file, err := intent.UploadMedia(ctx, portal.MXID, data, upload.FileName, upload.MimeType)
- if err != nil {
- return nil, err
- }
-
- return &attachment.ReuploadedAttachment{
- AttachmentReupload: upload,
- DownloadedSize: len(data),
- MXC: mxc,
- EncryptedFile: file,
- }, nil
-}
diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go
index f14e151..b49354d 100644
--- a/pkg/connector/backfill.go
+++ b/pkg/connector/backfill.go
@@ -100,7 +100,7 @@ func (dc *DiscordClient) FetchMessages(ctx context.Context, fetchParams bridgev2
converted = append(converted, &bridgev2.BackfillMessage{
ID: networkid.MessageID(msg.ID),
- ConvertedMessage: dc.connector.MsgConv.ToMatrix(ctx, fetchParams.Portal, intent, dc.UserLogin, msg),
+ ConvertedMessage: dc.connector.MsgConv.ToMatrix(ctx, fetchParams.Portal, intent, dc.UserLogin, dc.Session, msg),
Sender: sender,
Timestamp: ts,
StreamOrder: streamOrder,
diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go
index 10a625f..d866e49 100644
--- a/pkg/connector/connector.go
+++ b/pkg/connector/connector.go
@@ -30,14 +30,21 @@ type DiscordConnector struct {
MsgConv *msgconv.MessageConverter
}
-var _ bridgev2.NetworkConnector = (*DiscordConnector)(nil)
+var (
+ _ bridgev2.NetworkConnector = (*DiscordConnector)(nil)
+ _ bridgev2.MaxFileSizeingNetwork = (*DiscordConnector)(nil)
+)
func (d *DiscordConnector) Init(bridge *bridgev2.Bridge) {
d.Bridge = bridge
- d.MsgConv = msgconv.NewMessageConverter(bridge, d.ReuploadMedia)
+ d.MsgConv = msgconv.NewMessageConverter(bridge)
d.setUpProvisioningAPIs()
}
+func (d *DiscordConnector) SetMaxFileSize(maxSize int64) {
+ d.MsgConv.MaxFileSize = maxSize
+}
+
func (d *DiscordConnector) Start(ctx context.Context) error {
return nil
}
diff --git a/pkg/connector/handlediscord.go b/pkg/connector/handlediscord.go
index 67227e4..eb328dd 100644
--- a/pkg/connector/handlediscord.go
+++ b/pkg/connector/handlediscord.go
@@ -63,7 +63,7 @@ var (
)
func (m *DiscordMessage) ConvertMessage(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI) (*bridgev2.ConvertedMessage, error) {
- return m.Client.connector.MsgConv.ToMatrix(ctx, portal, intent, m.Client.UserLogin, m.Data), nil
+ return m.Client.connector.MsgConv.ToMatrix(ctx, portal, intent, m.Client.UserLogin, m.Client.Session, m.Data), nil
}
func (m *DiscordMessage) GetID() networkid.MessageID {
diff --git a/pkg/msgconv/attachments.go b/pkg/msgconv/attachments.go
new file mode 100644
index 0000000..72a7ae9
--- /dev/null
+++ b/pkg/msgconv/attachments.go
@@ -0,0 +1,147 @@
+// mautrix-discord - A Matrix-Discord puppeting bridge.
+// Copyright (C) 2026 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package msgconv
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "path"
+
+ "github.com/bwmarrin/discordgo"
+ "github.com/rs/zerolog"
+ "maunium.net/go/mautrix/bridgev2"
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+)
+
+type ReuploadedAttachment struct {
+ MXC id.ContentURIString
+ File *event.EncryptedFileInfo
+ Size int
+ FileName string
+ MimeType string
+}
+
+func (d *MessageConverter) ReuploadUnknownMedia(
+ ctx context.Context,
+ url string,
+ allowEncryption bool,
+) (*ReuploadedAttachment, error) {
+ return d.ReuploadMedia(ctx, url, "", "", -1, allowEncryption)
+}
+
+func mib(size int64) float64 {
+ return float64(size) / 1024 / 1024
+}
+
+func (d *MessageConverter) ReuploadMedia(
+ ctx context.Context,
+ downloadURL string,
+ mimeType string,
+ fileName string,
+ estimatedSize int,
+ allowEncryption bool,
+) (*ReuploadedAttachment, error) {
+ if fileName == "" {
+ parsedURL, err := url.Parse(downloadURL)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't parse URL to detect file name: %w", err)
+ }
+ fileName = path.Base(parsedURL.Path)
+ }
+
+ sess := ctx.Value(contextKeyDiscordClient).(*discordgo.Session)
+ httpClient := sess.Client
+ intent := ctx.Value(contextKeyIntent).(bridgev2.MatrixAPI)
+ var roomID id.RoomID
+ if allowEncryption {
+ roomID = ctx.Value(contextKeyPortal).(*bridgev2.Portal).MXID
+ }
+
+ req, err := http.NewRequest(http.MethodGet, downloadURL, nil)
+ if err != nil {
+ return nil, err
+ }
+ if sess.IsUser {
+ for key, value := range discordgo.DroidDownloadHeaders {
+ req.Header.Set(key, value)
+ }
+ }
+
+ resp, err := httpClient.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode > 300 {
+ errBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
+ logEvt := zerolog.Ctx(ctx).Error().
+ Str("media_url", downloadURL).
+ Int("status_code", resp.StatusCode)
+ if json.Valid(errBody) {
+ logEvt.RawJSON("error_json", errBody)
+ } else {
+ logEvt.Bytes("error_body", errBody)
+ }
+ logEvt.Msg("Media download failed")
+ return nil, fmt.Errorf("%w: unexpected status code %d", bridgev2.ErrMediaDownloadFailed, resp.StatusCode)
+ } else if resp.ContentLength > d.MaxFileSize {
+ return nil, fmt.Errorf("%w (%.2f MiB > %.2f MiB)", bridgev2.ErrMediaTooLarge, mib(resp.ContentLength), mib(d.MaxFileSize))
+ }
+
+ requireFile := mimeType == ""
+ var size int64
+ mxc, file, err := intent.UploadMediaStream(ctx, roomID, int64(estimatedSize), requireFile, func(file io.Writer) (*bridgev2.FileStreamResult, error) {
+ var mbe *http.MaxBytesError
+ size, err = io.Copy(file, http.MaxBytesReader(nil, resp.Body, d.MaxFileSize))
+ if err != nil {
+ if errors.As(err, &mbe) {
+ return nil, fmt.Errorf("%w (over %.2f MiB)", bridgev2.ErrMediaTooLarge, mib(d.MaxFileSize))
+ }
+ return nil, err
+ }
+ if mimeType == "" {
+ mimeBuf := make([]byte, 512)
+ n, err := file.(*os.File).ReadAt(mimeBuf, 0)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't read file for mime detection: %w", err)
+ }
+ mimeType = http.DetectContentType(mimeBuf[:n])
+ }
+ return &bridgev2.FileStreamResult{
+ FileName: fileName,
+ MimeType: mimeType,
+ }, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return &ReuploadedAttachment{
+ Size: int(size),
+ MXC: mxc,
+ File: file,
+ FileName: fileName,
+ MimeType: mimeType,
+ }, nil
+}
diff --git a/pkg/msgconv/from-discord.go b/pkg/msgconv/from-discord.go
index b99ddfe..5dfa9dd 100644
--- a/pkg/msgconv/from-discord.go
+++ b/pkg/msgconv/from-discord.go
@@ -26,22 +26,36 @@ import (
"github.com/bwmarrin/discordgo"
"github.com/rs/zerolog"
+ "go.mau.fi/util/exmaps"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/networkid"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"
- "go.mau.fi/mautrix-discord/pkg/attachment"
"go.mau.fi/mautrix-discord/pkg/discordid"
)
+type contextKey int
+
+const (
+ contextKeyPortal contextKey = iota
+ contextKeyIntent
+ contextKeyUserLogin
+ contextKeyDiscordClient
+)
+
func (mc *MessageConverter) ToMatrix(
ctx context.Context,
portal *bridgev2.Portal,
intent bridgev2.MatrixAPI,
source *bridgev2.UserLogin,
+ session *discordgo.Session,
msg *discordgo.Message,
) *bridgev2.ConvertedMessage {
+ ctx = context.WithValue(ctx, contextKeyUserLogin, source)
+ ctx = context.WithValue(ctx, contextKeyIntent, intent)
+ ctx = context.WithValue(ctx, contextKeyPortal, portal)
+ ctx = context.WithValue(ctx, contextKeyDiscordClient, session)
predictedLength := len(msg.Attachments) + len(msg.StickerItems)
if msg.Content != "" {
predictedLength++
@@ -56,28 +70,26 @@ func (mc *MessageConverter) ToMatrix(
Str("message_id", msg.ID).
Logger().WithContext(ctx)
log := zerolog.Ctx(ctx)
- handledIDs := make(map[string]struct{})
+ handledIDs := make(exmaps.Set[string])
for _, att := range msg.Attachments {
- if _, handled := handledIDs[att.ID]; handled {
+ if !handledIDs.Add(att.ID) {
continue
}
- handledIDs[att.ID] = struct{}{}
log := log.With().Str("attachment_id", att.ID).Logger()
- if part := mc.renderDiscordAttachment(log.WithContext(ctx), intent, portal, att); part != nil {
+ if part := mc.renderDiscordAttachment(log.WithContext(ctx), att); part != nil {
parts = append(parts, part)
}
}
for _, sticker := range msg.StickerItems {
- if _, handled := handledIDs[sticker.ID]; handled {
+ if !handledIDs.Add(sticker.ID) {
continue
}
- handledIDs[sticker.ID] = struct{}{}
log := log.With().Str("sticker_id", sticker.ID).Logger()
- if part := mc.renderDiscordSticker(log.WithContext(ctx), intent, sticker); part != nil {
+ if part := mc.renderDiscordSticker(log.WithContext(ctx), sticker); part != nil {
parts = append(parts, part)
}
}
@@ -88,17 +100,16 @@ func (mc *MessageConverter) ToMatrix(
continue
}
// Discord deduplicates embeds by URL. It makes things easier for us too.
- if _, handled := handledIDs[embed.URL]; handled {
+ if !handledIDs.Add(embed.URL) {
continue
}
- handledIDs[embed.URL] = struct{}{}
log := log.With().
Str("computed_embed_type", "video").
Str("embed_type", string(embed.Type)).
Int("embed_index", i).
Logger()
- part := mc.renderDiscordVideoEmbed(log.WithContext(ctx), intent, portal, embed)
+ part := mc.renderDiscordVideoEmbed(log.WithContext(ctx), embed)
if part != nil {
parts = append(parts, part)
}
@@ -235,7 +246,7 @@ func (mc *MessageConverter) renderDiscordTextMessage(ctx context.Context, intent
len(msg.MessageSnapshots) > 0 &&
msg.MessageSnapshots[0].Message != nil {
// Bridge forwarded messages.
- htmlParts = append(htmlParts, mc.forwardedMessageHtmlPart(ctx, portal, source, msg))
+ htmlParts = append(htmlParts, mc.forwardedMessageHTMLPart(ctx, portal, source, msg))
}
previews := make([]*event.BeeperLinkPreview, 0)
@@ -251,10 +262,10 @@ func (mc *MessageConverter) renderDiscordTextMessage(ctx context.Context, intent
switch getEmbedType(msg, embed) {
case EmbedRich:
log := with.Str("computed_embed_type", "rich").Logger()
- htmlParts = append(htmlParts, mc.renderDiscordRichEmbed(log.WithContext(ctx), intent, portal, embed))
+ htmlParts = append(htmlParts, mc.renderDiscordRichEmbed(log.WithContext(ctx), embed))
case EmbedLinkPreview:
log := with.Str("computed_embed_type", "link preview").Logger()
- previews = append(previews, mc.renderDiscordLinkEmbed(log.WithContext(ctx), intent, portal, embed))
+ previews = append(previews, mc.renderDiscordLinkEmbed(log.WithContext(ctx), embed))
case EmbedVideo:
// Video embeds are handled as separate messages via renderDiscordVideoEmbed.
default:
@@ -284,7 +295,7 @@ func (mc *MessageConverter) renderDiscordTextMessage(ctx context.Context, intent
return &bridgev2.ConvertedMessagePart{Type: event.EventMessage, Content: &content, Extra: extraContent}
}
-func (mc *MessageConverter) forwardedMessageHtmlPart(ctx context.Context, portal *bridgev2.Portal, source *bridgev2.UserLogin, msg *discordgo.Message) string {
+func (mc *MessageConverter) forwardedMessageHTMLPart(ctx context.Context, portal *bridgev2.Portal, source *bridgev2.UserLogin, msg *discordgo.Message) string {
log := zerolog.Ctx(ctx)
forwardedHTML := mc.renderDiscordMarkdownOnlyHTMLNoUnwrap(portal, msg.MessageSnapshots[0].Message.Content, true)
@@ -327,7 +338,7 @@ func mediaFailedMessage(err error) *event.MessageEventContent {
}
}
-func (mc *MessageConverter) renderDiscordVideoEmbed(ctx context.Context, intent bridgev2.MatrixAPI, portal *bridgev2.Portal, embed *discordgo.MessageEmbed) *bridgev2.ConvertedMessagePart {
+func (mc *MessageConverter) renderDiscordVideoEmbed(ctx context.Context, embed *discordgo.MessageEmbed) *bridgev2.ConvertedMessagePart {
var proxyURL string
if embed.Video != nil {
proxyURL = embed.Video.ProxyURL
@@ -344,10 +355,7 @@ func (mc *MessageConverter) renderDiscordVideoEmbed(ctx context.Context, intent
}
}
- upload := attachment.AttachmentReupload{
- DownloadingURL: proxyURL,
- }
- reupload, err := mc.ReuploadMedia(ctx, intent, portal, upload)
+ reupload, err := mc.ReuploadUnknownMedia(ctx, proxyURL, true)
if err != nil {
zerolog.Ctx(ctx).Err(err).Msg("Failed to copy video embed to Matrix")
return &bridgev2.ConvertedMessagePart{
@@ -358,16 +366,13 @@ func (mc *MessageConverter) renderDiscordVideoEmbed(ctx context.Context, intent
content := &event.MessageEventContent{
Body: embed.URL,
+ URL: reupload.MXC,
+ File: reupload.File,
Info: &event.FileInfo{
MimeType: reupload.MimeType,
- Size: reupload.DownloadedSize,
+ Size: reupload.Size,
},
}
- if reupload.EncryptedFile != nil {
- content.File = reupload.EncryptedFile
- } else {
- content.URL = reupload.MXC
- }
if embed.Video != nil {
content.MsgType = event.MsgVideo
@@ -398,7 +403,7 @@ func (mc *MessageConverter) renderDiscordVideoEmbed(ctx context.Context, intent
}
}
-func (mc *MessageConverter) renderDiscordSticker(context context.Context, intent bridgev2.MatrixAPI, sticker *discordgo.StickerItem) *bridgev2.ConvertedMessagePart {
+func (mc *MessageConverter) renderDiscordSticker(ctx context.Context, sticker *discordgo.StickerItem) *bridgev2.ConvertedMessagePart {
panic("unimplemented")
}
@@ -423,7 +428,7 @@ const (
embedFooterDateSeparator = ` • `
)
-func (mc *MessageConverter) renderDiscordRichEmbed(ctx context.Context, intent bridgev2.MatrixAPI, portal *bridgev2.Portal, embed *discordgo.MessageEmbed) string {
+func (mc *MessageConverter) renderDiscordRichEmbed(ctx context.Context, embed *discordgo.MessageEmbed) string {
log := zerolog.Ctx(ctx)
var htmlParts []string
if embed.Author != nil {
@@ -434,9 +439,7 @@ func (mc *MessageConverter) renderDiscordRichEmbed(ctx context.Context, intent b
}
authorHTML = fmt.Sprintf(embedHTMLAuthorPlain, authorNameHTML)
if embed.Author.ProxyIconURL != "" {
- reupload, err := mc.ReuploadMedia(ctx, intent, portal, attachment.AttachmentReupload{
- DownloadingURL: embed.Author.ProxyIconURL,
- })
+ reupload, err := mc.ReuploadUnknownMedia(ctx, embed.Author.ProxyIconURL, false)
if err != nil {
log.Warn().Err(err).Msg("Failed to reupload author icon in embed")
@@ -447,6 +450,7 @@ func (mc *MessageConverter) renderDiscordRichEmbed(ctx context.Context, intent b
htmlParts = append(htmlParts, authorHTML)
}
+ portal := ctx.Value(contextKeyPortal).(*bridgev2.Portal)
if embed.Title != "" {
var titleHTML string
baseTitleHTML := mc.renderDiscordMarkdownOnlyHTML(portal, embed.Title, false)
@@ -492,9 +496,7 @@ func (mc *MessageConverter) renderDiscordRichEmbed(ctx context.Context, intent b
}
if embed.Image != nil {
- reupload, err := mc.ReuploadMedia(ctx, intent, portal, attachment.AttachmentReupload{
- DownloadingURL: embed.Image.ProxyURL,
- })
+ reupload, err := mc.ReuploadUnknownMedia(ctx, embed.Image.ProxyURL, false)
if err != nil {
log.Warn().Err(err).Msg("Failed to reupload image in embed")
} else {
@@ -522,9 +524,7 @@ func (mc *MessageConverter) renderDiscordRichEmbed(ctx context.Context, intent b
}
footerHTML = fmt.Sprintf(embedHTMLFooterPlain, html.EscapeString(embed.Footer.Text), datePart)
if embed.Footer.ProxyIconURL != "" {
- reupload, err := mc.ReuploadMedia(ctx, intent, portal, attachment.AttachmentReupload{
- DownloadingURL: embed.Footer.ProxyIconURL,
- })
+ reupload, err := mc.ReuploadUnknownMedia(ctx, embed.Footer.ProxyIconURL, false)
if err != nil {
log.Warn().Err(err).Msg("Failed to reupload footer icon in embed")
@@ -550,10 +550,10 @@ func (mc *MessageConverter) renderDiscordRichEmbed(ctx context.Context, intent b
return compiledHTML
}
-func (mc *MessageConverter) renderDiscordLinkEmbedImage(ctx context.Context, intent bridgev2.MatrixAPI, portal *bridgev2.Portal, url string, width, height int, preview *event.BeeperLinkPreview) {
- reupload, err := mc.ReuploadMedia(ctx, intent, portal, attachment.AttachmentReupload{
- DownloadingURL: url,
- })
+func (mc *MessageConverter) renderDiscordLinkEmbedImage(
+ ctx context.Context, url string, width, height int, preview *event.BeeperLinkPreview,
+) {
+ reupload, err := mc.ReuploadUnknownMedia(ctx, url, true)
if err != nil {
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to reupload image in URL preview, ignoring")
return
@@ -563,39 +563,42 @@ func (mc *MessageConverter) renderDiscordLinkEmbedImage(ctx context.Context, int
preview.ImageWidth = event.IntOrString(width)
preview.ImageHeight = event.IntOrString(height)
}
- preview.ImageSize = event.IntOrString(reupload.DownloadedSize)
+ preview.ImageSize = event.IntOrString(reupload.Size)
preview.ImageType = reupload.MimeType
- if reupload.EncryptedFile != nil {
- preview.ImageEncryption = &event.EncryptedFileInfo{
- EncryptedFile: reupload.EncryptedFile.EncryptedFile,
- URL: reupload.MXC,
- }
- }
+ preview.ImageURL, preview.ImageEncryption = reupload.MXC, reupload.File
}
-func (mc *MessageConverter) renderDiscordLinkEmbed(ctx context.Context, intent bridgev2.MatrixAPI, portal *bridgev2.Portal, embed *discordgo.MessageEmbed) *event.BeeperLinkPreview {
+func (mc *MessageConverter) renderDiscordLinkEmbed(ctx context.Context, embed *discordgo.MessageEmbed) *event.BeeperLinkPreview {
var preview event.BeeperLinkPreview
preview.MatchedURL = embed.URL
preview.Title = embed.Title
preview.Description = embed.Description
if embed.Image != nil {
- mc.renderDiscordLinkEmbedImage(ctx, intent, portal, embed.Image.ProxyURL, embed.Image.Width, embed.Image.Height, &preview)
+ mc.renderDiscordLinkEmbedImage(ctx, embed.Image.ProxyURL, embed.Image.Width, embed.Image.Height, &preview)
} else if embed.Thumbnail != nil {
- mc.renderDiscordLinkEmbedImage(ctx, intent, portal, embed.Thumbnail.ProxyURL, embed.Thumbnail.Width, embed.Thumbnail.Height, &preview)
+ mc.renderDiscordLinkEmbedImage(ctx, embed.Thumbnail.ProxyURL, embed.Thumbnail.Width, embed.Thumbnail.Height, &preview)
}
return &preview
}
-func (mc *MessageConverter) renderDiscordAttachment(ctx context.Context, intent bridgev2.MatrixAPI, portal *bridgev2.Portal, att *discordgo.MessageAttachment) *bridgev2.ConvertedMessagePart {
+func (mc *MessageConverter) renderDiscordAttachment(ctx context.Context, att *discordgo.MessageAttachment) *bridgev2.ConvertedMessagePart {
+ // TODO(skip): Support direct media.
+ reupload, err := mc.ReuploadMedia(ctx, att.URL, att.ContentType, att.Filename, att.Size, true)
+ if err != nil {
+ zerolog.Ctx(ctx).Err(err).Msg("Failed to copy attachment to Matrix")
+ return &bridgev2.ConvertedMessagePart{
+ Type: event.EventMessage,
+ Content: mediaFailedMessage(err),
+ }
+ }
+
content := &event.MessageEventContent{
- Body: att.Filename,
+ Body: reupload.FileName,
Info: &event.FileInfo{
Width: att.Width,
Height: att.Height,
- MimeType: att.ContentType,
-
- // This gets overwritten later after the file is uploaded to the homeserver
- Size: att.Size,
+ MimeType: reupload.MimeType,
+ Size: reupload.Size,
},
}
@@ -607,10 +610,10 @@ func (mc *MessageConverter) renderDiscordAttachment(ctx context.Context, intent
if att.Description != "" {
content.Body = att.Description
- content.FileName = att.Filename
+ content.FileName = reupload.FileName
}
- switch strings.ToLower(strings.Split(att.ContentType, "/")[0]) {
+ switch strings.ToLower(strings.Split(content.Info.MimeType, "/")[0]) {
case "audio":
content.MsgType = event.MsgAudio
if att.Waveform != nil {
@@ -630,28 +633,12 @@ func (mc *MessageConverter) renderDiscordAttachment(ctx context.Context, intent
content.MsgType = event.MsgFile
}
- // TODO(skip): Support direct media.
- reupload, err := mc.ReuploadMedia(ctx, intent, portal, attachment.AttachmentReupload{
- DownloadingURL: att.URL,
- })
- if err != nil {
- zerolog.Ctx(ctx).Err(err).Msg("Failed to copy attachment to Matrix")
- return &bridgev2.ConvertedMessagePart{
- Type: event.EventMessage,
- Content: mediaFailedMessage(err),
- }
- }
-
- content.Info.Size = reupload.DownloadedSize
+ content.URL, content.File = reupload.MXC, reupload.File
+ content.Info.Size = reupload.Size
if content.Info.Width == 0 && content.Info.Height == 0 {
content.Info.Width = att.Width
content.Info.Height = att.Height
}
- if reupload.EncryptedFile != nil {
- content.File = reupload.EncryptedFile
- } else {
- content.URL = reupload.MXC
- }
return &bridgev2.ConvertedMessagePart{
Type: event.EventMessage,
diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go
index 317a48b..e8cd391 100644
--- a/pkg/msgconv/from-matrix.go
+++ b/pkg/msgconv/from-matrix.go
@@ -99,6 +99,8 @@ func (mc *MessageConverter) ToDiscord(
session *discordgo.Session,
msg *bridgev2.MatrixMessage,
) (*discordgo.MessageSend, error) {
+ ctx = context.WithValue(ctx, contextKeyPortal, msg.Portal)
+ ctx = context.WithValue(ctx, contextKeyDiscordClient, session)
var req discordgo.MessageSend
req.Nonce = generateMessageNonce()
log := zerolog.Ctx(ctx)
diff --git a/pkg/msgconv/msgconv.go b/pkg/msgconv/msgconv.go
index 7f40710..9636ac7 100644
--- a/pkg/msgconv/msgconv.go
+++ b/pkg/msgconv/msgconv.go
@@ -17,35 +17,25 @@
package msgconv
import (
- "context"
"math/rand"
"strconv"
"sync/atomic"
"maunium.net/go/mautrix/bridgev2"
-
- "go.mau.fi/mautrix-discord/pkg/attachment"
)
-type MediaReuploader func(ctx context.Context, intent bridgev2.MatrixAPI, portal *bridgev2.Portal, reupload attachment.AttachmentReupload) (*attachment.ReuploadedAttachment, error)
-
type MessageConverter struct {
Bridge *bridgev2.Bridge
nextDiscordUploadID atomic.Int32
- // ReuploadMedia is called when the message converter wants to upload some
- // media it is attempting to bridge.
- //
- // This can be directly forwarded to the ReuploadMedia method on DiscordConnector.
- // The indirection is only necessary to prevent an import cycle.
- ReuploadMedia MediaReuploader
+ MaxFileSize int64
}
-func NewMessageConverter(bridge *bridgev2.Bridge, reuploader MediaReuploader) *MessageConverter {
+func NewMessageConverter(bridge *bridgev2.Bridge) *MessageConverter {
mc := &MessageConverter{
- Bridge: bridge,
- ReuploadMedia: reuploader,
+ Bridge: bridge,
+ MaxFileSize: 50 * 1024 * 1024,
}
mc.nextDiscordUploadID.Store(rand.Int31n(100))