From 0f079827e51a5abead0c633b07ba5f6c513b5f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E5=87=89?= <927625802@qq.com> Date: Wed, 29 Dec 2021 22:07:26 +0800 Subject: [PATCH] :sparkles: support shandianpan #234 --- drivers/all.go | 1 + drivers/shandian/driver.go | 277 +++++++++++++++++++++++++++++++++++ drivers/shandian/shandian.go | 147 +++++++++++++++++++ 3 files changed, 425 insertions(+) create mode 100644 drivers/shandian/driver.go create mode 100644 drivers/shandian/shandian.go diff --git a/drivers/all.go b/drivers/all.go index 66ffb214..e953ad86 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -11,4 +11,5 @@ import ( _ "github.com/Xhofe/alist/drivers/native" _ "github.com/Xhofe/alist/drivers/onedrive" _ "github.com/Xhofe/alist/drivers/pikpak" + _ "github.com/Xhofe/alist/drivers/shandian" ) diff --git a/drivers/shandian/driver.go b/drivers/shandian/driver.go new file mode 100644 index 00000000..e95f6f9d --- /dev/null +++ b/drivers/shandian/driver.go @@ -0,0 +1,277 @@ +package shandian + +import ( + "errors" + "github.com/Xhofe/alist/conf" + "github.com/Xhofe/alist/drivers/base" + "github.com/Xhofe/alist/model" + "github.com/Xhofe/alist/utils" + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "path/filepath" + "strconv" +) + +type Shandian struct{} + +func (driver Shandian) Config() base.DriverConfig { + return base.DriverConfig{ + Name: "ShandianPan", + NoNeedSetLink: true, + } +} + +func (driver Shandian) 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: false, + }, + } +} + +func (driver Shandian) Save(account *model.Account, old *model.Account) error { + if account.RootFolder == "" { + account.RootFolder = "0" + } + return driver.Login(account) +} + +func (driver Shandian) 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 Shandian) Files(path string, account *model.Account) ([]model.File, error) { + path = utils.ParsePath(path) + var files []model.File + cache, err := base.GetCache(path, account) + if err == nil { + files, _ = cache.([]model.File) + } else { + file, err := driver.File(path, account) + if err != nil { + return nil, err + } + rawFiles, err := driver.GetFiles(file.Id, account) + if err != nil { + return nil, err + } + files = make([]model.File, 0) + for _, file := range rawFiles { + files = append(files, *driver.FormatFile(&file)) + } + if len(files) > 0 { + _ = base.SetCache(path, files, account) + } + } + return files, nil +} + +func (driver Shandian) Link(args base.Args, account *model.Account) (*base.Link, error) { + log.Debugf("shandian link") + file, err := driver.File(args.Path, account) + if err != nil { + return nil, err + } + var e Resp + res, err := base.NoRedirectClient.R().SetError(&e).SetHeader("Accept", "application/json").SetQueryParams(map[string]string{ + "id": file.Id, + "token": account.AccessToken, + }).Get("https://shandianpan.com/api/pan/file-download") + if err != nil { + return nil, err + } + if e.Code != 0 { + if e.Code == 10 { + err = driver.Login(account) + if err != nil { + return nil, err + } + return driver.Link(args, account) + } + return nil, errors.New(e.Msg) + } + return &base.Link{ + Url: res.Header().Get("location"), + }, nil +} + +func (driver Shandian) Path(path string, account *model.Account) (*model.File, []model.File, error) { + path = utils.ParsePath(path) + log.Debugf("shandian 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 Shandian) Proxy(c *gin.Context, account *model.Account) { + +} + +func (driver Shandian) Preview(path string, account *model.Account) (interface{}, error) { + return nil, base.ErrNotSupport +} + +func (driver Shandian) MakeDir(path string, account *model.Account) error { + parentFile, err := driver.File(utils.Dir(path), account) + if err != nil { + return err + } + data := map[string]interface{}{ + "id": parentFile.Id, + "name": utils.Base(path), + } + _, err = driver.Post("https://shandianpan.com/api/pan/mkdir", data, nil, account) + if err == nil { + _ = base.DeleteCache(utils.Dir(path), account) + } + return err +} + +func (driver Shandian) Move(src string, dst string, account *model.Account) error { + srcFile, err := driver.File(src, account) + if err != nil { + return err + } + if utils.Dir(src) == utils.Dir(dst) { + // rename + data := map[string]interface{}{ + "id": srcFile.Id, + "name": utils.Base(dst), + } + _, err = driver.Post("https://shandianpan.com/api/pan/change", data, nil, account) + } else { + dstParentFile, err := driver.File(utils.Dir(dst), account) + if err != nil { + return err + } + data := map[string]interface{}{ + "id": srcFile.Id, + "to_id": dstParentFile.Id, + } + _, err = driver.Post("https://shandianpan.com/api/pan/move", data, nil, account) + } + if err == nil { + _ = base.DeleteCache(utils.Dir(src), account) + if utils.Dir(src) != utils.Dir(dst) { + _ = base.DeleteCache(utils.Dir(dst), account) + } + } + return err +} + +func (driver Shandian) Copy(src string, dst string, account *model.Account) error { + return base.ErrNotSupport +} + +func (driver Shandian) Delete(path string, account *model.Account) error { + file, err := driver.File(path, account) + if err != nil { + return err + } + data := map[string]interface{}{ + "id": file.Id, + } + _, err = driver.Post("https://shandianpan.com/api/pan/recycle-in", data, nil, account) + if err == nil { + _ = base.DeleteCache(utils.Dir(path), account) + } + return err +} + +func (driver Shandian) Upload(file *model.FileStream, account *model.Account) error { + parentFile, err := driver.File(file.ParentPath, account) + if err != nil { + return err + } + var resp UploadResp + parentId, err := strconv.Atoi(parentFile.Id) + if err != nil { + return err + } + data := map[string]interface{}{ + "id": parentId, + "name": file.GetFileName(), + } + _, err = driver.Post("https://shandianpan.com/api/pan/upload", data, &resp, account) + if err != nil { + return err + } + if resp.Code != 0 { + if resp.Code == 10 { + err = driver.Login(account) + if err != nil { + return err + } + return driver.Upload(file, account) + } + return errors.New(resp.Msg) + } + var r Resp + _, err = base.RestyClient.R().SetMultipartFormData(map[string]string{ + "token": account.AccessToken, + "id": "0", + "key": resp.Data.Key, + "ossAccessKeyId": resp.Data.Accessid, + "policy": resp.Data.Policy, + "signature": resp.Data.Signature, + "callback": resp.Data.Callback, + }).SetMultipartField("file", file.GetFileName(), file.GetMIMEType(), file). + SetResult(&r).SetError(&r).Post("https:" + resp.Data.Host + "/") + if err != nil { + return err + } + if r.Code == 0 { + _ = base.DeleteCache(file.ParentPath, account) + return nil + } + return errors.New(r.Msg) +} + +var _ base.Driver = (*Shandian)(nil) diff --git a/drivers/shandian/shandian.go b/drivers/shandian/shandian.go new file mode 100644 index 00000000..986a583d --- /dev/null +++ b/drivers/shandian/shandian.go @@ -0,0 +1,147 @@ +package shandian + +import ( + "errors" + "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" + "strconv" + "time" +) + +type Resp struct { + Code int `json:"code"` + Msg string `json:"msg"` +} + +type LoginResp struct { + Resp + Data struct { + Token string `json:"token"` + } `json:"data"` +} + +func (driver Shandian) Login(account *model.Account) error { + var resp LoginResp + _, err := base.RestyClient.R().SetResult(&resp).SetHeader("Accept", "application/json").SetBody(base.Json{ + "mobile": account.Username, + "password": account.Password, + "smscode": "", + }).Post("https://shandianpan.com/api/login") + if err != nil { + return err + } + if resp.Code != 0 { + return errors.New(resp.Msg) + } + account.AccessToken = resp.Data.Token + _ = model.SaveAccount(account) + return nil +} + +type File struct { + Id int64 `json:"id"` + Type int `json:"type"` + Name string `json:"name"` + UpdateTime int64 `json:"update_time"` + Size int64 `json:"size"` + Ext string `json:"ext"` +} + +func (driver Shandian) FormatFile(file *File) *model.File { + t := time.Unix(file.UpdateTime, 0) + f := &model.File{ + Id: strconv.FormatInt(file.Id, 10), + Name: file.Name, + Size: file.Size, + Driver: driver.Config().Name, + UpdatedAt: &t, + } + if file.Type == 1 { + f.Type = conf.FOLDER + } else { + f.Type = utils.GetFileType(file.Ext) + if file.Ext != "" { + f.Name += "." + file.Ext + } + } + return f +} + +func (driver Shandian) Post(url string, data map[string]interface{}, resp interface{}, account *model.Account) ([]byte, error) { + req := base.RestyClient.R() + req.SetHeader("Accept", "application/json") + data["token"] = account.AccessToken + req.SetBody(data) + var e Resp + if resp != nil { + req.SetResult(resp) + } else { + req.SetResult(&e) + } + req.SetError(&e) + res, err := req.Post(url) + if err != nil { + return nil, err + } + log.Debug(res.String()) + if e.Code != 0 { + if e.Code == 10 { + err = driver.Login(account) + if err != nil { + return nil, err + } + return driver.Post(url, data, resp, account) + } + return nil, errors.New(e.Msg) + } + return res.Body(), nil +} + +type FilesResp struct { + Resp + Data []File `json:"data"` +} + +func (driver Shandian) GetFiles(id string, account *model.Account) ([]File, error) { + // TODO page not wok + //res := make([]File, 0) + page := 1 + //for { + data := map[string]interface{}{ + "id": id, + "page": page, + "page_size": 100, + } + var resp FilesResp + _, err := driver.Post("https://shandianpan.com/api/pan", data, &resp, account) + if err != nil { + return nil, err + } + //res = append(res, resp.Data...) + // if len(resp.Data) == 0 { + // break + // } + //} + //return res, nil + return resp.Data, nil +} + +type UploadResp struct { + Resp + Data struct { + Accessid string `json:"accessid"` + Policy string `json:"policy"` + Expire int `json:"expire"` + Callback string `json:"callback"` + Key string `json:"key"` + Host string `json:"host"` + Signature string `json:"signature"` + } `json:"data"` +} + +func init() { + base.RegisterDriver(&Shandian{}) +}