fix: xunlei upload error (#749)

This commit is contained in:
foxxorcat 2022-03-17 21:13:13 +08:00 committed by GitHub
parent b21801d505
commit 6db09a2736
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 120 additions and 67 deletions

View File

@ -4,12 +4,12 @@ import (
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strconv"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/go-resty/resty/v2"
"github.com/Xhofe/alist/conf"
"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) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
file, err := driver.File(utils.ParsePath(path), account)
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
@ -108,8 +109,16 @@ func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.Fi
files := make([]model.File, 0)
for {
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}}`))
if err = GetState(account).Request("GET", u, nil, &fileList, account); err != nil {
_, err = GetState(account).Request("GET", FILE_API_URL, func(r *resty.Request) {
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
}
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
}
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 &base.Link{
@ -194,7 +208,14 @@ func (driver XunLeiCloud) MakeDir(path string, account *model.Account) error {
if !parentFile.IsDir() {
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 {
@ -207,7 +228,14 @@ func (driver XunLeiCloud) Move(src string, dst string, account *model.Account) e
if err != nil {
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 {
@ -220,7 +248,13 @@ func (driver XunLeiCloud) Copy(src string, dst string, account *model.Account) e
if err != nil {
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 {
@ -228,7 +262,11 @@ func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
if err != nil {
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 {
@ -246,7 +284,6 @@ func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account)
return err
}
defer tempFile.Close()
defer os.Remove(tempFile.Name())
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
@ -254,29 +291,37 @@ func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account)
return err
}
var rep UploadTaskResponse
err = GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files", &base.Json{
"kind": FILE,
"parent_id": parentFile.Id,
"name": file.Name,
"size": fmt.Sprint(file.Size),
"hash": gcid,
"upload_type": UPLOAD_TYPE_RESUMABLE,
}, &rep, account)
tempFile.Close()
var resp UploadTaskResponse
_, err = GetState(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
r.SetBody(&base.Json{
"kind": FILE,
"parent_id": parentFile.Id,
"name": file.Name,
"size": fmt.Sprint(file.Size),
"hash": gcid,
"upload_type": UPLOAD_TYPE_RESUMABLE,
})
r.SetResult(&resp)
}, account)
if err != nil {
return err
}
param := rep.Resumable.Params
client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true), oss.HTTPClient(xunleiClient.GetClient()))
if err != nil {
return err
param := resp.Resumable.Params
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 {
return err
}
bucket, err := client.Bucket(param.Bucket)
if err != nil {
return err
}
return bucket.UploadFile(param.Key, tempFile.Name(), 1<<22, oss.Routines(3), oss.Checkpoint(true, ""), oss.Expires(param.Expiration))
}
bucket, err := client.Bucket(param.Bucket)
if err != nil {
return err
}
return bucket.UploadFile(param.Key, tempFile.Name(), 4*1024*1024, oss.Routines(3), oss.Checkpoint(true, ""), oss.Expires(param.Expiration))
return nil
}
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 {
return err
}
return GetState(account).Request("PATCH", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s", srcFile.Id), &base.Json{"name": dstName}, nil, account)
_, err = GetState(account).Request("PATCH", FILE_API_URL+"/{id}", func(r *resty.Request) {
r.SetPathParam("id", srcFile.Id)
r.SetBody(&base.Json{"name": dstName})
}, account)
return err
}
var _ base.Driver = (*XunLeiCloud)(nil)

View File

@ -37,6 +37,12 @@ var Algorithms = []string{
"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 (
FOLDER = "drive#folder"
FILE = "drive#file"

View File

@ -2,6 +2,7 @@ package xunlei
import (
"fmt"
"net/http"
"sync"
"time"
@ -12,7 +13,7 @@ import (
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 {
@ -102,16 +103,16 @@ func (s *State) newCaptchaToken(action string, meta map[string]string, account *
var e Erron
var resp CaptchaTokenResponse
_, err := xunleiClient.R().
SetHeader("X-Device-Id", driverID).
SetBody(&creq).
SetError(&e).
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 {
return "", err
}
if e.ErrorCode != 0 {
log.Debugf("%+v\n %+v", e, account)
return "", fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
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.captchaToken = resp.CaptchaToken
log.Debugf("%+v\n %+v", s.captchaToken, account)
return s.captchaToken, nil
}
@ -136,7 +136,7 @@ func (s *State) refreshToken_(account *model.Account) error {
"client_secret": CLIENT_SECRET,
}).
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 {
return err
}
@ -152,7 +152,6 @@ func (s *State) refreshToken_(account *model.Account) error {
s.userID = resp.UserID
return nil
default:
log.Debugf("%+v\n %+v", e, account)
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 {
s.init()
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)
if err != nil {
return err
@ -190,7 +189,6 @@ func (s *State) login(account *model.Account) error {
defer model.SaveAccount(account)
if e.ErrorCode != 0 {
account.Status = e.Error
log.Debugf("%+v\n %+v", e, account)
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
account.Status = "work"
@ -199,67 +197,68 @@ func (s *State) login(account *model.Account) error {
s.accessToken = resp.AccessToken
s.refreshToken = resp.RefreshToken
s.userID = resp.UserID
log.Debugf("%+v\n %+v", resp, account)
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()
token, err := s.getToken(account)
if err != nil {
return err
return nil, err
}
captchaToken, err := s.getCaptchaToken(getAction(method, url), account)
if err != nil {
return err
return nil, err
}
s.Unlock()
var e Erron
req := xunleiClient.R().
SetError(&e).
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).
SetHeader("Authorization", token).
SetHeader("X-Captcha-Token", captchaToken).
SetHeaders(map[string]string{
"X-Device-Id": utils.GetMD5Encode(account.Username),
"Authorization": token,
"X-Captcha-Token": captchaToken,
}).
SetQueryParam("client_id", CLIENT_ID)
if body != nil {
req.SetBody(body)
}
if resp != nil {
req.SetResult(resp)
}
callback(req)
s.Unlock()
var res *resty.Response
switch method {
case "GET":
_, err = req.Get(url)
res, err = req.Get(url)
case "POST":
_, err = req.Post(url)
res, err = req.Post(url)
case "DELETE":
_, err = req.Delete(url)
res, err = req.Delete(url)
case "PATCH":
_, err = req.Patch(url)
res, err = req.Patch(url)
case "PUT":
_, err = req.Put(url)
res, err = req.Put(url)
default:
return base.ErrNotSupport
return nil, base.ErrNotSupport
}
if err != nil {
return err
return nil, err
}
log.Debug(res.String())
var e Erron
utils.Json.Unmarshal(res.Body(), &e)
switch e.ErrorCode {
case 0:
return nil
case 9:
s.newCaptchaToken(getAction(method, url), nil, account)
fallthrough
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:
log.Debugf("%+v\n %+v", e, account)
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
return nil, fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
}