From 3629d7807c4cbd0e83e804fbc8db6ffa8ed813c8 Mon Sep 17 00:00:00 2001 From: Gary Kramlich Date: Mon, 11 Apr 2022 11:35:35 -0500 Subject: [PATCH] Implement channel name formatting and handle channel updates --- bridge/avatar.go | 31 ++++++++++++++++ bridge/matrix.go | 1 + bridge/portal.go | 92 ++++++++++++++++++++++++++++++++++++++---------- bridge/puppet.go | 25 +------------ bridge/user.go | 7 +--- config/bridge.go | 57 ++++++++++++++++++++++++++++++ 6 files changed, 164 insertions(+), 49 deletions(-) create mode 100644 bridge/avatar.go diff --git a/bridge/avatar.go b/bridge/avatar.go new file mode 100644 index 0000000..94af2a9 --- /dev/null +++ b/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 +} diff --git a/bridge/matrix.go b/bridge/matrix.go index a8ceb02..4e88f18 100644 --- a/bridge/matrix.go +++ b/bridge/matrix.go @@ -223,6 +223,7 @@ func (mh *matrixHandler) handleMembership(evt *event.Event) { return } } + if isSelf { portal.handleMatrixLeave(user) } else if puppet != nil { diff --git a/bridge/portal.go b/bridge/portal.go index 224072a..ccfa3cc 100644 --- a/bridge/portal.go +++ b/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") +} diff --git a/bridge/puppet.go b/bridge/puppet.go index c5741c4..f17e8f1 100644 --- a/bridge/puppet.go +++ b/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) diff --git a/bridge/user.go b/bridge/user.go index 33eba03..04a54d6 100644 --- a/bridge/user.go +++ b/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) { diff --git a/config/bridge.go b/config/bridge.go index d5168b2..9d0510c 100644 --- a/config/bridge.go +++ b/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 +}