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" "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,29 +291,37 @@ 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{
"kind": FILE, var resp UploadTaskResponse
"parent_id": parentFile.Id, _, err = GetState(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
"name": file.Name, r.SetBody(&base.Json{
"size": fmt.Sprint(file.Size), "kind": FILE,
"hash": gcid, "parent_id": parentFile.Id,
"upload_type": UPLOAD_TYPE_RESUMABLE, "name": file.Name,
}, &rep, account) "size": fmt.Sprint(file.Size),
"hash": gcid,
"upload_type": UPLOAD_TYPE_RESUMABLE,
})
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 {
if err != nil { client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true))
return err 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) return nil
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))
} }
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)

View File

@ -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"

View 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)
} }
} }