瀏覽代碼

Reupload webhook avatars to fill custom metadata

Tulir Asokan 2 年之前
父節點
當前提交
52fa4da8b2
共有 5 個文件被更改,包括 56 次插入69 次删除
  1. 0 40
      avatar.go
  2. 6 3
      guildportal.go
  3. 7 5
      portal.go
  4. 14 3
      portal_convert.go
  5. 29 18
      puppet.go

+ 0 - 40
avatar.go

@@ -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
-}

+ 6 - 3
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)

+ 7 - 5
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

+ 14 - 3
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,
 	}
 }

+ 29 - 18
puppet.go

@@ -228,33 +228,44 @@ func (puppet *Puppet) UpdateName(info *discordgo.User) bool {
 	return true
 }
 
+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"
+	}
+	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 {
-		info.Avatar = ""
+		avatarID = ""
 	}
-	if puppet.Avatar == info.Avatar && puppet.AvatarSet {
+	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
 	}