Explorar o código

Implement channel name formatting and handle channel updates

Gary Kramlich %!s(int64=3) %!d(string=hai) anos
pai
achega
3629d7807c
Modificáronse 6 ficheiros con 164 adicións e 49 borrados
  1. 31 0
      bridge/avatar.go
  2. 1 0
      bridge/matrix.go
  3. 73 19
      bridge/portal.go
  4. 1 24
      bridge/puppet.go
  5. 1 6
      bridge/user.go
  6. 57 0
      config/bridge.go

+ 31 - 0
bridge/avatar.go

@@ -0,0 +1,31 @@
+package bridge
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+
+	"maunium.net/go/mautrix/appservice"
+	"maunium.net/go/mautrix/id"
+)
+
+func uploadAvatar(intent *appservice.IntentAPI, url string) (id.ContentURI, error) {
+	getResp, err := http.DefaultClient.Get(url)
+	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
+}

+ 1 - 0
bridge/matrix.go

@@ -223,6 +223,7 @@ func (mh *matrixHandler) handleMembership(evt *event.Event) {
 				return
 			}
 		}
+
 		if isSelf {
 			portal.handleMatrixLeave(user)
 		} else if puppet != nil {

+ 73 - 19
bridge/portal.go

@@ -171,44 +171,44 @@ func (p *Portal) MainIntent() *appservice.IntentAPI {
 }
 
 func (p *Portal) createMatrixRoom(user *User, channel *discordgo.Channel) error {
-	p.roomCreateLock.Lock()
-	defer p.roomCreateLock.Unlock()
+	// If we have a matrix id the room should exist so we have nothing to do.
 	if p.MXID != "" {
 		return nil
 	}
 
+	p.roomCreateLock.Lock()
+	defer p.roomCreateLock.Unlock()
+
 	p.Type = channel.Type
 	if p.Type == discordgo.ChannelTypeDM {
 		p.DMUser = channel.Recipients[0].ID
 	}
 
-	// If we have a matrix id the room should exist so we have nothing to do.
-	if p.MXID != "" {
-		return nil
-	}
-
 	intent := p.MainIntent()
 	if err := intent.EnsureRegistered(); err != nil {
 		return err
 	}
 
-	// if p.IsPrivateChat() {
-	p.Name = channel.Name
+	name, err := p.bridge.Config.Bridge.FormatChannelname(channel, user.Session)
+	if err != nil {
+		p.log.Warnfln("failed to format name, proceeding with generic name: %v", err)
+		p.Name = channel.Name
+	} else {
+		p.Name = name
+	}
+
 	p.Topic = channel.Topic
 
 	// TODO: get avatars figured out
 	// p.Avatar = puppet.Avatar
 	// p.AvatarURL = puppet.AvatarURL
-	// }
 
 	p.log.Infoln("Creating Matrix room for channel:", p.Portal.Key.ChannelID)
 
 	initialState := []*event.Event{}
 
 	creationContent := make(map[string]interface{})
-	// if !portal.bridge.Config.Bridge.FederateRooms {
 	creationContent["m.federate"] = false
-	// }
 
 	var invite []id.UserID
 
@@ -650,13 +650,9 @@ func (p *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
 }
 
 func (p *Portal) handleMatrixLeave(sender *User) {
-	if p.IsPrivateChat() {
-		p.log.Debugln("User left private chat portal, cleaning up and deleting...")
-		p.delete()
-		p.cleanup(false)
-
-		return
-	}
+	p.log.Debugln("User left private chat portal, cleaning up and deleting...")
+	p.delete()
+	p.cleanup(false)
 
 	// TODO: figure out how to close a dm from the API.
 
@@ -977,3 +973,61 @@ func (p *Portal) handleMatrixRedaction(evt *event.Event) {
 
 	p.log.Warnfln("Failed to redact %s@%s: no event found", p.Key, evt.Redacts)
 }
+
+func (p *Portal) update(user *User, channel *discordgo.Channel) {
+	name, err := p.bridge.Config.Bridge.FormatChannelname(channel, user.Session)
+	if err != nil {
+		p.log.Warnln("Failed to format channel name, using existing:", err)
+	} else {
+		p.Name = name
+	}
+
+	intent := p.MainIntent()
+
+	if p.Name != name {
+		_, err = intent.SetRoomName(p.MXID, p.Name)
+		if err != nil {
+			p.log.Warnln("Failed to update room name:", err)
+		}
+	}
+
+	if p.Topic != channel.Topic {
+		p.Topic = channel.Topic
+		_, err = intent.SetRoomTopic(p.MXID, p.Topic)
+		if err != nil {
+			p.log.Warnln("Failed to update room topic:", err)
+		}
+	}
+
+	if p.Avatar != channel.Icon {
+		p.Avatar = channel.Icon
+
+		var url string
+
+		if p.Type == discordgo.ChannelTypeDM {
+			dmUser, err := user.Session.User(p.DMUser)
+			if err != nil {
+				p.log.Warnln("failed to lookup the dmuser", err)
+			} else {
+				url = dmUser.AvatarURL("")
+			}
+		} else {
+			url = discordgo.EndpointGroupIcon(channel.ID, channel.Icon)
+		}
+
+		p.AvatarURL = id.ContentURI{}
+		if url != "" {
+			uri, err := uploadAvatar(intent, url)
+			if err != nil {
+				p.log.Warnf("failed to upload avatar", err)
+			} else {
+				p.AvatarURL = uri
+			}
+		}
+
+		intent.SetRoomAvatar(p.MXID, p.AvatarURL)
+	}
+
+	p.Update()
+	p.log.Debugln("portal updated")
+}

+ 1 - 24
bridge/puppet.go

@@ -2,8 +2,6 @@ package bridge
 
 import (
 	"fmt"
-	"io"
-	"net/http"
 	"regexp"
 	"sync"
 
@@ -209,27 +207,6 @@ func (p *Puppet) updatePortalName() {
 	})
 }
 
-func (p *Puppet) uploadAvatar(intent *appservice.IntentAPI, url string) (id.ContentURI, error) {
-	getResp, err := http.DefaultClient.Get(url)
-	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
-}
-
 func (p *Puppet) updateAvatar(source *User) bool {
 	user, err := source.Session.User(p.ID)
 	if err != nil {
@@ -248,7 +225,7 @@ func (p *Puppet) updateAvatar(source *User) bool {
 		return false
 	}
 
-	url, err := p.uploadAvatar(p.DefaultIntent(), user.AvatarURL(""))
+	url, err := uploadAvatar(p.DefaultIntent(), user.AvatarURL(""))
 	if err != nil {
 		p.log.Warnln("Failed to upload user avatar:", err)
 

+ 1 - 6
bridge/user.go

@@ -540,12 +540,7 @@ func (u *User) channelUpdateHandler(s *discordgo.Session, c *discordgo.ChannelUp
 	key := database.NewPortalKey(c.ID, u.User.ID)
 	portal := u.bridge.GetPortalByID(key)
 
-	portal.Name = c.Name
-	portal.Topic = c.Topic
-	u.log.Debugln("channel icon", c.Icon)
-	portal.Update()
-
-	u.log.Debugln("channel update")
+	portal.update(u, c.Channel)
 }
 
 func (u *User) messageCreateHandler(s *discordgo.Session, m *discordgo.MessageCreate) {

+ 57 - 0
config/bridge.go

@@ -1,6 +1,7 @@
 package config
 
 import (
+	"fmt"
 	"strings"
 	"text/template"
 
@@ -12,6 +13,7 @@ import (
 type bridge struct {
 	UsernameTemplate    string `yaml:"username_template"`
 	DisplaynameTemplate string `yaml:"displayname_template"`
+	ChannelnameTemplate string `yaml:"channelname_template"`
 
 	CommandPrefix string `yaml:"command_prefix"`
 
@@ -30,6 +32,7 @@ type bridge struct {
 
 	usernameTemplate    *template.Template `yaml:"-"`
 	displaynameTemplate *template.Template `yaml:"-"`
+	channelnameTemplate *template.Template `yaml:"-"`
 }
 
 func (config *Config) CanAutoDoublePuppet(userID id.UserID) bool {
@@ -60,6 +63,15 @@ func (b *bridge) validate() error {
 		return err
 	}
 
+	if b.ChannelnameTemplate == "" {
+		b.ChannelnameTemplate = "{{if .Guild}}{{.Guild}} - {{end}}{{if .Folder}}{{.Folder}} - {{end}}{{.Name}} (D)"
+	}
+
+	b.channelnameTemplate, err = template.New("channelname").Parse(b.ChannelnameTemplate)
+	if err != nil {
+		return err
+	}
+
 	if b.PortalMessageBuffer <= 0 {
 		b.PortalMessageBuffer = 128
 	}
@@ -128,3 +140,48 @@ func (b bridge) FormatDisplayname(user *discordgo.User) string {
 
 	return buffer.String()
 }
+
+type simplfiedChannel struct {
+	Guild  string
+	Folder string
+	Name   string
+	NSFW   bool
+}
+
+func (b bridge) FormatChannelname(channel *discordgo.Channel, session *discordgo.Session) (string, error) {
+	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] = b.FormatDisplayname(user)
+			}
+
+			return strings.Join(recipients, ", "), nil
+		}
+	}
+
+	b.channelnameTemplate.Execute(&buffer, simplfiedChannel{
+		Guild:  guildName,
+		Folder: folderName,
+		Name:   channel.Name,
+		NSFW:   channel.NSFW,
+	})
+
+	return buffer.String(), nil
+}