Fix media upload code
This commit is contained in:
parent
114cf622d6
commit
6a630151d7
2 changed files with 76 additions and 63 deletions
137
libgm/media.go
137
libgm/media.go
|
@ -3,7 +3,7 @@ package libgm
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -99,15 +99,15 @@ func (c *Client) UploadMedia(data []byte, fileName, mime string) (*gmproto.Media
|
||||||
}
|
}
|
||||||
encryptedBytes, err := cryptor.EncryptData(data)
|
encryptedBytes, err := cryptor.EncryptData(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to encrypt media: %w", err)
|
||||||
}
|
}
|
||||||
startUploadImage, err := c.StartUploadMedia(encryptedBytes, mime)
|
startUploadImage, err := c.StartUploadMedia(encryptedBytes, mime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to start upload: %w", err)
|
||||||
}
|
}
|
||||||
upload, err := c.FinalizeUploadMedia(startUploadImage)
|
upload, err := c.FinalizeUploadMedia(startUploadImage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to finalize upload: %w", err)
|
||||||
}
|
}
|
||||||
return &gmproto.MediaContent{
|
return &gmproto.MediaContent{
|
||||||
Format: mediaType.Type,
|
Format: mediaType.Type,
|
||||||
|
@ -134,46 +134,61 @@ type MediaUpload struct {
|
||||||
MediaNumber int64
|
MediaNumber int64
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func isBase64Character(char byte) bool {
|
||||||
errStartUploadMedia = errors.New("failed to start uploading media")
|
return (char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == '+' || char == '/' || char == '='
|
||||||
errFinalizeUploadMedia = errors.New("failed to finalize uploading media")
|
}
|
||||||
)
|
|
||||||
|
func isStandardBase64(data []byte) bool {
|
||||||
|
if len(data)%4 != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, char := range data {
|
||||||
|
if !isBase64Character(char) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) FinalizeUploadMedia(upload *StartGoogleUpload) (*MediaUpload, error) {
|
func (c *Client) FinalizeUploadMedia(upload *StartGoogleUpload) (*MediaUpload, error) {
|
||||||
encryptedImageSize := strconv.Itoa(len(upload.EncryptedMediaBytes))
|
encryptedImageSize := strconv.Itoa(len(upload.EncryptedMediaBytes))
|
||||||
|
|
||||||
finalizeUploadHeaders := util.NewMediaUploadHeaders(encryptedImageSize, "upload, finalize", "0", upload.MimeType, "")
|
finalizeUploadHeaders := util.NewMediaUploadHeaders(encryptedImageSize, "upload, finalize", "0", upload.MimeType, "")
|
||||||
req, reqErr := http.NewRequest(http.MethodPost, upload.UploadURL, bytes.NewBuffer(upload.EncryptedMediaBytes))
|
req, err := http.NewRequest(http.MethodPost, upload.UploadURL, bytes.NewBuffer(upload.EncryptedMediaBytes))
|
||||||
if reqErr != nil {
|
if err != nil {
|
||||||
return nil, reqErr
|
return nil, fmt.Errorf("failed to prepare request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header = *finalizeUploadHeaders
|
req.Header = *finalizeUploadHeaders
|
||||||
|
|
||||||
res, resErr := c.http.Do(req)
|
res, err := c.http.Do(req)
|
||||||
if resErr != nil {
|
if err != nil {
|
||||||
panic(resErr)
|
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
statusCode := res.StatusCode
|
if res.StatusCode != 200 {
|
||||||
if statusCode != 200 {
|
return nil, fmt.Errorf("unexpected status code %d", res.StatusCode)
|
||||||
return nil, errFinalizeUploadMedia
|
}
|
||||||
|
respData, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||||
|
}
|
||||||
|
if isStandardBase64(respData) {
|
||||||
|
n, err := base64.StdEncoding.Decode(respData, respData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
respData = respData[:n]
|
||||||
}
|
}
|
||||||
|
|
||||||
rHeaders := res.Header
|
c.Logger.Debug().
|
||||||
googleResponse, err3 := io.ReadAll(base64.NewDecoder(base64.StdEncoding, res.Body))
|
Str("upload_status", res.Header.Get("x-goog-upload-status")).
|
||||||
if err3 != nil {
|
Msg("Upload complete")
|
||||||
return nil, err3
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadStatus := rHeaders.Get("x-goog-upload-status")
|
|
||||||
c.Logger.Debug().Str("upload_status", uploadStatus).Msg("Upload complete")
|
|
||||||
|
|
||||||
mediaIDs := &gmproto.UploadMediaResponse{}
|
mediaIDs := &gmproto.UploadMediaResponse{}
|
||||||
err3 = proto.Unmarshal(googleResponse, mediaIDs)
|
err = proto.Unmarshal(respData, mediaIDs)
|
||||||
if err3 != nil {
|
if err != nil {
|
||||||
return nil, err3
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||||
}
|
}
|
||||||
return &MediaUpload{
|
return &MediaUpload{
|
||||||
MediaID: mediaIDs.Media.MediaID,
|
MediaID: mediaIDs.Media.MediaID,
|
||||||
|
@ -185,42 +200,38 @@ func (c *Client) StartUploadMedia(encryptedImageBytes []byte, mime string) (*Sta
|
||||||
encryptedImageSize := strconv.Itoa(len(encryptedImageBytes))
|
encryptedImageSize := strconv.Itoa(len(encryptedImageBytes))
|
||||||
|
|
||||||
startUploadHeaders := util.NewMediaUploadHeaders(encryptedImageSize, "start", "", mime, "resumable")
|
startUploadHeaders := util.NewMediaUploadHeaders(encryptedImageSize, "start", "", mime, "resumable")
|
||||||
startUploadPayload, buildPayloadErr := c.buildStartUploadPayload()
|
startUploadPayload, err := c.buildStartUploadPayload()
|
||||||
if buildPayloadErr != nil {
|
if err != nil {
|
||||||
return nil, buildPayloadErr
|
return nil, fmt.Errorf("failed to build payload: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, reqErr := http.NewRequest(http.MethodPost, util.UploadMediaURL, bytes.NewBuffer([]byte(startUploadPayload)))
|
req, err := http.NewRequest(http.MethodPost, util.UploadMediaURL, bytes.NewBuffer([]byte(startUploadPayload)))
|
||||||
if reqErr != nil {
|
if err != nil {
|
||||||
return nil, reqErr
|
return nil, fmt.Errorf("failed to prepare request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header = *startUploadHeaders
|
req.Header = *startUploadHeaders
|
||||||
|
|
||||||
res, resErr := c.http.Do(req)
|
res, err := c.http.Do(req)
|
||||||
if resErr != nil {
|
if err != nil {
|
||||||
panic(resErr)
|
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
_ = res.Body.Close()
|
||||||
|
|
||||||
statusCode := res.StatusCode
|
if res.StatusCode != 200 {
|
||||||
if statusCode != 200 {
|
return nil, fmt.Errorf("unexpected status code %d", res.StatusCode)
|
||||||
return nil, errStartUploadMedia
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rHeaders := res.Header
|
chunkGranularity, err := strconv.Atoi(res.Header.Get("x-goog-upload-chunk-granularity"))
|
||||||
|
if err != nil {
|
||||||
chunkGranularity, convertErr := strconv.Atoi(rHeaders.Get("x-goog-upload-chunk-granularity"))
|
return nil, fmt.Errorf("failed to parse chunk granularity: %w", err)
|
||||||
if convertErr != nil {
|
|
||||||
return nil, convertErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadResponse := &StartGoogleUpload{
|
uploadResponse := &StartGoogleUpload{
|
||||||
UploadID: rHeaders.Get("x-guploader-uploadid"),
|
UploadID: res.Header.Get("x-guploader-uploadid"),
|
||||||
UploadURL: rHeaders.Get("x-goog-upload-url"),
|
UploadURL: res.Header.Get("x-goog-upload-url"),
|
||||||
UploadStatus: rHeaders.Get("x-goog-upload-status"),
|
UploadStatus: res.Header.Get("x-goog-upload-status"),
|
||||||
ChunkGranularity: int64(chunkGranularity),
|
ChunkGranularity: int64(chunkGranularity),
|
||||||
ControlURL: rHeaders.Get("x-goog-upload-control-url"),
|
ControlURL: res.Header.Get("x-goog-upload-control-url"),
|
||||||
MimeType: mime,
|
MimeType: mime,
|
||||||
|
|
||||||
EncryptedMediaBytes: encryptedImageBytes,
|
EncryptedMediaBytes: encryptedImageBytes,
|
||||||
|
@ -234,6 +245,7 @@ func (c *Client) buildStartUploadPayload() (string, error) {
|
||||||
AuthData: &gmproto.AuthMessage{
|
AuthData: &gmproto.AuthMessage{
|
||||||
RequestID: uuid.NewString(),
|
RequestID: uuid.NewString(),
|
||||||
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||||
|
Network: c.AuthData.AuthNetwork(),
|
||||||
ConfigVersion: util.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
Mobile: c.AuthData.Mobile,
|
Mobile: c.AuthData.Mobile,
|
||||||
|
@ -257,35 +269,36 @@ func (c *Client) DownloadMedia(mediaID string, key []byte) ([]byte, error) {
|
||||||
AuthData: &gmproto.AuthMessage{
|
AuthData: &gmproto.AuthMessage{
|
||||||
RequestID: uuid.NewString(),
|
RequestID: uuid.NewString(),
|
||||||
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
TachyonAuthToken: c.AuthData.TachyonAuthToken,
|
||||||
|
Network: c.AuthData.AuthNetwork(),
|
||||||
ConfigVersion: util.ConfigMessage,
|
ConfigVersion: util.ConfigMessage,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
downloadMetadataBytes, err := proto.Marshal(downloadMetadata)
|
downloadMetadataBytes, err := proto.Marshal(downloadMetadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to marshal download request: %w", err)
|
||||||
}
|
}
|
||||||
downloadMetadataEncoded := base64.StdEncoding.EncodeToString(downloadMetadataBytes)
|
downloadMetadataEncoded := base64.StdEncoding.EncodeToString(downloadMetadataBytes)
|
||||||
req, err := http.NewRequest(http.MethodGet, util.UploadMediaURL, nil)
|
req, err := http.NewRequest(http.MethodGet, util.UploadMediaURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to prepare request: %w", err)
|
||||||
}
|
}
|
||||||
util.BuildUploadHeaders(req, downloadMetadataEncoded)
|
util.BuildUploadHeaders(req, downloadMetadataEncoded)
|
||||||
res, reqErr := c.http.Do(req)
|
res, err := c.http.Do(req)
|
||||||
if reqErr != nil {
|
if err != nil {
|
||||||
return nil, reqErr
|
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
encryptedBuffImg, err := io.ReadAll(res.Body)
|
respData, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||||
}
|
}
|
||||||
cryptor, err := crypto.NewAESGCMHelper(key)
|
cryptor, err := crypto.NewAESGCMHelper(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
decryptedImageBytes, decryptionErr := cryptor.DecryptData(encryptedBuffImg)
|
decryptedImageBytes, err := cryptor.DecryptData(respData)
|
||||||
if decryptionErr != nil {
|
if err != nil {
|
||||||
return nil, decryptionErr
|
return nil, fmt.Errorf("failed to decrypt media: %w", err)
|
||||||
}
|
}
|
||||||
return decryptedImageBytes, nil
|
return decryptedImageBytes, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ func BuildUploadHeaders(req *http.Request, metadata string) {
|
||||||
func NewMediaUploadHeaders(imageSize string, command string, uploadOffset string, imageContentType string, protocol string) *http.Header {
|
func NewMediaUploadHeaders(imageSize string, command string, uploadOffset string, imageContentType string, protocol string) *http.Header {
|
||||||
headers := &http.Header{}
|
headers := &http.Header{}
|
||||||
|
|
||||||
headers.Set("host", "instantmessaging-pa.googleapis.com")
|
//headers.Set("host", "instantmessaging-pa.googleapis.com")
|
||||||
headers.Set("sec-ch-ua", SecUA)
|
headers.Set("sec-ch-ua", SecUA)
|
||||||
if protocol != "" {
|
if protocol != "" {
|
||||||
headers.Set("x-goog-upload-protocol", protocol)
|
headers.Set("x-goog-upload-protocol", protocol)
|
||||||
|
|
Loading…
Reference in a new issue