mirror of https://github.com/Xhofe/alist
205 lines
5.4 KiB
Go
205 lines
5.4 KiB
Go
package aliyundrive
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/alist-org/alist/v3/drivers/base"
|
|
"github.com/alist-org/alist/v3/internal/op"
|
|
"github.com/alist-org/alist/v3/pkg/utils"
|
|
"github.com/dustinxie/ecc"
|
|
"github.com/go-resty/resty/v2"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func (d *AliDrive) createSession() error {
|
|
state, ok := global.Load(d.UserID)
|
|
if !ok {
|
|
return fmt.Errorf("can't load user state, user_id: %s", d.UserID)
|
|
}
|
|
d.sign()
|
|
state.retry++
|
|
if state.retry > 3 {
|
|
state.retry = 0
|
|
return fmt.Errorf("createSession failed after three retries")
|
|
}
|
|
_, err, _ := d.request("https://api.alipan.com/users/v1/users/device/create_session", http.MethodPost, func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"deviceName": "samsung",
|
|
"modelName": "SM-G9810",
|
|
"nonce": 0,
|
|
"pubKey": PublicKeyToHex(&state.privateKey.PublicKey),
|
|
"refreshToken": d.RefreshToken,
|
|
})
|
|
}, nil)
|
|
if err == nil{
|
|
state.retry = 0
|
|
}
|
|
return err
|
|
}
|
|
|
|
// func (d *AliDrive) renewSession() error {
|
|
// _, err, _ := d.request("https://api.alipan.com/users/v1/users/device/renew_session", http.MethodPost, nil, nil)
|
|
// return err
|
|
// }
|
|
|
|
func (d *AliDrive) sign() {
|
|
state, _ := global.Load(d.UserID)
|
|
secpAppID := "5dde4e1bdf9e4966b387ba58f4b3fdc3"
|
|
singdata := fmt.Sprintf("%s:%s:%s:%d", secpAppID, state.deviceID, d.UserID, 0)
|
|
hash := sha256.Sum256([]byte(singdata))
|
|
data, _ := ecc.SignBytes(state.privateKey, hash[:], ecc.RecID|ecc.LowerS)
|
|
state.signature = hex.EncodeToString(data) //strconv.Itoa(state.nonce)
|
|
}
|
|
|
|
// do others that not defined in Driver interface
|
|
|
|
func (d *AliDrive) refreshToken() error {
|
|
url := "https://auth.alipan.com/v2/account/token"
|
|
var resp base.TokenResp
|
|
var e RespErr
|
|
_, err := base.RestyClient.R().
|
|
//ForceContentType("application/json").
|
|
SetBody(base.Json{"refresh_token": d.RefreshToken, "grant_type": "refresh_token"}).
|
|
SetResult(&resp).
|
|
SetError(&e).
|
|
Post(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if e.Code != "" {
|
|
return fmt.Errorf("failed to refresh token: %s", e.Message)
|
|
}
|
|
if resp.RefreshToken == "" {
|
|
return errors.New("failed to refresh token: refresh token is empty")
|
|
}
|
|
d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
|
|
op.MustSaveDriverStorage(d)
|
|
return nil
|
|
}
|
|
|
|
func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) {
|
|
req := base.RestyClient.R()
|
|
state, ok := global.Load(d.UserID)
|
|
if !ok {
|
|
if url == "https://api.alipan.com/v2/user/get" {
|
|
state = &State{}
|
|
} else {
|
|
return nil, fmt.Errorf("can't load user state, user_id: %s", d.UserID), RespErr{}
|
|
}
|
|
}
|
|
req.SetHeaders(map[string]string{
|
|
"Authorization": "Bearer\t" + d.AccessToken,
|
|
"content-type": "application/json",
|
|
"origin": "https://www.alipan.com",
|
|
"Referer": "https://alipan.com/",
|
|
"X-Signature": state.signature,
|
|
"x-request-id": uuid.NewString(),
|
|
"X-Canary": "client=Android,app=adrive,version=v4.1.0",
|
|
"X-Device-Id": state.deviceID,
|
|
})
|
|
if callback != nil {
|
|
callback(req)
|
|
} else {
|
|
req.SetBody("{}")
|
|
}
|
|
if resp != nil {
|
|
req.SetResult(resp)
|
|
}
|
|
var e RespErr
|
|
req.SetError(&e)
|
|
res, err := req.Execute(method, url)
|
|
if err != nil {
|
|
return nil, err, e
|
|
}
|
|
if e.Code != "" {
|
|
switch e.Code {
|
|
case "AccessTokenInvalid":
|
|
err = d.refreshToken()
|
|
if err != nil {
|
|
return nil, err, e
|
|
}
|
|
case "DeviceSessionSignatureInvalid":
|
|
err = d.createSession()
|
|
if err != nil {
|
|
return nil, err, e
|
|
}
|
|
default:
|
|
return nil, errors.New(e.Message), e
|
|
}
|
|
return d.request(url, method, callback, resp)
|
|
} else if res.IsError() {
|
|
return nil, errors.New("bad status code " + res.Status()), e
|
|
}
|
|
return res.Body(), nil, e
|
|
}
|
|
|
|
func (d *AliDrive) getFiles(fileId string) ([]File, error) {
|
|
marker := "first"
|
|
res := make([]File, 0)
|
|
for marker != "" {
|
|
if marker == "first" {
|
|
marker = ""
|
|
}
|
|
var resp Files
|
|
data := base.Json{
|
|
"drive_id": d.DriveId,
|
|
"fields": "*",
|
|
"image_thumbnail_process": "image/resize,w_400/format,jpeg",
|
|
"image_url_process": "image/resize,w_1920/format,jpeg",
|
|
"limit": 200,
|
|
"marker": marker,
|
|
"order_by": d.OrderBy,
|
|
"order_direction": d.OrderDirection,
|
|
"parent_file_id": fileId,
|
|
"video_thumbnail_process": "video/snapshot,t_0,f_jpg,ar_auto,w_300",
|
|
"url_expire_sec": 14400,
|
|
}
|
|
_, err, _ := d.request("https://api.alipan.com/v2/file/list", http.MethodPost, func(req *resty.Request) {
|
|
req.SetBody(data)
|
|
}, &resp)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
marker = resp.NextMarker
|
|
res = append(res, resp.Items...)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (d *AliDrive) batch(srcId, dstId string, url string) error {
|
|
res, err, _ := d.request("https://api.alipan.com/v3/batch", http.MethodPost, func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"requests": []base.Json{
|
|
{
|
|
"headers": base.Json{
|
|
"Content-Type": "application/json",
|
|
},
|
|
"method": "POST",
|
|
"id": srcId,
|
|
"body": base.Json{
|
|
"drive_id": d.DriveId,
|
|
"file_id": srcId,
|
|
"to_drive_id": d.DriveId,
|
|
"to_parent_file_id": dstId,
|
|
},
|
|
"url": url,
|
|
},
|
|
},
|
|
"resource": "file",
|
|
})
|
|
}, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
status := utils.Json.Get(res, "responses", 0, "status").ToInt()
|
|
if status < 400 && status >= 100 {
|
|
return nil
|
|
}
|
|
return errors.New(string(res))
|
|
}
|