Skip to content
This repository has been archived by the owner on Sep 6, 2023. It is now read-only.

Commit

Permalink
Move uploaded photo to the default album
Browse files Browse the repository at this point in the history
  • Loading branch information
canhlinh committed Apr 1, 2018
1 parent 4cebef5 commit d8fc641
Show file tree
Hide file tree
Showing 5 changed files with 356 additions and 66 deletions.
296 changes: 241 additions & 55 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/cookiejar"
Expand All @@ -27,16 +26,20 @@ const (
// GooglePhotoRequestUploadURL url to request create a new upload session
GooglePhotoRequestUploadURL = "https://photos.google.com/_/upload/uploadmedia/rupio/interactive?authuser=0"

// GooglePhotoCommandURL url to execute a specific command
GooglePhotoCommandURL = "https://photos.google.com/_/PhotosUi/mutate"
// GooglePhotoMutateQueryURL url to execute a specific command
GooglePhotoMutateQueryURL = "https://photos.google.com/_/PhotosUi/mutate"

// EnablePhotoKey a magic key
EnablePhotoKey = 137530650
// GooglePhotoDataQueryURL url do something
GooglePhotoDataQueryURL = "https://photos.google.com/_/PhotosUi/data"

// DefaultAlbum a required album need to do a magic thing
DefaultAlbum = "DefaultAlbum"
)

// Client present a upload client
type Client struct {
hClient *http.Client
hClient *http.Client
magicToken string
}

func NewClient(cookies ...*http.Cookie) *Client {
Expand Down Expand Up @@ -108,29 +111,46 @@ func (c *Client) Upload(filePath string) (string, string, error) {
return "", "", err
}

c.magicToken = atToken

// Start create a new upload session
uploadURL, err := c.createUploadURL(fileInfo.Name(), fileInfo.Size())
if err != nil {
return "", "", err
}
log.Println("Got new upload url " + uploadURL)

// start upload file
uploadToken, err := c.upload(uploadURL, file, fileInfo.Size())
if err != nil {
return "", "", err
}

return c.enableUploadedFile(uploadToken, atToken, fileInfo.Name(), fileInfo.ModTime().Unix()*1000)
photoID, photoURL, err := c.enableUploadedFile(uploadToken, fileInfo.Name(), fileInfo.ModTime().Unix()*1000)
if err != nil {
return "", "", err
}

if err := c.moveToAlbum(DefaultAlbum, photoID); err != nil {
return "", "", err
}

return photoID, photoURL, err

}

