From adb0739dfed02a6c421bb0209e6856e73330bd6c Mon Sep 17 00:00:00 2001 From: Andy Hsu Date: Sun, 23 Apr 2023 17:48:26 +0800 Subject: [PATCH] feat!(alist_v3): support username & password login (close #4226) Breaking changes: - rename access_token to token - rename old password to meta_password --- drivers/alist_v3/driver.go | 128 +++++++++++++++---------------------- drivers/alist_v3/meta.go | 8 ++- drivers/alist_v3/types.go | 16 +++++ drivers/alist_v3/util.go | 51 +++++++++++++-- 4 files changed, 116 insertions(+), 87 deletions(-) diff --git a/drivers/alist_v3/driver.go b/drivers/alist_v3/driver.go index 62999555..9bc4c14c 100644 --- a/drivers/alist_v3/driver.go +++ b/drivers/alist_v3/driver.go @@ -2,16 +2,15 @@ package alist_v3 import ( "context" - "errors" - "io" + "net/http" "path" "strconv" "strings" - "github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/server/common" + "github.com/go-resty/resty/v2" ) type AListV3 struct { @@ -29,9 +28,14 @@ func (d *AListV3) GetAddition() driver.Additional { func (d *AListV3) Init(ctx context.Context) error { d.Addition.Address = strings.TrimSuffix(d.Addition.Address, "/") - // TODO login / refresh token - //op.MustSaveDriverStorage(d) - return nil + var resp common.Resp[MeResp] + _, err := d.request("/me", http.MethodGet, func(req *resty.Request) { + req.SetResult(&resp) + }) + if d.Username != "" && d.Username != resp.Data.Username { + return d.login() + } + return err } func (d *AListV3) Drop(ctx context.Context) error { @@ -39,12 +43,9 @@ func (d *AListV3) Drop(ctx context.Context) error { } func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { - url := d.Address + "/api/fs/list" var resp common.Resp[FsListResp] - _, err := base.RestyClient.R(). - SetResult(&resp). - SetHeader("Authorization", d.AccessToken). - SetBody(ListReq{ + _, err := d.request("/fs/list", http.MethodPost, func(req *resty.Request) { + req.SetResult(&resp).SetBody(ListReq{ PageReq: model.PageReq{ Page: 1, PerPage: 0, @@ -52,13 +53,11 @@ func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs) Path: dir.GetPath(), Password: d.Password, Refresh: false, - }).Post(url) + }) + }) if err != nil { return nil, err } - if resp.Code != 200 { - return nil, errors.New(resp.Message) - } var files []model.Obj for _, f := range resp.Data.Content { file := model.ObjThumb{ @@ -76,107 +75,80 @@ func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs) } func (d *AListV3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { - url := d.Address + "/api/fs/get" var resp common.Resp[FsGetResp] - _, err := base.RestyClient.R(). - SetResult(&resp). - SetHeader("Authorization", d.AccessToken). - SetBody(FsGetReq{ + _, err := d.request("/fs/get", http.MethodPost, func(req *resty.Request) { + req.SetResult(&resp).SetBody(FsGetReq{ Path: file.GetPath(), Password: d.Password, - }).Post(url) + }) + }) if err != nil { return nil, err } - if resp.Code != 200 { - return nil, errors.New(resp.Message) - } return &model.Link{ URL: resp.Data.RawURL, }, nil } func (d *AListV3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { - url := d.Address + "/api/fs/mkdir" - var resp common.Resp[interface{}] - _, err := base.RestyClient.R(). - SetResult(&resp). - SetHeader("Authorization", d.AccessToken). - SetBody(MkdirOrLinkReq{ + _, err := d.request("/fs/mkdir", http.MethodPost, func(req *resty.Request) { + req.SetBody(MkdirOrLinkReq{ Path: path.Join(parentDir.GetPath(), dirName), - }).Post(url) - return checkResp(resp, err) + }) + }) + return err } func (d *AListV3) Move(ctx context.Context, srcObj, dstDir model.Obj) error { - url := d.Address + "/api/fs/move" - var resp common.Resp[interface{}] - _, err := base.RestyClient.R(). - SetResult(&resp). - SetHeader("Authorization", d.AccessToken). - SetBody(MoveCopyReq{ + _, err := d.request("/fs/move", http.MethodPost, func(req *resty.Request) { + req.SetBody(MoveCopyReq{ SrcDir: path.Dir(srcObj.GetPath()), DstDir: dstDir.GetPath(), Names: []string{srcObj.GetName()}, - }).Post(url) - return checkResp(resp, err) + }) + }) + return err } func (d *AListV3) Rename(ctx context.Context, srcObj model.Obj, newName string) error { - url := d.Address + "/api/fs/rename" - var resp common.Resp[interface{}] - _, err := base.RestyClient.R(). - SetResult(&resp). - SetHeader("Authorization", d.AccessToken). - SetBody(RenameReq{ + _, err := d.request("/fs/rename", http.MethodPost, func(req *resty.Request) { + req.SetBody(RenameReq{ Path: srcObj.GetPath(), Name: newName, - }).Post(url) - return checkResp(resp, err) + }) + }) + return err } func (d *AListV3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { - url := d.Address + "/api/fs/copy" - var resp common.Resp[interface{}] - _, err := base.RestyClient.R(). - SetResult(&resp). - SetHeader("Authorization", d.AccessToken). - SetBody(MoveCopyReq{ + _, err := d.request("/fs/copy", http.MethodPost, func(req *resty.Request) { + req.SetBody(MoveCopyReq{ SrcDir: path.Dir(srcObj.GetPath()), DstDir: dstDir.GetPath(), Names: []string{srcObj.GetName()}, - }).Post(url) - return checkResp(resp, err) + }) + }) + return err } func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error { - url := d.Address + "/api/fs/remove" - var resp common.Resp[interface{}] - _, err := base.RestyClient.R(). - SetResult(&resp). - SetHeader("Authorization", d.AccessToken). - SetBody(RemoveReq{ + _, err := d.request("/fs/remove", http.MethodPost, func(req *resty.Request) { + req.SetBody(RemoveReq{ Dir: path.Dir(obj.GetPath()), Names: []string{obj.GetName()}, - }).Post(url) - return checkResp(resp, err) + }) + }) + return err } func (d *AListV3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { - url := d.Address + "/api/fs/put" - var resp common.Resp[interface{}] - fileBytes, err := io.ReadAll(stream.GetReadCloser()) - if err != nil { - return nil - } - _, err = base.RestyClient.R().SetContext(ctx). - SetResult(&resp). - SetHeader("Authorization", d.AccessToken). - SetHeader("File-Path", path.Join(dstDir.GetPath(), stream.GetName())). - SetHeader("Password", d.Password). - SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)). - SetBody(fileBytes).Put(url) - return checkResp(resp, err) + _, err := d.request("/fs/put", http.MethodPut, func(req *resty.Request) { + req.SetHeader("File-Path", path.Join(dstDir.GetPath(), stream.GetName())). + SetHeader("Password", d.Password). + SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)). + SetBody(stream.GetReadCloser()) + }) + return err } //func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { diff --git a/drivers/alist_v3/meta.go b/drivers/alist_v3/meta.go index 45d8a482..d3f877fd 100644 --- a/drivers/alist_v3/meta.go +++ b/drivers/alist_v3/meta.go @@ -7,9 +7,11 @@ import ( type Addition struct { driver.RootPath - Address string `json:"url" required:"true"` - Password string `json:"password"` - AccessToken string `json:"access_token"` + Address string `json:"url" required:"true"` + MetaPassword string `json:"meta_password"` + Username string `json:"username"` + Password string `json:"password"` + Token string `json:"token"` } var config = driver.Config{ diff --git a/drivers/alist_v3/types.go b/drivers/alist_v3/types.go index 5acefde6..77801254 100644 --- a/drivers/alist_v3/types.go +++ b/drivers/alist_v3/types.go @@ -63,3 +63,19 @@ type RemoveReq struct { Dir string `json:"dir"` Names []string `json:"names"` } + +type LoginResp struct { + Token string `json:"token"` +} + +type MeResp struct { + Id int `json:"id"` + Username string `json:"username"` + Password string `json:"password"` + BasePath string `json:"base_path"` + Role int `json:"role"` + Disabled bool `json:"disabled"` + Permission int `json:"permission"` + SsoId string `json:"sso_id"` + Otp bool `json:"otp"` +} diff --git a/drivers/alist_v3/util.go b/drivers/alist_v3/util.go index 5682d624..b200f7b7 100644 --- a/drivers/alist_v3/util.go +++ b/drivers/alist_v3/util.go @@ -1,17 +1,56 @@ package alist_v3 import ( - "errors" + "fmt" + "net/http" + "github.com/alist-org/alist/v3/drivers/base" + "github.com/alist-org/alist/v3/internal/op" + "github.com/alist-org/alist/v3/pkg/utils" "github.com/alist-org/alist/v3/server/common" + "github.com/go-resty/resty/v2" ) -func checkResp(resp common.Resp[interface{}], err error) error { +func (d *AListV3) login() error { + var resp common.Resp[LoginResp] + _, err := d.request("/auth/login", http.MethodPost, func(req *resty.Request) { + req.SetResult(&resp).SetBody(base.Json{ + "username": d.Username, + "password": d.Password, + }) + }) if err != nil { return err } - if resp.Code == 200 { - return nil - } - return errors.New(resp.Message) + d.Token = resp.Data.Token + op.MustSaveDriverStorage(d) + return nil +} + +func (d *AListV3) request(api, method string, callback base.ReqCallback, retry ...bool) ([]byte, error) { + url := d.Address + "/api" + api + req := base.RestyClient.R() + req.SetHeader("Authorization", d.Token) + if callback != nil { + callback(req) + } + res, err := req.Execute(method, url) + if err != nil { + return nil, err + } + if res.StatusCode() >= 400 { + return nil, fmt.Errorf("request failed, status: %s", res.Status()) + } + code := utils.Json.Get(res.Body(), "code").ToInt() + if code != 200 { + if (code == 401 || code == 403) && !utils.IsBool(retry...) { + err = d.login() + if err != nil { + return nil, err + } + return d.request(api, method, callback, true) + } + return nil, fmt.Errorf("request failed,code: %d, message: %s", code, utils.Json.Get(res.Body(), "message").ToString()) + } + return res.Body(), nil }