refactor: init v3
This commit is contained in:
@ -1,419 +0,0 @@
|
||||
package xunlei
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
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: "captcha_token",
|
||||
Label: "verified captcha token",
|
||||
Type: base.TypeString,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
},
|
||||
{
|
||||
Name: "client_version",
|
||||
Label: "client version",
|
||||
Default: "7.43.0.7998",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "client_id",
|
||||
Label: "client id",
|
||||
Default: "Xp6vsxz_7IYVw2BB",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "client_secret",
|
||||
Label: "client secret",
|
||||
Default: "Xp6vsy4tN9toTVdMSpomVdXpRmES",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "algorithms",
|
||||
Label: "algorithms",
|
||||
Default: "hrVPGbeqYPs+CIscj05VpAtjalzY5yjpvlMS8bEo,DrI0uTP,HHK0VXyMgY0xk2K0o,BBaXsExvL3GadmIacjWv7ISUJp3ifAwqbJumu,5toJ7ejB+bh1,5LsZTFAFjgvFvIl1URBgOAJ,QcJ5Ry+,hYgZVz8r7REROaCYfd9,zw6gXgkk/8TtGrmx6EGfekPESLnbZfDFwqR,gtSwLnMBa8h12nF3DU6+LwEQPHxd,fMG8TvtAYbCkxuEbIm0Xi/Lb7Z",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "package_name",
|
||||
Label: "package name",
|
||||
Default: "com.xunlei.downloadprovider",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "user_agent",
|
||||
Label: "user agent",
|
||||
Default: "ANDROID-com.xunlei.downloadprovider/7.43.0.7998 netWorkType/WIFI appid/40 deviceName/Samsung_Sm-g9810 deviceModel/SM-G9810 OSVersion/7.1.2 protocolVersion/301 platformVersion/10 sdkVersion/220200 Oauth2Client/0.9 (Linux 4_0_9+) (JAVA 0)",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "device_id",
|
||||
Label: "device id",
|
||||
Default: utils.GetMD5Encode(uuid.NewString()),
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
client := GetClient(account)
|
||||
// 指定验证通过的captchaToken
|
||||
if account.CaptchaToken != "" {
|
||||
client.UpdateCaptchaToken(strings.TrimSpace(account.CaptchaToken))
|
||||
account.CaptchaToken = ""
|
||||
}
|
||||
|
||||
if client.token == "" {
|
||||
return client.Login(account)
|
||||
}
|
||||
|
||||
account.Status = "work"
|
||||
model.SaveAccount(account)
|
||||
return nil
|
||||
}
|
||||
|
||||
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) {
|
||||
path = utils.ParsePath(path)
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files, _ := cache.([]model.File)
|
||||
return files, nil
|
||||
}
|
||||
|
||||
parentFile, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 300)
|
||||
files := make([]model.File, 0)
|
||||
var pageToken string
|
||||
for {
|
||||
var fileList FileList
|
||||
_, err = GetClient(account).Request("GET", FILE_API_URL, func(r *resty.Request) {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"parent_id": parentFile.Id,
|
||||
"page_token": pageToken,
|
||||
"with_audit": "true",
|
||||
"limit": "100",
|
||||
"filters": `{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`,
|
||||
})
|
||||
r.SetResult(&fileList)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range fileList.Files {
|
||||
if file.Kind == FOLDER || (file.Kind == FILE && file.Audit.Status == "STATUS_OK") {
|
||||
files = append(files, *driver.formatFile(&file))
|
||||
}
|
||||
}
|
||||
if fileList.NextPageToken == "" {
|
||||
break
|
||||
}
|
||||
pageToken = fileList.NextPageToken
|
||||
}
|
||||
if len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
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
|
||||
clinet := GetClient(account)
|
||||
_, err = clinet.Request("GET", FILE_API_URL+"/{fileID}", func(r *resty.Request) {
|
||||
r.SetPathParam("fileID", file.Id)
|
||||
r.SetQueryParam("with_audit", "true")
|
||||
r.SetResult(&lFile)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &base.Link{
|
||||
Headers: []base.Header{
|
||||
{Name: "User-Agent", Value: clinet.userAgent},
|
||||
},
|
||||
Url: lFile.WebContentLink,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(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) Rename(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = GetClient(account).Request("PATCH", FILE_API_URL+"/{fileID}", func(r *resty.Request) {
|
||||
r.SetPathParam("fileID", srcFile.Id)
|
||||
r.SetBody(&base.Json{"name": filepath.Base(dst)})
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
_, err = GetClient(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 {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = GetClient(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 {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = GetClient(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 {
|
||||
srcFile, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = GetClient(account).Request("PATCH", FILE_API_URL+"/{fileID}/trash", func(r *resty.Request) {
|
||||
r.SetPathParam("fileID", srcFile.Id)
|
||||
r.SetBody(&base.Json{})
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
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 func() {
|
||||
_ = tempFile.Close()
|
||||
_ = os.Remove(tempFile.Name())
|
||||
}()
|
||||
|
||||
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
*/
|
||||
|
||||
var resp UploadTaskResponse
|
||||
_, err = GetClient(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
|
||||
r.SetBody(&base.Json{
|
||||
"kind": FILE,
|
||||
"parent_id": parentFile.Id,
|
||||
"name": file.Name,
|
||||
"size": file.Size,
|
||||
"hash": "1CF254FBC456E1B012CD45C546636AA62CF8350E",
|
||||
"upload_type": UPLOAD_TYPE_RESUMABLE,
|
||||
})
|
||||
r.SetResult(&resp)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
param := resp.Resumable.Params
|
||||
if resp.UploadType == UPLOAD_TYPE_RESUMABLE {
|
||||
param.Endpoint = strings.TrimLeft(param.Endpoint, param.Bucket+".")
|
||||
s, err := session.NewSession(&aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(param.AccessKeyID, param.AccessKeySecret, param.SecurityToken),
|
||||
Region: aws.String("xunlei"),
|
||||
Endpoint: aws.String(param.Endpoint),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s3manager.NewUploader(s).Upload(&s3manager.UploadInput{
|
||||
Bucket: aws.String(param.Bucket),
|
||||
Key: aws.String(param.Key),
|
||||
Expires: aws.Time(param.Expiration),
|
||||
Body: file,
|
||||
})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ base.Driver = (*XunLeiCloud)(nil)
|
@ -1,179 +0,0 @@
|
||||
package xunlei
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Erron struct {
|
||||
ErrorCode int64 `json:"error_code"`
|
||||
ErrorMsg string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
// ErrorDetails interface{} `json:"error_details"`
|
||||
}
|
||||
|
||||
func (e *Erron) HasError() bool {
|
||||
return e.ErrorCode != 0 || e.ErrorMsg != "" || e.ErrorDescription != ""
|
||||
}
|
||||
|
||||
func (e *Erron) Error() string {
|
||||
return fmt.Sprintf("ErrorCode: %d ,Error: %s ,ErrorDescription: %s ", e.ErrorCode, e.ErrorMsg, e.ErrorDescription)
|
||||
}
|
||||
|
||||
/*
|
||||
* 验证码Token
|
||||
**/
|
||||
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"`
|
||||
}
|
||||
|
||||
func (t *TokenResponse) Token() string {
|
||||
return fmt.Sprint(t.TokenType, " ", t.AccessToken)
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package xunlei
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
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"
|
||||
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 getAction(method string, u string) string {
|
||||
c, _ := url.Parse(u)
|
||||
return method + ":" + c.Path
|
||||
}
|
||||
|
||||
// 计算文件Gcid
|
||||
func getGcid(r io.Reader, size int64) (string, error) {
|
||||
calcBlockSize := func(j int64) int64 {
|
||||
if j >= 0 && j <= 0x8000000 {
|
||||
return 0x40000
|
||||
}
|
||||
if j <= 0x8000000 || j > 0x10000000 {
|
||||
if j <= 0x10000000 || j > 0x20000000 {
|
||||
return 0x200000
|
||||
}
|
||||
return 0x100000
|
||||
}
|
||||
return 0x80000
|
||||
}
|
||||
|
||||
hash1 := sha1.New()
|
||||
hash2 := sha1.New()
|
||||
readSize := calcBlockSize(size)
|
||||
for {
|
||||
hash2.Reset()
|
||||
if n, err := io.CopyN(hash2, r, readSize); err != nil && n == 0 {
|
||||
if err != io.EOF {
|
||||
return "", err
|
||||
}
|
||||
break
|
||||
}
|
||||
hash1.Write(hash2.Sum(nil))
|
||||
}
|
||||
return hex.EncodeToString(hash1.Sum(nil)), nil
|
||||
}
|
@ -1,282 +0,0 @@
|
||||
package xunlei
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"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 userClients sync.Map
|
||||
|
||||
func GetClient(account *model.Account) *Client {
|
||||
if v, ok := userClients.Load(account.Username); ok {
|
||||
return v.(*Client)
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
Client: base.RestyClient,
|
||||
|
||||
clientID: account.ClientId,
|
||||
clientSecret: account.ClientSecret,
|
||||
clientVersion: account.ClientVersion,
|
||||
packageName: account.PackageName,
|
||||
algorithms: strings.Split(account.Algorithms, ","),
|
||||
userAgent: account.UserAgent,
|
||||
deviceID: account.DeviceId,
|
||||
}
|
||||
userClients.Store(account.Username, client)
|
||||
return client
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*resty.Client
|
||||
sync.Mutex
|
||||
|
||||
clientID string
|
||||
clientSecret string
|
||||
clientVersion string
|
||||
packageName string
|
||||
algorithms []string
|
||||
userAgent string
|
||||
deviceID string
|
||||
|
||||
captchaToken string
|
||||
|
||||
token string
|
||||
refreshToken string
|
||||
userID string
|
||||
}
|
||||
|
||||
// 请求验证码token
|
||||
func (c *Client) requestCaptchaToken(action string, meta map[string]string) error {
|
||||
param := CaptchaTokenRequest{
|
||||
Action: action,
|
||||
CaptchaToken: c.captchaToken,
|
||||
ClientID: c.clientID,
|
||||
DeviceID: c.deviceID,
|
||||
Meta: meta,
|
||||
RedirectUri: "xlaccsdk01://xunlei.com/callback?state=harbor",
|
||||
}
|
||||
|
||||
var e Erron
|
||||
var resp CaptchaTokenResponse
|
||||
_, err := c.Client.R().
|
||||
SetBody(¶m).
|
||||
SetError(&e).
|
||||
SetResult(&resp).
|
||||
SetHeader("X-Device-Id", c.deviceID).
|
||||
SetQueryParam("client_id", c.clientID).
|
||||
Post(XLUSER_API_URL + "/shield/captcha/init")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.HasError() {
|
||||
return &e
|
||||
}
|
||||
|
||||
if resp.Url != "" {
|
||||
return fmt.Errorf("need verify:%s", resp.Url)
|
||||
}
|
||||
|
||||
if resp.CaptchaToken == "" {
|
||||
return fmt.Errorf("empty captchaToken")
|
||||
}
|
||||
c.captchaToken = resp.CaptchaToken
|
||||
return nil
|
||||
}
|
||||
|
||||
// 验证码签名
|
||||
func (c *Client) captchaSign(time string) string {
|
||||
str := fmt.Sprint(c.clientID, c.clientVersion, c.packageName, c.deviceID, time)
|
||||
for _, algorithm := range c.algorithms {
|
||||
str = utils.GetMD5Encode(str + algorithm)
|
||||
}
|
||||
return "1." + str
|
||||
}
|
||||
|
||||
// 登录
|
||||
func (c *Client) Login(account *model.Account) (err error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
} else {
|
||||
account.Status = "work"
|
||||
}
|
||||
model.SaveAccount(account)
|
||||
}()
|
||||
|
||||
meta := make(map[string]string)
|
||||
if strings.Contains(account.Username, "@") {
|
||||
meta["email"] = account.Username
|
||||
} else if len(account.Username) >= 11 {
|
||||
if !strings.Contains(account.Username, "+") {
|
||||
account.Username = "+86 " + account.Username
|
||||
}
|
||||
meta["phone_number"] = account.Username
|
||||
} else {
|
||||
meta["username"] = account.Username
|
||||
}
|
||||
|
||||
url := XLUSER_API_URL + "/auth/signin"
|
||||
err = c.requestCaptchaToken(getAction(http.MethodPost, url), meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var e Erron
|
||||
var resp TokenResponse
|
||||
_, err = c.Client.R().
|
||||
SetResult(&resp).
|
||||
SetError(&e).
|
||||
SetBody(&SignInRequest{
|
||||
CaptchaToken: c.captchaToken,
|
||||
ClientID: c.clientID,
|
||||
ClientSecret: c.clientSecret,
|
||||
Username: account.Username,
|
||||
Password: account.Password,
|
||||
}).
|
||||
SetHeader("X-Device-Id", c.deviceID).
|
||||
SetQueryParam("client_id", c.clientID).
|
||||
Post(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.HasError() {
|
||||
return &e
|
||||
}
|
||||
|
||||
if resp.RefreshToken == "" {
|
||||
return base.ErrEmptyToken
|
||||
}
|
||||
|
||||
c.token = resp.Token()
|
||||
c.refreshToken = resp.RefreshToken
|
||||
c.userID = resp.UserID
|
||||
return nil
|
||||
}
|
||||
|
||||
// 刷新验证码token
|
||||
func (c *Client) RefreshCaptchaToken(action string) error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
timestamp := fmt.Sprint(time.Now().UnixMilli())
|
||||
param := map[string]string{
|
||||
"client_version": c.clientVersion,
|
||||
"package_name": c.packageName,
|
||||
"user_id": c.userID,
|
||||
"captcha_sign": c.captchaSign(timestamp),
|
||||
"timestamp": timestamp,
|
||||
}
|
||||
return c.requestCaptchaToken(action, param)
|
||||
}
|
||||
|
||||
// 刷新token
|
||||
func (c *Client) RefreshToken() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
var e Erron
|
||||
var resp TokenResponse
|
||||
_, err := c.Client.R().
|
||||
SetError(&e).
|
||||
SetResult(&resp).
|
||||
SetBody(&base.Json{
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": c.refreshToken,
|
||||
"client_id": c.clientID,
|
||||
"client_secret": c.clientSecret,
|
||||
}).
|
||||
SetHeader("X-Device-Id", c.deviceID).
|
||||
SetQueryParam("client_id", c.clientID).
|
||||
Post(XLUSER_API_URL + "/auth/token")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.HasError() {
|
||||
return &e
|
||||
}
|
||||
|
||||
if resp.RefreshToken == "" {
|
||||
return base.ErrEmptyToken
|
||||
}
|
||||
|
||||
c.token = resp.TokenType + " " + resp.AccessToken
|
||||
c.refreshToken = resp.RefreshToken
|
||||
c.userID = resp.UserID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Request(method string, url string, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
|
||||
c.Lock()
|
||||
req := c.Client.R().
|
||||
SetHeaders(map[string]string{
|
||||
"X-Device-Id": c.deviceID,
|
||||
"Authorization": c.token,
|
||||
"X-Captcha-Token": c.captchaToken,
|
||||
"User-Agent": c.userAgent,
|
||||
"client_id": c.clientID,
|
||||
}).
|
||||
SetQueryParam("client_id", c.clientID)
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
c.Unlock()
|
||||
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
|
||||
var e Erron
|
||||
if err = utils.Json.Unmarshal(res.Body(), &e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 处理错误
|
||||
switch e.ErrorCode {
|
||||
case 0:
|
||||
return res, nil
|
||||
case 4122, 4121, 10: // token过期
|
||||
if err = c.RefreshToken(); err == nil {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case 16: // 登录失效
|
||||
if err = c.Login(account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 9: // 验证码token过期
|
||||
if err = c.RefreshCaptchaToken(getAction(method, url)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, &e
|
||||
}
|
||||
return c.Request(method, url, callback, account)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateCaptchaToken(captchaToken string) bool {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if captchaToken != "" {
|
||||
c.captchaToken = captchaToken
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
Reference in New Issue
Block a user