diff --git a/backfill.go b/backfill.go index a5fc702..c218646 100644 --- a/backfill.go +++ b/backfill.go @@ -226,7 +226,7 @@ func (portal *Portal) convertMessageBatch(log zerolog.Logger, source *User, mess Int("message_type", int(msg.Type)). Str("author_id", msg.Author.ID). Logger() - parts := portal.convertDiscordMessage(log.WithContext(ctx), intent, msg) + parts := portal.convertDiscordMessage(log.WithContext(ctx), puppet, intent, msg) for i, part := range parts { if replyTo != nil { part.Content.RelatesTo = &event.RelatesTo{InReplyTo: replyTo} diff --git a/portal.go b/portal.go index 9c7ae3a..91f0e13 100644 --- a/portal.go +++ b/portal.go @@ -638,7 +638,7 @@ func (portal *Portal) handleDiscordMessageCreate(user *User, msg *discordgo.Mess mentions := portal.convertDiscordMentions(msg, replySenderMXID, true) ts, _ := discordgo.SnowflakeTimestamp(msg.ID) - parts := portal.convertDiscordMessage(ctx, intent, msg) + parts := portal.convertDiscordMessage(ctx, puppet, intent, msg) dbParts := make([]database.MessagePart, 0, len(parts)) for i, part := range parts { if (replyTo != nil || threadRootEvent != "") && part.Content.RelatesTo == nil { @@ -844,7 +844,8 @@ func (portal *Portal) handleDiscordMessageUpdate(user *User, msg *discordgo.Mess return } - intent := portal.bridge.GetPuppetByID(msg.Author.ID).IntentFor(portal) + puppet := portal.bridge.GetPuppetByID(msg.Author.ID) + intent := puppet.IntentFor(portal) attachmentMap := map[string]*database.Message{} for _, existingPart := range existing { @@ -897,9 +898,8 @@ func (portal *Portal) handleDiscordMessageUpdate(user *User, msg *discordgo.Mess Msg("Dropping non-text edit") return } - if msg.WebhookID != "" { - portal.addWebhookMeta(converted, msg) - } + puppet.addWebhookMeta(converted, msg) + puppet.addMemberMeta(converted, msg) converted.Content.Mentions = portal.convertDiscordMentions(msg, "", false) converted.Content.SetEdit(existing[0].MXID) // Never actually mention new users of edits, only include mentions inside m.new_content diff --git a/portal_convert.go b/portal_convert.go index 29992d4..09c0d6b 100644 --- a/portal_convert.go +++ b/portal_convert.go @@ -257,7 +257,7 @@ func (portal *Portal) convertDiscordVideoEmbed(ctx context.Context, intent *apps } } -func (portal *Portal) convertDiscordMessage(ctx context.Context, intent *appservice.IntentAPI, msg *discordgo.Message) []*ConvertedMessage { +func (portal *Portal) convertDiscordMessage(ctx context.Context, puppet *Puppet, intent *appservice.IntentAPI, msg *discordgo.Message) []*ConvertedMessage { predictedLength := len(msg.Attachments) + len(msg.StickerItems) if msg.Content != "" { predictedLength++ @@ -308,15 +308,58 @@ func (portal *Portal) convertDiscordMessage(ctx context.Context, intent *appserv parts = append(parts, part) } } - if msg.WebhookID != "" { - for _, part := range parts { - portal.addWebhookMeta(part, msg) - } + for _, part := range parts { + puppet.addWebhookMeta(part, msg) + puppet.addMemberMeta(part, msg) } return parts } -func (portal *Portal) addWebhookMeta(part *ConvertedMessage, msg *discordgo.Message) { +func (puppet *Puppet) addMemberMeta(part *ConvertedMessage, msg *discordgo.Message) { + if msg.Member == nil { + return + } + if part.Extra == nil { + part.Extra = make(map[string]any) + } + var avatarURL id.ContentURI + if msg.Member.Avatar != "" { + var err error + avatarURL, err = puppet.bridge.reuploadUserAvatar(puppet.DefaultIntent(), msg.GuildID, msg.Author.ID, msg.Author.Avatar) + if err != nil { + puppet.log.Warn().Err(err). + Str("avatar_id", msg.Author.Avatar). + Msg("Failed to reupload guild user avatar") + } + } + var discordAvararURL string + if msg.Member.Avatar != "" { + discordAvararURL = msg.Member.AvatarURL("") + } + part.Extra["fi.mau.discord.guild_member_metadata"] = map[string]any{ + "nick": msg.Member.Nick, + "avatar_id": msg.Member.Avatar, + "avatar_url": discordAvararURL, + "avatar_mxc": avatarURL.String(), + } + if msg.Member.Nick != "" || !avatarURL.IsEmpty() { + perMessageProfile := map[string]any{ + "is_multiple_users": false, + + "displayname": msg.Member.Nick, + "avatar_url": avatarURL.String(), + } + if msg.Member.Nick == "" { + perMessageProfile["displayname"] = puppet.Name + } + if avatarURL.IsEmpty() { + perMessageProfile["avatar_url"] = puppet.AvatarURL.String() + } + part.Extra["com.beeper.per_message_profile"] = perMessageProfile + } +} + +func (puppet *Puppet) addWebhookMeta(part *ConvertedMessage, msg *discordgo.Message) { if msg.WebhookID == "" { return } @@ -326,9 +369,9 @@ func (portal *Portal) addWebhookMeta(part *ConvertedMessage, msg *discordgo.Mess var avatarURL id.ContentURI if msg.Author.Avatar != "" { var err error - avatarURL, err = portal.bridge.reuploadUserAvatar(portal.MainIntent(), msg.Author.ID, msg.Author.Avatar) + avatarURL, err = puppet.bridge.reuploadUserAvatar(puppet.DefaultIntent(), "", msg.Author.ID, msg.Author.Avatar) if err != nil { - portal.log.Warn().Err(err). + puppet.log.Warn().Err(err). Str("avatar_id", msg.Author.Avatar). Msg("Failed to reupload webhook avatar") } @@ -341,6 +384,8 @@ func (portal *Portal) addWebhookMeta(part *ConvertedMessage, msg *discordgo.Mess "avatar_mxc": avatarURL.String(), } part.Extra["com.beeper.per_message_profile"] = map[string]any{ + "is_multiple_users": true, + "avatar_url": avatarURL.String(), "displayname": msg.Author.Username, } diff --git a/puppet.go b/puppet.go index 2b9f935..6739d4a 100644 --- a/puppet.go +++ b/puppet.go @@ -228,19 +228,29 @@ 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" +func (br *DiscordBridge) reuploadUserAvatar(intent *appservice.IntentAPI, guildID, userID, avatarID string) (id.ContentURI, error) { + var downloadURL, ext string + if guildID == "" { + downloadURL = discordgo.EndpointUserAvatar(userID, avatarID) + ext = "png" + if strings.HasPrefix(avatarID, "a_") { + downloadURL = discordgo.EndpointUserAvatarAnimated(userID, avatarID) + ext = "gif" + } + } else { + downloadURL = discordgo.EndpointGuildMemberAvatar(guildID, userID, avatarID) + ext = "png" + if strings.HasPrefix(avatarID, "a_") { + downloadURL = discordgo.EndpointGuildMemberAvatarAnimated(guildID, 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), + AttachmentID: fmt.Sprintf("avatar/%s/%s/%s", guildID, userID, avatarID), }) if err != nil { return url, err @@ -262,7 +272,7 @@ func (puppet *Puppet) UpdateAvatar(info *discordgo.User) bool { puppet.AvatarURL = id.ContentURI{} if puppet.Avatar != "" && (puppet.AvatarURL.IsEmpty() || avatarChanged) { - url, err := puppet.bridge.reuploadUserAvatar(puppet.DefaultIntent(), info.ID, puppet.Avatar) + 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