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"
"errors"
2023-07-11 22:57:07 +00:00
"fmt"
"strings"
2023-07-02 14:21:55 +00:00
"time"
2023-08-08 13:13:44 +00:00
"go.mau.fi/util/dbutil"
2023-07-02 14:21:55 +00:00
"maunium.net/go/mautrix/id"
2023-07-12 21:44:57 +00:00
2023-07-17 13:51:31 +00:00
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
2023-07-02 14:21:55 +00:00
)
type MessageQuery struct {
db * Database
}
func ( mq * MessageQuery ) New ( ) * Message {
return & Message {
db : mq . db ,
}
}
func ( mq * MessageQuery ) getDB ( ) * Database {
return mq . db
}
const (
getMessageByIDQuery = `
2023-08-14 11:32:12 +00:00
SELECT conv_id , conv_receiver , id , mxid , mx_room , sender , timestamp , status FROM message
WHERE conv_receiver = $ 1 AND id = $ 2
2023-07-02 14:21:55 +00:00
`
2023-07-11 22:57:07 +00:00
getLastMessageInChatQuery = `
2023-08-14 11:32:12 +00:00
SELECT conv_id , conv_receiver , id , mxid , mx_room , sender , timestamp , status FROM message
2023-07-11 22:57:07 +00:00
WHERE conv_id = $ 1 AND conv_receiver = $ 2
ORDER BY timestamp DESC LIMIT 1
`
2023-08-10 08:30:17 +00:00
getLastMessageInChatWithMXIDQuery = `
2023-08-14 11:32:12 +00:00
SELECT conv_id , conv_receiver , id , mxid , mx_room , sender , timestamp , status FROM message
2023-08-10 08:30:17 +00:00
WHERE conv_id = $ 1 AND conv_receiver = $ 2 AND mxid NOT LIKE ' $ fake : : % '
ORDER BY timestamp DESC LIMIT 1
`
2023-07-02 14:21:55 +00:00
getMessageByMXIDQuery = `
2023-08-14 11:32:12 +00:00
SELECT conv_id , conv_receiver , id , mxid , mx_room , sender , timestamp , status FROM message
2023-07-02 14:21:55 +00:00
WHERE mxid = $ 1
`
2023-08-21 16:42:57 +00:00
deleteAllInChat = `
DELETE FROM message WHERE conv_id = $ 1 AND conv_receiver = $ 2
`
2023-07-02 14:21:55 +00:00
)
2023-08-14 11:32:12 +00:00
func ( mq * MessageQuery ) GetByID ( ctx context . Context , receiver int , messageID string ) ( * Message , error ) {
return get [ * Message ] ( mq , ctx , getMessageByIDQuery , receiver , messageID )
2023-07-02 14:21:55 +00:00
}
func ( mq * MessageQuery ) GetByMXID ( ctx context . Context , mxid id . EventID ) ( * Message , error ) {
return get [ * Message ] ( mq , ctx , getMessageByMXIDQuery , mxid )
}
2023-07-11 22:57:07 +00:00
func ( mq * MessageQuery ) GetLastInChat ( ctx context . Context , chat Key ) ( * Message , error ) {
return get [ * Message ] ( mq , ctx , getLastMessageInChatQuery , chat . ID , chat . Receiver )
}
2023-08-10 08:30:17 +00:00
func ( mq * MessageQuery ) GetLastInChatWithMXID ( ctx context . Context , chat Key ) ( * Message , error ) {
return get [ * Message ] ( mq , ctx , getLastMessageInChatWithMXIDQuery , chat . ID , chat . Receiver )
}
2023-08-21 16:42:57 +00:00
func ( mq * MessageQuery ) DeleteAllInChat ( ctx context . Context , chat Key ) error {
_ , err := mq . db . Conn ( ctx ) . ExecContext ( ctx , deleteAllInChat , chat . ID , chat . Receiver )
return err
}
2023-08-09 14:53:11 +00:00
type MediaPart struct {
EventID id . EventID ` json:"mxid,omitempty" `
PendingMedia bool ` json:"pending_media,omitempty" `
}
2023-07-11 22:57:07 +00:00
type MessageStatus struct {
2023-08-09 14:53:11 +00:00
Type gmproto . MessageStatusType ` json:"type,omitempty" `
MediaStatus string ` json:"media_status,omitempty" `
MediaParts map [ string ] MediaPart ` json:"media_parts,omitempty" `
2023-08-09 16:49:36 +00:00
PartCount int ` json:"part_count,omitempty" `
2023-08-08 15:45:48 +00:00
2023-08-09 14:53:11 +00:00
MSSSent bool ` json:"mss_sent,omitempty" `
MSSFailSent bool ` json:"mss_fail_sent,omitempty" `
MSSDeliverySent bool ` json:"mss_delivery_sent,omitempty" `
ReadReceiptSent bool ` json:"read_receipt_sent,omitempty" `
}
func ( ms * MessageStatus ) HasPendingMediaParts ( ) bool {
for _ , part := range ms . MediaParts {
if part . PendingMedia {
return true
}
}
return false
2023-07-11 22:57:07 +00:00
}
2023-07-02 14:21:55 +00:00
type Message struct {
2023-07-12 21:44:57 +00:00
db * Database
2023-07-02 14:21:55 +00:00
Chat Key
ID string
MXID id . EventID
2023-08-14 11:32:12 +00:00
RoomID id . RoomID
2023-07-02 14:21:55 +00:00
Sender string
Timestamp time . Time
2023-07-11 22:57:07 +00:00
Status MessageStatus
2023-07-02 14:21:55 +00:00
}
func ( msg * Message ) Scan ( row dbutil . Scannable ) ( * Message , error ) {
var ts int64
2023-08-14 11:32:12 +00:00
err := row . Scan ( & msg . Chat . ID , & msg . Chat . Receiver , & msg . ID , & msg . MXID , & msg . RoomID , & msg . Sender , & ts , dbutil . JSON { Data : & msg . Status } )
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 ts != 0 {
2023-07-02 20:47:31 +00:00
msg . Timestamp = time . UnixMicro ( ts )
2023-07-02 14:21:55 +00:00
}
return msg , nil
}
func ( msg * Message ) sqlVariables ( ) [ ] any {
2023-08-14 11:32:12 +00:00
return [ ] any { msg . Chat . ID , msg . Chat . Receiver , msg . ID , msg . MXID , msg . RoomID , msg . Sender , msg . Timestamp . UnixMicro ( ) , dbutil . JSON { Data : & msg . Status } }
2023-07-02 14:21:55 +00:00
}
func ( msg * Message ) Insert ( ctx context . Context ) error {
_ , err := msg . db . Conn ( ctx ) . ExecContext ( ctx , `
2023-08-14 11:32:12 +00:00
INSERT INTO message ( conv_id , conv_receiver , id , mxid , mx_room , sender , timestamp , status )
VALUES ( $ 1 , $ 2 , $ 3 , $ 4 , $ 5 , $ 6 , $ 7 , $ 8 )
2023-07-02 14:21:55 +00:00
` , msg . sqlVariables ( ) ... )
return err
}
2023-07-11 22:57:07 +00:00
func ( mq * MessageQuery ) MassInsert ( ctx context . Context , messages [ ] * Message ) error {
2023-08-14 11:32:12 +00:00
valueStringFormat := "($1, $2, $%d, $%d, $3, $%d, $%d, $%d)"
2023-07-11 22:57:07 +00:00
if mq . db . Dialect == dbutil . SQLite {
valueStringFormat = strings . ReplaceAll ( valueStringFormat , "$" , "?" )
}
placeholders := make ( [ ] string , len ( messages ) )
2023-08-14 11:32:12 +00:00
params := make ( [ ] any , 3 + len ( messages ) * 5 )
2023-07-11 22:57:07 +00:00
params [ 0 ] = messages [ 0 ] . Chat . ID
params [ 1 ] = messages [ 0 ] . Chat . Receiver
2023-08-14 11:32:12 +00:00
params [ 2 ] = messages [ 0 ] . RoomID
2023-07-11 22:57:07 +00:00
for i , msg := range messages {
2023-08-14 11:32:12 +00:00
baseIndex := 3 + i * 5
2023-07-11 22:57:07 +00:00
params [ baseIndex ] = msg . ID
params [ baseIndex + 1 ] = msg . MXID
params [ baseIndex + 2 ] = msg . Sender
params [ baseIndex + 3 ] = msg . Timestamp . UnixMicro ( )
params [ baseIndex + 4 ] = dbutil . JSON { Data : & msg . Status }
placeholders [ i ] = fmt . Sprintf ( valueStringFormat , baseIndex + 1 , baseIndex + 2 , baseIndex + 3 , baseIndex + 4 , baseIndex + 5 )
}
query := `
2023-08-14 11:32:12 +00:00
INSERT INTO message ( conv_id , conv_receiver , id , mxid , mx_room , sender , timestamp , status )
2023-07-11 22:57:07 +00:00
VALUES
` + strings . Join ( placeholders , "," )
_ , err := mq . db . Conn ( ctx ) . ExecContext ( ctx , query , params ... )
return err
}
2023-08-14 11:32:12 +00:00
func ( msg * Message ) Update ( ctx context . Context ) error {
_ , err := msg . db . Conn ( ctx ) . ExecContext ( ctx , `
UPDATE message
SET conv_id = $ 1 , mxid = $ 4 , mx_room = $ 5 , sender = $ 6 , timestamp = $ 7 , status = $ 8
WHERE conv_receiver = $ 2 AND id = $ 3
` , msg . sqlVariables ( ) ... )
return err
}
2023-07-11 22:57:07 +00:00
func ( msg * Message ) UpdateStatus ( ctx context . Context ) error {
2023-08-14 11:32:12 +00:00
_ , err := msg . db . Conn ( ctx ) . ExecContext ( ctx , "UPDATE message SET status=$1, timestamp=$2 WHERE conv_receiver=$3 AND id=$4" , dbutil . JSON { Data : & msg . Status } , msg . Timestamp . UnixMicro ( ) , msg . Chat . Receiver , msg . ID )
2023-07-11 22:57:07 +00:00
return err
}
2023-07-02 14:21:55 +00:00
func ( msg * Message ) Delete ( ctx context . Context ) error {
_ , err := msg . db . Conn ( ctx ) . ExecContext ( ctx , "DELETE FROM message WHERE conv_id=$1 AND conv_receiver=$2 AND id=$3" , msg . Chat . ID , msg . Chat . Receiver , msg . ID )
return err
}
2023-08-10 08:30:17 +00:00
func ( msg * Message ) IsFakeMXID ( ) bool {
return strings . HasPrefix ( msg . MXID . String ( ) , "$fake:" )
}