Implement channel name formatting and handle channel updates
This commit is contained in:
31
bridge/avatar.go
Normal file
31
bridge/avatar.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -223,6 +223,7 @@ func (mh *matrixHandler) handleMembership(evt *event.Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isSelf {
|
if isSelf {
|
||||||
portal.handleMatrixLeave(user)
|
portal.handleMatrixLeave(user)
|
||||||
} else if puppet != nil {
|
} else if puppet != nil {
|
||||||
|
|||||||
@@ -171,44 +171,44 @@ func (p *Portal) MainIntent() *appservice.IntentAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Portal) createMatrixRoom(user *User, channel *discordgo.Channel) error {
|
func (p *Portal) createMatrixRoom(user *User, channel *discordgo.Channel) error {
|
||||||
p.roomCreateLock.Lock()
|
// If we have a matrix id the room should exist so we have nothing to do.
|
||||||
defer p.roomCreateLock.Unlock()
|
|
||||||
if p.MXID != "" {
|
if p.MXID != "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.roomCreateLock.Lock()
|
||||||
|
defer p.roomCreateLock.Unlock()
|
||||||
|
|
||||||
p.Type = channel.Type
|
p.Type = channel.Type
|
||||||
if p.Type == discordgo.ChannelTypeDM {
|
if p.Type == discordgo.ChannelTypeDM {
|
||||||
p.DMUser = channel.Recipients[0].ID
|
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()
|
intent := p.MainIntent()
|
||||||
if err := intent.EnsureRegistered(); err != nil {
|
if err := intent.EnsureRegistered(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if p.IsPrivateChat() {
|
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
|
p.Name = channel.Name
|
||||||
|
} else {
|
||||||
|
p.Name = name
|
||||||
|
}
|
||||||
|
|
||||||
p.Topic = channel.Topic
|
p.Topic = channel.Topic
|
||||||
|
|
||||||
// TODO: get avatars figured out
|
// TODO: get avatars figured out
|
||||||
// p.Avatar = puppet.Avatar
|
// p.Avatar = puppet.Avatar
|
||||||
// p.AvatarURL = puppet.AvatarURL
|
// p.AvatarURL = puppet.AvatarURL
|
||||||
// }
|
|
||||||
|
|
||||||
p.log.Infoln("Creating Matrix room for channel:", p.Portal.Key.ChannelID)
|
p.log.Infoln("Creating Matrix room for channel:", p.Portal.Key.ChannelID)
|
||||||
|
|
||||||
initialState := []*event.Event{}
|
initialState := []*event.Event{}
|
||||||
|
|
||||||
creationContent := make(map[string]interface{})
|
creationContent := make(map[string]interface{})
|
||||||
// if !portal.bridge.Config.Bridge.FederateRooms {
|
|
||||||
creationContent["m.federate"] = false
|
creationContent["m.federate"] = false
|
||||||
// }
|
|
||||||
|
|
||||||
var invite []id.UserID
|
var invite []id.UserID
|
||||||
|
|
||||||
@@ -650,14 +650,10 @@ func (p *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Portal) handleMatrixLeave(sender *User) {
|
func (p *Portal) handleMatrixLeave(sender *User) {
|
||||||
if p.IsPrivateChat() {
|
|
||||||
p.log.Debugln("User left private chat portal, cleaning up and deleting...")
|
p.log.Debugln("User left private chat portal, cleaning up and deleting...")
|
||||||
p.delete()
|
p.delete()
|
||||||
p.cleanup(false)
|
p.cleanup(false)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: figure out how to close a dm from the API.
|
// TODO: figure out how to close a dm from the API.
|
||||||
|
|
||||||
p.cleanupIfEmpty()
|
p.cleanupIfEmpty()
|
||||||
@@ -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)
|
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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package bridge
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"sync"
|
"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 {
|
func (p *Puppet) updateAvatar(source *User) bool {
|
||||||
user, err := source.Session.User(p.ID)
|
user, err := source.Session.User(p.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -248,7 +225,7 @@ func (p *Puppet) updateAvatar(source *User) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
url, err := p.uploadAvatar(p.DefaultIntent(), user.AvatarURL(""))
|
url, err := uploadAvatar(p.DefaultIntent(), user.AvatarURL(""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Warnln("Failed to upload user avatar:", err)
|
p.log.Warnln("Failed to upload user avatar:", err)
|
||||||
|
|
||||||
|
|||||||
@@ -540,12 +540,7 @@ func (u *User) channelUpdateHandler(s *discordgo.Session, c *discordgo.ChannelUp
|
|||||||
key := database.NewPortalKey(c.ID, u.User.ID)
|
key := database.NewPortalKey(c.ID, u.User.ID)
|
||||||
portal := u.bridge.GetPortalByID(key)
|
portal := u.bridge.GetPortalByID(key)
|
||||||
|
|
||||||
portal.Name = c.Name
|
portal.update(u, c.Channel)
|
||||||
portal.Topic = c.Topic
|
|
||||||
u.log.Debugln("channel icon", c.Icon)
|
|
||||||
portal.Update()
|
|
||||||
|
|
||||||
u.log.Debugln("channel update")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) messageCreateHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
|
func (u *User) messageCreateHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ import (
|
|||||||
type bridge struct {
|
type bridge struct {
|
||||||
UsernameTemplate string `yaml:"username_template"`
|
UsernameTemplate string `yaml:"username_template"`
|
||||||
DisplaynameTemplate string `yaml:"displayname_template"`
|
DisplaynameTemplate string `yaml:"displayname_template"`
|
||||||
|
ChannelnameTemplate string `yaml:"channelname_template"`
|
||||||
|
|
||||||
CommandPrefix string `yaml:"command_prefix"`
|
CommandPrefix string `yaml:"command_prefix"`
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ type bridge struct {
|
|||||||
|
|
||||||
usernameTemplate *template.Template `yaml:"-"`
|
usernameTemplate *template.Template `yaml:"-"`
|
||||||
displaynameTemplate *template.Template `yaml:"-"`
|
displaynameTemplate *template.Template `yaml:"-"`
|
||||||
|
channelnameTemplate *template.Template `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) CanAutoDoublePuppet(userID id.UserID) bool {
|
func (config *Config) CanAutoDoublePuppet(userID id.UserID) bool {
|
||||||
@@ -60,6 +63,15 @@ func (b *bridge) validate() error {
|
|||||||
return err
|
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 {
|
if b.PortalMessageBuffer <= 0 {
|
||||||
b.PortalMessageBuffer = 128
|
b.PortalMessageBuffer = 128
|
||||||
}
|
}
|
||||||
@@ -128,3 +140,48 @@ func (b bridge) FormatDisplayname(user *discordgo.User) string {
|
|||||||
|
|
||||||
return buffer.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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user