fix: xunlei upload error (#749)
This commit is contained in:
parent
b21801d505
commit
6db09a2736
@ -4,12 +4,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
@ -95,12 +95,13 @@ func (driver XunLeiCloud) File(path string, account *model.Account) (*model.File
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.File, error) {
|
func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
cache, err := base.GetCache(path, account)
|
cache, err := base.GetCache(path, account)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
files, _ := cache.([]model.File)
|
files, _ := cache.([]model.File)
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
file, err := driver.File(utils.ParsePath(path), account)
|
file, err := driver.File(path, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -108,8 +109,16 @@ func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.Fi
|
|||||||
files := make([]model.File, 0)
|
files := make([]model.File, 0)
|
||||||
for {
|
for {
|
||||||
var fileList FileList
|
var fileList FileList
|
||||||
u := fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files?parent_id=%s&page_token=%s&with_audit=true&filters=%s", file.Id, fileList.NextPageToken, url.QueryEscape(`{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`))
|
_, err = GetState(account).Request("GET", FILE_API_URL, func(r *resty.Request) {
|
||||||
if err = GetState(account).Request("GET", u, nil, &fileList, account); err != nil {
|
r.SetQueryParams(map[string]string{
|
||||||
|
"parent_id": file.Id,
|
||||||
|
"page_token": fileList.NextPageToken,
|
||||||
|
"with_audit": "true",
|
||||||
|
"filters": `{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`,
|
||||||
|
})
|
||||||
|
r.SetResult(&fileList)
|
||||||
|
}, account)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, file := range fileList.Files {
|
for _, file := range fileList.Files {
|
||||||
@ -153,7 +162,12 @@ func (driver XunLeiCloud) Link(args base.Args, account *model.Account) (*base.Li
|
|||||||
return nil, base.ErrNotFile
|
return nil, base.ErrNotFile
|
||||||
}
|
}
|
||||||
var lFile Files
|
var lFile Files
|
||||||
if err = GetState(account).Request("GET", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s?&with_audit=true", file.Id), nil, &lFile, account); err != nil {
|
_, err = GetState(account).Request("GET", FILE_API_URL+"/{id}", func(r *resty.Request) {
|
||||||
|
r.SetPathParam("id", file.Id)
|
||||||
|
r.SetQueryParam("with_audit", "true")
|
||||||
|
r.SetResult(&lFile)
|
||||||
|
}, account)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &base.Link{
|
return &base.Link{
|
||||||
@ -194,7 +208,14 @@ func (driver XunLeiCloud) MakeDir(path string, account *model.Account) error {
|
|||||||
if !parentFile.IsDir() {
|
if !parentFile.IsDir() {
|
||||||
return base.ErrNotFolder
|
return base.ErrNotFolder
|
||||||
}
|
}
|
||||||
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files", &base.Json{"kind": FOLDER, "name": name, "parent_id": parentFile.Id}, nil, account)
|
_, err = GetState(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
|
||||||
|
r.SetBody(&base.Json{
|
||||||
|
"kind": FOLDER,
|
||||||
|
"name": name,
|
||||||
|
"parent_id": parentFile.Id,
|
||||||
|
})
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver XunLeiCloud) Move(src string, dst string, account *model.Account) error {
|
func (driver XunLeiCloud) Move(src string, dst string, account *model.Account) error {
|
||||||
@ -207,7 +228,14 @@ func (driver XunLeiCloud) Move(src string, dst string, account *model.Account) e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files:batchMove", &base.Json{"to": base.Json{"parent_id": dstDirFile.Id}, "ids": []string{srcFile.Id}}, nil, account)
|
|
||||||
|
_, err = GetState(account).Request("POST", FILE_API_URL+":batchMove", func(r *resty.Request) {
|
||||||
|
r.SetBody(&base.Json{
|
||||||
|
"to": base.Json{"parent_id": dstDirFile.Id},
|
||||||
|
"ids": []string{srcFile.Id},
|
||||||
|
})
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver XunLeiCloud) Copy(src string, dst string, account *model.Account) error {
|
func (driver XunLeiCloud) Copy(src string, dst string, account *model.Account) error {
|
||||||
@ -220,7 +248,13 @@ func (driver XunLeiCloud) Copy(src string, dst string, account *model.Account) e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files:batchCopy", &base.Json{"to": base.Json{"parent_id": dstDirFile.Id}, "ids": []string{srcFile.Id}}, nil, account)
|
_, err = GetState(account).Request("POST", FILE_API_URL+":batchCopy", func(r *resty.Request) {
|
||||||
|
r.SetBody(&base.Json{
|
||||||
|
"to": base.Json{"parent_id": dstDirFile.Id},
|
||||||
|
"ids": []string{srcFile.Id},
|
||||||
|
})
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
|
func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
|
||||||
@ -228,7 +262,11 @@ func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return GetState(account).Request("PATCH", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s/trash", srcFile.Id), &base.Json{}, nil, account)
|
_, err = GetState(account).Request("PATCH", FILE_API_URL+"/{id}/trash", func(r *resty.Request) {
|
||||||
|
r.SetPathParam("id", srcFile.Id)
|
||||||
|
r.SetBody(&base.Json{})
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account) error {
|
func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account) error {
|
||||||
@ -246,7 +284,6 @@ func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer tempFile.Close()
|
|
||||||
defer os.Remove(tempFile.Name())
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
|
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
|
||||||
@ -254,21 +291,27 @@ func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var rep UploadTaskResponse
|
tempFile.Close()
|
||||||
err = GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files", &base.Json{
|
|
||||||
|
var resp UploadTaskResponse
|
||||||
|
_, err = GetState(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
|
||||||
|
r.SetBody(&base.Json{
|
||||||
"kind": FILE,
|
"kind": FILE,
|
||||||
"parent_id": parentFile.Id,
|
"parent_id": parentFile.Id,
|
||||||
"name": file.Name,
|
"name": file.Name,
|
||||||
"size": fmt.Sprint(file.Size),
|
"size": fmt.Sprint(file.Size),
|
||||||
"hash": gcid,
|
"hash": gcid,
|
||||||
"upload_type": UPLOAD_TYPE_RESUMABLE,
|
"upload_type": UPLOAD_TYPE_RESUMABLE,
|
||||||
}, &rep, account)
|
})
|
||||||
|
r.SetResult(&resp)
|
||||||
|
}, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
param := rep.Resumable.Params
|
param := resp.Resumable.Params
|
||||||
client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true), oss.HTTPClient(xunleiClient.GetClient()))
|
if resp.UploadType == UPLOAD_TYPE_RESUMABLE {
|
||||||
|
client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -276,7 +319,9 @@ func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return bucket.UploadFile(param.Key, tempFile.Name(), 4*1024*1024, oss.Routines(3), oss.Checkpoint(true, ""), oss.Expires(param.Expiration))
|
return bucket.UploadFile(param.Key, tempFile.Name(), 1<<22, oss.Routines(3), oss.Checkpoint(true, ""), oss.Expires(param.Expiration))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account) error {
|
func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account) error {
|
||||||
@ -285,8 +330,11 @@ func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
_, err = GetState(account).Request("PATCH", FILE_API_URL+"/{id}", func(r *resty.Request) {
|
||||||
return GetState(account).Request("PATCH", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s", srcFile.Id), &base.Json{"name": dstName}, nil, account)
|
r.SetPathParam("id", srcFile.Id)
|
||||||
|
r.SetBody(&base.Json{"name": dstName})
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ base.Driver = (*XunLeiCloud)(nil)
|
var _ base.Driver = (*XunLeiCloud)(nil)
|
||||||
|
@ -37,6 +37,12 @@ var Algorithms = []string{
|
|||||||
"T78dnANexYRbiZy",
|
"T78dnANexYRbiZy",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
API_URL = "https://api-pan.xunlei.com/drive/v1"
|
||||||
|
FILE_API_URL = API_URL + "/files"
|
||||||
|
XLUSER_API_URL = "https://xluser-ssl.xunlei.com/v1"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FOLDER = "drive#folder"
|
FOLDER = "drive#folder"
|
||||||
FILE = "drive#file"
|
FILE = "drive#file"
|
||||||
|
@ -2,6 +2,7 @@ package xunlei
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var xunleiClient = resty.New().SetTimeout(120 * time.Second)
|
var xunleiClient = resty.New().SetHeaders(map[string]string{"Accept": "application/json;charset=UTF-8"})
|
||||||
|
|
||||||
// 一个账户只允许登陆一次
|
// 一个账户只允许登陆一次
|
||||||
var userStateCache = struct {
|
var userStateCache = struct {
|
||||||
@ -102,16 +103,16 @@ func (s *State) newCaptchaToken(action string, meta map[string]string, account *
|
|||||||
var e Erron
|
var e Erron
|
||||||
var resp CaptchaTokenResponse
|
var resp CaptchaTokenResponse
|
||||||
_, err := xunleiClient.R().
|
_, err := xunleiClient.R().
|
||||||
SetHeader("X-Device-Id", driverID).
|
|
||||||
SetBody(&creq).
|
SetBody(&creq).
|
||||||
SetError(&e).
|
SetError(&e).
|
||||||
SetResult(&resp).
|
SetResult(&resp).
|
||||||
Post("https://xluser-ssl.xunlei.com/v1/shield/captcha/init?client_id=" + CLIENT_ID)
|
SetHeader("X-Device-Id", driverID).
|
||||||
|
SetQueryParam("client_id", CLIENT_ID).
|
||||||
|
Post(XLUSER_API_URL + "/shield/captcha/init")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if e.ErrorCode != 0 {
|
if e.ErrorCode != 0 {
|
||||||
log.Debugf("%+v\n %+v", e, account)
|
|
||||||
return "", fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
return "", fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||||
}
|
}
|
||||||
if resp.Url != "" {
|
if resp.Url != "" {
|
||||||
@ -120,7 +121,6 @@ func (s *State) newCaptchaToken(action string, meta map[string]string, account *
|
|||||||
|
|
||||||
s.captchaTokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000
|
s.captchaTokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000
|
||||||
s.captchaToken = resp.CaptchaToken
|
s.captchaToken = resp.CaptchaToken
|
||||||
log.Debugf("%+v\n %+v", s.captchaToken, account)
|
|
||||||
return s.captchaToken, nil
|
return s.captchaToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ func (s *State) refreshToken_(account *model.Account) error {
|
|||||||
"client_secret": CLIENT_SECRET,
|
"client_secret": CLIENT_SECRET,
|
||||||
}).
|
}).
|
||||||
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).SetQueryParam("client_id", CLIENT_ID).
|
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).SetQueryParam("client_id", CLIENT_ID).
|
||||||
Post("https://xluser-ssl.xunlei.com/v1/auth/token")
|
Post(XLUSER_API_URL + "/auth/token")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -152,7 +152,6 @@ func (s *State) refreshToken_(account *model.Account) error {
|
|||||||
s.userID = resp.UserID
|
s.userID = resp.UserID
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
log.Debugf("%+v\n %+v", e, account)
|
|
||||||
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,7 +159,7 @@ func (s *State) refreshToken_(account *model.Account) error {
|
|||||||
func (s *State) login(account *model.Account) error {
|
func (s *State) login(account *model.Account) error {
|
||||||
s.init()
|
s.init()
|
||||||
ctime := time.Now().UnixMilli()
|
ctime := time.Now().UnixMilli()
|
||||||
url := "https://xluser-ssl.xunlei.com/v1/auth/signin"
|
url := XLUSER_API_URL + "/auth/signin"
|
||||||
captchaToken, err := s.newCaptchaToken(getAction("POST", url), map[string]string{"username": account.Username}, account)
|
captchaToken, err := s.newCaptchaToken(getAction("POST", url), map[string]string{"username": account.Username}, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -190,7 +189,6 @@ func (s *State) login(account *model.Account) error {
|
|||||||
defer model.SaveAccount(account)
|
defer model.SaveAccount(account)
|
||||||
if e.ErrorCode != 0 {
|
if e.ErrorCode != 0 {
|
||||||
account.Status = e.Error
|
account.Status = e.Error
|
||||||
log.Debugf("%+v\n %+v", e, account)
|
|
||||||
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||||
}
|
}
|
||||||
account.Status = "work"
|
account.Status = "work"
|
||||||
@ -199,67 +197,68 @@ func (s *State) login(account *model.Account) error {
|
|||||||
s.accessToken = resp.AccessToken
|
s.accessToken = resp.AccessToken
|
||||||
s.refreshToken = resp.RefreshToken
|
s.refreshToken = resp.RefreshToken
|
||||||
s.userID = resp.UserID
|
s.userID = resp.UserID
|
||||||
log.Debugf("%+v\n %+v", resp, account)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Request(method string, url string, body interface{}, resp interface{}, account *model.Account) error {
|
func (s *State) Request(method string, url string, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
token, err := s.getToken(account)
|
token, err := s.getToken(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
captchaToken, err := s.getCaptchaToken(getAction(method, url), account)
|
captchaToken, err := s.getCaptchaToken(getAction(method, url), account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.Unlock()
|
|
||||||
var e Erron
|
|
||||||
req := xunleiClient.R().
|
req := xunleiClient.R().
|
||||||
SetError(&e).
|
SetHeaders(map[string]string{
|
||||||
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).
|
"X-Device-Id": utils.GetMD5Encode(account.Username),
|
||||||
SetHeader("Authorization", token).
|
"Authorization": token,
|
||||||
SetHeader("X-Captcha-Token", captchaToken).
|
"X-Captcha-Token": captchaToken,
|
||||||
|
}).
|
||||||
SetQueryParam("client_id", CLIENT_ID)
|
SetQueryParam("client_id", CLIENT_ID)
|
||||||
|
|
||||||
if body != nil {
|
callback(req)
|
||||||
req.SetBody(body)
|
s.Unlock()
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var res *resty.Response
|
||||||
switch method {
|
switch method {
|
||||||
case "GET":
|
case "GET":
|
||||||
_, err = req.Get(url)
|
res, err = req.Get(url)
|
||||||
case "POST":
|
case "POST":
|
||||||
_, err = req.Post(url)
|
res, err = req.Post(url)
|
||||||
case "DELETE":
|
case "DELETE":
|
||||||
_, err = req.Delete(url)
|
res, err = req.Delete(url)
|
||||||
case "PATCH":
|
case "PATCH":
|
||||||
_, err = req.Patch(url)
|
res, err = req.Patch(url)
|
||||||
case "PUT":
|
case "PUT":
|
||||||
_, err = req.Put(url)
|
res, err = req.Put(url)
|
||||||
default:
|
default:
|
||||||
return base.ErrNotSupport
|
return nil, base.ErrNotSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debug(res.String())
|
||||||
|
|
||||||
|
var e Erron
|
||||||
|
utils.Json.Unmarshal(res.Body(), &e)
|
||||||
switch e.ErrorCode {
|
switch e.ErrorCode {
|
||||||
case 0:
|
|
||||||
return nil
|
|
||||||
case 9:
|
case 9:
|
||||||
s.newCaptchaToken(getAction(method, url), nil, account)
|
s.newCaptchaToken(getAction(method, url), nil, account)
|
||||||
fallthrough
|
fallthrough
|
||||||
case 4122, 4121:
|
case 4122, 4121:
|
||||||
return s.Request(method, url, body, resp, account)
|
return s.Request(method, url, callback, account)
|
||||||
|
case 0:
|
||||||
|
if res.StatusCode() == http.StatusOK {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(res.String())
|
||||||
default:
|
default:
|
||||||
log.Debugf("%+v\n %+v", e, account)
|
return nil, fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||||
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user