2018-08-13 22:24:44 +02:00
|
|
|
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
|
2022-02-18 11:12:15 +01:00
|
|
|
// Copyright (C) 2022 Tulir Asokan
|
2018-08-13 22:24:44 +02:00
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package database
|
|
|
|
|
|
|
|
import (
|
2018-08-26 21:53:13 +02:00
|
|
|
"database/sql"
|
2021-12-29 20:40:08 +01:00
|
|
|
"sync"
|
|
|
|
"time"
|
2019-01-11 20:17:31 +01:00
|
|
|
|
2023-08-05 17:59:17 +02:00
|
|
|
"go.mau.fi/util/dbutil"
|
|
|
|
"go.mau.fi/whatsmeow/types"
|
2019-01-11 20:17:31 +01:00
|
|
|
log "maunium.net/go/maulogger/v2"
|
2021-12-29 20:40:08 +01:00
|
|
|
|
2020-05-08 21:32:22 +02:00
|
|
|
"maunium.net/go/mautrix/id"
|
2018-08-13 22:24:44 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type UserQuery struct {
|
|
|
|
db *Database
|
2018-08-16 18:20:07 +02:00
|
|
|
log log.Logger
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (uq *UserQuery) New() *User {
|
|
|
|
return &User{
|
2018-08-26 21:53:13 +02:00
|
|
|
db: uq.db,
|
|
|
|
log: uq.log,
|
2021-12-29 20:40:08 +01:00
|
|
|
|
|
|
|
lastReadCache: make(map[PortalKey]time.Time),
|
|
|
|
inSpaceCache: make(map[PortalKey]bool),
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (uq *UserQuery) GetAll() (users []*User) {
|
2022-05-11 23:37:30 +02:00
|
|
|
rows, err := uq.db.Query(`SELECT mxid, username, agent, device, management_room, space_room, phone_last_seen, phone_last_pinged, timezone FROM "user"`)
|
2018-08-13 22:24:44 +02:00
|
|
|
if err != nil || rows == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
|
|
users = append(users, uq.New().Scan(rows))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-08 21:32:22 +02:00
|
|
|
func (uq *UserQuery) GetByMXID(userID id.UserID) *User {
|
2022-05-11 23:37:30 +02:00
|
|
|
row := uq.db.QueryRow(`SELECT mxid, username, agent, device, management_room, space_room, phone_last_seen, phone_last_pinged, timezone FROM "user" WHERE mxid=$1`, userID)
|
2018-08-13 22:24:44 +02:00
|
|
|
if row == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return uq.New().Scan(row)
|
|
|
|
}
|
|
|
|
|
2021-10-22 19:14:34 +02:00
|
|
|
func (uq *UserQuery) GetByUsername(username string) *User {
|
2022-05-11 23:37:30 +02:00
|
|
|
row := uq.db.QueryRow(`SELECT mxid, username, agent, device, management_room, space_room, phone_last_seen, phone_last_pinged, timezone FROM "user" WHERE username=$1`, username)
|
2018-08-28 23:40:54 +02:00
|
|
|
if row == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return uq.New().Scan(row)
|
|
|
|
}
|
|
|
|
|
2018-08-13 22:24:44 +02:00
|
|
|
type User struct {
|
|
|
|
db *Database
|
2018-08-16 18:20:07 +02:00
|
|
|
log log.Logger
|
2018-08-13 22:24:44 +02:00
|
|
|
|
2022-02-18 11:12:15 +01:00
|
|
|
MXID id.UserID
|
|
|
|
JID types.JID
|
|
|
|
ManagementRoom id.RoomID
|
|
|
|
SpaceRoom id.RoomID
|
|
|
|
PhoneLastSeen time.Time
|
|
|
|
PhoneLastPinged time.Time
|
2022-05-11 23:37:30 +02:00
|
|
|
Timezone string
|
2021-12-29 20:40:08 +01:00
|
|
|
|
|
|
|
lastReadCache map[PortalKey]time.Time
|
|
|
|
lastReadCacheLock sync.Mutex
|
|
|
|
inSpaceCache map[PortalKey]bool
|
|
|
|
inSpaceCacheLock sync.Mutex
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
func (user *User) Scan(row dbutil.Scannable) *User {
|
2022-05-12 19:51:37 +02:00
|
|
|
var username, timezone sql.NullString
|
2021-10-22 19:14:34 +02:00
|
|
|
var device, agent sql.NullByte
|
2022-02-18 11:12:15 +01:00
|
|
|
var phoneLastSeen, phoneLastPinged sql.NullInt64
|
2022-05-12 19:51:37 +02:00
|
|
|
err := row.Scan(&user.MXID, &username, &agent, &device, &user.ManagementRoom, &user.SpaceRoom, &phoneLastSeen, &phoneLastPinged, &timezone)
|
2018-08-13 22:24:44 +02:00
|
|
|
if err != nil {
|
2018-08-18 21:57:08 +02:00
|
|
|
if err != sql.ErrNoRows {
|
2018-08-26 00:55:21 +02:00
|
|
|
user.log.Errorln("Database scan failed:", err)
|
2018-08-18 21:57:08 +02:00
|
|
|
}
|
|
|
|
return nil
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
2022-05-12 19:51:37 +02:00
|
|
|
user.Timezone = timezone.String
|
2021-10-22 19:14:34 +02:00
|
|
|
if len(username.String) > 0 {
|
|
|
|
user.JID = types.NewADJID(username.String, agent.Byte, device.Byte)
|
2018-08-16 14:59:18 +02:00
|
|
|
}
|
2022-01-25 13:26:24 +01:00
|
|
|
if phoneLastSeen.Valid {
|
|
|
|
user.PhoneLastSeen = time.Unix(phoneLastSeen.Int64, 0)
|
|
|
|
}
|
2022-02-18 11:12:15 +01:00
|
|
|
if phoneLastPinged.Valid {
|
|
|
|
user.PhoneLastPinged = time.Unix(phoneLastPinged.Int64, 0)
|
|
|
|
}
|
2018-08-13 22:24:44 +02:00
|
|
|
return user
|
|
|
|
}
|
|
|
|
|
2021-10-22 19:14:34 +02:00
|
|
|
func (user *User) usernamePtr() *string {
|
|
|
|
if !user.JID.IsEmpty() {
|
|
|
|
return &user.JID.User
|
2018-08-30 00:10:26 +02:00
|
|
|
}
|
2021-10-22 19:14:34 +02:00
|
|
|
return nil
|
2018-08-28 23:40:54 +02:00
|
|
|
}
|
|
|
|
|
2021-10-22 19:14:34 +02:00
|
|
|
func (user *User) agentPtr() *uint8 {
|
|
|
|
if !user.JID.IsEmpty() {
|
2023-08-08 14:11:30 +02:00
|
|
|
zero := uint8(0)
|
|
|
|
return &zero
|
2018-09-01 22:38:03 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-22 19:14:34 +02:00
|
|
|
func (user *User) devicePtr() *uint8 {
|
|
|
|
if !user.JID.IsEmpty() {
|
2023-08-08 14:11:30 +02:00
|
|
|
device := uint8(user.JID.Device)
|
|
|
|
return &device
|
2018-08-16 14:59:18 +02:00
|
|
|
}
|
2021-10-22 19:14:34 +02:00
|
|
|
return nil
|
2018-08-28 23:40:54 +02:00
|
|
|
}
|
|
|
|
|
2022-01-25 13:26:24 +01:00
|
|
|
func (user *User) phoneLastSeenPtr() *int64 {
|
|
|
|
if user.PhoneLastSeen.IsZero() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
ts := user.PhoneLastSeen.Unix()
|
|
|
|
return &ts
|
|
|
|
}
|
|
|
|
|
2022-02-18 11:12:15 +01:00
|
|
|
func (user *User) phoneLastPingedPtr() *int64 {
|
|
|
|
if user.PhoneLastPinged.IsZero() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
ts := user.PhoneLastPinged.Unix()
|
|
|
|
return &ts
|
|
|
|
}
|
|
|
|
|
2019-01-21 22:55:16 +01:00
|
|
|
func (user *User) Insert() {
|
2022-05-11 23:37:30 +02:00
|
|
|
_, err := user.db.Exec(`INSERT INTO "user" (mxid, username, agent, device, management_room, space_room, phone_last_seen, phone_last_pinged, timezone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
|
|
user.MXID, user.usernamePtr(), user.agentPtr(), user.devicePtr(), user.ManagementRoom, user.SpaceRoom, user.phoneLastSeenPtr(), user.phoneLastPingedPtr(), user.Timezone)
|
2018-09-01 22:38:03 +02:00
|
|
|
if err != nil {
|
|
|
|
user.log.Warnfln("Failed to insert %s: %v", user.MXID, err)
|
|
|
|
}
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
|
|
|
|
2019-01-21 22:55:16 +01:00
|
|
|
func (user *User) Update() {
|
2022-05-11 23:37:30 +02:00
|
|
|
_, err := user.db.Exec(`UPDATE "user" SET username=$1, agent=$2, device=$3, management_room=$4, space_room=$5, phone_last_seen=$6, phone_last_pinged=$7, timezone=$8 WHERE mxid=$9`,
|
|
|
|
user.usernamePtr(), user.agentPtr(), user.devicePtr(), user.ManagementRoom, user.SpaceRoom, user.phoneLastSeenPtr(), user.phoneLastPingedPtr(), user.Timezone, user.MXID)
|
2018-09-01 22:38:03 +02:00
|
|
|
if err != nil {
|
|
|
|
user.log.Warnfln("Failed to update %s: %v", user.MXID, err)
|
|
|
|
}
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
2022-05-16 10:22:41 +02:00
|
|
|
|
|
|
|
func (user *User) GetLastAppStateKeyID() ([]byte, error) {
|
|
|
|
var keyID []byte
|
|
|
|
err := user.db.QueryRow("SELECT key_id FROM whatsmeow_app_state_sync_keys ORDER BY timestamp DESC LIMIT 1").Scan(&keyID)
|
|
|
|
return keyID, err
|
|
|
|
}
|