diff --git a/config/bridge.go b/config/bridge.go index 406fc55..8f38350 100644 --- a/config/bridge.go +++ b/config/bridge.go @@ -32,6 +32,8 @@ type BridgeConfig struct { UsernameTemplate string `yaml:"username_template"` DisplaynameTemplate string `yaml:"displayname_template"` + PersonalFilteringSpaces bool `yaml:"personal_filtering_spaces"` + DeliveryReceipts bool `yaml:"delivery_receipts"` PortalMessageBuffer int `yaml:"portal_message_buffer"` CallStartNotices bool `yaml:"call_start_notices"` diff --git a/config/config.go b/config/config.go index b35b869..c383b69 100644 --- a/config/config.go +++ b/config/config.go @@ -59,6 +59,8 @@ type Config struct { Username string `yaml:"username"` Displayname string `yaml:"displayname"` Avatar string `yaml:"avatar"` + + ParsedAvatar id.ContentURI `yaml:"-"` } `yaml:"bot"` EphemeralEvents bool `yaml:"ephemeral_events"` diff --git a/config/upgrade.go b/config/upgrade.go index 7ab8f78..d651a8e 100644 --- a/config/upgrade.go +++ b/config/upgrade.go @@ -63,6 +63,7 @@ func (helper *UpgradeHelper) doUpgrade() { helper.Copy(Str, "bridge", "username_template") helper.Copy(Str, "bridge", "displayname_template") + helper.Copy(Bool, "bridge", "personal_filtering_spaces") helper.Copy(Bool, "bridge", "delivery_receipts") helper.Copy(Int, "bridge", "portal_message_buffer") helper.Copy(Bool, "bridge", "call_start_notices") diff --git a/database/upgrades/2021-12-28-management-space.go b/database/upgrades/2021-12-28-management-space.go deleted file mode 100644 index 1f3d772..0000000 --- a/database/upgrades/2021-12-28-management-space.go +++ /dev/null @@ -1,12 +0,0 @@ -package upgrades - -import ( - "database/sql" -) - -func init() { - upgrades[32] = upgrade{"Store space in user table", func(tx *sql.Tx, ctx context) error { - _, err := tx.Exec(`ALTER TABLE "user" ADD COLUMN space_room TEXT NOT NULL DEFAULT ''`) - return err - }} -} diff --git a/database/upgrades/2021-12-29-personal-filtering-spaces.go b/database/upgrades/2021-12-29-personal-filtering-spaces.go new file mode 100644 index 0000000..4dbf3df --- /dev/null +++ b/database/upgrades/2021-12-29-personal-filtering-spaces.go @@ -0,0 +1,16 @@ +package upgrades + +import ( + "database/sql" +) + +func init() { + upgrades[33] = upgrade{"Add personal filtering space info to user tables", func(tx *sql.Tx, ctx context) error { + _, err := tx.Exec(`ALTER TABLE "user" ADD COLUMN space_room TEXT NOT NULL DEFAULT ''`) + if err != nil { + return err + } + _, err = tx.Exec(`ALTER TABLE user_portal ADD COLUMN in_space BOOLEAN NOT NULL DEFAULT false`) + return err + }} +} diff --git a/database/user.go b/database/user.go index 3f10636..8fd5bd7 100644 --- a/database/user.go +++ b/database/user.go @@ -18,8 +18,11 @@ package database import ( "database/sql" + "sync" + "time" log "maunium.net/go/maulogger/v2" + "maunium.net/go/mautrix/id" "go.mau.fi/whatsmeow/types" @@ -34,6 +37,9 @@ func (uq *UserQuery) New() *User { return &User{ db: uq.db, log: uq.log, + + lastReadCache: make(map[PortalKey]time.Time), + inSpaceCache: make(map[PortalKey]bool), } } @@ -73,6 +79,11 @@ type User struct { JID types.JID ManagementRoom id.RoomID SpaceRoom id.RoomID + + lastReadCache map[PortalKey]time.Time + lastReadCacheLock sync.Mutex + inSpaceCache map[PortalKey]bool + inSpaceCacheLock sync.Mutex } func (user *User) Scan(row Scannable) *User { @@ -127,95 +138,3 @@ func (user *User) Update() { user.log.Warnfln("Failed to update %s: %v", user.MXID, err) } } - -//type PortalKeyWithMeta struct { -// PortalKey -// InCommunity bool -//} -// -//func (user *User) SetPortalKeys(newKeys []PortalKeyWithMeta) error { -// tx, err := user.db.Begin() -// if err != nil { -// return err -// } -// _, err = tx.Exec("DELETE FROM user_portal WHERE user_jid=$1", user.jidPtr()) -// if err != nil { -// _ = tx.Rollback() -// return err -// } -// valueStrings := make([]string, len(newKeys)) -// values := make([]interface{}, len(newKeys)*4) -// for i, key := range newKeys { -// pos := i * 4 -// valueStrings[i] = fmt.Sprintf("($%d, $%d, $%d, $%d)", pos+1, pos+2, pos+3, pos+4) -// values[pos] = user.jidPtr() -// values[pos+1] = key.JID -// values[pos+2] = key.Receiver -// values[pos+3] = key.InCommunity -// } -// query := fmt.Sprintf("INSERT INTO user_portal (user_jid, portal_jid, portal_receiver, in_community) VALUES %s", -// strings.Join(valueStrings, ", ")) -// _, err = tx.Exec(query, values...) -// if err != nil { -// _ = tx.Rollback() -// return err -// } -// return tx.Commit() -//} -// -//func (user *User) IsInPortal(key PortalKey) bool { -// row := user.db.QueryRow(`SELECT EXISTS(SELECT 1 FROM user_portal WHERE user_jid=$1 AND portal_jid=$2 AND portal_receiver=$3)`, user.jidPtr(), &key.JID, &key.Receiver) -// var exists bool -// _ = row.Scan(&exists) -// return exists -//} -// -//func (user *User) GetPortalKeys() []PortalKey { -// rows, err := user.db.Query(`SELECT portal_jid, portal_receiver FROM user_portal WHERE user_jid=$1`, user.jidPtr()) -// if err != nil { -// user.log.Warnln("Failed to get user portal keys:", err) -// return nil -// } -// var keys []PortalKey -// for rows.Next() { -// var key PortalKey -// err = rows.Scan(&key.JID, &key.Receiver) -// if err != nil { -// user.log.Warnln("Failed to scan row:", err) -// continue -// } -// keys = append(keys, key) -// } -// return keys -//} -// -//func (user *User) GetInCommunityMap() map[PortalKey]bool { -// rows, err := user.db.Query(`SELECT portal_jid, portal_receiver, in_community FROM user_portal WHERE user_jid=$1`, user.jidPtr()) -// if err != nil { -// user.log.Warnln("Failed to get user portal keys:", err) -// return nil -// } -// keys := make(map[PortalKey]bool) -// for rows.Next() { -// var key PortalKey -// var inCommunity bool -// err = rows.Scan(&key.JID, &key.Receiver, &inCommunity) -// if err != nil { -// user.log.Warnln("Failed to scan row:", err) -// continue -// } -// keys[key] = inCommunity -// } -// return keys -//} -// -//func (user *User) CreateUserPortal(newKey PortalKeyWithMeta) { -// user.log.Debugfln("Creating new portal %s for %s", newKey.PortalKey.JID, newKey.PortalKey.Receiver) -// _, err := user.db.Exec(`INSERT INTO user_portal (user_jid, portal_jid, portal_receiver, in_community) VALUES ($1, $2, $3, $4)`, -// user.jidPtr(), -// newKey.PortalKey.JID, newKey.PortalKey.Receiver, -// newKey.InCommunity) -// if err != nil { -// user.log.Warnfln("Failed to insert %s: %v", user.MXID, err) -// } -//} diff --git a/database/userportal.go b/database/userportal.go index 355da77..e463a9a 100644 --- a/database/userportal.go +++ b/database/userportal.go @@ -19,37 +19,67 @@ package database import ( "database/sql" "errors" - "fmt" "time" ) func (user *User) GetLastReadTS(portal PortalKey) time.Time { + user.lastReadCacheLock.Lock() + defer user.lastReadCacheLock.Unlock() + if cached, ok := user.lastReadCache[portal]; ok { + return cached + } var ts int64 err := user.db.QueryRow("SELECT last_read_ts FROM user_portal WHERE user_mxid=$1 AND portal_jid=$2 AND portal_receiver=$3", user.MXID, portal.JID, portal.Receiver).Scan(&ts) if err != nil && !errors.Is(err, sql.ErrNoRows) { user.log.Warnfln("Failed to scan last read timestamp from user portal table: %v", err) } if ts == 0 { - return time.Time{} + user.lastReadCache[portal] = time.Time{} + } else { + user.lastReadCache[portal] = time.Unix(ts, 0) } - return time.Unix(ts, 0) + return user.lastReadCache[portal] } func (user *User) SetLastReadTS(portal PortalKey, ts time.Time) { - var err error - if user.db.dialect == "postgres" { - _, err = user.db.Exec(` + user.lastReadCacheLock.Lock() + defer user.lastReadCacheLock.Unlock() + _, err := user.db.Exec(` INSERT INTO user_portal (user_mxid, portal_jid, portal_receiver, last_read_ts) VALUES ($1, $2, $3, $4) - ON CONFLICT (user_mxid, portal_jid, portal_receiver) DO UPDATE SET last_read_ts=$4 WHERE user_portal.last_read_ts<$4 + ON CONFLICT (user_mxid, portal_jid, portal_receiver) DO UPDATE SET last_read_ts=excluded.last_read_ts WHERE user_portal.last_read_ts 0 { + return user.SpaceRoom + } + + resp, err := user.bridge.Bot.CreateRoom(&mautrix.ReqCreateRoom{ + Visibility: "private", + Name: "WhatsApp", + Topic: "Your WhatsApp bridged chats", + InitialState: []*event.Event{{ + Type: event.StateRoomAvatar, + Content: event.Content{ + Parsed: &event.RoomAvatarEventContent{ + URL: user.bridge.Config.AppService.Bot.ParsedAvatar, + }, + }, + }}, + CreationContent: map[string]interface{}{ + "type": event.RoomTypeSpace, + }, + }) + + if err != nil { + user.log.Errorln("Failed to auto-create space room:", err) + } else { + user.SpaceRoom = resp.RoomID + user.Update() + user.ensureInvited(user.bridge.Bot, user.SpaceRoom, false) + } + } else if !user.spaceMembershipChecked && !user.bridge.StateStore.IsInRoom(user.SpaceRoom, user.MXID) { + user.ensureInvited(user.bridge.Bot, user.SpaceRoom, false) + } + user.spaceMembershipChecked = true + + return user.SpaceRoom } func (user *User) GetManagementRoom() id.RoomID {