From 2964d5a6db3938464e565207df4673e1226918d8 Mon Sep 17 00:00:00 2001 From: foxxorcat Date: Thu, 3 Mar 2022 15:44:41 +0800 Subject: [PATCH] xunleicloud support --- drivers/all.go | 1 + drivers/xunlei/driver.go | 274 +++++++++++++++++++++++++++++++++++++ drivers/xunlei/types.go | 154 +++++++++++++++++++++ drivers/xunlei/util.go | 103 ++++++++++++++ drivers/xunlei/xunlei.go | 288 +++++++++++++++++++++++++++++++++++++++ go.mod | 3 + go.sum | 3 + 7 files changed, 826 insertions(+) create mode 100644 drivers/xunlei/driver.go create mode 100644 drivers/xunlei/types.go create mode 100644 drivers/xunlei/util.go create mode 100644 drivers/xunlei/xunlei.go diff --git a/drivers/all.go b/drivers/all.go index 9996382e..ff533a0c 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -20,6 +20,7 @@ import ( _ "github.com/Xhofe/alist/drivers/shandian" _ "github.com/Xhofe/alist/drivers/teambition" _ "github.com/Xhofe/alist/drivers/webdav" + _ "github.com/Xhofe/alist/drivers/xunlei" _ "github.com/Xhofe/alist/drivers/yandex" log "github.com/sirupsen/logrus" "strings" diff --git a/drivers/xunlei/driver.go b/drivers/xunlei/driver.go new file mode 100644 index 00000000..93d23ede --- /dev/null +++ b/drivers/xunlei/driver.go @@ -0,0 +1,274 @@ +package xunlei + +import ( + "fmt" + "io" + "io/ioutil" + "net/url" + "os" + "path/filepath" + "strconv" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + + "github.com/Xhofe/alist/conf" + "github.com/Xhofe/alist/drivers/base" + "github.com/Xhofe/alist/model" + "github.com/Xhofe/alist/utils" + log "github.com/sirupsen/logrus" +) + +type XunLeiCloud struct{} + +func init() { + base.RegisterDriver(new(XunLeiCloud)) +} + +func (driver XunLeiCloud) Config() base.DriverConfig { + return base.DriverConfig{ + Name: "XunLeiCloud", + LocalSort: true, + } +} + +func (driver XunLeiCloud) Items() []base.Item { + return []base.Item{ + { + Name: "username", + Label: "username", + Type: base.TypeString, + Required: true, + Description: "account username/phone number", + }, + { + Name: "password", + Label: "password", + Type: base.TypeString, + Required: true, + Description: "account password", + }, + { + Name: "root_folder", + Label: "root folder file_id", + Type: base.TypeString, + Required: true, + }, + } +} + +func (driver XunLeiCloud) Save(account *model.Account, old *model.Account) error { + if account == nil { + return nil + } + return GetState(account).Login(account) +} + +func (driver XunLeiCloud) File(path string, account *model.Account) (*model.File, error) { + path = utils.ParsePath(path) + if path == "/" { + return &model.File{ + Id: account.RootFolder, + Name: account.Name, + Size: 0, + Type: conf.FOLDER, + Driver: driver.Config().Name, + UpdatedAt: account.UpdatedAt, + }, nil + } + dir, name := filepath.Split(path) + files, err := driver.Files(dir, account) + if err != nil { + return nil, err + } + for _, file := range files { + if file.Name == name { + return &file, nil + } + } + return nil, base.ErrPathNotFound +} + +func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.File, error) { + file, err := driver.File(utils.ParsePath(path), account) + if err != nil { + return nil, err + } + + var fileList FileList + url := fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files?parent_id=%s&page_token=%s&with_audit=true&filters=%s", file.Id, "", url.QueryEscape(`{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`)) + if err = GetState(account).Request("GET", url, nil, &fileList, account); err != nil { + return nil, err + } + + files := make([]model.File, 0, len(fileList.Files)) + for _, file := range fileList.Files { + if file.Kind == FOLDER || (file.Kind == FILE && file.Audit.Status == "STATUS_OK") { + files = append(files, *driver.formatFile(&file)) + } + } + return files, nil +} + +func (driver XunLeiCloud) formatFile(file *Files) *model.File { + size, _ := strconv.ParseInt(file.Size, 10, 64) + tp := conf.FOLDER + if file.Kind == FILE { + tp = utils.GetFileType(file.FileExtension) + } + return &model.File{ + Id: file.ID, + Name: file.Name, + Size: size, + Type: tp, + Driver: driver.Config().Name, + UpdatedAt: file.CreatedTime, + Thumbnail: file.ThumbnailLink, + } +} + +func (driver XunLeiCloud) Link(args base.Args, account *model.Account) (*base.Link, error) { + file, err := driver.File(utils.ParsePath(args.Path), account) + if err != nil { + return nil, err + } + if file.Type == conf.FOLDER { + 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 { + return nil, err + } + return &base.Link{ + Headers: []base.Header{ + {Name: "User-Agent", Value: base.UserAgent}, + }, + Url: lfile.WebContentLink, + }, nil +} + +func (driver XunLeiCloud) Path(path string, account *model.Account) (*model.File, []model.File, error) { + path = utils.ParsePath(path) + log.Debugf("xunlei path: %s", path) + file, err := driver.File(path, account) + if err != nil { + return nil, nil, err + } + if !file.IsDir() { + return file, nil, nil + } + files, err := driver.Files(path, account) + if err != nil { + return nil, nil, err + } + return nil, files, nil +} + +func (driver XunLeiCloud) Preview(path string, account *model.Account) (interface{}, error) { + return nil, base.ErrNotSupport +} + +func (driver XunLeiCloud) MakeDir(path string, account *model.Account) error { + dir, name := filepath.Split(path) + parentFile, err := driver.File(dir, account) + if err != nil { + return err + } + 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) +} + +func (driver XunLeiCloud) Move(src string, dst string, account *model.Account) error { + srcFile, err := driver.File(src, account) + if err != nil { + return err + } + + dstDirFile, err := driver.File(filepath.Dir(dst), account) + 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) +} + +func (driver XunLeiCloud) Copy(src string, dst string, account *model.Account) error { + srcFile, err := driver.File(src, account) + if err != nil { + return err + } + + dstDirFile, err := driver.File(filepath.Dir(dst), account) + 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) +} + +func (driver XunLeiCloud) Delete(path string, account *model.Account) error { + srcFile, err := driver.File(path, account) + 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) +} + +func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account) error { + if file == nil { + return base.ErrEmptyFile + } + + parentFile, err := driver.File(file.ParentPath, account) + if err != nil { + return err + } + + tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*") + if err != nil { + return err + } + + defer tempFile.Close() + defer os.Remove(tempFile.Name()) + + gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size)) + if err != nil { + 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) + 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 + } + 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)) +} + +func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account) error { + _, dstName := filepath.Split(dst) + srcFile, err := driver.File(src, 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) +} + +var _ base.Driver = (*XunLeiCloud)(nil) diff --git a/drivers/xunlei/types.go b/drivers/xunlei/types.go new file mode 100644 index 00000000..c73c292c --- /dev/null +++ b/drivers/xunlei/types.go @@ -0,0 +1,154 @@ +package xunlei + +import ( + "time" +) + +type Erron struct { + Error string `json:"error"` + ErrorCode int64 `json:"error_code"` + ErrorDescription string `json:"error_description"` + // ErrorDetails interface{} `json:"error_details"` +} + +type CaptchaTokenRequest struct { + Action string `json:"action"` + CaptchaToken string `json:"captcha_token"` + ClientID string `json:"client_id"` + DeviceID string `json:"device_id"` + Meta map[string]string `json:"meta"` + //RedirectUri string `json:"redirect_uri"` +} + +type CaptchaTokenResponse struct { + CaptchaToken string `json:"captcha_token"` + ExpiresIn int64 `json:"expires_in"` + Url string `json:"url"` +} + +type TokenResponse struct { + TokenType string `json:"token_type"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int64 `json:"expires_in"` + + Sub string `json:"sub"` + UserID string `json:"user_id"` +} + +type SignInRequest struct { + CaptchaToken string `json:"captcha_token"` + + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + + Username string `json:"username"` + Password string `json:"password"` +} + +type FileList struct { + Kind string `json:"kind"` + NextPageToken string `json:"next_page_token"` + Files []Files `json:"files"` + Version string `json:"version"` + VersionOutdated bool `json:"version_outdated"` +} + +type Files struct { + Kind string `json:"kind"` + ID string `json:"id"` + ParentID string `json:"parent_id"` + Name string `json:"name"` + UserID string `json:"user_id"` + Size string `json:"size"` + Revision string `json:"revision"` + FileExtension string `json:"file_extension"` + MimeType string `json:"mime_type"` + Starred bool `json:"starred"` + WebContentLink string `json:"web_content_link"` + CreatedTime *time.Time `json:"created_time"` + ModifiedTime *time.Time `json:"modified_time"` + IconLink string `json:"icon_link"` + ThumbnailLink string `json:"thumbnail_link"` + Md5Checksum string `json:"md5_checksum"` + Hash string `json:"hash"` + //Links struct{} `json:"links"` + Phase string `json:"phase"` + Audit struct { + Status string `json:"status"` + Message string `json:"message"` + Title string `json:"title"` + } `json:"audit"` + /* Medias []struct { + Category string `json:"category"` + IconLink string `json:"icon_link"` + IsDefault bool `json:"is_default"` + IsOrigin bool `json:"is_origin"` + IsVisible bool `json:"is_visible"` + //Link interface{} `json:"link"` + MediaID string `json:"media_id"` + MediaName string `json:"media_name"` + NeedMoreQuota bool `json:"need_more_quota"` + Priority int `json:"priority"` + RedirectLink string `json:"redirect_link"` + ResolutionName string `json:"resolution_name"` + Video struct { + AudioCodec string `json:"audio_codec"` + BitRate int `json:"bit_rate"` + Duration int `json:"duration"` + FrameRate int `json:"frame_rate"` + Height int `json:"height"` + VideoCodec string `json:"video_codec"` + VideoType string `json:"video_type"` + Width int `json:"width"` + } `json:"video"` + VipTypes []string `json:"vip_types"` + } `json:"medias"` */ + Trashed bool `json:"trashed"` + DeleteTime string `json:"delete_time"` + OriginalURL string `json:"original_url"` + //Params struct{} `json:"params"` + OriginalFileIndex int `json:"original_file_index"` + Space string `json:"space"` + //Apps []interface{} `json:"apps"` + Writable bool `json:"writable"` + FolderType string `json:"folder_type"` + //Collection interface{} `json:"collection"` +} + +type UploadTaskResponse struct { + UploadType string `json:"upload_type"` + + /*//UPLOAD_TYPE_FORM + Form struct { + //Headers struct{} `json:"headers"` + Kind string `json:"kind"` + Method string `json:"method"` + MultiParts struct { + OSSAccessKeyID string `json:"OSSAccessKeyId"` + Signature string `json:"Signature"` + Callback string `json:"callback"` + Key string `json:"key"` + Policy string `json:"policy"` + XUserData string `json:"x:user_data"` + } `json:"multi_parts"` + URL string `json:"url"` + } `json:"form"`*/ + + //UPLOAD_TYPE_RESUMABLE + Resumable struct { + Kind string `json:"kind"` + Params struct { + AccessKeyID string `json:"access_key_id"` + AccessKeySecret string `json:"access_key_secret"` + Bucket string `json:"bucket"` + Endpoint string `json:"endpoint"` + Expiration time.Time `json:"expiration"` + Key string `json:"key"` + SecurityToken string `json:"security_token"` + } `json:"params"` + Provider string `json:"provider"` + } `json:"resumable"` + + File Files `json:"file"` +} diff --git a/drivers/xunlei/util.go b/drivers/xunlei/util.go new file mode 100644 index 00000000..6b64ab3c --- /dev/null +++ b/drivers/xunlei/util.go @@ -0,0 +1,103 @@ +package xunlei + +import ( + "crypto/sha1" + "encoding/hex" + "fmt" + "io" + "net/url" + + "github.com/Xhofe/alist/utils" +) + +const ( + // 小米浏览器 + CLIENT_ID = "X7MtiU0Gb5YqWv-6" + CLIENT_SECRET = "84MYEih3Eeu2HF4RrGce3Q" + CLIENT_VERSION = "5.1.0.51045" + + ALG_VERSION = "1" + PACKAGE_NAME = "com.xunlei.xcloud.lib" +) + +var Algorithms = []string{ + "", + "BXza40wm+P4zw8rEFpHA", + "UfZLfKfYRmKTA0", + "OMBGVt/9Wcaln1XaBz", + "Jn217F4rk5FPPWyhoeV", + "w5OwkGo0pGpb0Xe/XZ5T3", + "5guM3DNiY4F78x49zQ97q75", + "QXwn4D2j884wJgrYXjGClM/IVrJX", + "NXBRosYvbHIm6w8vEB", + "2kZ8Ie1yW2ib4O2iAkNpJobP", + "11CoVJJQEc", + "xf3QWysVwnVsNv5DCxU+cgNT1rK", + "9eEfKkrqkfw", + "T78dnANexYRbiZy", +} + +const ( + FOLDER = "drive#folder" + FILE = "drive#file" + + RESUMABLE = "drive#resumable" +) + +const ( + UPLOAD_TYPE_UNKNOWN = "UPLOAD_TYPE_UNKNOWN" + //UPLOAD_TYPE_FORM = "UPLOAD_TYPE_FORM" + UPLOAD_TYPE_RESUMABLE = "UPLOAD_TYPE_RESUMABLE" + UPLOAD_TYPE_URL = "UPLOAD_TYPE_URL" +) + +func captchaSign(driverID string, time int64) string { + str := fmt.Sprint(CLIENT_ID, CLIENT_VERSION, PACKAGE_NAME, driverID, time) + for _, algorithm := range Algorithms { + str = utils.GetMD5Encode(fmt.Sprint(str, algorithm)) + } + return fmt.Sprint(ALG_VERSION, ".", str) +} + +func getAction(method string, u string) string { + c, _ := url.Parse(u) + return fmt.Sprint(method, ":", c.Path) +} + +func getGcid(r io.Reader, size int64) (string, error) { + calcBlockSize := func(j int64) int64 { + if j >= 0 && j <= 134217728 { + return 262144 + } + if j <= 134217728 || j > 268435456 { + if j <= 268435456 || j > 536870912 { + return 2097152 + } + return 1048576 + } + return 524288 + } + /* + calcBlockSize := func(j int64) int64 { + psize := int64(0x40000) + for j/psize > 0x200 { + psize <<= 1 + } + return psize + } + */ + + hash1 := sha1.New() + hash2 := sha1.New() + for { + hash2.Reset() + if n, err := io.CopyN(hash2, r, calcBlockSize(size)); err != nil && n == 0 { + if err != io.EOF { + return "", err + } + break + } + hash1.Write(hash2.Sum(nil)) + } + return hex.EncodeToString(hash1.Sum(nil)), nil +} diff --git a/drivers/xunlei/xunlei.go b/drivers/xunlei/xunlei.go new file mode 100644 index 00000000..67d9b801 --- /dev/null +++ b/drivers/xunlei/xunlei.go @@ -0,0 +1,288 @@ +package xunlei + +import ( + "fmt" + "sync" + "time" + + "github.com/Xhofe/alist/drivers/base" + "github.com/Xhofe/alist/model" + "github.com/Xhofe/alist/utils" + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" +) + +var xunleiClient = resty.New().SetTimeout(120 * time.Second) + +// 一个账户只允许登陆一次 +var userStateCache = struct { + sync.Mutex + States map[string]*State +}{States: make(map[string]*State)} + +func GetState(account *model.Account) *State { + userStateCache.Lock() + defer userStateCache.Unlock() + if v, ok := userStateCache.States[account.Username]; ok && v != nil { + return v + } + state := new(State).Init() + userStateCache.States[account.Username] = state + return state +} + +type State struct { + sync.Mutex + captchaToken string + captchaTokenExpiresTime int64 + + tokenType string + accessToken string + refreshToken string + tokenExpiresTime int64 //Milli + + userID string +} + +func (s *State) init() *State { + s.captchaToken = "" + s.captchaTokenExpiresTime = 0 + s.tokenType = "" + s.accessToken = "" + s.refreshToken = "" + s.tokenExpiresTime = 0 + s.userID = "0" + return s +} + +func (s *State) getToken(account *model.Account) (string, error) { + if s.isTokensExpires() { + if err := s.refreshToken_(account); err != nil { + return "", err + } + } + return fmt.Sprint(s.tokenType, " ", s.accessToken), nil +} + +func (s *State) getCaptchaToken(action string, account *model.Account) (string, error) { + if s.isCaptchaTokenExpires() { + return s.newCaptchaToken(action, nil, account) + } + return s.captchaToken, nil +} + +func (s *State) isCaptchaTokenExpires() bool { + return time.Now().UnixMilli() >= s.captchaTokenExpiresTime || s.captchaToken == "" || s.tokenType == "" +} + +func (s *State) isTokensExpires() bool { + return time.Now().UnixMilli() >= s.tokenExpiresTime || s.accessToken == "" +} + +func (s *State) newCaptchaToken(action string, meta map[string]string, account *model.Account) (string, error) { + ctime := time.Now().UnixMilli() + driverID := utils.GetMD5Encode(account.Username) + creq := CaptchaTokenRequest{ + Action: action, + CaptchaToken: s.captchaToken, + ClientID: CLIENT_ID, + DeviceID: driverID, + Meta: map[string]string{ + "captcha_sign": captchaSign(driverID, ctime), + "client_version": CLIENT_VERSION, + "package_name": PACKAGE_NAME, + "timestamp": fmt.Sprint(ctime), + "user_id": s.userID, + }, + } + for k, v := range meta { + creq.Meta[k] = v + } + + 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) + 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 != "" { + return "", fmt.Errorf("需要验证验证码") + } + + s.captchaTokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000 + s.captchaToken = resp.CaptchaToken + log.Debugf("%+v\n %+v", s.captchaToken, account) + return s.captchaToken, nil +} + +func (s *State) refreshToken_(account *model.Account) error { + var e Erron + var resp TokenResponse + _, err := xunleiClient.R(). + SetResult(&resp).SetError(&e). + SetBody(&base.Json{ + "grant_type": "refresh_token", + "refresh_token": s.refreshToken, + "client_id": CLIENT_ID, + "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") + if err != nil { + return err + } + + switch e.ErrorCode { + case 4122, 4121: + return s.login(account) + case 0: + s.tokenExpiresTime = (time.Now().UnixMilli() + resp.ExpiresIn*1000) - 30000 + s.tokenType = resp.TokenType + s.accessToken = resp.AccessToken + s.refreshToken = resp.RefreshToken + s.userID = resp.UserID + return nil + default: + log.Debugf("%+v\n %+v", e, account) + return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription) + } +} + +func (s *State) login(account *model.Account) error { + s.init() + ctime := time.Now().UnixMilli() + url := "https://xluser-ssl.xunlei.com/v1/auth/signin" + captchaToken, err := s.newCaptchaToken(getAction("POST", url), map[string]string{"username": account.Username}, account) + if err != nil { + return err + } + + signReq := SignInRequest{ + CaptchaToken: captchaToken, + ClientID: CLIENT_ID, + ClientSecret: CLIENT_SECRET, + Username: account.Username, + Password: account.Password, + } + + var e Erron + var resp TokenResponse + _, err = xunleiClient.R(). + SetResult(&resp). + SetError(&e). + SetBody(&signReq). + SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)). + SetQueryParam("client_id", CLIENT_ID). + Post(url) + if err != nil { + return err + } + + 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" + s.tokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000 + s.tokenType = resp.TokenType + 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 { + s.Lock() + token, err := s.getToken(account) + if err != nil { + return err + } + + captchaToken, err := s.getCaptchaToken(getAction(method, url), account) + if err != nil { + return 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). + SetQueryParam("client_id", CLIENT_ID) + + if body != nil { + req.SetBody(body) + } + if resp != nil { + req.SetResult(resp) + } + + switch method { + case "GET": + _, err = req.Get(url) + case "POST": + _, err = req.Post(url) + case "DELETE": + _, err = req.Delete(url) + case "PATCH": + _, err = req.Patch(url) + case "PUT": + _, err = req.Put(url) + default: + return base.ErrNotSupport + } + + if err != nil { + return err + } + + 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) + default: + log.Debugf("%+v\n %+v", e, account) + return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription) + } +} + +func (s *State) Init() *State { + s.Lock() + defer s.Unlock() + return s.init() +} + +func (s *State) GetCaptchaToken(action string, account *model.Account) (string, error) { + s.Lock() + defer s.Unlock() + return s.getCaptchaToken(action, account) +} + +func (s *State) GetToken(account *model.Account) (string, error) { + s.Lock() + defer s.Unlock() + return s.getToken(account) +} + +func (s *State) Login(account *model.Account) error { + s.Lock() + defer s.Unlock() + return s.login(account) +} diff --git a/go.mod b/go.mod index b78719f6..53e65e8a 100644 --- a/go.mod +++ b/go.mod @@ -23,8 +23,11 @@ require ( gorm.io/gorm v1.23.1 ) +require golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect + require ( github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect + github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible github.com/beorn7/perks v1.0.1 // indirect github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect github.com/cenkalti/backoff/v4 v4.1.0 // indirect diff --git a/go.sum b/go.sum index cf5380ee..a58d12c4 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible h1:uuJIwCFhbZy+zdvLy5zrcIToPEQP0s5CFOZ0Zj03O/w= +github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/allegro/bigcache/v2 v2.2.5 h1:mRc8r6GQjuJsmSKQNPsR5jQVXc8IJ1xsW5YXUYMLfqI= github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -659,6 +661,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=