Bridge friend nicks as DM room name

This commit is contained in:
Tulir Asokan
2023-04-22 02:50:14 +03:00
parent 049ef48fb0
commit 3f3c86754d
8 changed files with 124 additions and 31 deletions

View File

@@ -15,7 +15,7 @@ import (
const ( const (
portalSelect = ` portalSelect = `
SELECT dcid, receiver, type, other_user_id, dc_guild_id, dc_parent_id, mxid, 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, plain_name, name, name_set, friend_nick, topic, topic_set, avatar, avatar_url, avatar_set,
encrypted, in_space, first_event_id, relay_webhook_id, relay_webhook_secret encrypted, in_space, first_event_id, relay_webhook_id, relay_webhook_secret
FROM portal FROM portal
` `
@@ -68,6 +68,10 @@ func (pq *PortalQuery) GetByMXID(mxid id.RoomID) *Portal {
return pq.get(portalSelect+" WHERE mxid=$1", mxid) return pq.get(portalSelect+" WHERE mxid=$1", mxid)
} }
func (pq *PortalQuery) FindPrivateChatBetween(id, receiver string) *Portal {
return pq.get(portalSelect+" WHERE other_user_id=$1 AND receiver=$2 AND type=$3", id, receiver, discordgo.ChannelTypeDM)
}
func (pq *PortalQuery) FindPrivateChatsWith(id string) []*Portal { func (pq *PortalQuery) FindPrivateChatsWith(id string) []*Portal {
return pq.getAll(portalSelect+" WHERE other_user_id=$1 AND type=$2", id, discordgo.ChannelTypeDM) return pq.getAll(portalSelect+" WHERE other_user_id=$1 AND type=$2", id, discordgo.ChannelTypeDM)
} }
@@ -109,16 +113,17 @@ type Portal struct {
MXID id.RoomID MXID id.RoomID
PlainName string PlainName string
Name string Name string
NameSet bool NameSet bool
Topic string FriendNick bool
TopicSet bool Topic string
Avatar string TopicSet bool
AvatarURL id.ContentURI Avatar string
AvatarSet bool AvatarURL id.ContentURI
Encrypted bool AvatarSet bool
InSpace id.RoomID Encrypted bool
InSpace id.RoomID
FirstEventID id.EventID FirstEventID id.EventID
@@ -132,7 +137,7 @@ func (p *Portal) Scan(row dbutil.Scannable) *Portal {
var avatarURL string var avatarURL string
err := row.Scan(&p.Key.ChannelID, &p.Key.Receiver, &chanType, &otherUserID, &guildID, &parentID, err := row.Scan(&p.Key.ChannelID, &p.Key.Receiver, &chanType, &otherUserID, &guildID, &parentID,
&mxid, &p.PlainName, &p.Name, &p.NameSet, &p.Topic, &p.TopicSet, &p.Avatar, &avatarURL, &p.AvatarSet, &mxid, &p.PlainName, &p.Name, &p.NameSet, &p.FriendNick, &p.Topic, &p.TopicSet, &p.Avatar, &avatarURL, &p.AvatarSet,
&p.Encrypted, &p.InSpace, &firstEventID, &relayWebhookID, &relayWebhookSecret) &p.Encrypted, &p.InSpace, &firstEventID, &relayWebhookID, &relayWebhookSecret)
if err != nil { if err != nil {
@@ -160,13 +165,13 @@ func (p *Portal) Scan(row dbutil.Scannable) *Portal {
func (p *Portal) Insert() { func (p *Portal) Insert() {
query := ` query := `
INSERT INTO portal (dcid, receiver, type, other_user_id, dc_guild_id, dc_parent_id, mxid, INSERT INTO portal (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, plain_name, name, name_set, friend_nick, topic, topic_set, avatar, avatar_url, avatar_set,
encrypted, in_space, first_event_id, relay_webhook_id, relay_webhook_secret) encrypted, in_space, first_event_id, relay_webhook_id, relay_webhook_secret)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)
` `
_, err := p.db.Exec(query, p.Key.ChannelID, p.Key.Receiver, p.Type, _, 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)), strPtr(p.OtherUserID), strPtr(p.GuildID), strPtr(p.ParentID), strPtr(string(p.MXID)),
p.PlainName, p.Name, p.NameSet, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet, p.PlainName, p.Name, p.NameSet, p.FriendNick, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet,
p.Encrypted, p.InSpace, p.FirstEventID.String(), strPtr(p.RelayWebhookID), strPtr(p.RelayWebhookSecret)) p.Encrypted, p.InSpace, p.FirstEventID.String(), strPtr(p.RelayWebhookID), strPtr(p.RelayWebhookSecret))
if err != nil { if err != nil {
@@ -179,14 +184,16 @@ func (p *Portal) Update() {
query := ` query := `
UPDATE portal UPDATE portal
SET type=$1, other_user_id=$2, dc_guild_id=$3, dc_parent_id=$4, mxid=$5, 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, plain_name=$6, name=$7, name_set=$8, friend_nick=$9, topic=$10, topic_set=$11,
encrypted=$14, in_space=$15, first_event_id=$16, relay_webhook_id=$17, relay_webhook_secret=$18 avatar=$12, avatar_url=$13, avatar_set=$14, encrypted=$15, in_space=$16, first_event_id=$17,
WHERE dcid=$19 AND receiver=$20 relay_webhook_id=$18, relay_webhook_secret=$19
WHERE dcid=$20 AND receiver=$21
` `
_, err := p.db.Exec(query, _, err := p.db.Exec(query,
p.Type, strPtr(p.OtherUserID), strPtr(p.GuildID), strPtr(p.ParentID), strPtr(string(p.MXID)), p.Type, strPtr(p.OtherUserID), strPtr(p.GuildID), strPtr(p.ParentID), strPtr(string(p.MXID)),
p.PlainName, p.Name, p.NameSet, p.Topic, p.TopicSet, p.Avatar, p.AvatarURL.String(), p.AvatarSet, p.PlainName, p.Name, p.NameSet, p.FriendNick, p.Topic, p.TopicSet,
p.Encrypted, p.InSpace, p.FirstEventID.String(), strPtr(p.RelayWebhookID), strPtr(p.RelayWebhookSecret), p.Avatar, p.AvatarURL.String(), p.AvatarSet, p.Encrypted, p.InSpace, p.FirstEventID.String(),
strPtr(p.RelayWebhookID), strPtr(p.RelayWebhookSecret),
p.Key.ChannelID, p.Key.Receiver) p.Key.ChannelID, p.Key.Receiver)
if err != nil { if err != nil {

View File

@@ -1,4 +1,4 @@
-- v0 -> v16: Latest revision -- v0 -> v17: Latest revision
CREATE TABLE guild ( CREATE TABLE guild (
dcid TEXT PRIMARY KEY, dcid TEXT PRIMARY KEY,
@@ -29,6 +29,7 @@ CREATE TABLE portal (
plain_name TEXT NOT NULL, plain_name TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
name_set BOOLEAN NOT NULL, name_set BOOLEAN NOT NULL,
friend_nick BOOLEAN NOT NULL,
topic TEXT NOT NULL, topic TEXT NOT NULL,
topic_set BOOLEAN NOT NULL, topic_set BOOLEAN NOT NULL,
avatar TEXT NOT NULL, avatar TEXT NOT NULL,

View File

@@ -0,0 +1,2 @@
-- v17: Store whether DM portal name is a friend nickname
ALTER TABLE portal ADD COLUMN friend_nick BOOLEAN NOT NULL DEFAULT false;

2
go.mod
View File

@@ -37,4 +37,4 @@ require (
maunium.net/go/mauflag v1.0.0 // indirect maunium.net/go/mauflag v1.0.0 // indirect
) )
replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20230421223629-940c512c92de replace github.com/bwmarrin/discordgo => github.com/beeper/discordgo v0.0.0-20230421234845-798d8c4943a5

4
go.sum
View File

@@ -1,6 +1,6 @@
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/beeper/discordgo v0.0.0-20230421223629-940c512c92de h1:jq5xgpkSvFJiiXH8w0SWGjK6jzwU8gZvrNahAq24nyI= github.com/beeper/discordgo v0.0.0-20230421234845-798d8c4943a5 h1:goe2lr+0qYbVtdidyl3UMurIGdFnXXZqmNq0vWZo4f4=
github.com/beeper/discordgo v0.0.0-20230421223629-940c512c92de/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/beeper/discordgo v0.0.0-20230421234845-798d8c4943a5/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@@ -165,6 +165,20 @@ func (user *User) GetPortalByID(id string, chanType discordgo.ChannelType) *Port
return user.bridge.GetPortalByID(database.NewPortalKey(id, user.DiscordID), chanType) return user.bridge.GetPortalByID(database.NewPortalKey(id, user.DiscordID), chanType)
} }
func (user *User) FindPrivateChatWith(userID string) *Portal {
user.bridge.portalsLock.Lock()
defer user.bridge.portalsLock.Unlock()
dbPortal := user.bridge.DB.Portal.FindPrivateChatBetween(userID, user.DiscordID)
if dbPortal == nil {
return nil
}
existing, ok := user.bridge.portalsByID[dbPortal.Key]
if ok {
return existing
}
return user.bridge.loadPortal(dbPortal, nil, discordgo.ChannelTypeDM)
}
func (br *DiscordBridge) GetExistingPortalByID(key database.PortalKey) *Portal { func (br *DiscordBridge) GetExistingPortalByID(key database.PortalKey) *Portal {
br.portalsLock.Lock() br.portalsLock.Lock()
defer br.portalsLock.Unlock() defer br.portalsLock.Unlock()
@@ -466,7 +480,7 @@ func (portal *Portal) CreateMatrixRoom(user *User, channel *discordgo.Channel) e
InitialState: initialState, InitialState: initialState,
CreationContent: creationContent, CreationContent: creationContent,
} }
if !portal.shouldSetDMRoomMetadata() { if !portal.shouldSetDMRoomMetadata() && !portal.FriendNick {
req.Name = "" req.Name = ""
} }
@@ -1854,11 +1868,13 @@ func (portal *Portal) UpdateName(meta *discordgo.Channel) bool {
GuildName: guildName, GuildName: guildName,
NSFW: meta.NSFW, NSFW: meta.NSFW,
Type: meta.Type, Type: meta.Type,
})) || plainNameChanged }), false) || plainNameChanged
} }
func (portal *Portal) UpdateNameDirect(name string) bool { func (portal *Portal) UpdateNameDirect(name string, isFriendNick bool) bool {
if portal.Name == name && (portal.NameSet || portal.MXID == "" || !portal.shouldSetDMRoomMetadata()) { if portal.FriendNick && !isFriendNick {
return false
} else if portal.Name == name && (portal.NameSet || portal.MXID == "" || (!portal.shouldSetDMRoomMetadata() && !isFriendNick)) {
return false return false
} }
portal.log.Debugfln("Updating name %q -> %q", portal.Name, name) portal.log.Debugfln("Updating name %q -> %q", portal.Name, name)
@@ -1869,7 +1885,7 @@ func (portal *Portal) UpdateNameDirect(name string) bool {
} }
func (portal *Portal) updateRoomName() { func (portal *Portal) updateRoomName() {
if portal.MXID != "" && portal.shouldSetDMRoomMetadata() { if portal.MXID != "" && (portal.shouldSetDMRoomMetadata() || portal.FriendNick) {
_, err := portal.MainIntent().SetRoomName(portal.MXID, portal.Name) _, err := portal.MainIntent().SetRoomName(portal.MXID, portal.Name)
if err != nil { if err != nil {
portal.log.Warnln("Failed to update room name:", err) portal.log.Warnln("Failed to update room name:", err)
@@ -2073,7 +2089,13 @@ func (portal *Portal) UpdateInfo(source *User, meta *discordgo.Channel) *discord
if portal.OtherUserID != "" { if portal.OtherUserID != "" {
puppet := portal.bridge.GetPuppetByID(portal.OtherUserID) puppet := portal.bridge.GetPuppetByID(portal.OtherUserID)
changed = portal.UpdateAvatarFromPuppet(puppet) || changed changed = portal.UpdateAvatarFromPuppet(puppet) || changed
changed = portal.UpdateNameDirect(puppet.Name) || changed if rel, ok := source.relationships[portal.OtherUserID]; ok && rel.Nickname != "" {
portal.FriendNick = true
changed = portal.UpdateNameDirect(rel.Nickname, true) || changed
} else {
portal.FriendNick = false
changed = portal.UpdateNameDirect(puppet.Name, false) || changed
}
} }
case discordgo.ChannelTypeGroupDM: case discordgo.ChannelTypeGroupDM:
changed = portal.UpdateGroupDMAvatar(meta.Icon) || changed changed = portal.UpdateGroupDMAvatar(meta.Icon) || changed

View File

@@ -205,7 +205,7 @@ func (puppet *Puppet) UpdateName(info *discordgo.User) bool {
puppet.log.Warn().Err(err).Msg("Failed to update displayname") puppet.log.Warn().Err(err).Msg("Failed to update displayname")
} else { } else {
go puppet.updatePortalMeta(func(portal *Portal) { go puppet.updatePortalMeta(func(portal *Portal) {
if portal.UpdateNameDirect(puppet.Name) { if portal.UpdateNameDirect(puppet.Name, false) {
portal.Update() portal.Update()
portal.UpdateBridgeInfo() portal.UpdateBridgeInfo()
} }

61
user.go
View File

@@ -62,6 +62,8 @@ type User struct {
pendingInteractionsLock sync.Mutex pendingInteractionsLock sync.Mutex
nextDiscordUploadID atomic.Int32 nextDiscordUploadID atomic.Int32
relationships map[string]*discordgo.Relationship
} }
func (user *User) GetRemoteID() string { func (user *User) GetRemoteID() string {
@@ -189,6 +191,8 @@ func (br *DiscordBridge) NewUser(dbUser *database.User) *User {
PermissionLevel: br.Config.Bridge.Permissions.Get(dbUser.MXID), PermissionLevel: br.Config.Bridge.Permissions.Get(dbUser.MXID),
pendingInteractions: make(map[string]*WrappedCommandEvent), pendingInteractions: make(map[string]*WrappedCommandEvent),
relationships: make(map[string]*discordgo.Relationship),
} }
user.nextDiscordUploadID.Store(rand.Int31n(100)) user.nextDiscordUploadID.Store(rand.Int31n(100))
user.BridgeState = br.NewBridgeStateQueue(user) user.BridgeState = br.NewBridgeStateQueue(user)
@@ -581,6 +585,10 @@ func (user *User) Connect() error {
user.Session.AddHandler(user.channelPinsUpdateHandler) user.Session.AddHandler(user.channelPinsUpdateHandler)
user.Session.AddHandler(user.channelUpdateHandler) user.Session.AddHandler(user.channelUpdateHandler)
user.Session.AddHandler(user.relationshipAddHandler)
user.Session.AddHandler(user.relationshipRemoveHandler)
user.Session.AddHandler(user.relationshipUpdateHandler)
user.Session.AddHandler(user.messageCreateHandler) user.Session.AddHandler(user.messageCreateHandler)
user.Session.AddHandler(user.messageDeleteHandler) user.Session.AddHandler(user.messageDeleteHandler)
user.Session.AddHandler(user.messageUpdateHandler) user.Session.AddHandler(user.messageUpdateHandler)
@@ -660,6 +668,10 @@ func (user *User) readyHandler(_ *discordgo.Session, r *discordgo.Ready) {
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateBackfilling}) user.BridgeState.Send(status.BridgeState{StateEvent: status.StateBackfilling})
user.tryAutomaticDoublePuppeting() user.tryAutomaticDoublePuppeting()
for _, relationship := range r.Relationships {
user.relationships[relationship.ID] = relationship
}
updateTS := time.Now() updateTS := time.Now()
portalsInSpace := make(map[string]bool) portalsInSpace := make(map[string]bool)
for _, guild := range user.GetPortals() { for _, guild := range user.GetPortals() {
@@ -738,6 +750,55 @@ func (user *User) addPrivateChannelToSpace(portal *Portal) bool {
} }
} }
func (user *User) relationshipAddHandler(_ *discordgo.Session, r *discordgo.RelationshipAdd) {
user.log.Debug().Interface("relationship", r.Relationship).Msg("Relationship added")
user.relationships[r.ID] = r.Relationship
user.handleRelationshipChange(r.ID, r.Nickname)
}
func (user *User) relationshipUpdateHandler(_ *discordgo.Session, r *discordgo.RelationshipUpdate) {
user.log.Debug().Interface("relationship", r.Relationship).Msg("Relationship update")
user.relationships[r.ID] = r.Relationship
user.handleRelationshipChange(r.ID, r.Nickname)
}
func (user *User) relationshipRemoveHandler(_ *discordgo.Session, r *discordgo.RelationshipRemove) {
user.log.Debug().Str("other_user_id", r.ID).Msg("Relationship removed")
delete(user.relationships, r.ID)
user.handleRelationshipChange(r.ID, "")
}
func (user *User) handleRelationshipChange(userID, nickname string) {
puppet := user.bridge.GetPuppetByID(userID)
portal := user.FindPrivateChatWith(userID)
if portal == nil || puppet == nil {
return
}
updated := portal.FriendNick == (nickname != "")
portal.FriendNick = nickname != ""
if nickname != "" {
updated = portal.UpdateNameDirect(nickname, true)
} else if portal.Name != puppet.Name {
if portal.shouldSetDMRoomMetadata() {
updated = portal.UpdateNameDirect(puppet.Name, false)
} else if portal.NameSet {
_, err := portal.MainIntent().SendStateEvent(portal.MXID, event.StateRoomName, "", map[string]any{})
if err != nil {
portal.zlog.Warn().Err(err).Msg("Failed to clear room name after friend nickname was removed")
} else {
portal.zlog.Debug().Msg("Cleared room name after friend nickname was removed")
portal.NameSet = false
portal.Update()
updated = true
}
}
}
if !updated {
portal.Update()
}
}
func (user *User) handlePrivateChannel(portal *Portal, meta *discordgo.Channel, timestamp time.Time, create, isInSpace bool) { func (user *User) handlePrivateChannel(portal *Portal, meta *discordgo.Channel, timestamp time.Time, create, isInSpace bool) {
if create && portal.MXID == "" { if create && portal.MXID == "" {
err := portal.CreateMatrixRoom(user, meta) err := portal.CreateMatrixRoom(user, meta)