瀏覽代碼

Re-add support for channel name templates

Tulir Asokan 3 年之前
父節點
當前提交
c6fbaa90bf
共有 10 個文件被更改,包括 125 次插入93 次删除
  1. 31 45
      config/bridge.go
  2. 3 1
      config/upgrade.go
  3. 10 8
      database/guild.go
  4. 18 12
      database/portal.go
  5. 2 0
      database/upgrades/00-latest-revision.sql
  6. 9 0
      database/upgrades/08-channel-plain-name.sql
  7. 17 4
      example-config.yaml
  8. 8 10
      guildportal.go
  9. 24 12
      portal.go
  10. 3 1
      puppet.go

+ 31 - 45
config/bridge.go

@@ -28,9 +28,11 @@ import (
 )
 
 type BridgeConfig struct {
-	UsernameTemplate    string `yaml:"username_template"`
-	DisplaynameTemplate string `yaml:"displayname_template"`
-	ChannelnameTemplate string `yaml:"channelname_template"`
+	UsernameTemplate      string `yaml:"username_template"`
+	DisplaynameTemplate   string `yaml:"displayname_template"`
+	ChannelNameTemplate   string `yaml:"channel_name_template"`
+	GuildNameTemplate     string `yaml:"guild_name_template"`
+	PrivateChatPortalMeta bool   `yaml:"private_chat_portal_meta"`
 
 	DeliveryReceipts    bool `yaml:"delivery_receipts"`
 	MessageStatusEvents bool `yaml:"message_status_events"`
@@ -62,7 +64,8 @@ type BridgeConfig struct {
 
 	usernameTemplate    *template.Template `yaml:"-"`
 	displaynameTemplate *template.Template `yaml:"-"`
-	channelnameTemplate *template.Template `yaml:"-"`
+	channelNameTemplate *template.Template `yaml:"-"`
+	guildNameTemplate   *template.Template `yaml:"-"`
 }
 
 func (bc *BridgeConfig) GetResendBridgeInfo() bool {
@@ -109,13 +112,15 @@ func (bc *BridgeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	} else if !strings.Contains(bc.FormatUsername("1234567890"), "1234567890") {
 		return fmt.Errorf("username template is missing user ID placeholder")
 	}
-
 	bc.displaynameTemplate, err = template.New("displayname").Parse(bc.DisplaynameTemplate)
 	if err != nil {
 		return err
 	}
-
-	bc.channelnameTemplate, err = template.New("channelname").Parse(bc.ChannelnameTemplate)
+	bc.channelNameTemplate, err = template.New("channel_name").Parse(bc.ChannelNameTemplate)
+	if err != nil {
+		return err
+	}
+	bc.guildNameTemplate, err = template.New("guild_name").Parse(bc.GuildNameTemplate)
 	if err != nil {
 		return err
 	}
@@ -137,9 +142,9 @@ func (bc BridgeConfig) GetManagementRoomTexts() bridgeconfig.ManagementRoomTexts
 	return bc.ManagementRoomText
 }
 
-func (bc BridgeConfig) FormatUsername(userid string) string {
+func (bc BridgeConfig) FormatUsername(userID string) string {
 	var buffer strings.Builder
-	_ = bc.usernameTemplate.Execute(&buffer, userid)
+	_ = bc.usernameTemplate.Execute(&buffer, userID)
 	return buffer.String()
 }
 
@@ -149,45 +154,26 @@ func (bc BridgeConfig) FormatDisplayname(user *discordgo.User) string {
 	return buffer.String()
 }
 
-type wrappedChannel struct {
-	*discordgo.Channel
-	Guild  string
-	Folder string
+type ChannelNameParams struct {
+	Name       string
+	ParentName string
+	GuildName  string
+	NSFW       bool
+	Type       discordgo.ChannelType
 }
 
-func (bc BridgeConfig) FormatChannelname(channel *discordgo.Channel, session *discordgo.Session) (string, error) {
+func (bc BridgeConfig) FormatChannelName(params ChannelNameParams) string {
 	var buffer strings.Builder
-	var guildName, folderName string
-
-	if channel.Type != discordgo.ChannelTypeDM && channel.Type != discordgo.ChannelTypeGroupDM {
-		guild, err := session.Guild(channel.GuildID)
-		if err != nil {
-			return "", fmt.Errorf("find guild: %w", err)
-		}
-		guildName = guild.Name
-
-		folder, err := session.Channel(channel.ParentID)
-		if err == nil {
-			folderName = folder.Name
-		}
-	} else {
-		// Group DM's can have a name, but DM's can't, so if we didn't get a
-		// name return a comma separated list of the formatted user names.
-		if channel.Name == "" {
-			recipients := make([]string, len(channel.Recipients))
-			for idx, user := range channel.Recipients {
-				recipients[idx] = bc.FormatDisplayname(user)
-			}
-
-			return strings.Join(recipients, ", "), nil
-		}
-	}
+	_ = bc.channelNameTemplate.Execute(&buffer, params)
+	return buffer.String()
+}
 
-	_ = bc.channelnameTemplate.Execute(&buffer, wrappedChannel{
-		Channel: channel,
-		Guild:   guildName,
-		Folder:  folderName,
-	})
+type GuildNameParams struct {
+	Name string
+}
 
-	return buffer.String(), nil
+func (bc BridgeConfig) FormatGuildName(params GuildNameParams) string {
+	var buffer strings.Builder
+	_ = bc.guildNameTemplate.Execute(&buffer, params)
+	return buffer.String()
 }

+ 3 - 1
config/upgrade.go

@@ -27,7 +27,9 @@ func DoUpgrade(helper *up.Helper) {
 
 	helper.Copy(up.Str, "bridge", "username_template")
 	helper.Copy(up.Str, "bridge", "displayname_template")
-	helper.Copy(up.Str, "bridge", "channelname_template")
+	helper.Copy(up.Str, "bridge", "channel_name_template")
+	helper.Copy(up.Str, "bridge", "guild_name_template")
+	helper.Copy(up.Bool, "bridge", "private_chat_portal_meta")
 	helper.Copy(up.Int, "bridge", "portal_message_buffer")
 	helper.Copy(up.Bool, "bridge", "delivery_receipts")
 	helper.Copy(up.Bool, "bridge", "message_status_events")

+ 10 - 8
database/guild.go

@@ -16,7 +16,7 @@ type GuildQuery struct {
 }
 
 const (
-	guildSelect = "SELECT dcid, mxid, name, name_set, avatar, avatar_url, avatar_set, auto_bridge_channels FROM guild"
+	guildSelect = "SELECT dcid, mxid, plain_name, name, name_set, avatar, avatar_url, avatar_set, auto_bridge_channels FROM guild"
 )
 
 func (gq *GuildQuery) New() *Guild {
@@ -60,6 +60,7 @@ type Guild struct {
 
 	ID        string
 	MXID      id.RoomID
+	PlainName string
 	Name      string
 	NameSet   bool
 	Avatar    string
@@ -72,7 +73,7 @@ type Guild struct {
 func (g *Guild) Scan(row dbutil.Scannable) *Guild {
 	var mxid sql.NullString
 	var avatarURL string
-	err := row.Scan(&g.ID, &mxid, &g.Name, &g.NameSet, &g.Avatar, &avatarURL, &g.AvatarSet, &g.AutoBridgeChannels)
+	err := row.Scan(&g.ID, &mxid, &g.PlainName, &g.Name, &g.NameSet, &g.Avatar, &avatarURL, &g.AvatarSet, &g.AutoBridgeChannels)
 	if err != nil {
 		if !errors.Is(err, sql.ErrNoRows) {
 			g.log.Errorln("Database scan failed:", err)
@@ -92,12 +93,13 @@ func (g *Guild) mxidPtr() *id.RoomID {
 	}
 	return nil
 }
+
 func (g *Guild) Insert() {
 	query := `
-		INSERT INTO guild (dcid, mxid, name, name_set, avatar, avatar_url, avatar_set, auto_bridge_channels)
-		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
+		INSERT INTO guild (dcid, mxid, plain_name, name, name_set, avatar, avatar_url, avatar_set, auto_bridge_channels)
+		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
 	`
-	_, err := g.db.Exec(query, g.ID, g.mxidPtr(), g.Name, g.NameSet, g.Avatar, g.AvatarURL.String(), g.AvatarSet, g.AutoBridgeChannels)
+	_, err := g.db.Exec(query, g.ID, g.mxidPtr(), g.PlainName, g.Name, g.NameSet, g.Avatar, g.AvatarURL.String(), g.AvatarSet, g.AutoBridgeChannels)
 	if err != nil {
 		g.log.Warnfln("Failed to insert %s: %v", g.ID, err)
 		panic(err)
@@ -106,10 +108,10 @@ func (g *Guild) Insert() {
 
 func (g *Guild) Update() {
 	query := `
-		UPDATE guild SET mxid=$1, name=$2, name_set=$3, avatar=$4, avatar_url=$5, avatar_set=$6, auto_bridge_channels=$7
-		WHERE dcid=$8
+		UPDATE guild SET mxid=$1, plain_name=$2, name=$3, name_set=$4, avatar=$5, avatar_url=$6, avatar_set=$7, auto_bridge_channels=$8
+		WHERE dcid=$9
 	`
-	_, err := g.db.Exec(query, g.mxidPtr(), g.Name, g.NameSet, g.Avatar, g.AvatarURL.String(), g.AvatarSet, g.AutoBridgeChannels, g.ID)
+	_, err := g.db.Exec(query, g.mxidPtr(), g.PlainName, g.Name, g.NameSet, g.Avatar, g.AvatarURL.String(), g.AvatarSet, g.AutoBridgeChannels, g.ID)
 	if err != nil {
 		g.log.Warnfln("Failed to update %s: %v", g.ID, err)
 		panic(err)

+ 18 - 12
database/portal.go

@@ -11,10 +11,14 @@ import (
 	"maunium.net/go/mautrix/util/dbutil"
 )
 
+// language=postgresql
 const (
-	portalSelect = "SELECT dcid, receiver, type, other_user_id, dc_guild_id, dc_parent_id, " +
-		" mxid, name, name_set, topic, topic_set, avatar, avatar_url, avatar_set, encrypted, in_space, first_event_id" +
-		" FROM portal"
+	portalSelect = `
+		SELECT dcid, receiver, type, other_user_id, dc_guild_id, dc_parent_id, mxid,
+		       plain_name, name, name_set, topic, topic_set, avatar, avatar_url, avatar_set,
+		       encrypted, in_space, first_event_id
+		FROM portal
+	`
 )
 
 type PortalKey struct {
@@ -101,6 +105,7 @@ type Portal struct {
 
 	MXID id.RoomID
 
+	PlainName string
 	Name      string
 	NameSet   bool
 	Topic     string
@@ -120,7 +125,7 @@ func (p *Portal) Scan(row dbutil.Scannable) *Portal {
 	var avatarURL string
 
 	err := row.Scan(&p.Key.ChannelID, &p.Key.Receiver, &chanType, &otherUserID, &guildID, &parentID,
-		&mxid, &p.Name, &p.NameSet, &p.Topic, &p.TopicSet, &p.Avatar, &avatarURL, &p.AvatarSet,
+		&mxid, &p.PlainName, &p.Name, &p.NameSet, &p.Topic, &p.TopicSet, &p.Avatar, &avatarURL, &p.AvatarSet,
 		&p.Encrypted, &p.InSpace, &firstEventID)
 
 	if err != nil {
@@ -146,13 +151,13 @@ func (p *Portal) Scan(row dbutil.Scannable) *Portal {
 func (p *Portal) Insert() {
 	query := `
 		INSERT INTO portal (dcid, receiver, type, other_user_id, dc_guild_id, dc_parent_id, mxid,
-		                    name, name_set, topic, topic_set, avatar, avatar_url, avatar_set,
+		                    plain_name, name, name_set, topic, topic_set, avatar, avatar_url, avatar_set,
 		                    encrypted, in_space, first_event_id)
-		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
+		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
 	`
 	_, err := p.db.Exec(query, p.Key.ChannelID, p.Key.Receiver, p.Type,
 		strPtr(p.OtherUserID), strPtr(p.GuildID), strPtr(p.ParentID), strPtr(string(p.MXID)),
-		p.Name, p.NameSet, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet,
+		p.PlainName, p.Name, p.NameSet, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet,
 		p.Encrypted, p.InSpace, p.FirstEventID.String())
 
 	if err != nil {
@@ -163,14 +168,15 @@ func (p *Portal) Insert() {
 
 func (p *Portal) Update() {
 	query := `
-		UPDATE portal SET type=$1, other_user_id=$2, dc_guild_id=$3, dc_parent_id=$4, mxid=$5,
-		                  name=$6, name_set=$7, topic=$8, topic_set=$9, avatar=$10, avatar_url=$11, avatar_set=$12,
-		                  encrypted=$13, in_space=$14, first_event_id=$15
-		WHERE dcid=$16 AND receiver=$17
+		UPDATE portal
+		SET type=$1, other_user_id=$2, dc_guild_id=$3, dc_parent_id=$4, mxid=$5,
+			plain_name=$6, name=$7, name_set=$8, topic=$9, topic_set=$10, avatar=$11, avatar_url=$12, avatar_set=$13,
+			encrypted=$14, in_space=$15, first_event_id=$16
+		WHERE dcid=$17 AND receiver=$18
 	`
 	_, err := p.db.Exec(query,
 		p.Type, strPtr(p.OtherUserID), strPtr(p.GuildID), strPtr(p.ParentID), strPtr(string(p.MXID)),
-		p.Name, p.NameSet, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet,
+		p.PlainName, p.Name, p.NameSet, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet,
 		p.Encrypted, p.InSpace, p.FirstEventID.String(),
 		p.Key.ChannelID, p.Key.Receiver)
 

+ 2 - 0
database/upgrades/00-latest-revision.sql

@@ -3,6 +3,7 @@
 CREATE TABLE guild (
     dcid       TEXT PRIMARY KEY,
     mxid       TEXT UNIQUE,
+    plain_name TEXT NOT NULL,
     name       TEXT NOT NULL,
     name_set   BOOLEAN NOT NULL,
     avatar     TEXT NOT NULL,
@@ -25,6 +26,7 @@ CREATE TABLE portal (
     dc_parent_receiver TEXT NOT NULL DEFAULT '',
 
     mxid       TEXT UNIQUE,
+    plain_name TEXT NOT NULL,
     name       TEXT NOT NULL,
     name_set   BOOLEAN NOT NULL,
     topic      TEXT NOT NULL,

+ 9 - 0
database/upgrades/08-channel-plain-name.sql

@@ -0,0 +1,9 @@
+-- v8: Store plain name of channels and guilds
+ALTER TABLE guild ADD COLUMN plain_name TEXT;
+ALTER TABLE portal ADD COLUMN plain_name TEXT;
+UPDATE guild SET plain_name=name;
+UPDATE portal SET plain_name=name;
+UPDATE portal SET plain_name='' WHERE type=1;
+-- only: postgres for next 2 lines
+ALTER TABLE guild ALTER COLUMN plain_name SET NOT NULL;
+ALTER TABLE portal ALTER COLUMN plain_name SET NOT NULL;

+ 17 - 4
example-config.yaml

@@ -67,15 +67,29 @@ bridge:
     # Localpart template of MXIDs for Discord users.
     # {{.}} is replaced with the internal ID of the Discord user.
     username_template: discord_{{.}}
-    # Displayname template for Discord users.
-    # Available fields:
+    # Displayname template for Discord users. This is also used as the room name in DMs if private_chat_portal_meta is enabled.
+    # Available variables:
     #   .ID - Internal user ID
     #   .Username - User's displayname on Discord
     #   .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
     displayname_template: '{{.Username}}#{{.Discriminator}} {{if .Bot}} (bot){{end}}'
-    channelname_template: '{{if .Guild}}{{.Guild}} - {{end}}{{if .Folder}}{{.Folder}} - {{end}}{{.Name}} (D)'
+    # Displayname template for Discord channels (bridged as rooms, or spaces when type=4).
+    # Available variables:
+    #   .Name - Channel name, or user displayname (pre-formatted with displayname_template) in DMs.
+    #   .ParentName - Parent channel name (used for categories).
+    #   .GuildName - Guild name.
+    #   .NSFW - Whether the channel is marked as NSFW.
+    #   .Type - Channel type (see values at https://github.com/bwmarrin/discordgo/blob/v0.25.0/structs.go#L251-L267)
+    channel_name_template: '{{if or (eq .Type 3) (eq .Type 4)}}{{.Name}}{{else}}#{{.Name}}{{end}}'
+    # Displayname template for Discord guilds (bridged as spaces).
+    # Available variables:
+    #   .Name - Guild name
+    guild_name_template: '{{.Name}}'
+    # Should the bridge explicitly set the avatar and room name for DM portal rooms?
+    # This is implicitly enabled in encrypted rooms.
+    private_chat_portal_meta: false
 
     portal_message_buffer: 128
 
@@ -133,7 +147,6 @@ bridge:
         allow: false
         # Default to encryption, force-enable encryption in all portals the bridge creates
         # This will cause the bridge bot to be in private chats for the encryption to work properly.
-        # It is recommended to also set private_chat_portal_meta to true when using this.
         default: false
         # Require encryption, drop any unencrypted messages.
         require: false

+ 8 - 10
guildportal.go

@@ -28,6 +28,7 @@ import (
 
 	"github.com/bwmarrin/discordgo"
 
+	"go.mau.fi/mautrix-discord/config"
 	"go.mau.fi/mautrix-discord/database"
 )
 
@@ -227,14 +228,7 @@ func (guild *Guild) UpdateInfo(source *User, meta *discordgo.Guild) *discordgo.G
 		return meta
 	}
 	changed := false
-	// FIXME
-	//name, err := guild.bridge.Config.Bridge.FormatChannelname(meta, user.Session)
-	//if err != nil {
-	//	guild.log.Warnfln("failed to format name, proceeding with generic name: %v", err)
-	//	guild.Name = meta.Name
-	//} else {
-	//}
-	changed = guild.UpdateName(meta.Name) || changed
+	changed = guild.UpdateName(meta) || changed
 	changed = guild.UpdateAvatar(meta.Icon) || changed
 	if changed {
 		guild.UpdateBridgeInfo()
@@ -243,11 +237,15 @@ func (guild *Guild) UpdateInfo(source *User, meta *discordgo.Guild) *discordgo.G
 	return meta
 }
 
-func (guild *Guild) UpdateName(name string) bool {
-	if guild.Name == name && guild.NameSet {
+func (guild *Guild) UpdateName(meta *discordgo.Guild) bool {
+	name := guild.bridge.Config.Bridge.FormatGuildName(config.GuildNameParams{
+		Name: meta.Name,
+	})
+	if guild.PlainName == meta.Name && guild.Name == name && guild.NameSet {
 		return false
 	}
 	guild.Name = name
+	guild.PlainName = meta.Name
 	guild.NameSet = false
 	if guild.MXID != "" {
 		_, err := guild.bridge.Bot.SetRoomName(guild.MXID, guild.Name)

+ 24 - 12
portal.go

@@ -23,6 +23,7 @@ import (
 	"maunium.net/go/mautrix/event"
 	"maunium.net/go/mautrix/id"
 
+	"go.mau.fi/mautrix-discord/config"
 	"go.mau.fi/mautrix-discord/database"
 )
 
@@ -1509,11 +1510,29 @@ func (portal *Portal) HandleMatrixTyping(newTyping []id.UserID) {
 	}
 }
 
-func (portal *Portal) UpdateName(name string) bool {
+func (portal *Portal) UpdateName(meta *discordgo.Channel) bool {
+	var parentName, guildName string
+	if portal.Parent != nil {
+		parentName = portal.Parent.PlainName
+	}
+	if portal.Guild != nil {
+		guildName = portal.Guild.PlainName
+	}
+	plainNameChanged := portal.PlainName != meta.Name
+	portal.PlainName = meta.Name
+	return portal.UpdateNameDirect(portal.bridge.Config.Bridge.FormatChannelName(config.ChannelNameParams{
+		Name:       meta.Name,
+		ParentName: parentName,
+		GuildName:  guildName,
+		NSFW:       meta.NSFW,
+		Type:       meta.Type,
+	})) || plainNameChanged
+}
+
+func (portal *Portal) UpdateNameDirect(name string) bool {
 	if portal.Name == name && portal.NameSet {
 		return false
-	} else if !portal.Encrypted && portal.IsPrivateChat() {
-		// TODO custom config option for always setting private chat portal meta?
+	} else if !portal.Encrypted && !portal.bridge.Config.Bridge.PrivateChatPortalMeta && portal.IsPrivateChat() {
 		return false
 	}
 	portal.Name = name
@@ -1708,25 +1727,18 @@ func (portal *Portal) UpdateInfo(source *User, meta *discordgo.Channel) *discord
 		changed = true
 	}
 
-	// FIXME
-	//name, err := portal.bridge.Config.Bridge.FormatChannelname(meta, source.Session)
-	//if err != nil {
-	//	portal.log.Errorln("Failed to format channel name:", err)
-	//	return
-	//}
-
 	switch portal.Type {
 	case discordgo.ChannelTypeDM:
 		if portal.OtherUserID != "" {
 			puppet := portal.bridge.GetPuppetByID(portal.OtherUserID)
 			changed = portal.UpdateAvatarFromPuppet(puppet) || changed
-			changed = portal.UpdateName(puppet.Name) || changed
+			changed = portal.UpdateNameDirect(puppet.Name) || changed
 		}
 	case discordgo.ChannelTypeGroupDM:
 		changed = portal.UpdateGroupDMAvatar(meta.Icon) || changed
 		fallthrough
 	default:
-		changed = portal.UpdateName(meta.Name) || changed
+		changed = portal.UpdateName(meta) || changed
 	}
 	changed = portal.UpdateTopic(meta.Topic) || changed
 	changed = portal.UpdateParent(meta.ParentID) || changed

+ 3 - 1
puppet.go

@@ -202,8 +202,9 @@ func (puppet *Puppet) UpdateName(info *discordgo.User) bool {
 		puppet.log.Warnln("Failed to update displayname:", err)
 	} else {
 		go puppet.updatePortalMeta(func(portal *Portal) {
-			if portal.UpdateName(puppet.Name) {
+			if portal.UpdateNameDirect(puppet.Name) {
 				portal.Update()
+				portal.UpdateBridgeInfo()
 			}
 		})
 		puppet.NameSet = true
@@ -237,6 +238,7 @@ func (puppet *Puppet) UpdateAvatar(info *discordgo.User) bool {
 		go puppet.updatePortalMeta(func(portal *Portal) {
 			if portal.UpdateAvatarFromPuppet(puppet) {
 				portal.Update()
+				portal.UpdateBridgeInfo()
 			}
 		})
 		puppet.AvatarSet = true