From c9ea9bce817851fc87fa56557617fe96e81447bd Mon Sep 17 00:00:00 2001 From: foxxorcat <95907542+foxxorcat@users.noreply.github.com> Date: Tue, 1 Aug 2023 19:44:57 +0800 Subject: [PATCH] feat(lanzou): support login with account (close #4880 in #4885) --- drivers/lanzou/driver.go | 44 ++++++++++++----------- drivers/lanzou/help.go | 12 +++++++ drivers/lanzou/meta.go | 13 +++++-- drivers/lanzou/types.go | 1 + drivers/lanzou/util.go | 77 ++++++++++++++++++++++++++++++++++------ 5 files changed, 114 insertions(+), 33 deletions(-) diff --git a/drivers/lanzou/driver.go b/drivers/lanzou/driver.go index c2e60f5ed8c..cdb56f79658 100644 --- a/drivers/lanzou/driver.go +++ b/drivers/lanzou/driver.go @@ -2,9 +2,7 @@ package lanzou import ( "context" - "fmt" "net/http" - "regexp" "github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/internal/driver" @@ -19,6 +17,8 @@ type LanZou struct { model.Storage uid string vei string + + flag int32 } func (d *LanZou) Config() driver.Config { @@ -30,16 +30,18 @@ func (d *LanZou) GetAddition() driver.Additional { } func (d *LanZou) Init(ctx context.Context) (err error) { - if d.IsCookie() { + switch d.Type { + case "account": + _, err := d.Login() + if err != nil { + return err + } + fallthrough + case "cookie": if d.RootFolderID == "" { d.RootFolderID = "-1" } - ylogin := regexp.MustCompile("ylogin=(.*?);").FindStringSubmatch(d.Cookie) - if len(ylogin) < 2 { - return fmt.Errorf("cookie does not contain ylogin") - } - d.uid = ylogin[1] - d.vei, err = d.getVei() + d.vei, d.uid, err = d.getVeiAndUid() } return } @@ -51,7 +53,7 @@ func (d *LanZou) Drop(ctx context.Context) error { // 获取的大小和时间不准确 func (d *LanZou) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { - if d.IsCookie() { + if d.IsCookie() || d.IsAccount() { return d.GetAllFiles(dir.GetID()) } else { return d.GetFileOrFolderByShareUrl(dir.GetID(), d.SharePassword) @@ -119,7 +121,7 @@ func (d *LanZou) Link(ctx context.Context, file model.Obj, args model.LinkArgs) } func (d *LanZou) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) { - if d.IsCookie() { + if d.IsCookie() || d.IsAccount() { data, err := d.doupload(func(req *resty.Request) { req.SetContext(ctx) req.SetFormData(map[string]string{ @@ -137,11 +139,11 @@ func (d *LanZou) MakeDir(ctx context.Context, parentDir model.Obj, dirName strin FolID: utils.Json.Get(data, "text").ToString(), }, nil } - return nil, errs.NotImplement + return nil, errs.NotSupport } func (d *LanZou) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) { - if d.IsCookie() { + if d.IsCookie() || d.IsAccount() { if !srcObj.IsDir() { _, err := d.doupload(func(req *resty.Request) { req.SetContext(ctx) @@ -157,11 +159,11 @@ func (d *LanZou) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, return srcObj, nil } } - return nil, errs.NotImplement + return nil, errs.NotSupport } func (d *LanZou) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) { - if d.IsCookie() { + if d.IsCookie() || d.IsAccount() { if !srcObj.IsDir() { _, err := d.doupload(func(req *resty.Request) { req.SetContext(ctx) @@ -179,11 +181,11 @@ func (d *LanZou) Rename(ctx context.Context, srcObj model.Obj, newName string) ( return srcObj, nil } } - return nil, errs.NotImplement + return nil, errs.NotSupport } func (d *LanZou) Remove(ctx context.Context, obj model.Obj) error { - if d.IsCookie() { + if d.IsCookie() || d.IsAccount() { _, err := d.doupload(func(req *resty.Request) { req.SetContext(ctx) if obj.IsDir() { @@ -200,13 +202,13 @@ func (d *LanZou) Remove(ctx context.Context, obj model.Obj) error { }, nil) return err } - return errs.NotImplement + return errs.NotSupport } func (d *LanZou) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) { - if d.IsCookie() { + if d.IsCookie() || d.IsAccount() { var resp RespText[[]FileOrFolder] - _, err := d._post(d.BaseUrl+"/fileup.php", func(req *resty.Request) { + _, err := d._post(d.BaseUrl+"/html5up.php", func(req *resty.Request) { req.SetFormData(map[string]string{ "task": "1", "vie": "2", @@ -221,5 +223,5 @@ func (d *LanZou) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr } return &resp.Text[0], nil } - return nil, errs.NotImplement + return nil, errs.NotSupport } diff --git a/drivers/lanzou/help.go b/drivers/lanzou/help.go index ae9b0ac23d0..89f8e0711c3 100644 --- a/drivers/lanzou/help.go +++ b/drivers/lanzou/help.go @@ -3,6 +3,7 @@ package lanzou import ( "bytes" "fmt" + "net/http" "regexp" "strconv" "strings" @@ -190,3 +191,14 @@ func GetExpirationTime(url string) (etime time.Duration) { etime = time.Duration(timestamp-time.Now().Unix()) * time.Second return } + +func CookieToString(cookies []*http.Cookie) string { + if cookies == nil { + return "" + } + cookieStrings := make([]string, len(cookies)) + for i, cookie := range cookies { + cookieStrings[i] = cookie.Name + "=" + cookie.Value + } + return strings.Join(cookieStrings, ";") +} diff --git a/drivers/lanzou/meta.go b/drivers/lanzou/meta.go index 5fe21d26ac8..c8db6448476 100644 --- a/drivers/lanzou/meta.go +++ b/drivers/lanzou/meta.go @@ -6,8 +6,13 @@ import ( ) type Addition struct { - Type string `json:"type" type:"select" options:"cookie,url" default:"cookie"` - Cookie string `json:"cookie" required:"true" help:"about 15 days valid, ignore if shareUrl is used"` + Type string `json:"type" type:"select" options:"account,cookie,url" default:"cookie"` + + Account string `json:"account"` + Password string `json:"password"` + + Cookie string `json:"cookie" help:"about 15 days valid, ignore if shareUrl is used"` + driver.RootID SharePassword string `json:"share_password"` BaseUrl string `json:"baseUrl" required:"true" default:"https://pc.woozooo.com" help:"basic URL for file operation"` @@ -19,6 +24,10 @@ func (a *Addition) IsCookie() bool { return a.Type == "cookie" } +func (a *Addition) IsAccount() bool { + return a.Type == "account" +} + var config = driver.Config{ Name: "Lanzou", LocalSort: true, diff --git a/drivers/lanzou/types.go b/drivers/lanzou/types.go index 8be8dd3402d..2e2daf461a5 100644 --- a/drivers/lanzou/types.go +++ b/drivers/lanzou/types.go @@ -8,6 +8,7 @@ import ( var ErrFileShareCancel = errors.New("file sharing cancellation") var ErrFileNotExist = errors.New("file does not exist") +var ErrCookieExpiration = errors.New("cookie expiration") type RespText[T any] struct { Text T `json:"text"` diff --git a/drivers/lanzou/util.go b/drivers/lanzou/util.go index 1fb1752147d..6e2f05cc56f 100644 --- a/drivers/lanzou/util.go +++ b/drivers/lanzou/util.go @@ -5,13 +5,16 @@ import ( "fmt" "net/http" "regexp" + "runtime" "strconv" "strings" "sync" + "sync/atomic" "time" "github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/internal/op" "github.com/alist-org/alist/v3/pkg/utils" "github.com/go-resty/resty/v2" log "github.com/sirupsen/logrus" @@ -37,7 +40,24 @@ func (d *LanZou) get(url string, callback base.ReqCallback) ([]byte, error) { } func (d *LanZou) post(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) { - return d._post(url, callback, resp, false) + data, err := d._post(url, callback, resp, false) + if err == ErrCookieExpiration && d.IsAccount() { + if atomic.CompareAndSwapInt32(&d.flag, 0, 1) { + _, err2 := d.Login() + atomic.SwapInt32(&d.flag, 0) + if err2 != nil { + err = errors.Join(err, err2) + d.Status = err.Error() + op.MustSaveDriverStorage(d) + return data, err + } + } + for atomic.LoadInt32(&d.flag) != 0 { + runtime.Gosched() + } + return d._post(url, callback, resp, false) + } + return data, err } func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{}, up bool) ([]byte, error) { @@ -49,10 +69,12 @@ func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{}, } return false }) - callback(req) + if callback != nil { + callback(req) + } }, up) if err != nil { - return nil, err + return data, err } switch utils.Json.Get(data, "zt").ToInt() { case 1, 2, 4: @@ -61,12 +83,14 @@ func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{}, utils.Json.Unmarshal(data, resp) } return data, nil + case 9: // 登录过期 + return data, ErrCookieExpiration default: info := utils.Json.Get(data, "inf").ToString() if info == "" { info = utils.Json.Get(data, "info").ToString() } - return nil, fmt.Errorf(info) + return data, fmt.Errorf(info) } } @@ -101,6 +125,28 @@ func (d *LanZou) request(url string, method string, callback base.ReqCallback, u return res.Body(), err } +func (d *LanZou) Login() ([]*http.Cookie, error) { + resp, err := base.NewRestyClient().SetRedirectPolicy(resty.NoRedirectPolicy()). + R().SetFormData(map[string]string{ + "task": "3", + "uid": d.Account, + "pwd": d.Password, + "setSessionId": "", + "setSig": "", + "setScene": "", + "setTocen": "", + "formhash": "", + }).Post("https://up.woozooo.com/mlogin.php") + if err != nil { + return nil, err + } + if utils.Json.Get(resp.Body(), "zt").ToInt() != 1 { + return nil, fmt.Errorf("login err: %s", resp.Body()) + } + d.Cookie = CookieToString(resp.Cookies()) + return resp.Cookies(), nil +} + /* 通过cookie获取数据 */ @@ -451,21 +497,32 @@ func (d *LanZou) getFileRealInfo(downURL string) (*int64, *time.Time) { return &size, &time } -func (d *LanZou) getVei() (string, error) { - resp, err := d.get("https://pc.woozooo.com/mydisk.php", func(req *resty.Request) { +func (d *LanZou) getVeiAndUid() (vei string, uid string, err error) { + var resp []byte + resp, err = d.get("https://pc.woozooo.com/mydisk.php", func(req *resty.Request) { req.SetQueryParams(map[string]string{ "item": "files", "action": "index", - "u": d.uid, }) }) if err != nil { - return "", err + return + } + // uid + uids := regexp.MustCompile(`uid=([^'"&;]+)`).FindStringSubmatch(string(resp)) + if len(uids) < 2 { + err = fmt.Errorf("uid variable not find") + return } + uid = uids[1] + + // vei html := RemoveNotes(string(resp)) data, err := htmlJsonToMap(html) if err != nil { - return "", err + return } - return data["vei"], nil + vei = data["vei"] + + return }