From e25fe05a538059472bd81cda1baf1833e0a6e041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E5=87=89?= Date: Tue, 1 Feb 2022 17:15:11 +0800 Subject: [PATCH] :sparkles: yandex disk support --- drivers/all.go | 1 + drivers/yandex/driver.go | 224 +++++++++++++++++++++++++++++++++++++++ drivers/yandex/types.go | 74 +++++++++++++ drivers/yandex/util.go | 1 + drivers/yandex/yandex.go | 154 +++++++++++++++++++++++++++ 5 files changed, 454 insertions(+) create mode 100644 drivers/yandex/driver.go create mode 100644 drivers/yandex/types.go create mode 100644 drivers/yandex/util.go create mode 100644 drivers/yandex/yandex.go diff --git a/drivers/all.go b/drivers/all.go index 0910487c..3acf041a 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -18,6 +18,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/yandex" log "github.com/sirupsen/logrus" "strings" ) diff --git a/drivers/yandex/driver.go b/drivers/yandex/driver.go new file mode 100644 index 00000000..4b1a1592 --- /dev/null +++ b/drivers/yandex/driver.go @@ -0,0 +1,224 @@ +package yandex + +import ( + "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" + "net/http" + "path/filepath" + "strconv" +) + +type Yandex struct{} + +func (driver Yandex) Config() base.DriverConfig { + return base.DriverConfig{ + Name: "Yandex.Disk", + } +} + +func (driver Yandex) Items() []base.Item { + return []base.Item{ + { + Name: "refresh_token", + Label: "refresh token", + Type: base.TypeString, + Required: true, + }, + { + Name: "root_folder", + Label: "root folder path", + Type: base.TypeString, + Default: "/", + Required: true, + }, + { + Name: "order_by", + Label: "order_by", + Type: base.TypeSelect, + Default: "name", + Values: "name,path,created,modified,size", + Required: false, + }, + { + Name: "order_direction", + Label: "order_direction", + Type: base.TypeSelect, + Values: "asc,desc", + Default: "asc", + Required: false, + }, + { + Name: "client_id", + Label: "client id", + Default: "a78d5a69054042fa936f6c77f9a0ae8b", + Type: base.TypeString, + Required: true, + }, + { + Name: "client_secret", + Label: "client secret", + Default: "9c119bbb04b346d2a52aa64401936b2b", + Type: base.TypeString, + Required: true, + }, + } +} + +func (driver Yandex) Save(account *model.Account, old *model.Account) error { + return driver.RefreshToken(account) +} + +func (driver Yandex) 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 Yandex) 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 + } + files, err := driver.GetFiles(path, account) + if err != nil { + return nil, err + } + if len(files) > 0 { + _ = base.SetCache(path, files, account) + } + return files, nil +} + +func (driver Yandex) Link(args base.Args, account *model.Account) (*base.Link, error) { + path := utils.Join(account.RootFolder, args.Path) + log.Debugln("down path:", path) + var resp DownResp + _, err := driver.Request("/download", base.Get, nil, map[string]string{ + "path": path, + }, nil, nil, &resp, account) + if err != nil { + return nil, err + } + link := base.Link{ + Url: resp.Href, + } + return &link, nil +} + +func (driver Yandex) Path(path string, account *model.Account) (*model.File, []model.File, error) { + 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 Yandex) Proxy(r *http.Request, account *model.Account) { + +} + +func (driver Yandex) Preview(path string, account *model.Account) (interface{}, error) { + return nil, base.ErrNotSupport +} + +func (driver Yandex) MakeDir(path string, account *model.Account) error { + path = utils.Join(account.RootFolder, path) + _, err := driver.Request("", base.Put, nil, map[string]string{ + "path": path, + }, nil, nil, nil, account) + return err +} + +func (driver Yandex) Move(src string, dst string, account *model.Account) error { + from := utils.Join(account.RootFolder, src) + path := utils.Join(account.RootFolder, dst) + _, err := driver.Request("/move", base.Post, nil, map[string]string{ + "from": from, + "path": path, + "overwrite": "true", + }, nil, nil, nil, account) + return err +} + +func (driver Yandex) Rename(src string, dst string, account *model.Account) error { + return driver.Move(src, dst, account) +} + +func (driver Yandex) Copy(src string, dst string, account *model.Account) error { + from := utils.Join(account.RootFolder, src) + path := utils.Join(account.RootFolder, dst) + _, err := driver.Request("/copy", base.Post, nil, map[string]string{ + "from": from, + "path": path, + "overwrite": "true", + }, nil, nil, nil, account) + return err +} + +func (driver Yandex) Delete(path string, account *model.Account) error { + path = utils.Join(account.RootFolder, path) + _, err := driver.Request("", base.Delete, nil, map[string]string{ + "path": path, + }, nil, nil, nil, account) + return err +} + +func (driver Yandex) Upload(file *model.FileStream, account *model.Account) error { + if file == nil { + return base.ErrEmptyFile + } + path := utils.Join(account.RootFolder, file.ParentPath, file.Name) + var resp UploadResp + _, err := driver.Request("/upload", base.Get, nil, map[string]string{ + "path": path, + "overwrite": "true", + }, nil, nil, &resp, account) + if err != nil { + return err + } + req, err := http.NewRequest(resp.Method, resp.Href, file) + if err != nil { + return err + } + req.Header.Set("Content-Length", strconv.FormatUint(file.Size, 10)) + req.Header.Set("Content-Type", "application/octet-stream") + _, err = base.HttpClient.Do(req) + //res, err := base.RestyClient.R(). + // SetHeader("Content-Length", strconv.FormatUint(file.Size, 10)). + // SetBody(file).Put(resp.Href) + //log.Debugln(res.Status(), res.String()) + return err +} + +var _ base.Driver = (*Yandex)(nil) diff --git a/drivers/yandex/types.go b/drivers/yandex/types.go new file mode 100644 index 00000000..9aed679d --- /dev/null +++ b/drivers/yandex/types.go @@ -0,0 +1,74 @@ +package yandex + +import "time" + +type TokenErrResp struct { + ErrorDescription string `json:"error_description"` + Error string `json:"error"` +} + +type ErrResp struct { + Message string `json:"message"` + Description string `json:"description"` + Error string `json:"error"` +} + +type File struct { + //AntivirusStatus string `json:"antivirus_status"` + Size int64 `json:"size"` + //CommentIds struct { + // PrivateResource string `json:"private_resource"` + // PublicResource string `json:"public_resource"` + //} `json:"comment_ids"` + Name string `json:"name"` + //Exif struct { + // DateTime time.Time `json:"date_time"` + //} `json:"exif"` + //Created time.Time `json:"created"` + //ResourceId string `json:"resource_id"` + Modified time.Time `json:"modified"` + //MimeType string `json:"mime_type"` + File string `json:"file"` + //MediaType string `json:"media_type"` + Preview string `json:"preview"` + Path string `json:"path"` + //Sha256 string `json:"sha256"` + Type string `json:"type"` + //Md5 string `json:"md5"` + //Revision int64 `json:"revision"` +} + +type FilesResp struct { + Embedded struct { + Sort string `json:"sort"` + Items []File `json:"items"` + Limit int `json:"limit"` + Offset int `json:"offset"` + Path string `json:"path"` + Total int `json:"total"` + } `json:"_embedded"` + Name string `json:"name"` + Exif struct { + } `json:"exif"` + ResourceId string `json:"resource_id"` + Created time.Time `json:"created"` + Modified time.Time `json:"modified"` + Path string `json:"path"` + CommentIds struct { + } `json:"comment_ids"` + Type string `json:"type"` + Revision int64 `json:"revision"` +} + +type DownResp struct { + Href string `json:"href"` + Method string `json:"method"` + Templated bool `json:"templated"` +} + +type UploadResp struct { + OperationId string `json:"operation_id"` + Href string `json:"href"` + Method string `json:"method"` + Templated bool `json:"templated"` +} diff --git a/drivers/yandex/util.go b/drivers/yandex/util.go new file mode 100644 index 00000000..cbe4e219 --- /dev/null +++ b/drivers/yandex/util.go @@ -0,0 +1 @@ +package yandex diff --git a/drivers/yandex/yandex.go b/drivers/yandex/yandex.go new file mode 100644 index 00000000..edb38672 --- /dev/null +++ b/drivers/yandex/yandex.go @@ -0,0 +1,154 @@ +package yandex + +import ( + "errors" + "fmt" + "github.com/Xhofe/alist/conf" + "github.com/Xhofe/alist/drivers/base" + "github.com/Xhofe/alist/model" + "github.com/Xhofe/alist/utils" + "github.com/go-resty/resty/v2" + "path" + "strconv" +) + +func (driver Yandex) RefreshToken(account *model.Account) error { + err := driver.refreshToken(account) + if err != nil && err == base.ErrEmptyToken { + err = driver.refreshToken(account) + } + if err != nil { + account.Status = err.Error() + } + _ = model.SaveAccount(account) + return err +} + +func (driver Yandex) refreshToken(account *model.Account) error { + u := "https://oauth.yandex.com/token" + var resp base.TokenResp + var e TokenErrResp + _, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{ + "grant_type": "refresh_token", + "refresh_token": account.RefreshToken, + "client_id": account.ClientId, + "client_secret": account.ClientSecret, + }).Post(u) + if err != nil { + return err + } + if e.Error != "" { + return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription) + } + if resp.RefreshToken == "" { + return base.ErrEmptyToken + } + account.Status = "work" + account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken + return nil +} + +func (driver Yandex) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) { + u := "https://cloud-api.yandex.net/v1/disk/resources" + pathname + req := base.RestyClient.R() + req.SetHeader("Authorization", "OAuth "+account.AccessToken) + if headers != nil { + req.SetHeaders(headers) + } + if query != nil { + req.SetQueryParams(query) + } + if form != nil { + req.SetFormData(form) + } + if data != nil { + req.SetBody(data) + } + if resp != nil { + req.SetResult(resp) + } + var res *resty.Response + var err error + var e ErrResp + req.SetError(&e) + switch method { + case base.Get: + res, err = req.Get(u) + case base.Post: + res, err = req.Post(u) + case base.Patch: + res, err = req.Patch(u) + case base.Delete: + res, err = req.Delete(u) + case base.Put: + res, err = req.Put(u) + default: + return nil, base.ErrNotSupport + } + if err != nil { + return nil, err + } + //log.Debug(res.String()) + if e.Error != "" { + if e.Error == "UnauthorizedError" { + err = driver.RefreshToken(account) + if err != nil { + return nil, err + } + return driver.Request(pathname, method, headers, query, form, data, resp, account) + } + return nil, errors.New(e.Description) + } + return res.Body(), nil +} + +func (driver Yandex) GetFiles(rawPath string, account *model.Account) ([]model.File, error) { + path_ := utils.Join(account.RootFolder, rawPath) + limit := 100 + page := 1 + res := make([]model.File, 0) + for { + offset := (page - 1) * limit + query := map[string]string{ + "path": path_, + "limit": strconv.Itoa(limit), + "offset": strconv.Itoa(offset), + } + if account.OrderBy != "" { + if account.OrderDirection == "desc" { + query["sort"] = "-" + account.OrderBy + } else { + query["sort"] = account.OrderBy + } + } + var resp FilesResp + _, err := driver.Request("", base.Get, nil, query, nil, nil, &resp, account) + if err != nil { + return nil, err + } + for _, file := range resp.Embedded.Items { + f := model.File{ + Name: file.Name, + Size: file.Size, + Driver: driver.Config().Name, + UpdatedAt: &file.Modified, + Thumbnail: file.Preview, + Url: file.File, + } + if file.Type == "dir" { + f.Type = conf.FOLDER + } else { + f.Type = utils.GetFileType(path.Ext(file.Name)) + } + res = append(res, f) + } + if resp.Embedded.Total <= offset+limit { + break + } + } + return res, nil +} + +func init() { + base.RegisterDriver(&Yandex{}) +}