2023-06-30 11:05:33 +00:00
|
|
|
package libgm
|
2023-06-30 09:54:08 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2023-07-15 17:43:28 +00:00
|
|
|
"encoding/base64"
|
2023-06-30 09:54:08 +00:00
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
|
2023-07-15 17:43:28 +00:00
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
2023-06-30 09:54:08 +00:00
|
|
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
2023-07-09 11:16:52 +00:00
|
|
|
"go.mau.fi/mautrix-gmessages/libgm/payload"
|
2023-06-30 09:54:08 +00:00
|
|
|
"go.mau.fi/mautrix-gmessages/libgm/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
type StartGoogleUpload struct {
|
2023-06-30 13:26:46 +00:00
|
|
|
UploadID string
|
|
|
|
UploadURL string
|
2023-06-30 09:54:08 +00:00
|
|
|
UploadStatus string
|
|
|
|
ChunkGranularity int64
|
2023-06-30 13:26:46 +00:00
|
|
|
ControlURL string
|
2023-07-15 17:08:11 +00:00
|
|
|
MimeType string
|
2023-06-30 09:54:08 +00:00
|
|
|
|
|
|
|
EncryptedMediaBytes []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type MediaUpload struct {
|
2023-06-30 13:26:46 +00:00
|
|
|
MediaID string
|
2023-06-30 09:54:08 +00:00
|
|
|
MediaNumber int64
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
errStartUploadMedia = errors.New("failed to start uploading media")
|
|
|
|
errFinalizeUploadMedia = errors.New("failed to finalize uploading media")
|
|
|
|
)
|
|
|
|
|
|
|
|
func (c *Client) FinalizeUploadMedia(upload *StartGoogleUpload) (*MediaUpload, error) {
|
|
|
|
encryptedImageSize := strconv.Itoa(len(upload.EncryptedMediaBytes))
|
|
|
|
|
2023-07-15 17:08:11 +00:00
|
|
|
finalizeUploadHeaders := util.NewMediaUploadHeaders(encryptedImageSize, "upload, finalize", "0", upload.MimeType, "")
|
2023-06-30 13:26:46 +00:00
|
|
|
req, reqErr := http.NewRequest("POST", upload.UploadURL, bytes.NewBuffer(upload.EncryptedMediaBytes))
|
2023-06-30 09:54:08 +00:00
|
|
|
if reqErr != nil {
|
|
|
|
return nil, reqErr
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header = *finalizeUploadHeaders
|
|
|
|
|
|
|
|
res, resErr := c.http.Do(req)
|
|
|
|
if resErr != nil {
|
2023-07-09 20:32:19 +00:00
|
|
|
panic(resErr)
|
2023-06-30 09:54:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
statusCode := res.StatusCode
|
|
|
|
if statusCode != 200 {
|
|
|
|
return nil, errFinalizeUploadMedia
|
|
|
|
}
|
|
|
|
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
rHeaders := res.Header
|
2023-07-15 17:43:28 +00:00
|
|
|
googleResponse, err3 := io.ReadAll(base64.NewDecoder(base64.StdEncoding, res.Body))
|
2023-06-30 09:54:08 +00:00
|
|
|
if err3 != nil {
|
|
|
|
return nil, err3
|
|
|
|
}
|
|
|
|
|
|
|
|
uploadStatus := rHeaders.Get("x-goog-upload-status")
|
2023-07-09 20:32:19 +00:00
|
|
|
c.Logger.Debug().Str("upload_status", uploadStatus).Msg("Upload complete")
|
2023-06-30 09:54:08 +00:00
|
|
|
|
2023-06-30 13:26:46 +00:00
|
|
|
mediaIDs := &binary.UploadMediaResponse{}
|
2023-07-15 17:43:28 +00:00
|
|
|
err3 = proto.Unmarshal(googleResponse, mediaIDs)
|
2023-06-30 09:54:08 +00:00
|
|
|
if err3 != nil {
|
|
|
|
return nil, err3
|
|
|
|
}
|
|
|
|
return &MediaUpload{
|
2023-06-30 13:26:46 +00:00
|
|
|
MediaID: mediaIDs.Media.MediaID,
|
|
|
|
MediaNumber: mediaIDs.Media.MediaNumber,
|
2023-06-30 09:54:08 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-07-15 17:08:11 +00:00
|
|
|
func (c *Client) StartUploadMedia(encryptedImageBytes []byte, mime string) (*StartGoogleUpload, error) {
|
2023-06-30 09:54:08 +00:00
|
|
|
encryptedImageSize := strconv.Itoa(len(encryptedImageBytes))
|
|
|
|
|
2023-07-15 17:08:11 +00:00
|
|
|
startUploadHeaders := util.NewMediaUploadHeaders(encryptedImageSize, "start", "", mime, "resumable")
|
2023-06-30 09:54:08 +00:00
|
|
|
startUploadPayload, buildPayloadErr := c.buildStartUploadPayload()
|
|
|
|
if buildPayloadErr != nil {
|
|
|
|
return nil, buildPayloadErr
|
|
|
|
}
|
|
|
|
|
2023-07-15 23:21:53 +00:00
|
|
|
req, reqErr := http.NewRequest("POST", util.UploadMediaURL, bytes.NewBuffer([]byte(startUploadPayload)))
|
2023-06-30 09:54:08 +00:00
|
|
|
if reqErr != nil {
|
|
|
|
return nil, reqErr
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header = *startUploadHeaders
|
|
|
|
|
|
|
|
res, resErr := c.http.Do(req)
|
|
|
|
if resErr != nil {
|
2023-07-09 20:32:19 +00:00
|
|
|
panic(resErr)
|
2023-06-30 09:54:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
statusCode := res.StatusCode
|
|
|
|
if statusCode != 200 {
|
|
|
|
return nil, errStartUploadMedia
|
|
|
|
}
|
|
|
|
|
|
|
|
rHeaders := res.Header
|
|
|
|
|
|
|
|
chunkGranularity, convertErr := strconv.Atoi(rHeaders.Get("x-goog-upload-chunk-granularity"))
|
|
|
|
if convertErr != nil {
|
|
|
|
return nil, convertErr
|
|
|
|
}
|
|
|
|
|
|
|
|
uploadResponse := &StartGoogleUpload{
|
2023-06-30 13:26:46 +00:00
|
|
|
UploadID: rHeaders.Get("x-guploader-uploadid"),
|
|
|
|
UploadURL: rHeaders.Get("x-goog-upload-url"),
|
2023-06-30 09:54:08 +00:00
|
|
|
UploadStatus: rHeaders.Get("x-goog-upload-status"),
|
|
|
|
ChunkGranularity: int64(chunkGranularity),
|
2023-06-30 13:26:46 +00:00
|
|
|
ControlURL: rHeaders.Get("x-goog-upload-control-url"),
|
2023-07-15 17:08:11 +00:00
|
|
|
MimeType: mime,
|
2023-06-30 09:54:08 +00:00
|
|
|
|
|
|
|
EncryptedMediaBytes: encryptedImageBytes,
|
|
|
|
}
|
|
|
|
return uploadResponse, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) buildStartUploadPayload() (string, error) {
|
2023-07-09 11:16:52 +00:00
|
|
|
requestID := util.RandomUUIDv4()
|
2023-06-30 09:54:08 +00:00
|
|
|
protoData := &binary.StartMediaUploadPayload{
|
|
|
|
ImageType: 1,
|
2023-06-30 12:49:32 +00:00
|
|
|
AuthData: &binary.AuthMessage{
|
2023-07-09 11:16:52 +00:00
|
|
|
RequestID: requestID,
|
|
|
|
TachyonAuthToken: c.authData.TachyonAuthToken,
|
|
|
|
ConfigVersion: payload.ConfigMessage,
|
2023-06-30 09:54:08 +00:00
|
|
|
},
|
2023-07-09 11:16:52 +00:00
|
|
|
Mobile: c.authData.DevicePair.Mobile,
|
2023-06-30 09:54:08 +00:00
|
|
|
}
|
|
|
|
|
2023-07-15 17:43:28 +00:00
|
|
|
protoDataBytes, err := proto.Marshal(protoData)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2023-06-30 09:54:08 +00:00
|
|
|
}
|
2023-07-15 17:43:28 +00:00
|
|
|
protoDataEncoded := base64.StdEncoding.EncodeToString(protoDataBytes)
|
2023-06-30 09:54:08 +00:00
|
|
|
|
|
|
|
return protoDataEncoded, nil
|
|
|
|
}
|