From 6db09a2736c1164de5839c242f0ce4c7bad0517e Mon Sep 17 00:00:00 2001 From: foxxorcat <95907542+foxxorcat@users.noreply.github.com> Date: Thu, 17 Mar 2022 21:13:13 +0800 Subject: [PATCH] fix: xunlei upload error (#749) --- drivers/xunlei/driver.go | 108 ++++++++++++++++++++++++++++----------- drivers/xunlei/util.go | 6 +++ drivers/xunlei/xunlei.go | 73 +++++++++++++------------- 3 files changed, 120 insertions(+), 67 deletions(-) diff --git a/drivers/xunlei/driver.go b/drivers/xunlei/driver.go index 8ecde652..6d39ad22 100644 --- a/drivers/xunlei/driver.go +++ b/drivers/xunlei/driver.go @@ -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) diff --git a/drivers/xunlei/util.go b/drivers/xunlei/util.go index 6b64ab3c..6c5315c9 100644 --- a/drivers/xunlei/util.go +++ b/drivers/xunlei/util.go @@ -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" diff --git a/drivers/xunlei/xunlei.go b/drivers/xunlei/xunlei.go index 67d9b801..cd9f32f5 100644 --- a/drivers/xunlei/xunlei.go +++ b/drivers/xunlei/xunlei.go @@ -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) } }