2018-08-13 22:24:44 +02:00
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
2019-01-12 14:54:04 +01:00
// Copyright (C) 2019 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"
2019-05-28 20:31:25 +02:00
"fmt"
2018-08-30 00:10:26 +02:00
"strings"
2019-05-22 15:46:18 +02:00
"time"
2018-08-26 21:53:13 +02:00
2018-08-13 22:24:44 +02:00
"github.com/Rhymen/go-whatsapp"
2019-01-11 20:17:31 +01:00
log "maunium.net/go/maulogger/v2"
2018-08-16 23:11:28 +02:00
"maunium.net/go/mautrix-whatsapp/types"
2018-08-30 00:10:26 +02:00
"maunium.net/go/mautrix-whatsapp/whatsapp-ext"
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 ,
2018-08-13 22:24:44 +02:00
}
}
func ( uq * UserQuery ) GetAll ( ) ( users [ ] * User ) {
2019-06-13 20:28:14 +02:00
rows , err := uq . db . Query ( ` SELECT mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key 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
}
2018-08-28 23:40:54 +02:00
func ( uq * UserQuery ) GetByMXID ( userID types . MatrixUserID ) * User {
2019-06-13 20:28:14 +02:00
row := uq . db . QueryRow ( ` SELECT mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key FROM "user" WHERE mxid=$1 ` , userID )
2018-08-13 22:24:44 +02:00
if row == nil {
return nil
}
return uq . New ( ) . Scan ( row )
}
2018-08-28 23:40:54 +02:00
func ( uq * UserQuery ) GetByJID ( userID types . WhatsAppID ) * User {
2019-06-13 20:28:14 +02:00
row := uq . db . QueryRow ( ` SELECT mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key FROM "user" WHERE jid=$1 ` , stripSuffix ( userID ) )
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
2018-08-28 23:40:54 +02:00
MXID types . MatrixUserID
JID types . WhatsAppID
2018-08-16 23:11:28 +02:00
ManagementRoom types . MatrixRoomID
2018-08-16 14:59:18 +02:00
Session * whatsapp . Session
2019-05-22 15:46:18 +02:00
LastConnection uint64
2018-08-13 22:24:44 +02:00
}
func ( user * User ) Scan ( row Scannable ) * User {
2018-10-16 21:36:16 +02:00
var jid , clientID , clientToken , serverToken sql . NullString
2018-08-30 00:10:26 +02:00
var encKey , macKey [ ] byte
2019-06-13 20:28:14 +02:00
err := row . Scan ( & user . MXID , & jid , & user . ManagementRoom , & user . LastConnection , & clientID , & clientToken , & serverToken , & encKey , & macKey )
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
}
2018-08-30 00:10:26 +02:00
if len ( jid . String ) > 0 && len ( clientID . String ) > 0 {
user . JID = jid . String + whatsappExt . NewUserSuffix
user . Session = & whatsapp . Session {
ClientId : clientID . String ,
ClientToken : clientToken . String ,
ServerToken : serverToken . String ,
EncKey : encKey ,
MacKey : macKey ,
Wid : jid . String + whatsappExt . OldUserSuffix ,
}
2018-08-16 14:59:18 +02:00
} else {
user . Session = nil
}
2018-08-13 22:24:44 +02:00
return user
}
2018-08-30 00:10:26 +02:00
func stripSuffix ( jid types . WhatsAppID ) string {
if len ( jid ) == 0 {
return jid
}
index := strings . IndexRune ( jid , '@' )
if index < 0 {
return jid
2018-08-28 23:40:54 +02:00
}
2018-08-30 00:10:26 +02:00
return jid [ : index ]
2018-08-28 23:40:54 +02:00
}
2018-09-01 22:38:03 +02:00
func ( user * User ) jidPtr ( ) * string {
if len ( user . JID ) > 0 {
str := stripSuffix ( user . JID )
return & str
}
return nil
}
2018-08-28 23:40:54 +02:00
func ( user * User ) sessionUnptr ( ) ( sess whatsapp . Session ) {
2018-08-16 14:59:18 +02:00
if user . Session != nil {
sess = * user . Session
}
2018-08-28 23:40:54 +02:00
return
}
2019-01-21 22:55:16 +01:00
func ( user * User ) Insert ( ) {
2018-08-28 23:40:54 +02:00
sess := user . sessionUnptr ( )
2019-05-22 15:46:18 +02:00
_ , err := user . db . Exec ( ` INSERT INTO "user" (mxid, jid, management_room, last_connection, client_id, client_token, server_token, enc_key, mac_key) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ` ,
user . MXID , user . jidPtr ( ) ,
user . ManagementRoom , user . LastConnection ,
2018-08-30 00:10:26 +02:00
sess . ClientId , sess . ClientToken , sess . ServerToken , sess . EncKey , sess . MacKey )
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-05-22 15:46:18 +02:00
func ( user * User ) UpdateLastConnection ( ) {
user . LastConnection = uint64 ( time . Now ( ) . Unix ( ) )
_ , err := user . db . Exec ( ` UPDATE "user" SET last_connection=$1 WHERE mxid=$2 ` ,
user . LastConnection , user . MXID )
if err != nil {
user . log . Warnfln ( "Failed to update last connection ts: %v" , err )
}
}
2019-01-21 22:55:16 +01:00
func ( user * User ) Update ( ) {
2018-08-28 23:40:54 +02:00
sess := user . sessionUnptr ( )
2019-05-22 15:46:18 +02:00
_ , err := user . db . Exec ( ` UPDATE "user" SET jid=$1, management_room=$2, last_connection=$3, client_id=$4, client_token=$5, server_token=$6, enc_key=$7, mac_key=$8 WHERE mxid=$9 ` ,
user . jidPtr ( ) , user . ManagementRoom , user . LastConnection ,
2018-08-30 00:10:26 +02:00
sess . ClientId , sess . ClientToken , sess . ServerToken , sess . EncKey , sess . MacKey ,
2018-08-28 23:40:54 +02:00
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
}
2019-05-28 20:31:25 +02:00
2019-08-10 14:24:53 +02:00
type PortalKeyWithMeta struct {
PortalKey
InCommunity bool
}
func ( user * User ) SetPortalKeys ( newKeys [ ] PortalKeyWithMeta ) error {
2019-05-28 20:31:25 +02:00
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 ) )
2019-08-10 14:24:53 +02:00
values := make ( [ ] interface { } , len ( newKeys ) * 4 )
2019-05-28 20:31:25 +02:00
for i , key := range newKeys {
2019-08-10 14:24:53 +02:00
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" ,
2019-05-28 20:31:25 +02:00
strings . Join ( valueStrings , ", " ) )
_ , err = tx . Exec ( query , values ... )
if err != nil {
_ = tx . Rollback ( )
return err
}
return tx . Commit ( )
}
2019-11-11 21:41:58 +01:00
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
2019-11-10 20:22:11 +01:00
}
2019-05-28 20:31:25 +02:00
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
}
2019-08-10 14:24:53 +02:00
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
}