2023-07-02 14:21:55 +00:00
// mautrix-gmessages - A Matrix-Google Messages puppeting bridge.
// Copyright (C) 2023 Tulir Asokan
//
// 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 (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
2023-07-19 17:32:01 +00:00
"sync"
2023-07-02 14:21:55 +00:00
2023-08-08 13:13:44 +00:00
"go.mau.fi/util/dbutil"
2023-07-19 17:32:01 +00:00
"golang.org/x/exp/slices"
2023-07-02 14:21:55 +00:00
"maunium.net/go/mautrix/id"
2023-07-09 11:16:52 +00:00
"go.mau.fi/mautrix-gmessages/libgm"
2023-08-30 16:35:02 +00:00
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
2023-07-02 14:21:55 +00:00
)
type UserQuery struct {
db * Database
}
func ( uq * UserQuery ) New ( ) * User {
return & User {
db : uq . db ,
}
}
func ( uq * UserQuery ) getDB ( ) * Database {
return uq . db
}
func ( uq * UserQuery ) GetAllWithSession ( ctx context . Context ) ( [ ] * User , error ) {
2023-08-30 16:35:02 +00:00
return getAll [ * User ] ( uq , ctx , ` SELECT rowid, mxid, phone_id, session, self_participant_ids, sim_metadata, management_room, space_room, access_token FROM "user" WHERE session IS NOT NULL ` )
2023-07-02 14:21:55 +00:00
}
func ( uq * UserQuery ) GetAllWithDoublePuppet ( ctx context . Context ) ( [ ] * User , error ) {
2023-08-30 16:35:02 +00:00
return getAll [ * User ] ( uq , ctx , ` SELECT rowid, mxid, phone_id, session, self_participant_ids, sim_metadata, management_room, space_room, access_token FROM "user" WHERE access_token<>'' ` )
2023-07-02 14:21:55 +00:00
}
func ( uq * UserQuery ) GetByRowID ( ctx context . Context , rowID int ) ( * User , error ) {
2023-08-30 16:35:02 +00:00
return get [ * User ] ( uq , ctx , ` SELECT rowid, mxid, phone_id, session, self_participant_ids, sim_metadata, management_room, space_room, access_token FROM "user" WHERE rowid=$1 ` , rowID )
2023-07-02 14:21:55 +00:00
}
func ( uq * UserQuery ) GetByMXID ( ctx context . Context , userID id . UserID ) ( * User , error ) {
2023-08-30 16:35:02 +00:00
return get [ * User ] ( uq , ctx , ` SELECT rowid, mxid, phone_id, session, self_participant_ids, sim_metadata, management_room, space_room, access_token FROM "user" WHERE mxid=$1 ` , userID )
2023-07-02 14:21:55 +00:00
}
type User struct {
db * Database
RowID int
MXID id . UserID
2023-07-19 17:32:01 +00:00
PhoneID string
2023-07-09 11:16:52 +00:00
Session * libgm . AuthData
2023-07-02 14:21:55 +00:00
ManagementRoom id . RoomID
SpaceRoom id . RoomID
2023-07-19 17:32:01 +00:00
SelfParticipantIDs [ ] string
selfParticipantIDsLock sync . RWMutex
2023-08-30 16:35:02 +00:00
simMetadata map [ string ] * gmproto . SIMCard
simMetadataLock sync . RWMutex
2023-07-02 14:21:55 +00:00
AccessToken string
}
func ( user * User ) Scan ( row dbutil . Scannable ) ( * User , error ) {
2023-07-19 17:32:01 +00:00
var phoneID , session , managementRoom , spaceRoom , accessToken sql . NullString
2023-08-30 16:35:02 +00:00
var selfParticipantIDs , simMetadata string
err := row . Scan ( & user . RowID , & user . MXID , & phoneID , & session , & selfParticipantIDs , & simMetadata , & managementRoom , & spaceRoom , & accessToken )
2023-07-02 14:21:55 +00:00
if errors . Is ( err , sql . ErrNoRows ) {
return nil , nil
} else if err != nil {
return nil , err
}
if session . String != "" {
2023-07-09 11:16:52 +00:00
var sess libgm . AuthData
2023-07-02 14:21:55 +00:00
err = json . Unmarshal ( [ ] byte ( session . String ) , & sess )
if err != nil {
return nil , fmt . Errorf ( "failed to parse session: %w" , err )
}
user . Session = & sess
}
2023-07-19 17:32:01 +00:00
user . selfParticipantIDsLock . Lock ( )
err = json . Unmarshal ( [ ] byte ( selfParticipantIDs ) , & user . SelfParticipantIDs )
user . selfParticipantIDsLock . Unlock ( )
if err != nil {
return nil , fmt . Errorf ( "failed to parse self participant IDs: %w" , err )
}
2023-08-30 16:35:02 +00:00
user . simMetadataLock . Lock ( )
err = json . Unmarshal ( [ ] byte ( simMetadata ) , & user . simMetadata )
user . simMetadataLock . Unlock ( )
if err != nil {
return nil , fmt . Errorf ( "failed to parse SIM metadata: %w" , err )
}
2023-07-19 17:32:01 +00:00
user . PhoneID = phoneID . String
2023-07-02 14:21:55 +00:00
user . AccessToken = accessToken . String
user . ManagementRoom = id . RoomID ( managementRoom . String )
user . SpaceRoom = id . RoomID ( spaceRoom . String )
return user , nil
}
func ( user * User ) sqlVariables ( ) [ ] any {
2023-07-19 17:32:01 +00:00
var phoneID , session , managementRoom , spaceRoom , accessToken * string
if user . PhoneID != "" {
phoneID = & user . PhoneID
2023-07-02 14:21:55 +00:00
}
if user . Session != nil {
data , _ := json . Marshal ( user . Session )
strData := string ( data )
session = & strData
}
if user . ManagementRoom != "" {
managementRoom = ( * string ) ( & user . ManagementRoom )
}
if user . SpaceRoom != "" {
spaceRoom = ( * string ) ( & user . SpaceRoom )
}
if user . AccessToken != "" {
accessToken = & user . AccessToken
}
2023-07-19 17:32:01 +00:00
user . selfParticipantIDsLock . RLock ( )
selfParticipantIDs , _ := json . Marshal ( user . SelfParticipantIDs )
user . selfParticipantIDsLock . RUnlock ( )
2023-08-30 16:35:02 +00:00
user . simMetadataLock . RLock ( )
simMetadata , err := json . Marshal ( user . simMetadata )
if err != nil {
panic ( err )
}
user . simMetadataLock . RUnlock ( )
return [ ] any { user . MXID , phoneID , session , string ( selfParticipantIDs ) , string ( simMetadata ) , managementRoom , spaceRoom , accessToken }
2023-07-19 17:32:01 +00:00
}
func ( user * User ) IsSelfParticipantID ( id string ) bool {
user . selfParticipantIDsLock . RLock ( )
defer user . selfParticipantIDsLock . RUnlock ( )
return slices . Contains ( user . SelfParticipantIDs , id )
}
2023-08-30 16:35:02 +00:00
type bridgeStateSIMMeta struct {
CarrierName string ` json:"carrier_name" `
ColorHex string ` json:"color_hex" `
ParticipantID string ` json:"participant_id" `
RCSEnabled bool ` json:"rcs_enabled" `
}
func ( user * User ) GetSIMsForBridgeState ( ) [ ] bridgeStateSIMMeta {
user . simMetadataLock . RLock ( )
data := make ( [ ] bridgeStateSIMMeta , 0 , len ( user . simMetadata ) )
for _ , sim := range user . simMetadata {
data = append ( data , bridgeStateSIMMeta {
CarrierName : sim . GetSIMData ( ) . GetCarrierName ( ) ,
ColorHex : sim . GetSIMData ( ) . GetHexHash ( ) ,
ParticipantID : sim . GetSIMParticipant ( ) . GetID ( ) ,
RCSEnabled : sim . GetRCSChats ( ) . GetEnabled ( ) ,
} )
}
user . simMetadataLock . RUnlock ( )
return data
}
func ( user * User ) GetSIM ( participantID string ) * gmproto . SIMCard {
user . simMetadataLock . Lock ( )
defer user . simMetadataLock . Unlock ( )
return user . simMetadata [ participantID ]
}
func simsAreEqualish ( a , b * gmproto . SIMCard ) bool {
return a . GetRCSChats ( ) . GetEnabled ( ) != b . GetRCSChats ( ) . GetEnabled ( ) ||
a . GetSIMData ( ) . GetCarrierName ( ) != b . GetSIMData ( ) . GetCarrierName ( ) ||
a . GetSIMData ( ) . GetSIMPayload ( ) . GetSIMNumber ( ) != b . GetSIMData ( ) . GetSIMPayload ( ) . GetSIMNumber ( ) ||
a . GetSIMData ( ) . GetSIMPayload ( ) . GetTwo ( ) != b . GetSIMData ( ) . GetSIMPayload ( ) . GetTwo ( )
}
func ( user * User ) SetSIMs ( sims [ ] * gmproto . SIMCard ) bool {
user . simMetadataLock . Lock ( )
defer user . simMetadataLock . Unlock ( )
user . selfParticipantIDsLock . Lock ( )
defer user . selfParticipantIDsLock . Unlock ( )
newMap := make ( map [ string ] * gmproto . SIMCard )
participantIDsChanged := false
for _ , sim := range sims {
participantID := sim . GetSIMParticipant ( ) . GetID ( )
newMap [ sim . GetSIMParticipant ( ) . GetID ( ) ] = sim
if ! slices . Contains ( user . SelfParticipantIDs , participantID ) {
user . SelfParticipantIDs = append ( user . SelfParticipantIDs , participantID )
participantIDsChanged = true
}
}
oldMap := user . simMetadata
user . simMetadata = newMap
if participantIDsChanged || len ( newMap ) != len ( oldMap ) {
return true
}
for participantID , sim := range newMap {
existing , ok := oldMap [ participantID ]
if ! ok || ! simsAreEqualish ( existing , sim ) {
return true
}
}
return false
}
2023-07-19 17:32:01 +00:00
func ( user * User ) AddSelfParticipantID ( ctx context . Context , id string ) error {
user . selfParticipantIDsLock . Lock ( )
defer user . selfParticipantIDsLock . Unlock ( )
if ! slices . Contains ( user . SelfParticipantIDs , id ) {
user . SelfParticipantIDs = append ( user . SelfParticipantIDs , id )
selfParticipantIDs , _ := json . Marshal ( user . SelfParticipantIDs )
_ , err := user . db . Conn ( ctx ) . ExecContext ( ctx , ` UPDATE "user" SET self_participant_ids=$2 WHERE mxid=$1 ` , user . MXID , selfParticipantIDs )
return err
}
return nil
2023-07-02 14:21:55 +00:00
}
func ( user * User ) Insert ( ctx context . Context ) error {
err := user . db . Conn ( ctx ) .
2023-08-30 16:35:02 +00:00
QueryRowContext ( ctx , ` INSERT INTO "user" (mxid, phone_id, session, self_participant_ids, sim_metadata, management_room, space_room, access_token) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING rowid ` , user . sqlVariables ( ) ... ) .
2023-07-02 14:21:55 +00:00
Scan ( & user . RowID )
return err
}
func ( user * User ) Update ( ctx context . Context ) error {
2023-08-30 16:35:02 +00:00
_ , err := user . db . Conn ( ctx ) . ExecContext ( ctx , ` UPDATE "user" SET phone_id=$2, session=$3, self_participant_ids=$4, sim_metadata=$5, management_room=$6, space_room=$7, access_token=$8 WHERE mxid=$1 ` , user . sqlVariables ( ) ... )
2023-07-02 14:21:55 +00:00
return err
}