From 52fa4da8b2f67d21816c5b1f7de368ae23c2b9cd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 27 May 2023 13:35:37 +0300 Subject: [PATCH] Reupload webhook avatars to fill custom metadata --- avatar.go | 40 ------------------------------------- guildportal.go | 9 ++++++--- portal.go | 12 ++++++----- portal_convert.go | 17 +++++++++++++--- puppet.go | 51 ++++++++++++++++++++++++++++------------------- 5 files changed, 58 insertions(+), 71 deletions(-) delete mode 100644 avatar.go diff --git a/avatar.go b/avatar.go deleted file mode 100644 index 320ac72..0000000 --- a/avatar.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "fmt" - "io" - "net/http" - - "maunium.net/go/mautrix/appservice" - "maunium.net/go/mautrix/id" - - "github.com/bwmarrin/discordgo" -) - -func uploadAvatar(intent *appservice.IntentAPI, url string) (id.ContentURI, error) { - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return id.ContentURI{}, fmt.Errorf("failed to prepare request: %w", err) - } - for key, value := range discordgo.DroidImageHeaders { - req.Header.Set(key, value) - } - getResp, err := http.DefaultClient.Do(req) - if err != nil { - return id.ContentURI{}, fmt.Errorf("failed to download avatar: %w", err) - } - - data, err := io.ReadAll(getResp.Body) - _ = getResp.Body.Close() - if err != nil { - return id.ContentURI{}, fmt.Errorf("failed to read avatar data: %w", err) - } - - mime := http.DetectContentType(data) - resp, err := intent.UploadBytes(data, mime) - if err != nil { - return id.ContentURI{}, fmt.Errorf("failed to upload avatar to Matrix: %w", err) - } - - return resp.ContentURI, nil -} diff --git a/guildportal.go b/guildportal.go index 554422f..6f00355 100644 --- a/guildportal.go +++ b/guildportal.go @@ -272,12 +272,15 @@ func (guild *Guild) UpdateAvatar(iconID string) bool { guild.Avatar = iconID guild.AvatarURL = id.ContentURI{} if guild.Avatar != "" { - var err error - guild.AvatarURL, err = uploadAvatar(guild.bridge.Bot, discordgo.EndpointGuildIcon(guild.ID, iconID)) + // TODO direct media support + copied, err := guild.bridge.copyAttachmentToMatrix(guild.bridge.Bot, discordgo.EndpointGuildIcon(guild.ID, iconID), false, AttachmentMeta{ + AttachmentID: fmt.Sprintf("guild_avatar/%s/%s", guild.ID, iconID), + }) if err != nil { - guild.log.Warnfln("Failed to reupload guild avatar %s: %v", guild.Avatar, err) + guild.log.Warnfln("Failed to reupload guild avatar %s: %v", iconID, err) return true } + guild.AvatarURL = copied.MXC } if guild.MXID != "" { _, err := guild.bridge.Bot.SetRoomAvatar(guild.MXID, guild.AvatarURL) diff --git a/portal.go b/portal.go index 3ddba24..62fe02c 100644 --- a/portal.go +++ b/portal.go @@ -898,7 +898,7 @@ func (portal *Portal) handleDiscordMessageUpdate(user *User, msg *discordgo.Mess return } if msg.WebhookID != "" { - addWebhookMeta(converted, msg) + portal.addWebhookMeta(converted, msg) } converted.Content.Mentions = portal.convertDiscordMentions(msg, "", false) converted.Content.SetEdit(existing[0].MXID) @@ -2152,13 +2152,15 @@ func (portal *Portal) UpdateGroupDMAvatar(iconID string) bool { portal.AvatarSet = false portal.AvatarURL = id.ContentURI{} if portal.Avatar != "" { - uri, err := uploadAvatar(portal.MainIntent(), discordgo.EndpointGroupIcon(portal.Key.ChannelID, portal.Avatar)) + // TODO direct media support + copied, err := portal.bridge.copyAttachmentToMatrix(portal.MainIntent(), discordgo.EndpointGroupIcon(portal.Key.ChannelID, portal.Avatar), false, AttachmentMeta{ + AttachmentID: fmt.Sprintf("private_channel_avatar/%s/%s", portal.Key.ChannelID, iconID), + }) if err != nil { - portal.log.Err(err).Str("avatar_id", portal.Avatar).Msg("Failed to reupload channel avatar") + portal.log.Err(err).Str("avatar_id", iconID).Msg("Failed to reupload channel avatar") return true - } else { - portal.AvatarURL = uri } + portal.AvatarURL = copied.MXC } portal.updateRoomAvatar() return true diff --git a/portal_convert.go b/portal_convert.go index 2a8fb55..29992d4 100644 --- a/portal_convert.go +++ b/portal_convert.go @@ -310,27 +310,38 @@ func (portal *Portal) convertDiscordMessage(ctx context.Context, intent *appserv } if msg.WebhookID != "" { for _, part := range parts { - addWebhookMeta(part, msg) + portal.addWebhookMeta(part, msg) } } return parts } -func addWebhookMeta(part *ConvertedMessage, msg *discordgo.Message) { +func (portal *Portal) addWebhookMeta(part *ConvertedMessage, msg *discordgo.Message) { if msg.WebhookID == "" { return } if part.Extra == nil { part.Extra = make(map[string]any) } + var avatarURL id.ContentURI + if msg.Author.Avatar != "" { + var err error + avatarURL, err = portal.bridge.reuploadUserAvatar(portal.MainIntent(), msg.Author.ID, msg.Author.Avatar) + if err != nil { + portal.log.Warn().Err(err). + Str("avatar_id", msg.Author.Avatar). + Msg("Failed to reupload webhook avatar") + } + } part.Extra["fi.mau.discord.webhook_metadata"] = map[string]any{ "id": msg.WebhookID, "name": msg.Author.Username, "avatar_id": msg.Author.Avatar, "avatar_url": msg.Author.AvatarURL(""), + "avatar_mxc": avatarURL.String(), } part.Extra["com.beeper.per_message_profile"] = map[string]any{ - "avatar_url": msg.Author.AvatarURL(""), + "avatar_url": avatarURL.String(), "displayname": msg.Author.Username, } } diff --git a/puppet.go b/puppet.go index 44406e8..2b9f935 100644 --- a/puppet.go +++ b/puppet.go @@ -228,33 +228,44 @@ func (puppet *Puppet) UpdateName(info *discordgo.User) bool { return true } -func (puppet *Puppet) UpdateAvatar(info *discordgo.User) bool { - if puppet.IsWebhook && !puppet.bridge.Config.Bridge.EnableWebhookAvatars { - info.Avatar = "" +func (br *DiscordBridge) reuploadUserAvatar(intent *appservice.IntentAPI, userID, avatarID string) (id.ContentURI, error) { + downloadURL := discordgo.EndpointUserAvatar(userID, avatarID) + ext := "png" + if strings.HasPrefix(avatarID, "a_") { + downloadURL = discordgo.EndpointUserAvatarAnimated(userID, avatarID) + ext = "gif" } - if puppet.Avatar == info.Avatar && puppet.AvatarSet { + url := br.Config.Bridge.MediaPatterns.Avatar(userID, avatarID, ext) + if !url.IsEmpty() { + return url, nil + } + copied, err := br.copyAttachmentToMatrix(intent, downloadURL, false, AttachmentMeta{ + AttachmentID: fmt.Sprintf("avatar/%s/%s", userID, avatarID), + }) + if err != nil { + return url, err + } + return copied.MXC, nil +} + +func (puppet *Puppet) UpdateAvatar(info *discordgo.User) bool { + avatarID := info.Avatar + if puppet.IsWebhook && !puppet.bridge.Config.Bridge.EnableWebhookAvatars { + avatarID = "" + } + if puppet.Avatar == avatarID && puppet.AvatarSet { return false } - avatarChanged := info.Avatar != puppet.Avatar - puppet.Avatar = info.Avatar + avatarChanged := avatarID != puppet.Avatar + puppet.Avatar = avatarID puppet.AvatarSet = false puppet.AvatarURL = id.ContentURI{} if puppet.Avatar != "" && (puppet.AvatarURL.IsEmpty() || avatarChanged) { - downloadURL := discordgo.EndpointUserAvatar(info.ID, info.Avatar) - ext := "png" - if strings.HasPrefix(info.Avatar, "a_") { - downloadURL = discordgo.EndpointUserAvatarAnimated(info.ID, info.Avatar) - ext = "gif" - } - url := puppet.bridge.Config.Bridge.MediaPatterns.Avatar(info.ID, info.Avatar, ext) - if url.IsEmpty() { - var err error - url, err = uploadAvatar(puppet.DefaultIntent(), downloadURL) - if err != nil { - puppet.log.Warn().Err(err).Str("avatar_id", puppet.Avatar).Msg("Failed to reupload user avatar") - return true - } + url, err := puppet.bridge.reuploadUserAvatar(puppet.DefaultIntent(), info.ID, puppet.Avatar) + if err != nil { + puppet.log.Warn().Err(err).Str("avatar_id", puppet.Avatar).Msg("Failed to reupload user avatar") + return true } puppet.AvatarURL = url }