Add periodic ghost avatar resync

This commit is contained in:
Tulir Asokan 2022-06-28 14:37:49 +03:00
parent 267799cbe0
commit 9f0901f560
8 changed files with 233 additions and 64 deletions

View file

@ -1069,7 +1069,7 @@ var cmdSync = &commands.FullHandler{
func fnSync(ce *WrappedCommandEvent) { func fnSync(ce *WrappedCommandEvent) {
if len(ce.Args) == 0 { if len(ce.Args) == 0 {
ce.Reply("**Usage:** `sync <appstate/contacts/groups/space> [--create-portals]`") ce.Reply("**Usage:** `sync <appstate/contacts/avatars/groups/space> [--contact-avatars] [--create-portals]`")
return return
} }
args := strings.ToLower(strings.Join(ce.Args, " ")) args := strings.ToLower(strings.Join(ce.Args, " "))
@ -1078,6 +1078,11 @@ func fnSync(ce *WrappedCommandEvent) {
space := strings.Contains(args, "space") space := strings.Contains(args, "space")
groups := strings.Contains(args, "groups") || space groups := strings.Contains(args, "groups") || space
createPortals := strings.Contains(args, "--create-portals") createPortals := strings.Contains(args, "--create-portals")
contactAvatars := strings.Contains(args, "--contact-avatars")
if contactAvatars && (!contacts || appState) {
ce.Reply("`--contact-avatars` can only be used with `sync contacts`")
return
}
if appState { if appState {
for _, name := range appstate.AllPatchNames { for _, name := range appstate.AllPatchNames {
@ -1094,7 +1099,7 @@ func fnSync(ce *WrappedCommandEvent) {
} }
} }
} else if contacts { } else if contacts {
err := ce.User.ResyncContacts() err := ce.User.ResyncContacts(contactAvatars)
if err != nil { if err != nil {
ce.Reply("Error resyncing contacts: %v", err) ce.Reply("Error resyncing contacts: %v", err)
} else { } else {

View file

@ -64,7 +64,7 @@ func (pq *PortalQuery) New() *Portal {
} }
} }
const portalColumns = "jid, receiver, mxid, name, topic, avatar, avatar_url, encrypted, first_event_id, next_batch_id, relay_user_id, expiration_time" const portalColumns = "jid, receiver, mxid, name, name_set, topic, topic_set, avatar, avatar_url, avatar_set, encrypted, first_event_id, next_batch_id, relay_user_id, expiration_time"
func (pq *PortalQuery) GetAll() []*Portal { func (pq *PortalQuery) GetAll() []*Portal {
return pq.getAll(fmt.Sprintf("SELECT %s FROM portal", portalColumns)) return pq.getAll(fmt.Sprintf("SELECT %s FROM portal", portalColumns))
@ -135,9 +135,12 @@ type Portal struct {
MXID id.RoomID MXID id.RoomID
Name string Name string
NameSet bool
Topic string Topic string
TopicSet bool
Avatar string Avatar string
AvatarURL id.ContentURI AvatarURL id.ContentURI
AvatarSet bool
Encrypted bool Encrypted bool
FirstEventID id.EventID FirstEventID id.EventID
@ -150,7 +153,7 @@ type Portal struct {
func (portal *Portal) Scan(row dbutil.Scannable) *Portal { func (portal *Portal) Scan(row dbutil.Scannable) *Portal {
var mxid, avatarURL, firstEventID, nextBatchID, relayUserID sql.NullString var mxid, avatarURL, firstEventID, nextBatchID, relayUserID sql.NullString
err := row.Scan(&portal.Key.JID, &portal.Key.Receiver, &mxid, &portal.Name, &portal.Topic, &portal.Avatar, &avatarURL, &portal.Encrypted, &firstEventID, &nextBatchID, &relayUserID, &portal.ExpirationTime) err := row.Scan(&portal.Key.JID, &portal.Key.Receiver, &mxid, &portal.Name, &portal.NameSet, &portal.Topic, &portal.TopicSet, &portal.Avatar, &avatarURL, &portal.AvatarSet, &portal.Encrypted, &firstEventID, &nextBatchID, &relayUserID, &portal.ExpirationTime)
if err != nil { if err != nil {
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
portal.log.Errorln("Database scan failed:", err) portal.log.Errorln("Database scan failed:", err)
@ -180,8 +183,14 @@ func (portal *Portal) relayUserPtr() *id.UserID {
} }
func (portal *Portal) Insert() { func (portal *Portal) Insert() {
_, err := portal.db.Exec("INSERT INTO portal (jid, receiver, mxid, name, topic, avatar, avatar_url, encrypted, first_event_id, next_batch_id, relay_user_id, expiration_time) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)", _, err := portal.db.Exec(`
portal.Key.JID, portal.Key.Receiver, portal.mxidPtr(), portal.Name, portal.Topic, portal.Avatar, portal.AvatarURL.String(), portal.Encrypted, portal.FirstEventID.String(), portal.NextBatchID.String(), portal.relayUserPtr(), portal.ExpirationTime) INSERT INTO portal (jid, receiver, mxid, name, name_set, topic, topic_set, avatar, avatar_url, avatar_set,
encrypted, first_event_id, next_batch_id, relay_user_id, expiration_time)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
`,
portal.Key.JID, portal.Key.Receiver, portal.mxidPtr(), portal.Name, portal.NameSet, portal.Topic, portal.TopicSet,
portal.Avatar, portal.AvatarURL.String(), portal.AvatarSet, portal.Encrypted, portal.FirstEventID.String(),
portal.NextBatchID.String(), portal.relayUserPtr(), portal.ExpirationTime)
if err != nil { if err != nil {
portal.log.Warnfln("Failed to insert %s: %v", portal.Key, err) portal.log.Warnfln("Failed to insert %s: %v", portal.Key, err)
} }
@ -190,11 +199,14 @@ func (portal *Portal) Insert() {
func (portal *Portal) Update(txn *sql.Tx) { func (portal *Portal) Update(txn *sql.Tx) {
query := ` query := `
UPDATE portal UPDATE portal
SET mxid=$1, name=$2, topic=$3, avatar=$4, avatar_url=$5, encrypted=$6, first_event_id=$7, next_batch_id=$8, relay_user_id=$9, expiration_time=$10 SET mxid=$1, name=$2, name_set=$3, topic=$4, topic_set=$5, avatar=$6, avatar_url=$7, avatar_set=$8,
WHERE jid=$11 AND receiver=$12 encrypted=$9, first_event_id=$10, next_batch_id=$11, relay_user_id=$12, expiration_time=$13
WHERE jid=$14 AND receiver=$15
` `
args := []interface{}{ args := []interface{}{
portal.mxidPtr(), portal.Name, portal.Topic, portal.Avatar, portal.AvatarURL.String(), portal.Encrypted, portal.FirstEventID.String(), portal.NextBatchID.String(), portal.relayUserPtr(), portal.ExpirationTime, portal.Key.JID, portal.Key.Receiver, portal.mxidPtr(), portal.Name, portal.Topic, portal.Avatar, portal.AvatarURL.String(), portal.Encrypted,
portal.FirstEventID.String(), portal.NextBatchID.String(), portal.relayUserPtr(), portal.ExpirationTime,
portal.Key.JID, portal.Key.Receiver,
} }
var err error var err error
if txn != nil { if txn != nil {

View file

@ -18,6 +18,7 @@ package database
import ( import (
"database/sql" "database/sql"
"time"
log "maunium.net/go/maulogger/v2" log "maunium.net/go/maulogger/v2"
@ -43,7 +44,7 @@ func (pq *PuppetQuery) New() *Puppet {
} }
func (pq *PuppetQuery) GetAll() (puppets []*Puppet) { func (pq *PuppetQuery) GetAll() (puppets []*Puppet) {
rows, err := pq.db.Query("SELECT username, avatar, avatar_url, displayname, name_quality, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet") rows, err := pq.db.Query("SELECT username, avatar, avatar_url, displayname, name_quality, name_set, avatar_set, last_sync, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet")
if err != nil || rows == nil { if err != nil || rows == nil {
return nil return nil
} }
@ -55,7 +56,7 @@ func (pq *PuppetQuery) GetAll() (puppets []*Puppet) {
} }
func (pq *PuppetQuery) Get(jid types.JID) *Puppet { func (pq *PuppetQuery) Get(jid types.JID) *Puppet {
row := pq.db.QueryRow("SELECT username, avatar, avatar_url, displayname, name_quality, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet WHERE username=$1", jid.User) row := pq.db.QueryRow("SELECT username, avatar, avatar_url, displayname, name_quality, name_set, avatar_set, last_sync, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet WHERE username=$1", jid.User)
if row == nil { if row == nil {
return nil return nil
} }
@ -63,7 +64,7 @@ func (pq *PuppetQuery) Get(jid types.JID) *Puppet {
} }
func (pq *PuppetQuery) GetByCustomMXID(mxid id.UserID) *Puppet { func (pq *PuppetQuery) GetByCustomMXID(mxid id.UserID) *Puppet {
row := pq.db.QueryRow("SELECT username, avatar, avatar_url, displayname, name_quality, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet WHERE custom_mxid=$1", mxid) row := pq.db.QueryRow("SELECT username, avatar, avatar_url, displayname, name_quality, name_set, avatar_set, last_sync, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet WHERE custom_mxid=$1", mxid)
if row == nil { if row == nil {
return nil return nil
} }
@ -71,7 +72,7 @@ func (pq *PuppetQuery) GetByCustomMXID(mxid id.UserID) *Puppet {
} }
func (pq *PuppetQuery) GetAllWithCustomMXID() (puppets []*Puppet) { func (pq *PuppetQuery) GetAllWithCustomMXID() (puppets []*Puppet) {
rows, err := pq.db.Query("SELECT username, avatar, avatar_url, displayname, name_quality, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet WHERE custom_mxid<>''") rows, err := pq.db.Query("SELECT username, avatar, avatar_url, displayname, name_quality, name_set, avatar_set, last_sync, custom_mxid, access_token, next_batch, enable_presence, enable_receipts FROM puppet WHERE custom_mxid<>''")
if err != nil || rows == nil { if err != nil || rows == nil {
return nil return nil
} }
@ -89,8 +90,11 @@ type Puppet struct {
JID types.JID JID types.JID
Avatar string Avatar string
AvatarURL id.ContentURI AvatarURL id.ContentURI
AvatarSet bool
Displayname string Displayname string
NameQuality int8 NameQuality int8
NameSet bool
LastSync time.Time
CustomMXID id.UserID CustomMXID id.UserID
AccessToken string AccessToken string
@ -101,10 +105,10 @@ type Puppet struct {
func (puppet *Puppet) Scan(row dbutil.Scannable) *Puppet { func (puppet *Puppet) Scan(row dbutil.Scannable) *Puppet {
var displayname, avatar, avatarURL, customMXID, accessToken, nextBatch sql.NullString var displayname, avatar, avatarURL, customMXID, accessToken, nextBatch sql.NullString
var quality sql.NullInt64 var quality, lastSync sql.NullInt64
var enablePresence, enableReceipts sql.NullBool var enablePresence, enableReceipts, nameSet, avatarSet sql.NullBool
var username string var username string
err := row.Scan(&username, &avatar, &avatarURL, &displayname, &quality, &customMXID, &accessToken, &nextBatch, &enablePresence, &enableReceipts) err := row.Scan(&username, &avatar, &avatarURL, &displayname, &quality, &nameSet, &avatarSet, &lastSync, &customMXID, &accessToken, &nextBatch, &enablePresence, &enableReceipts)
if err != nil { if err != nil {
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
puppet.log.Errorln("Database scan failed:", err) puppet.log.Errorln("Database scan failed:", err)
@ -116,6 +120,11 @@ func (puppet *Puppet) Scan(row dbutil.Scannable) *Puppet {
puppet.Avatar = avatar.String puppet.Avatar = avatar.String
puppet.AvatarURL, _ = id.ParseContentURI(avatarURL.String) puppet.AvatarURL, _ = id.ParseContentURI(avatarURL.String)
puppet.NameQuality = int8(quality.Int64) puppet.NameQuality = int8(quality.Int64)
puppet.NameSet = nameSet.Bool
puppet.AvatarSet = avatarSet.Bool
if lastSync.Int64 > 0 {
puppet.LastSync = time.Unix(lastSync.Int64, 0)
}
puppet.CustomMXID = id.UserID(customMXID.String) puppet.CustomMXID = id.UserID(customMXID.String)
puppet.AccessToken = accessToken.String puppet.AccessToken = accessToken.String
puppet.NextBatch = nextBatch.String puppet.NextBatch = nextBatch.String
@ -129,16 +138,36 @@ func (puppet *Puppet) Insert() {
puppet.log.Warnfln("Not inserting %s: not a user", puppet.JID) puppet.log.Warnfln("Not inserting %s: not a user", puppet.JID)
return return
} }
_, err := puppet.db.Exec("INSERT INTO puppet (username, avatar, avatar_url, displayname, name_quality, custom_mxid, access_token, next_batch, enable_presence, enable_receipts) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", var lastSyncTs int64
puppet.JID.User, puppet.Avatar, puppet.AvatarURL.String(), puppet.Displayname, puppet.NameQuality, puppet.CustomMXID, puppet.AccessToken, puppet.NextBatch, puppet.EnablePresence, puppet.EnableReceipts) if !puppet.LastSync.IsZero() {
lastSyncTs = puppet.LastSync.Unix()
}
_, err := puppet.db.Exec(`
INSERT INTO puppet (username, avatar, avatar_url, avatar_set, displayname, name_quality, name_set, last_sync,
custom_mxid, access_token, next_batch, enable_presence, enable_receipts)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
`, puppet.JID.User, puppet.Avatar, puppet.AvatarURL.String(), puppet.AvatarSet, puppet.Displayname,
puppet.NameQuality, puppet.NameSet, lastSyncTs, puppet.CustomMXID, puppet.AccessToken, puppet.NextBatch,
puppet.EnablePresence, puppet.EnableReceipts,
)
if err != nil { if err != nil {
puppet.log.Warnfln("Failed to insert %s: %v", puppet.JID, err) puppet.log.Warnfln("Failed to insert %s: %v", puppet.JID, err)
} }
} }
func (puppet *Puppet) Update() { func (puppet *Puppet) Update() {
_, err := puppet.db.Exec("UPDATE puppet SET displayname=$1, name_quality=$2, avatar=$3, avatar_url=$4, custom_mxid=$5, access_token=$6, next_batch=$7, enable_presence=$8, enable_receipts=$9 WHERE username=$10", var lastSyncTs int64
puppet.Displayname, puppet.NameQuality, puppet.Avatar, puppet.AvatarURL.String(), puppet.CustomMXID, puppet.AccessToken, puppet.NextBatch, puppet.EnablePresence, puppet.EnableReceipts, puppet.JID.User) if !puppet.LastSync.IsZero() {
lastSyncTs = puppet.LastSync.Unix()
}
_, err := puppet.db.Exec(`
UPDATE puppet
SET displayname=$1, name_quality=$2, name_set=$3, avatar=$4, avatar_url=$5, avatar_set=$6, last_sync=$7,
custom_mxid=$8, access_token=$9, next_batch=$10, enable_presence=$11, enable_receipts=$12
WHERE username=$13
`, puppet.Displayname, puppet.NameQuality, puppet.NameSet, puppet.Avatar, puppet.AvatarURL.String(), puppet.AvatarSet,
lastSyncTs, puppet.CustomMXID, puppet.AccessToken, puppet.NextBatch, puppet.EnablePresence, puppet.EnableReceipts,
puppet.JID.User)
if err != nil { if err != nil {
puppet.log.Warnfln("Failed to update %s: %v", puppet.JID, err) puppet.log.Warnfln("Failed to update %s: %v", puppet.JID, err)
} }

View file

@ -1,4 +1,4 @@
-- v0 -> v49: Latest revision -- v0 -> v50: Latest revision
CREATE TABLE "user" ( CREATE TABLE "user" (
mxid TEXT PRIMARY KEY, mxid TEXT PRIMARY KEY,
@ -19,10 +19,13 @@ CREATE TABLE portal (
jid TEXT, jid TEXT,
receiver TEXT, receiver TEXT,
mxid TEXT UNIQUE, mxid TEXT UNIQUE,
name TEXT NOT NULL, name TEXT NOT NULL,
topic TEXT NOT NULL, name_set BOOLEAN NOT NULL DEFAULT false,
avatar TEXT NOT NULL, topic TEXT NOT NULL,
topic_set BOOLEAN NOT NULL DEFAULT false,
avatar TEXT NOT NULL,
avatar_url TEXT, avatar_url TEXT,
avatar_set BOOLEAN NOT NULL DEFAULT false,
encrypted BOOLEAN NOT NULL DEFAULT false, encrypted BOOLEAN NOT NULL DEFAULT false,
first_event_id TEXT, first_event_id TEXT,
@ -39,6 +42,9 @@ CREATE TABLE puppet (
name_quality SMALLINT, name_quality SMALLINT,
avatar TEXT, avatar TEXT,
avatar_url TEXT, avatar_url TEXT,
name_set BOOLEAN NOT NULL DEFAULT false,
avatar_set BOOLEAN NOT NULL DEFAULT false,
last_sync BIGINT NOT NULL DEFAULT 0,
custom_mxid TEXT, custom_mxid TEXT,
access_token TEXT, access_token TEXT,

View file

@ -0,0 +1,13 @@
-- v50: Add last sync timestamp for puppets
ALTER TABLE puppet ADD COLUMN last_sync BIGINT NOT NULL DEFAULT 0;
ALTER TABLE puppet ADD COLUMN name_set BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE puppet ADD COLUMN avatar_set BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE portal ADD COLUMN name_set BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE portal ADD COLUMN avatar_set BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE portal ADD COLUMN topic_set BOOLEAN NOT NULL DEFAULT false;
UPDATE puppet SET name_set=true WHERE displayname<>'';
UPDATE puppet SET avatar_set=true WHERE avatar<>'';
UPDATE portal SET name_set=true WHERE name<>'';
UPDATE portal SET avatar_set=true WHERE avatar<>'';
UPDATE portal SET topic_set=true WHERE topic<>'';

View file

@ -811,20 +811,20 @@ func (portal *Portal) markHandled(txn *sql.Tx, msg *database.Message, info *type
return msg return msg
} }
func (portal *Portal) getMessagePuppet(user *User, info *types.MessageInfo) *Puppet { func (portal *Portal) getMessagePuppet(user *User, info *types.MessageInfo) (puppet *Puppet) {
if info.IsFromMe { if info.IsFromMe {
return portal.bridge.GetPuppetByJID(user.JID) return portal.bridge.GetPuppetByJID(user.JID)
} else if portal.IsPrivateChat() { } else if portal.IsPrivateChat() {
return portal.bridge.GetPuppetByJID(portal.Key.JID) puppet = portal.bridge.GetPuppetByJID(portal.Key.JID)
} else { } else {
puppet := portal.bridge.GetPuppetByJID(info.Sender) puppet = portal.bridge.GetPuppetByJID(info.Sender)
if puppet == nil {
portal.log.Warnfln("Message %+v doesn't seem to have a valid sender (%s): puppet is nil", *info, info.Sender)
return nil
}
puppet.SyncContact(user, true, true, "handling message")
return puppet
} }
if puppet == nil {
portal.log.Warnfln("Message %+v doesn't seem to have a valid sender (%s): puppet is nil", *info, info.Sender)
return nil
}
puppet.SyncContact(user, true, true, "handling message")
return puppet
} }
func (portal *Portal) getMessageIntent(user *User, info *types.MessageInfo) *appservice.IntentAPI { func (portal *Portal) getMessageIntent(user *User, info *types.MessageInfo) *appservice.IntentAPI {
@ -939,12 +939,12 @@ func (portal *Portal) UpdateAvatar(user *User, setBy types.JID, updateInfo bool)
} }
return false return false
} else if avatar == nil { } else if avatar == nil {
if portal.Avatar == "remove" { if portal.Avatar == "remove" && portal.AvatarSet {
return false return false
} }
portal.AvatarURL = id.ContentURI{} portal.AvatarURL = id.ContentURI{}
avatar = &types.ProfilePictureInfo{ID: "remove"} avatar = &types.ProfilePictureInfo{ID: "remove"}
} else if avatar.ID == portal.Avatar { } else if avatar.ID == portal.Avatar && portal.AvatarSet {
return false return false
} else if len(avatar.URL) == 0 { } else if len(avatar.URL) == 0 {
portal.log.Warnln("Didn't get URL in response to avatar query") portal.log.Warnln("Didn't get URL in response to avatar query")
@ -957,6 +957,8 @@ func (portal *Portal) UpdateAvatar(user *User, setBy types.JID, updateInfo bool)
} }
portal.AvatarURL = url portal.AvatarURL = url
} }
portal.Avatar = avatar.ID
portal.AvatarSet = false
if len(portal.MXID) > 0 { if len(portal.MXID) > 0 {
intent := portal.MainIntent() intent := portal.MainIntent()
@ -970,11 +972,13 @@ func (portal *Portal) UpdateAvatar(user *User, setBy types.JID, updateInfo bool)
if err != nil { if err != nil {
portal.log.Warnln("Failed to set room avatar:", err) portal.log.Warnln("Failed to set room avatar:", err)
return false return false
} else {
portal.AvatarSet = true
} }
} }
portal.Avatar = avatar.ID
if updateInfo { if updateInfo {
portal.UpdateBridgeInfo() portal.UpdateBridgeInfo()
portal.Update(nil)
} }
return true return true
} }
@ -983,9 +987,10 @@ func (portal *Portal) UpdateName(name string, setBy types.JID, updateInfo bool)
if name == "" && portal.IsBroadcastList() { if name == "" && portal.IsBroadcastList() {
name = UnnamedBroadcastName name = UnnamedBroadcastName
} }
if portal.Name != name { if portal.Name != name || !portal.NameSet {
portal.log.Debugfln("Updating name %s -> %s", portal.Name, name) portal.log.Debugfln("Updating name %q -> %q", portal.Name, name)
portal.Name = name portal.Name = name
portal.NameSet = false
intent := portal.MainIntent() intent := portal.MainIntent()
if !setBy.IsEmpty() { if !setBy.IsEmpty() {
@ -996,12 +1001,13 @@ func (portal *Portal) UpdateName(name string, setBy types.JID, updateInfo bool)
_, err = portal.MainIntent().SetRoomName(portal.MXID, name) _, err = portal.MainIntent().SetRoomName(portal.MXID, name)
} }
if err == nil { if err == nil {
portal.NameSet = true
if updateInfo { if updateInfo {
portal.UpdateBridgeInfo() portal.UpdateBridgeInfo()
portal.Update(nil)
} }
return true return true
} else { } else {
portal.Name = ""
portal.log.Warnln("Failed to set room name:", err) portal.log.Warnln("Failed to set room name:", err)
} }
} }
@ -1009,9 +1015,10 @@ func (portal *Portal) UpdateName(name string, setBy types.JID, updateInfo bool)
} }
func (portal *Portal) UpdateTopic(topic string, setBy types.JID, updateInfo bool) bool { func (portal *Portal) UpdateTopic(topic string, setBy types.JID, updateInfo bool) bool {
if portal.Topic != topic { if portal.Topic != topic || !portal.TopicSet {
portal.log.Debugfln("Updating topic %s -> %s", portal.Topic, topic) portal.log.Debugfln("Updating topic %q -> %q", portal.Topic, topic)
portal.Topic = topic portal.Topic = topic
portal.TopicSet = false
intent := portal.MainIntent() intent := portal.MainIntent()
if !setBy.IsEmpty() { if !setBy.IsEmpty() {
@ -1022,12 +1029,13 @@ func (portal *Portal) UpdateTopic(topic string, setBy types.JID, updateInfo bool
_, err = portal.MainIntent().SetRoomTopic(portal.MXID, topic) _, err = portal.MainIntent().SetRoomTopic(portal.MXID, topic)
} }
if err == nil { if err == nil {
portal.TopicSet = true
if updateInfo { if updateInfo {
portal.UpdateBridgeInfo() portal.UpdateBridgeInfo()
portal.Update(nil)
} }
return true return true
} else { } else {
portal.Topic = ""
portal.log.Warnln("Failed to set room topic:", err) portal.log.Warnln("Failed to set room topic:", err)
} }
} }
@ -1376,6 +1384,7 @@ func (portal *Portal) CreateMatrixRoom(user *User, groupInfo *types.GroupInfo, i
Parsed: event.RoomAvatarEventContent{URL: portal.AvatarURL}, Parsed: event.RoomAvatarEventContent{URL: portal.AvatarURL},
}, },
}) })
portal.AvatarSet = true
} }
var invite []id.UserID var invite []id.UserID
@ -1410,6 +1419,8 @@ func (portal *Portal) CreateMatrixRoom(user *User, groupInfo *types.GroupInfo, i
if err != nil { if err != nil {
return err return err
} }
portal.NameSet = len(portal.Name) > 0
portal.TopicSet = len(portal.Topic) > 0
portal.MXID = resp.RoomID portal.MXID = resp.RoomID
portal.bridge.portalsLock.Lock() portal.bridge.portalsLock.Lock()
portal.bridge.portalsByMXID[portal.MXID] = portal portal.bridge.portalsByMXID[portal.MXID] = portal

View file

@ -244,21 +244,22 @@ func (puppet *Puppet) UpdateAvatar(source *User) bool {
puppet.log.Warnln("Failed to get avatar URL:", err) puppet.log.Warnln("Failed to get avatar URL:", err)
} else if puppet.Avatar == "" { } else if puppet.Avatar == "" {
puppet.Avatar = "unauthorized" puppet.Avatar = "unauthorized"
puppet.AvatarSet = false
return true return true
} }
return false return false
} else if avatar == nil { } else if avatar == nil {
if puppet.Avatar == "remove" { if puppet.Avatar == "remove" && puppet.AvatarSet {
return false return false
} }
puppet.AvatarURL = id.ContentURI{} puppet.AvatarURL = id.ContentURI{}
avatar = &types.ProfilePictureInfo{ID: "remove"} avatar = &types.ProfilePictureInfo{ID: "remove"}
} else if avatar.ID == puppet.Avatar { } else if avatar.ID == puppet.Avatar && puppet.AvatarSet {
return false return false
} else if len(avatar.URL) == 0 { } else if len(avatar.URL) == 0 {
puppet.log.Warnln("Didn't get URL in response to avatar query") puppet.log.Warnln("Didn't get URL in response to avatar query")
return false return false
} else { } else if avatar.ID != puppet.Avatar || puppet.AvatarURL.IsEmpty() {
url, err := reuploadAvatar(puppet.DefaultIntent(), avatar.URL) url, err := reuploadAvatar(puppet.DefaultIntent(), avatar.URL)
if err != nil { if err != nil {
puppet.log.Warnln("Failed to reupload avatar:", err) puppet.log.Warnln("Failed to reupload avatar:", err)
@ -267,27 +268,31 @@ func (puppet *Puppet) UpdateAvatar(source *User) bool {
puppet.AvatarURL = url puppet.AvatarURL = url
} }
puppet.Avatar = avatar.ID
puppet.AvatarSet = false
err = puppet.DefaultIntent().SetAvatarURL(puppet.AvatarURL) err = puppet.DefaultIntent().SetAvatarURL(puppet.AvatarURL)
if err != nil { if err != nil {
puppet.log.Warnln("Failed to set avatar:", err) puppet.log.Warnln("Failed to set avatar:", err)
} else {
puppet.log.Debugln("Updated avatar", puppet.Avatar, "->", avatar.ID)
puppet.AvatarSet = true
} }
puppet.log.Debugln("Updated avatar", puppet.Avatar, "->", avatar.ID)
puppet.Avatar = avatar.ID
go puppet.updatePortalAvatar() go puppet.updatePortalAvatar()
return true return true
} }
func (puppet *Puppet) UpdateName(source *User, contact types.ContactInfo) bool { func (puppet *Puppet) UpdateName(contact types.ContactInfo) bool {
newName, quality := puppet.bridge.Config.Bridge.FormatDisplayname(puppet.JID, contact) newName, quality := puppet.bridge.Config.Bridge.FormatDisplayname(puppet.JID, contact)
if puppet.Displayname != newName && quality >= puppet.NameQuality { if (puppet.Displayname != newName || !puppet.NameSet) && quality >= puppet.NameQuality {
puppet.Displayname = newName
puppet.NameQuality = quality
puppet.NameSet = false
err := puppet.DefaultIntent().SetDisplayName(newName) err := puppet.DefaultIntent().SetDisplayName(newName)
if err == nil { if err == nil {
puppet.log.Debugln("Updated name", puppet.Displayname, "->", newName) puppet.log.Debugln("Updated name", puppet.Displayname, "->", newName)
puppet.Displayname = newName puppet.NameSet = true
puppet.NameQuality = quality
go puppet.updatePortalName() go puppet.updatePortalName()
puppet.Update()
} else { } else {
puppet.log.Warnln("Failed to set display name:", err) puppet.log.Warnln("Failed to set display name:", err)
} }
@ -336,6 +341,7 @@ func (puppet *Puppet) updatePortalName() {
func (puppet *Puppet) SyncContact(source *User, onlyIfNoName, shouldHavePushName bool, reason string) { func (puppet *Puppet) SyncContact(source *User, onlyIfNoName, shouldHavePushName bool, reason string) {
if onlyIfNoName && len(puppet.Displayname) > 0 && (!shouldHavePushName || puppet.NameQuality > config.NameQualityPhone) { if onlyIfNoName && len(puppet.Displayname) > 0 && (!shouldHavePushName || puppet.NameQuality > config.NameQualityPhone) {
source.EnqueuePuppetResync(puppet)
return return
} }
@ -345,10 +351,10 @@ func (puppet *Puppet) SyncContact(source *User, onlyIfNoName, shouldHavePushName
} else if !contact.Found { } else if !contact.Found {
puppet.log.Warnfln("No contact info found through %s in SyncContact (sync reason: %s)", source.MXID, reason) puppet.log.Warnfln("No contact info found through %s in SyncContact (sync reason: %s)", source.MXID, reason)
} }
puppet.Sync(source, contact) puppet.Sync(source, &contact, false)
} }
func (puppet *Puppet) Sync(source *User, contact types.ContactInfo) { func (puppet *Puppet) Sync(source *User, contact *types.ContactInfo, forceAvatarSync bool) {
puppet.syncLock.Lock() puppet.syncLock.Lock()
defer puppet.syncLock.Unlock() defer puppet.syncLock.Unlock()
err := puppet.DefaultIntent().EnsureRegistered() err := puppet.DefaultIntent().EnsureRegistered()
@ -356,16 +362,20 @@ func (puppet *Puppet) Sync(source *User, contact types.ContactInfo) {
puppet.log.Errorln("Failed to ensure registered:", err) puppet.log.Errorln("Failed to ensure registered:", err)
} }
if puppet.JID.User == source.JID.User { puppet.log.Debugfln("Syncing info through %s", source.JID)
contact.PushName = source.Client.Store.PushName
}
update := false update := false
update = puppet.UpdateName(source, contact) || update if contact != nil {
if len(puppet.Avatar) == 0 || puppet.bridge.Config.Bridge.UserAvatarSync { if puppet.JID.User == source.JID.User {
contact.PushName = source.Client.Store.PushName
}
update = puppet.UpdateName(*contact) || update
}
if len(puppet.Avatar) == 0 || forceAvatarSync || puppet.bridge.Config.Bridge.UserAvatarSync {
update = puppet.UpdateAvatar(source) || update update = puppet.UpdateAvatar(source) || update
} }
if update { if update || puppet.LastSync.Add(24*time.Hour).Before(time.Now()) {
puppet.LastSync = time.Now()
puppet.Update() puppet.Update()
} }
} }

91
user.go
View file

@ -83,6 +83,11 @@ type User struct {
BackfillQueue *BackfillQueue BackfillQueue *BackfillQueue
BridgeState *bridge.BridgeStateQueue BridgeState *bridge.BridgeStateQueue
puppetResyncQueue []*Puppet
puppetResyncQueueDedup map[types.JID]struct{}
puppetResyncQueueLock sync.Mutex
nextPuppetResync time.Time
} }
func (br *WABridge) getUserByMXID(userID id.UserID, onlyIfExists bool) *User { func (br *WABridge) getUserByMXID(userID id.UserID, onlyIfExists bool) *User {
@ -216,6 +221,8 @@ func (br *WABridge) NewUser(dbUser *database.User) *User {
historySyncs: make(chan *events.HistorySync, 32), historySyncs: make(chan *events.HistorySync, 32),
lastPresence: types.PresenceUnavailable, lastPresence: types.PresenceUnavailable,
puppetResyncQueueDedup: make(map[types.JID]struct{}),
} }
user.PermissionLevel = user.bridge.Config.Bridge.Permissions.Get(user.MXID) user.PermissionLevel = user.bridge.Config.Bridge.Permissions.Get(user.MXID)
@ -223,9 +230,85 @@ func (br *WABridge) NewUser(dbUser *database.User) *User {
user.Whitelisted = user.PermissionLevel >= bridgeconfig.PermissionLevelUser user.Whitelisted = user.PermissionLevel >= bridgeconfig.PermissionLevelUser
user.Admin = user.PermissionLevel >= bridgeconfig.PermissionLevelAdmin user.Admin = user.PermissionLevel >= bridgeconfig.PermissionLevelAdmin
user.BridgeState = br.NewBridgeStateQueue(user, user.log) user.BridgeState = br.NewBridgeStateQueue(user, user.log)
go user.puppetResyncLoop()
return user return user
} }
const puppetSyncMinInterval = 7 * 24 * time.Hour
const puppetSyncLoopInterval = 4 * time.Hour
func (user *User) puppetResyncLoop() {
user.nextPuppetResync = time.Now().Add(puppetSyncLoopInterval)
for {
time.Sleep(user.nextPuppetResync.Sub(time.Now()))
user.nextPuppetResync = time.Now().Add(puppetSyncLoopInterval)
user.doPuppetResync()
}
}
func (user *User) EnqueuePuppetResync(puppet *Puppet) {
if puppet.LastSync.Add(puppetSyncMinInterval).After(time.Now()) {
return
}
user.puppetResyncQueueLock.Lock()
if _, exists := user.puppetResyncQueueDedup[puppet.JID]; !exists {
user.puppetResyncQueueDedup[puppet.JID] = struct{}{}
user.puppetResyncQueue = append(user.puppetResyncQueue, puppet)
user.log.Infofln("Enqueued resync for %s (next sync in %s)", puppet.JID, user.nextPuppetResync.Sub(time.Now()))
}
user.puppetResyncQueueLock.Unlock()
}
func (user *User) doPuppetResync() {
if !user.IsLoggedIn() {
return
}
user.puppetResyncQueueLock.Lock()
if len(user.puppetResyncQueue) == 0 {
user.puppetResyncQueueLock.Unlock()
return
}
queue := user.puppetResyncQueue
user.puppetResyncQueue = nil
user.puppetResyncQueueDedup = make(map[types.JID]struct{})
user.puppetResyncQueueLock.Unlock()
var jids []types.JID
var filteredPuppets []*Puppet
for _, puppet := range queue {
if puppet.LastSync.Add(puppetSyncMinInterval).After(time.Now()) {
user.log.Debugfln("Not resyncing %s, last sync was %s ago", puppet.JID, time.Now().Sub(puppet.LastSync))
continue
}
jids = append(jids, puppet.JID)
filteredPuppets = append(filteredPuppets, puppet)
}
if len(jids) == 0 {
user.log.Debugfln("Skipping background sync, all puppets in queue have been synced in the past 3 days")
return
}
user.log.Debugfln("Doing background sync for %+v", jids)
infos, err := user.Client.GetUserInfo(jids)
if err != nil {
user.log.Errorfln("Error getting user info for background sync: %v", err)
return
}
for _, puppet := range filteredPuppets {
info, ok := infos[puppet.JID]
if !ok {
user.log.Warnfln("Didn't get info for %s in background sync", puppet.JID)
continue
}
var contactPtr *types.ContactInfo
contact, err := user.Session.Contacts.GetContact(puppet.JID)
if err != nil {
user.log.Warnfln("Failed to get contact info for %s in background sync: %v", puppet.JID, err)
} else if contact.Found {
contactPtr = &contact
}
puppet.Sync(user, contactPtr, info.PictureID != puppet.Avatar)
}
}
func (user *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID, isDirect bool) (ok bool) { func (user *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID, isDirect bool) (ok bool) {
inviteContent := event.Content{ inviteContent := event.Content{
Parsed: &event.MemberEventContent{ Parsed: &event.MemberEventContent{
@ -670,7 +753,7 @@ func (user *User) HandleEvent(event interface{}) {
} }
} else if v.Name == appstate.WAPatchCriticalUnblockLow { } else if v.Name == appstate.WAPatchCriticalUnblockLow {
go func() { go func() {
err := user.ResyncContacts() err := user.ResyncContacts(false)
if err != nil { if err != nil {
user.log.Errorln("Failed to resync puppets: %v", err) user.log.Errorln("Failed to resync puppets: %v", err)
} }
@ -1020,7 +1103,7 @@ func (user *User) syncPuppet(jid types.JID, reason string) {
user.bridge.GetPuppetByJID(jid).SyncContact(user, false, false, reason) user.bridge.GetPuppetByJID(jid).SyncContact(user, false, false, reason)
} }
func (user *User) ResyncContacts() error { func (user *User) ResyncContacts(forceAvatarSync bool) error {
contacts, err := user.Client.Store.Contacts.GetAllContacts() contacts, err := user.Client.Store.Contacts.GetAllContacts()
if err != nil { if err != nil {
return fmt.Errorf("failed to get cached contacts: %w", err) return fmt.Errorf("failed to get cached contacts: %w", err)
@ -1029,7 +1112,7 @@ func (user *User) ResyncContacts() error {
for jid, contact := range contacts { for jid, contact := range contacts {
puppet := user.bridge.GetPuppetByJID(jid) puppet := user.bridge.GetPuppetByJID(jid)
if puppet != nil { if puppet != nil {
puppet.Sync(user, contact) puppet.Sync(user, &contact, forceAvatarSync)
} else { } else {
user.log.Warnfln("Got a nil puppet for %s while syncing contacts", jid) user.log.Warnfln("Got a nil puppet for %s while syncing contacts", jid)
} }
@ -1196,7 +1279,7 @@ func (user *User) handlePictureUpdate(evt *events.Picture) {
puppet := user.bridge.GetPuppetByJID(evt.JID) puppet := user.bridge.GetPuppetByJID(evt.JID)
user.log.Debugfln("Received picture update for puppet %s (current: %s, new: %s)", evt.JID, puppet.Avatar, evt.PictureID) user.log.Debugfln("Received picture update for puppet %s (current: %s, new: %s)", evt.JID, puppet.Avatar, evt.PictureID)
if puppet.Avatar != evt.PictureID { if puppet.Avatar != evt.PictureID {
puppet.UpdateAvatar(user) puppet.Sync(user, nil, true)
} }
} else if portal := user.GetPortalByJID(evt.JID); portal != nil { } else if portal := user.GetPortalByJID(evt.JID); portal != nil {
user.log.Debugfln("Received picture update for portal %s (current: %s, new: %s)", evt.JID, portal.Avatar, evt.PictureID) user.log.Debugfln("Received picture update for portal %s (current: %s, new: %s)", evt.JID, portal.Avatar, evt.PictureID)