feat(lanzou): support login with account (close #4880 in #4885)

pull/4882/head
foxxorcat 2023-08-01 19:44:57 +08:00 committed by GitHub
parent 9f08353d31
commit c9ea9bce81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 114 additions and 33 deletions

View File

@ -2,9 +2,7 @@ package lanzou
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"regexp"
"github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/driver"
@ -19,6 +17,8 @@ type LanZou struct {
model.Storage model.Storage
uid string uid string
vei string vei string
flag int32
} }
func (d *LanZou) Config() driver.Config { 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) { 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 == "" { if d.RootFolderID == "" {
d.RootFolderID = "-1" d.RootFolderID = "-1"
} }
ylogin := regexp.MustCompile("ylogin=(.*?);").FindStringSubmatch(d.Cookie) d.vei, d.uid, err = d.getVeiAndUid()
if len(ylogin) < 2 {
return fmt.Errorf("cookie does not contain ylogin")
}
d.uid = ylogin[1]
d.vei, err = d.getVei()
} }
return 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) { 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()) return d.GetAllFiles(dir.GetID())
} else { } else {
return d.GetFileOrFolderByShareUrl(dir.GetID(), d.SharePassword) 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) { 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) { data, err := d.doupload(func(req *resty.Request) {
req.SetContext(ctx) req.SetContext(ctx)
req.SetFormData(map[string]string{ 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(), FolID: utils.Json.Get(data, "text").ToString(),
}, nil }, nil
} }
return nil, errs.NotImplement return nil, errs.NotSupport
} }
func (d *LanZou) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) { 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() { if !srcObj.IsDir() {
_, err := d.doupload(func(req *resty.Request) { _, err := d.doupload(func(req *resty.Request) {
req.SetContext(ctx) req.SetContext(ctx)
@ -157,11 +159,11 @@ func (d *LanZou) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj,
return srcObj, nil 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) { 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() { if !srcObj.IsDir() {
_, err := d.doupload(func(req *resty.Request) { _, err := d.doupload(func(req *resty.Request) {
req.SetContext(ctx) req.SetContext(ctx)
@ -179,11 +181,11 @@ func (d *LanZou) Rename(ctx context.Context, srcObj model.Obj, newName string) (
return srcObj, nil return srcObj, nil
} }
} }
return nil, errs.NotImplement return nil, errs.NotSupport
} }
func (d *LanZou) Remove(ctx context.Context, obj model.Obj) error { 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) { _, err := d.doupload(func(req *resty.Request) {
req.SetContext(ctx) req.SetContext(ctx)
if obj.IsDir() { if obj.IsDir() {
@ -200,13 +202,13 @@ func (d *LanZou) Remove(ctx context.Context, obj model.Obj) error {
}, nil) }, nil)
return err 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) { 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] 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{ req.SetFormData(map[string]string{
"task": "1", "task": "1",
"vie": "2", "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 &resp.Text[0], nil
} }
return nil, errs.NotImplement return nil, errs.NotSupport
} }

View File

@ -3,6 +3,7 @@ package lanzou
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"net/http"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -190,3 +191,14 @@ func GetExpirationTime(url string) (etime time.Duration) {
etime = time.Duration(timestamp-time.Now().Unix()) * time.Second etime = time.Duration(timestamp-time.Now().Unix()) * time.Second
return 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, ";")
}

View File

@ -6,8 +6,13 @@ import (
) )
type Addition struct { type Addition struct {
Type string `json:"type" type:"select" options:"cookie,url" default:"cookie"` Type string `json:"type" type:"select" options:"account,cookie,url" default:"cookie"`
Cookie string `json:"cookie" required:"true" help:"about 15 days valid, ignore if shareUrl is used"`
Account string `json:"account"`
Password string `json:"password"`
Cookie string `json:"cookie" help:"about 15 days valid, ignore if shareUrl is used"`
driver.RootID driver.RootID
SharePassword string `json:"share_password"` SharePassword string `json:"share_password"`
BaseUrl string `json:"baseUrl" required:"true" default:"https://pc.woozooo.com" help:"basic URL for file operation"` 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" return a.Type == "cookie"
} }
func (a *Addition) IsAccount() bool {
return a.Type == "account"
}
var config = driver.Config{ var config = driver.Config{
Name: "Lanzou", Name: "Lanzou",
LocalSort: true, LocalSort: true,

View File

@ -8,6 +8,7 @@ import (
var ErrFileShareCancel = errors.New("file sharing cancellation") var ErrFileShareCancel = errors.New("file sharing cancellation")
var ErrFileNotExist = errors.New("file does not exist") var ErrFileNotExist = errors.New("file does not exist")
var ErrCookieExpiration = errors.New("cookie expiration")
type RespText[T any] struct { type RespText[T any] struct {
Text T `json:"text"` Text T `json:"text"`

View File

@ -5,13 +5,16 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"regexp" "regexp"
"runtime"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/model" "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/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -37,8 +40,25 @@ func (d *LanZou) get(url string, callback base.ReqCallback) ([]byte, error) {
} }
func (d *LanZou) post(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) { func (d *LanZou) post(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
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 d._post(url, callback, resp, false)
} }
return data, err
}
func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{}, up bool) ([]byte, error) { func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{}, up bool) ([]byte, error) {
data, err := d.request(url, http.MethodPost, func(req *resty.Request) { data, err := d.request(url, http.MethodPost, func(req *resty.Request) {
@ -49,10 +69,12 @@ func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{},
} }
return false return false
}) })
if callback != nil {
callback(req) callback(req)
}
}, up) }, up)
if err != nil { if err != nil {
return nil, err return data, err
} }
switch utils.Json.Get(data, "zt").ToInt() { switch utils.Json.Get(data, "zt").ToInt() {
case 1, 2, 4: case 1, 2, 4:
@ -61,12 +83,14 @@ func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{},
utils.Json.Unmarshal(data, resp) utils.Json.Unmarshal(data, resp)
} }
return data, nil return data, nil
case 9: // 登录过期
return data, ErrCookieExpiration
default: default:
info := utils.Json.Get(data, "inf").ToString() info := utils.Json.Get(data, "inf").ToString()
if info == "" { if info == "" {
info = utils.Json.Get(data, "info").ToString() 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 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 cookie
*/ */
@ -451,21 +497,32 @@ func (d *LanZou) getFileRealInfo(downURL string) (*int64, *time.Time) {
return &size, &time return &size, &time
} }
func (d *LanZou) getVei() (string, error) { func (d *LanZou) getVeiAndUid() (vei string, uid string, err error) {
resp, err := d.get("https://pc.woozooo.com/mydisk.php", func(req *resty.Request) { var resp []byte
resp, err = d.get("https://pc.woozooo.com/mydisk.php", func(req *resty.Request) {
req.SetQueryParams(map[string]string{ req.SetQueryParams(map[string]string{
"item": "files", "item": "files",
"action": "index", "action": "index",
"u": d.uid,
}) })
}) })
if err != nil { 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)) html := RemoveNotes(string(resp))
data, err := htmlJsonToMap(html) data, err := htmlJsonToMap(html)
if err != nil { if err != nil {
return "", err return
} }
return data["vei"], nil vei = data["vei"]
return
} }