Procházet zdrojové kódy

Add options to improve handling of webhook messages sent by other bridges

Tulir Asokan před 2 roky
rodič
revize
4393772ccc
6 změnil soubory, kde provedl 58 přidání a 4 odebrání
  1. 9 2
      config/bridge.go
  2. 2 0
      config/upgrade.go
  3. 6 0
      example-config.yaml
  4. 3 0
      portal.go
  5. 30 0
      portal_convert.go
  6. 8 2
      puppet.go

+ 9 - 2
config/bridge.go

@@ -51,6 +51,8 @@ type BridgeConfig struct {
 	DeletePortalOnChannelDelete bool `yaml:"delete_portal_on_channel_delete"`
 	DeleteGuildOnLeave          bool `yaml:"delete_guild_on_leave"`
 	FederateRooms               bool `yaml:"federate_rooms"`
+	PrefixWebhookMessages       bool `yaml:"prefix_webhook_messages"`
+	EnableWebhookAvatars        bool `yaml:"enable_webhook_avatars"`
 	UseDiscordCDNUpload         bool `yaml:"use_discord_cdn_upload"`
 
 	CacheMedia    string        `yaml:"cache_media"`
@@ -287,9 +289,14 @@ func (bc BridgeConfig) FormatUsername(userID string) string {
 	return buffer.String()
 }
 
-func (bc BridgeConfig) FormatDisplayname(user *discordgo.User) string {
+type DisplaynameParams struct {
+	*discordgo.User
+	Webhook bool
+}
+
+func (bc BridgeConfig) FormatDisplayname(user *discordgo.User, webhook bool) string {
 	var buffer strings.Builder
-	_ = bc.displaynameTemplate.Execute(&buffer, user)
+	_ = bc.displaynameTemplate.Execute(&buffer, &DisplaynameParams{user, webhook})
 	return buffer.String()
 }
 

+ 2 - 0
config/upgrade.go

@@ -55,6 +55,8 @@ func DoUpgrade(helper *up.Helper) {
 	helper.Copy(up.Bool, "bridge", "delete_portal_on_channel_delete")
 	helper.Copy(up.Bool, "bridge", "delete_guild_on_leave")
 	helper.Copy(up.Bool, "bridge", "federate_rooms")
+	helper.Copy(up.Bool, "bridge", "prefix_webhook_messages")
+	helper.Copy(up.Bool, "bridge", "enable_webhook_avatars")
 	helper.Copy(up.Bool, "bridge", "use_discord_cdn_upload")
 	helper.Copy(up.Bool, "bridge", "media_patterns", "enabled")
 	helper.Copy(up.Str, "bridge", "cache_media")

+ 6 - 0
example-config.yaml

@@ -85,6 +85,7 @@ bridge:
     #   .Discriminator - The 4 numbers after the name on Discord
     #   .Bot - Whether the user is a bot
     #   .System - Whether the user is an official system user
+    #   .Webhook - Whether the user is a webhook
     displayname_template: '{{or .GlobalName .Username}}{{if .Bot}} (bot){{end}}'
     # Displayname template for Discord channels (bridged as rooms, or spaces when type=4).
     # Available variables:
@@ -146,6 +147,11 @@ bridge:
     # Whether or not created rooms should have federation enabled.
     # If false, created portal rooms will never be federated.
     federate_rooms: true
+    # Prefix messages from webhooks with the profile info? This can be used along with a custom displayname_template
+    # to better handle webhooks that change their name all the time (like ones used by bridges).
+    prefix_webhook_messages: false
+    # Bridge webhook avatars?
+    enable_webhook_avatars: true
     # Should the bridge upload media to the Discord CDN directly before sending the message when using a user token,
     # like the official client does? The other option is sending the media in the message send request as a form part
     # (which is always used by bots and webhooks).

+ 3 - 0
portal.go

@@ -897,6 +897,9 @@ func (portal *Portal) handleDiscordMessageUpdate(user *User, msg *discordgo.Mess
 			Msg("Dropping non-text edit")
 		return
 	}
+	if msg.WebhookID != "" {
+		addWebhookMeta(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

+ 30 - 0
portal_convert.go

@@ -308,9 +308,33 @@ func (portal *Portal) convertDiscordMessage(ctx context.Context, intent *appserv
 			parts = append(parts, part)
 		}
 	}
+	if msg.WebhookID != "" {
+		for _, part := range parts {
+			addWebhookMeta(part, msg)
+		}
+	}
 	return parts
 }
 
+func addWebhookMeta(part *ConvertedMessage, msg *discordgo.Message) {
+	if msg.WebhookID == "" {
+		return
+	}
+	if part.Extra == nil {
+		part.Extra = make(map[string]any)
+	}
+	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(""),
+	}
+	part.Extra["com.beeper.per_message_profile"] = map[string]any{
+		"avatar_url":  msg.Author.AvatarURL(""),
+		"displayname": msg.Author.Username,
+	}
+}
+
 const (
 	embedHTMLWrapper         = `<blockquote class="discord-embed">%s</blockquote>`
 	embedHTMLWrapperColor    = `<blockquote class="discord-embed" background-color="#%06X">%s</blockquote>`
@@ -628,5 +652,11 @@ func (portal *Portal) convertDiscordTextMessage(ctx context.Context, intent *app
 		"com.beeper.linkpreviews": previews,
 	}
 
+	if msg.WebhookID != "" && portal.bridge.Config.Bridge.PrefixWebhookMessages {
+		content.EnsureHasHTML()
+		content.Body = fmt.Sprintf("%s: %s", msg.Author.Username, content.Body)
+		content.FormattedBody = fmt.Sprintf("<strong>%s</strong>: %s", html.EscapeString(msg.Author.Username), content.FormattedBody)
+	}
+
 	return &ConvertedMessage{Type: event.EventMessage, Content: &content, Extra: extraContent}
 }

+ 8 - 2
puppet.go

@@ -207,7 +207,7 @@ func (puppet *Puppet) updatePortalMeta(meta func(portal *Portal)) {
 }
 
 func (puppet *Puppet) UpdateName(info *discordgo.User) bool {
-	newName := puppet.bridge.Config.Bridge.FormatDisplayname(info)
+	newName := puppet.bridge.Config.Bridge.FormatDisplayname(info, puppet.IsWebhook)
 	if puppet.Name == newName && puppet.NameSet {
 		return false
 	}
@@ -229,6 +229,9 @@ func (puppet *Puppet) UpdateName(info *discordgo.User) bool {
 }
 
 func (puppet *Puppet) UpdateAvatar(info *discordgo.User) bool {
+	if puppet.IsWebhook && !puppet.bridge.Config.Bridge.EnableWebhookAvatars {
+		info.Avatar = ""
+	}
 	if puppet.Avatar == info.Avatar && puppet.AvatarSet {
 		return false
 	}
@@ -324,7 +327,7 @@ func (puppet *Puppet) UpdateContactInfo(info *discordgo.User) bool {
 		puppet.IsBot = info.Bot
 		changed = true
 	}
-	if changed {
+	if (changed && !puppet.IsWebhook) || !puppet.ContactInfoSet {
 		puppet.ContactInfoSet = false
 		puppet.ResendContactInfo()
 		return true
@@ -345,6 +348,9 @@ func (puppet *Puppet) ResendContactInfo() {
 		"com.beeper.bridge.network":        puppet.bridge.BeeperNetworkName,
 		"com.beeper.bridge.is_network_bot": puppet.IsBot,
 	}
+	if puppet.IsWebhook {
+		contactInfo["com.beeper.bridge.identifiers"] = []string{}
+	}
 	err := puppet.DefaultIntent().BeeperUpdateProfile(contactInfo)
 	if err != nil {
 		puppet.log.Warn().Err(err).Msg("Failed to store custom contact info in profile")