2023-06-30 11:05:33 +00:00
|
|
|
package libgm
|
2023-06-30 09:54:08 +00:00
|
|
|
|
|
|
|
import (
|
2023-06-30 10:48:52 +00:00
|
|
|
"encoding/base64"
|
2023-06-30 09:54:08 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
2023-07-02 14:19:00 +00:00
|
|
|
"net/http/cookiejar"
|
2023-06-30 09:54:08 +00:00
|
|
|
"net/url"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
|
|
|
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
|
|
|
"go.mau.fi/mautrix-gmessages/libgm/crypto"
|
|
|
|
"go.mau.fi/mautrix-gmessages/libgm/payload"
|
|
|
|
"go.mau.fi/mautrix-gmessages/libgm/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
type DevicePair struct {
|
|
|
|
Mobile *binary.Device
|
|
|
|
Browser *binary.Device
|
|
|
|
}
|
|
|
|
type Proxy func(*http.Request) (*url.URL, error)
|
|
|
|
type EventHandler func(evt interface{})
|
|
|
|
type Client struct {
|
|
|
|
Logger zerolog.Logger
|
|
|
|
Conversations *Conversations
|
|
|
|
Session *Session
|
|
|
|
rpc *RPC
|
|
|
|
devicePair *DevicePair
|
|
|
|
pairer *Pairer
|
|
|
|
cryptor *crypto.Cryptor
|
|
|
|
imageCryptor *crypto.ImageCryptor
|
|
|
|
evHandler EventHandler
|
|
|
|
sessionHandler *SessionHandler
|
|
|
|
instructions *Instructions
|
|
|
|
|
2023-06-30 12:49:32 +00:00
|
|
|
rpcKey []byte
|
2023-06-30 09:54:08 +00:00
|
|
|
ttl int64
|
|
|
|
|
|
|
|
proxy Proxy
|
|
|
|
http *http.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewClient(devicePair *DevicePair, cryptor *crypto.Cryptor, logger zerolog.Logger, proxy *string) *Client {
|
|
|
|
sessionHandler := &SessionHandler{
|
|
|
|
requests: make(map[string]map[int64]*ResponseChan),
|
|
|
|
responseTimeout: time.Duration(5000) * time.Millisecond,
|
|
|
|
}
|
|
|
|
if cryptor == nil {
|
|
|
|
cryptor = crypto.NewCryptor(nil, nil)
|
|
|
|
}
|
2023-07-02 14:19:00 +00:00
|
|
|
jar, _ := cookiejar.New(nil)
|
2023-06-30 09:54:08 +00:00
|
|
|
cli := &Client{
|
|
|
|
Logger: logger,
|
|
|
|
devicePair: devicePair,
|
|
|
|
sessionHandler: sessionHandler,
|
|
|
|
cryptor: cryptor,
|
|
|
|
imageCryptor: &crypto.ImageCryptor{},
|
2023-07-02 14:19:00 +00:00
|
|
|
http: &http.Client{
|
|
|
|
Jar: jar,
|
|
|
|
},
|
2023-06-30 09:54:08 +00:00
|
|
|
}
|
|
|
|
sessionHandler.client = cli
|
|
|
|
cli.instructions = NewInstructions(cli.cryptor)
|
|
|
|
if proxy != nil {
|
|
|
|
cli.SetProxy(*proxy)
|
|
|
|
}
|
|
|
|
rpc := &RPC{client: cli, http: &http.Client{Transport: &http.Transport{Proxy: cli.proxy}}}
|
|
|
|
cli.rpc = rpc
|
|
|
|
cli.Logger.Debug().Any("data", cryptor).Msg("Cryptor")
|
|
|
|
cli.setApiMethods()
|
|
|
|
return cli
|
|
|
|
}
|
|
|
|
|
2023-07-02 14:19:00 +00:00
|
|
|
var baseURL, _ = url.Parse("https://messages.google.com/")
|
|
|
|
|
|
|
|
func (c *Client) GetCookies() []*http.Cookie {
|
|
|
|
return c.http.Jar.Cookies(baseURL)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) SetCookies(cookies []*http.Cookie) {
|
|
|
|
c.http.Jar.SetCookies(baseURL, cookies)
|
|
|
|
}
|
|
|
|
|
2023-06-30 09:54:08 +00:00
|
|
|
func (c *Client) SetEventHandler(eventHandler EventHandler) {
|
2023-07-01 15:19:57 +00:00
|
|
|
if eventHandler == nil {
|
|
|
|
eventHandler = func(_ interface{}) {}
|
|
|
|
}
|
2023-06-30 09:54:08 +00:00
|
|
|
c.evHandler = eventHandler
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) SetProxy(proxy string) error {
|
|
|
|
proxyParsed, err := url.Parse(proxy)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Fatal().Err(err).Msg("Failed to set proxy")
|
|
|
|
}
|
|
|
|
proxyUrl := http.ProxyURL(proxyParsed)
|
|
|
|
c.http.Transport = &http.Transport{
|
|
|
|
Proxy: proxyUrl,
|
|
|
|
}
|
|
|
|
c.proxy = proxyUrl
|
|
|
|
c.Logger.Debug().Any("proxy", proxyParsed.Host).Msg("SetProxy")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-30 12:49:32 +00:00
|
|
|
func (c *Client) Connect(rpcKey []byte) error {
|
2023-06-30 13:26:46 +00:00
|
|
|
rpcPayload, receiveMesageSessionID, err := payload.ReceiveMessages(rpcKey)
|
2023-06-30 09:54:08 +00:00
|
|
|
if err != nil {
|
2023-06-30 11:48:50 +00:00
|
|
|
panic(err)
|
2023-06-30 09:54:08 +00:00
|
|
|
return err
|
|
|
|
}
|
2023-06-30 13:26:46 +00:00
|
|
|
c.rpc.rpcSessionID = receiveMesageSessionID
|
2023-06-30 09:54:08 +00:00
|
|
|
c.rpcKey = rpcKey
|
2023-07-01 09:51:13 +00:00
|
|
|
go c.rpc.ListenReceiveMessages(rpcPayload)
|
2023-06-30 09:54:08 +00:00
|
|
|
c.Logger.Debug().Any("rpcKey", rpcKey).Msg("Successfully connected to server")
|
2023-07-01 09:51:13 +00:00
|
|
|
if c.devicePair != nil {
|
|
|
|
sendInitialDataErr := c.rpc.sendInitialData()
|
|
|
|
if sendInitialDataErr != nil {
|
|
|
|
panic(sendInitialDataErr)
|
|
|
|
}
|
|
|
|
}
|
2023-06-30 09:54:08 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-30 12:49:32 +00:00
|
|
|
func (c *Client) Reconnect(rpcKey []byte) error {
|
2023-06-30 09:54:08 +00:00
|
|
|
c.rpc.CloseConnection()
|
|
|
|
for c.rpc.conn != nil {
|
|
|
|
time.Sleep(time.Millisecond * 100)
|
|
|
|
}
|
|
|
|
err := c.Connect(rpcKey)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Err(err).Any("rpcKey", rpcKey).Msg("Failed to reconnect")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.Logger.Debug().Any("rpcKey", rpcKey).Msg("Successfully reconnected to server")
|
|
|
|
sendInitialDataErr := c.rpc.sendInitialData()
|
|
|
|
if sendInitialDataErr != nil {
|
2023-06-30 11:48:50 +00:00
|
|
|
panic(sendInitialDataErr)
|
2023-06-30 09:54:08 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-01 15:19:57 +00:00
|
|
|
func (c *Client) Disconnect() {
|
|
|
|
c.rpc.CloseConnection()
|
|
|
|
c.http.CloseIdleConnections()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) IsConnected() bool {
|
|
|
|
return c.rpc != nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) IsLoggedIn() bool {
|
|
|
|
return c.devicePair != nil
|
|
|
|
}
|
|
|
|
|
2023-06-30 09:54:08 +00:00
|
|
|
func (c *Client) triggerEvent(evt interface{}) {
|
|
|
|
if c.evHandler != nil {
|
|
|
|
c.evHandler(evt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) setApiMethods() {
|
|
|
|
c.Conversations = &Conversations{
|
|
|
|
client: c,
|
|
|
|
openConversation: openConversation{
|
|
|
|
client: c,
|
|
|
|
},
|
|
|
|
fetchConversationMessages: fetchConversationMessages{
|
|
|
|
client: c,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
c.Session = &Session{
|
|
|
|
client: c,
|
|
|
|
prepareNewSession: prepareNewSession{
|
|
|
|
client: c,
|
|
|
|
},
|
|
|
|
newSession: newSession{
|
|
|
|
client: c,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) decryptImages(messages *binary.FetchMessagesResponse) error {
|
|
|
|
for _, msg := range messages.Messages {
|
|
|
|
switch msg.GetType() {
|
|
|
|
case *binary.MessageType_IMAGE.Enum():
|
|
|
|
for _, details := range msg.GetMessageInfo() {
|
|
|
|
switch data := details.GetData().(type) {
|
|
|
|
case *binary.MessageInfo_ImageContent:
|
2023-06-30 13:26:46 +00:00
|
|
|
decryptedImageData, err := c.decryptImageData(data.ImageContent.ImageID, data.ImageContent.DecryptionKey)
|
2023-06-30 09:54:08 +00:00
|
|
|
if err != nil {
|
2023-06-30 11:48:50 +00:00
|
|
|
panic(err)
|
2023-06-30 09:54:08 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
data.ImageContent.ImageData = decryptedImageData
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) decryptImageData(imageId string, key []byte) ([]byte, error) {
|
|
|
|
reqId := util.RandomUUIDv4()
|
|
|
|
download_metadata := &binary.UploadImagePayload{
|
|
|
|
MetaData: &binary.ImageMetaData{
|
2023-06-30 13:26:46 +00:00
|
|
|
ImageID: imageId,
|
2023-06-30 09:54:08 +00:00
|
|
|
Encrypted: true,
|
|
|
|
},
|
2023-06-30 12:49:32 +00:00
|
|
|
AuthData: &binary.AuthMessage{
|
2023-06-30 13:26:46 +00:00
|
|
|
RequestID: reqId,
|
2023-06-30 12:49:32 +00:00
|
|
|
RpcKey: c.rpcKey,
|
2023-06-30 09:54:08 +00:00
|
|
|
Date: &binary.Date{
|
|
|
|
Year: 2023,
|
|
|
|
Seq1: 6,
|
2023-07-02 14:19:00 +00:00
|
|
|
Seq2: 22,
|
2023-06-30 09:54:08 +00:00
|
|
|
Seq3: 4,
|
|
|
|
Seq4: 6,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
download_metadata_bytes, err2 := binary.EncodeProtoMessage(download_metadata)
|
|
|
|
if err2 != nil {
|
|
|
|
return nil, err2
|
|
|
|
}
|
2023-06-30 10:48:52 +00:00
|
|
|
download_metadata_b64 := base64.StdEncoding.EncodeToString(download_metadata_bytes)
|
2023-06-30 09:54:08 +00:00
|
|
|
req, err := http.NewRequest("GET", util.UPLOAD_MEDIA, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
util.BuildUploadHeaders(req, download_metadata_b64)
|
|
|
|
res, reqErr := c.http.Do(req)
|
|
|
|
if reqErr != nil {
|
|
|
|
return nil, reqErr
|
|
|
|
}
|
|
|
|
c.Logger.Info().Any("url", util.UPLOAD_MEDIA).Any("headers", res.Request.Header).Msg("Decrypt Image Headers")
|
|
|
|
defer res.Body.Close()
|
|
|
|
encryptedBuffImg, err3 := io.ReadAll(res.Body)
|
|
|
|
if err3 != nil {
|
|
|
|
return nil, err3
|
|
|
|
}
|
|
|
|
c.Logger.Debug().Any("key", key).Any("encryptedLength", len(encryptedBuffImg)).Msg("Attempting to decrypt image")
|
|
|
|
c.imageCryptor.UpdateDecryptionKey(key)
|
|
|
|
decryptedImageBytes, decryptionErr := c.imageCryptor.DecryptData(encryptedBuffImg)
|
|
|
|
if decryptionErr != nil {
|
2023-06-30 11:48:50 +00:00
|
|
|
c.Logger.Err(err).Msg("Image decryption failed")
|
2023-06-30 09:54:08 +00:00
|
|
|
return nil, decryptionErr
|
|
|
|
}
|
|
|
|
return decryptedImageBytes, nil
|
|
|
|
}
|