//getAtToken get the at token ( a magic token )
func (c *Client) getAtToken() (string, error) {
log.Println("Request to get the magic token")

res, err := c.hClient.Get(GooglePhotoURL)
if err != nil {
return "", err
}

if res.StatusCode > 299 {
return "", errors.New(res.Status)
}

doc, _ := goquery.NewDocumentFromReader(res.Body)

var magicToken MagicToken
Expand All @@ -154,6 +174,7 @@ func (c *Client) getAtToken() (string, error) {

//createUploadURL create an new upload url
func (c *Client) createUploadURL(fileName string, fileSize int64) (string, error) {
log.Println("Request to create a new upload url")

body := NewJSONBody(NewUploadSessionRequest(fileName, fileSize))

Expand All @@ -168,10 +189,10 @@ func (c *Client) createUploadURL(fileName string, fileSize int64) (string, error
defer resp.Body.Close()

if resp.StatusCode > 299 {
return "", fmt.Errorf("Failed to create a new upload's id, got error %s", StringFromBody(resp.Body))
return "", fmt.Errorf("Failed to create a new upload's id, got error %s", BodyToString(resp.Body))
}

result := NewSessionUploadFromJson(StringFromBody(resp.Body))
result := NewSessionUploadFromJson(BodyToString(resp.Body))
if len(result.SessionStatus.ExternalFieldTransfers) <= 0 {
return "", errors.New("An array of the request URL response is empty")
}
Expand All @@ -181,6 +202,7 @@ func (c *Client) createUploadURL(fileName string, fileSize int64) (string, error

// upload uploads file to server then you will get a upload token
func (c *Client) upload(uploadURL string, file io.ReadCloser, fileSize int64) (string, error) {
log.Println("Request to upload file data")

req, _ := http.NewRequest(http.MethodPost, uploadURL, file)
req.Header.Add("content-type", "application/octet-stream")
Expand All @@ -192,10 +214,10 @@ func (c *Client) upload(uploadURL string, file io.ReadCloser, fileSize int64) (s
}

if resp.StatusCode > 299 {
return "", fmt.Errorf("Failed to upload file, got error %s", StringFromBody(resp.Body))
return "", fmt.Errorf("Failed to upload file, got error %s", BodyToString(resp.Body))
}

stringBody := StringFromBody(resp.Body)
stringBody := BodyToString(resp.Body)
uploadToken := NewSessionUploadFromJson(stringBody).SessionStatus.AdditionalInfo.GoogleRupioAdditionalInfo.CompletionInfo.CustomerSpecificInfo.UploadToken
if uploadToken == "" {
log.Println(stringBody)
Expand All @@ -204,67 +226,231 @@ func (c *Client) upload(uploadURL string, file io.ReadCloser, fileSize int64) (s
return uploadToken, nil
}

func (c *Client) enableUploadedFile(uploadBase64Token, atToken, fileName string, fileModAt int64) (string, string, error) {

jsonReq := EnableImageRequest{
"af.maf",
[]FirstItemEnableImageRequest{
[]InnerItemFirstItemEnableImageRequest{
"af.add",
EnablePhotoKey,
SecondInnerArray{
MapOfItemsToEnable{
fmt.Sprintf("%v", EnablePhotoKey): ItemToEnable{
ItemToEnableArray{
[]InnerItemToEnableArray{
uploadBase64Token,
fileName,
fileModAt,
},
},
},
},
func (c *Client) enableUploadedFile(uploadBase64Token, fileName string, fileModAt int64) (string, string, error) {
log.Println("Request to enable the uploaded photo")

query := NewMutateQuery(QueryNumberEnableImage,
[]interface{}{
[]interface{}{
[]interface{}{
uploadBase64Token,
fileName,
fileModAt,
},
},
},
)

body, err := c.DoQuery(GooglePhotoMutateQueryURL, query)
if err != nil {
return "", "", err
}

form := url.Values{}
form.Add("f.req", StringFromBody(NewJSONBody(jsonReq)))
form.Add("at", atToken)
var enableImage EnableImageResponse
if err := json.Unmarshal(BodyToBytes(body)[6:], &enableImage); err != nil {
return "", "", err
}

req, _ := http.NewRequest(http.MethodPost, GooglePhotoCommandURL, strings.NewReader(form.Encode()))
req.Header.Add("user-agent", ChromeUserAgent)
req.Header.Add("content-type", "application/x-www-form-urlencoded;charset=UTF-8")
req.Header.Add("authority", "photos.google.com")
req.Header.Add("origin", GooglePhotoURL)
req.Header.Add("referer", GooglePhotoURL)
req.Header.Add("x-chrome-uma-enabled", "1")
req.Header.Add("x-same-domain", "1")
resp, err := c.hClient.Do(req)
photoURL, err := enableImage.getEnabledImageURL()
if err != nil {
return "", "", err
}
defer resp.Body.Close()

if resp.StatusCode > 299 {
return "", "", fmt.Errorf("Failed to enable the uploaded file, got error %s", StringFromBody(resp.Body))
return enableImage.getEnabledImageId(), photoURL, nil
}

func (client *Client) DoQuery(endpoint string, query string) (io.ReadCloser, error) {

form := url.Values{}
form.Add("at", client.magicToken)
form.Add("f.req", query)

req, _ := http.NewRequest("POST", endpoint, strings.NewReader(form.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
req.Header.Add("User-Agent", ChromeUserAgent)

res, err := client.hClient.Do(req)
if err != nil {
return nil, err
}
if res.StatusCode > 299 {
// DumpRequest(req)
// DumpResponse(res)
return nil, errors.New(res.Status)
}

bytesResponse, err := ioutil.ReadAll(resp.Body)
return res.Body, nil
}

func (client *Client) GetAlbums() (Albums, error) {
log.Println("Request to get albums")

body, err := client.DoQuery(GooglePhotoDataQueryURL, NewDataQuery(QueryNumberGetAlbum, []interface{}{nil, nil, nil, nil, 1}))
if err != nil {
return "", "", err
return nil, err
}
defer body.Close()

var enableImage EnableImageResponse
if err := json.Unmarshal(bytesResponse[6:], &enableImage); err != nil {
return "", "", err
var r []interface{}
if err := json.Unmarshal(BodyToBytes(body)[6:], &r); err != nil {
return nil, err
}

photoURL, err := enableImage.getEnabledImageURL()
var albums = Albums{}

r = r[0].([]interface{})
r = r[2].(map[string]interface{})[fmt.Sprintf("%v", QueryNumberGetAlbum)].([]interface{})

if len(r) == 0 {
return albums, nil
}

r = r[0].([]interface{})

for _, al := range r {
infos := al.([]interface{})
mdetails := infos[12].(map[string]interface{})
details := mdetails[fmt.Sprintf("%v", QueryNumberGetAlbum)].([]interface{})

albums = append(albums, &Album{
ID: infos[0].(string),
Name: details[1].(string),
})
}

return albums, nil
}

func (c *Client) SearchOrCreteaAlbum(name string, photoID string) (*Album, error) {
albums, err := c.GetAlbums()
if err != nil {
return "", "", err
return nil, err
}

return enableImage.getEnabledImageId(), photoURL, nil
for _, album := range albums {
if name == album.Name {
return album, nil
}
}

return c.CreateAlbum(name, photoID)
}

func (c *Client) CreateAlbum(albumName string, photoID string) (*Album, error) {
log.Printf("Request to create new album %v with photo's id %s \n", albumName, photoID)

query := NewMutateQuery(QueryNumberCreateAlbum, []interface{}{
[]string{photoID},
nil,
albumName,
})

body, err := c.DoQuery(GooglePhotoMutateQueryURL, query)
if err != nil {
return nil, err
}
defer body.Close()

var album Album
var r interface{}
if err := json.Unmarshal(BodyToBytes(body)[6:], &r); err != nil {
return nil, err
}

r = (r.([]interface{})[0]).([]interface{})[1]
r = r.(map[string]interface{})[fmt.Sprintf("%d", QueryNumberCreateAlbum)]
album.ID = r.([]interface{})[0].(string)
album.Name = albumName

return &album, nil
}

func (client *Client) AddPhotoToAlbum(albumID, photoID string) error {
log.Printf("Request to add photo %s to album %s", photoID, albumID)

query := NewMutateQuery(QueryNumberAddPhotoToAlbum,
[]interface{}{
[]string{photoID},
albumID,
},
)

body, err := client.DoQuery(GooglePhotoMutateQueryURL, query)
if err != nil {
return err
}
body.Close()

return nil
}

func (client *Client) AddPhotoToSharedAlbum(albumID, photoID string) error {
log.Printf("Request to add photo %s to shared album %s", photoID, albumID)

query := NewMutateQuery(QueryNumberAddPhotoToSharedAlbum,
[]interface{}{
[]string{albumID},
[]interface{}{
2,
nil,
[]interface{}{
[][]string{[]string{photoID}},
},
nil,
nil,
[]interface{}{},
[]interface{}{},
},
},
)

body, err := client.DoQuery(GooglePhotoMutateQueryURL, query)
if err != nil {
return err
}
body.Close()

return nil
}

func (c *Client) RemoveFromAlbum(photoID string) error {
log.Printf("Request to remove photo %s from the relevant album", photoID)

query := NewMutateQuery(
QueryNumberRemovePhotoFromAlbum,
[]interface{}{
[]string{photoID},
[]interface{}{},
},
)

body, err := c.DoQuery(GooglePhotoMutateQueryURL, query)
if err != nil {
return err
}
defer body.Close()
return nil
}

func (c *Client) moveToAlbum(name string, photoID string) error {
log.Println("Request to move the upload file to the Default Album")

albums, err := c.GetAlbums()
if err != nil {
return err
}

album := albums.Get(name)
if album == nil {
if _, err := c.CreateAlbum(name, photoID); err != nil {
return err
}
return nil
}

if err := c.AddPhotoToAlbum(album.ID, photoID); err != nil {
return c.AddPhotoToSharedAlbum(album.ID, photoID)
}

return nil
}
Loading

0 comments on commit d8fc641

Please sign in to comment.