Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
4bf46268da | |||
b7ea73b3c2 | |||
9fbc54314d | |||
cf8ab29a17 | |||
51cadd2d49 | |||
2bae8e129e | |||
9d55ad3af6 | |||
36cd504783 | |||
49f13b9b90 | |||
adb0739dfe | |||
340cb940e3 | |||
8711f2a1c5 | |||
7f35aab071 | |||
ecd167d2f9 | |||
220fd30830 |
@ -97,7 +97,8 @@ func (d *Pan123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
u_ := u.String()
|
u_ := u.String()
|
||||||
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Head(u_)
|
log.Debug("download url: ", u_)
|
||||||
|
res, err := base.NoRedirectClient.R().Get(u_)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -108,6 +109,8 @@ func (d *Pan123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
|||||||
log.Debugln("res code: ", res.StatusCode())
|
log.Debugln("res code: ", res.StatusCode())
|
||||||
if res.StatusCode() == 302 {
|
if res.StatusCode() == 302 {
|
||||||
link.URL = res.Header().Get("location")
|
link.URL = res.Header().Get("location")
|
||||||
|
} else if res.StatusCode() == 200 {
|
||||||
|
link.URL = utils.Json.Get(res.Body(), "data", "redirect_url").ToString()
|
||||||
}
|
}
|
||||||
return &link, nil
|
return &link, nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -19,7 +19,7 @@ const (
|
|||||||
SignIn = API + "/user/sign_in"
|
SignIn = API + "/user/sign_in"
|
||||||
UserInfo = API + "/user/info"
|
UserInfo = API + "/user/info"
|
||||||
FileList = API + "/file/list/new"
|
FileList = API + "/file/list/new"
|
||||||
DownloadInfo = API + "/file/download_info"
|
DownloadInfo = "https://www.123pan.com/a/api/file/download_info"
|
||||||
Mkdir = API + "/file/upload_request"
|
Mkdir = API + "/file/upload_request"
|
||||||
Move = API + "/file/mod_pid"
|
Move = API + "/file/mod_pid"
|
||||||
Rename = API + "/file/rename"
|
Rename = API + "/file/rename"
|
||||||
|
@ -72,11 +72,21 @@ func (d *Alias) list(ctx context.Context, dst, sub string) ([]model.Obj, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return utils.SliceConvert(objs, func(obj model.Obj) (model.Obj, error) {
|
return utils.SliceConvert(objs, func(obj model.Obj) (model.Obj, error) {
|
||||||
return &model.Object{
|
thumb, ok := model.GetThumb(obj)
|
||||||
|
objRes := model.Object{
|
||||||
Name: obj.GetName(),
|
Name: obj.GetName(),
|
||||||
Size: obj.GetSize(),
|
Size: obj.GetSize(),
|
||||||
Modified: obj.ModTime(),
|
Modified: obj.ModTime(),
|
||||||
IsFolder: obj.IsDir(),
|
IsFolder: obj.IsDir(),
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return &objRes, nil
|
||||||
|
}
|
||||||
|
return &model.ObjThumb{
|
||||||
|
Object: objRes,
|
||||||
|
Thumbnail: model.Thumbnail{
|
||||||
|
Thumbnail: thumb,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,19 @@ package alist_v3
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"fmt"
|
||||||
"io"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AListV3 struct {
|
type AListV3 struct {
|
||||||
@ -29,9 +32,39 @@ func (d *AListV3) GetAddition() driver.Additional {
|
|||||||
|
|
||||||
func (d *AListV3) Init(ctx context.Context) error {
|
func (d *AListV3) Init(ctx context.Context) error {
|
||||||
d.Addition.Address = strings.TrimSuffix(d.Addition.Address, "/")
|
d.Addition.Address = strings.TrimSuffix(d.Addition.Address, "/")
|
||||||
// TODO login / refresh token
|
var resp common.Resp[MeResp]
|
||||||
//op.MustSaveDriverStorage(d)
|
_, err := d.request("/me", http.MethodGet, func(req *resty.Request) {
|
||||||
return nil
|
req.SetResult(&resp)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// if the username is not empty and the username is not the same as the current username, then login again
|
||||||
|
if d.Username != "" && d.Username != resp.Data.Username {
|
||||||
|
err = d.login()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// re-get the user info
|
||||||
|
_, err = d.request("/me", http.MethodGet, func(req *resty.Request) {
|
||||||
|
req.SetResult(&resp)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.Data.Role == model.GUEST {
|
||||||
|
url := d.Address + "/api/public/settings"
|
||||||
|
res, err := base.RestyClient.R().Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
allowMounted := utils.Json.Get(res.Body(), "data", conf.AllowMounted).ToString() == "true"
|
||||||
|
if !allowMounted {
|
||||||
|
return fmt.Errorf("the site does not allow mounted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) Drop(ctx context.Context) error {
|
func (d *AListV3) Drop(ctx context.Context) error {
|
||||||
@ -39,26 +72,21 @@ func (d *AListV3) Drop(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, 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]
|
var resp common.Resp[FsListResp]
|
||||||
_, err := base.RestyClient.R().
|
_, err := d.request("/fs/list", http.MethodPost, func(req *resty.Request) {
|
||||||
SetResult(&resp).
|
req.SetResult(&resp).SetBody(ListReq{
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(ListReq{
|
|
||||||
PageReq: model.PageReq{
|
PageReq: model.PageReq{
|
||||||
Page: 1,
|
Page: 1,
|
||||||
PerPage: 0,
|
PerPage: 0,
|
||||||
},
|
},
|
||||||
Path: dir.GetPath(),
|
Path: dir.GetPath(),
|
||||||
Password: d.Password,
|
Password: d.MetaPassword,
|
||||||
Refresh: false,
|
Refresh: false,
|
||||||
}).Post(url)
|
})
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.Code != 200 {
|
|
||||||
return nil, errors.New(resp.Message)
|
|
||||||
}
|
|
||||||
var files []model.Obj
|
var files []model.Obj
|
||||||
for _, f := range resp.Data.Content {
|
for _, f := range resp.Data.Content {
|
||||||
file := model.ObjThumb{
|
file := model.ObjThumb{
|
||||||
@ -76,107 +104,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) {
|
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]
|
var resp common.Resp[FsGetResp]
|
||||||
_, err := base.RestyClient.R().
|
_, err := d.request("/fs/get", http.MethodPost, func(req *resty.Request) {
|
||||||
SetResult(&resp).
|
req.SetResult(&resp).SetBody(FsGetReq{
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(FsGetReq{
|
|
||||||
Path: file.GetPath(),
|
Path: file.GetPath(),
|
||||||
Password: d.Password,
|
Password: d.MetaPassword,
|
||||||
}).Post(url)
|
})
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.Code != 200 {
|
|
||||||
return nil, errors.New(resp.Message)
|
|
||||||
}
|
|
||||||
return &model.Link{
|
return &model.Link{
|
||||||
URL: resp.Data.RawURL,
|
URL: resp.Data.RawURL,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *AListV3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
url := d.Address + "/api/fs/mkdir"
|
_, err := d.request("/fs/mkdir", http.MethodPost, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetBody(MkdirOrLinkReq{
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(MkdirOrLinkReq{
|
|
||||||
Path: path.Join(parentDir.GetPath(), dirName),
|
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 {
|
func (d *AListV3) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
url := d.Address + "/api/fs/move"
|
_, err := d.request("/fs/move", http.MethodPost, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetBody(MoveCopyReq{
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(MoveCopyReq{
|
|
||||||
SrcDir: path.Dir(srcObj.GetPath()),
|
SrcDir: path.Dir(srcObj.GetPath()),
|
||||||
DstDir: dstDir.GetPath(),
|
DstDir: dstDir.GetPath(),
|
||||||
Names: []string{srcObj.GetName()},
|
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 {
|
func (d *AListV3) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
url := d.Address + "/api/fs/rename"
|
_, err := d.request("/fs/rename", http.MethodPost, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetBody(RenameReq{
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(RenameReq{
|
|
||||||
Path: srcObj.GetPath(),
|
Path: srcObj.GetPath(),
|
||||||
Name: newName,
|
Name: newName,
|
||||||
}).Post(url)
|
})
|
||||||
return checkResp(resp, err)
|
})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
func (d *AListV3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
url := d.Address + "/api/fs/copy"
|
_, err := d.request("/fs/copy", http.MethodPost, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetBody(MoveCopyReq{
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(MoveCopyReq{
|
|
||||||
SrcDir: path.Dir(srcObj.GetPath()),
|
SrcDir: path.Dir(srcObj.GetPath()),
|
||||||
DstDir: dstDir.GetPath(),
|
DstDir: dstDir.GetPath(),
|
||||||
Names: []string{srcObj.GetName()},
|
Names: []string{srcObj.GetName()},
|
||||||
}).Post(url)
|
})
|
||||||
return checkResp(resp, err)
|
})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error {
|
func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
url := d.Address + "/api/fs/remove"
|
_, err := d.request("/fs/remove", http.MethodPost, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetBody(RemoveReq{
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(RemoveReq{
|
|
||||||
Dir: path.Dir(obj.GetPath()),
|
Dir: path.Dir(obj.GetPath()),
|
||||||
Names: []string{obj.GetName()},
|
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 {
|
func (d *AListV3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
url := d.Address + "/api/fs/put"
|
_, err := d.request("/fs/put", http.MethodPut, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetHeader("File-Path", path.Join(dstDir.GetPath(), stream.GetName())).
|
||||||
fileBytes, err := io.ReadAll(stream.GetReadCloser())
|
SetHeader("Password", d.MetaPassword).
|
||||||
if err != nil {
|
SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)).
|
||||||
return nil
|
SetBody(stream.GetReadCloser())
|
||||||
}
|
})
|
||||||
_, err = base.RestyClient.R().SetContext(ctx).
|
return err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
//func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
|
@ -7,15 +7,18 @@ import (
|
|||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
driver.RootPath
|
driver.RootPath
|
||||||
Address string `json:"url" required:"true"`
|
Address string `json:"url" required:"true"`
|
||||||
Password string `json:"password"`
|
MetaPassword string `json:"meta_password"`
|
||||||
AccessToken string `json:"access_token"`
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
Name: "AList V3",
|
Name: "AList V3",
|
||||||
LocalSort: true,
|
LocalSort: true,
|
||||||
DefaultRoot: "/",
|
DefaultRoot: "/",
|
||||||
|
CheckStatus: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -63,3 +63,19 @@ type RemoveReq struct {
|
|||||||
Dir string `json:"dir"`
|
Dir string `json:"dir"`
|
||||||
Names []string `json:"names"`
|
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"`
|
||||||
|
}
|
||||||
|
@ -1,17 +1,56 @@
|
|||||||
package alist_v3
|
package alist_v3
|
||||||
|
|
||||||
import (
|
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/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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.Code == 200 {
|
d.Token = resp.Data.Token
|
||||||
return nil
|
op.MustSaveDriverStorage(d)
|
||||||
}
|
return nil
|
||||||
return errors.New(resp.Message)
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ var config = driver.Config{
|
|||||||
func init() {
|
func init() {
|
||||||
op.RegisterDriver(func() driver.Driver {
|
op.RegisterDriver(func() driver.Driver {
|
||||||
return &AliyundriveOpen{
|
return &AliyundriveOpen{
|
||||||
base: "https://open.aliyundrive.com",
|
base: "https://openapi.aliyundrive.com",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ func (d *AliyundriveOpen) request(uri, method string, callback base.ReqCallback,
|
|||||||
}
|
}
|
||||||
isRetry := len(retry) > 0 && retry[0]
|
isRetry := len(retry) > 0 && retry[0]
|
||||||
if e.Code != "" {
|
if e.Code != "" {
|
||||||
if !isRetry && e.Code == "AccessTokenInvalid" {
|
if !isRetry && (utils.SliceContains([]string{"AccessTokenInvalid", "AccessTokenExpired", "I400JD"}, e.Code) || d.AccessToken == "") {
|
||||||
err = d.refreshToken()
|
err = d.refreshToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -249,7 +249,7 @@ func (d *BaiduPhoto) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
|
|||||||
"isdir": "0",
|
"isdir": "0",
|
||||||
"rtype": "1",
|
"rtype": "1",
|
||||||
"ctype": "11",
|
"ctype": "11",
|
||||||
"path": stream.GetName(),
|
"path": fmt.Sprintf("/%s", stream.GetName()),
|
||||||
"size": fmt.Sprint(stream.GetSize()),
|
"size": fmt.Sprint(stream.GetSize()),
|
||||||
"slice-md5": slice_md5,
|
"slice-md5": slice_md5,
|
||||||
"content-md5": content_md5,
|
"content-md5": content_md5,
|
||||||
|
@ -5,15 +5,18 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"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/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||||
"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"
|
||||||
@ -64,12 +67,58 @@ func (d *Quark) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
u := resp.Data[0].DownloadUrl
|
||||||
|
start, end := int64(0), file.GetSize()
|
||||||
return &model.Link{
|
return &model.Link{
|
||||||
URL: resp.Data[0].DownloadUrl,
|
Handle: func(w http.ResponseWriter, r *http.Request) error {
|
||||||
Header: http.Header{
|
if rg := r.Header.Get("Range"); rg != "" {
|
||||||
"Cookie": []string{d.Cookie},
|
parseRange, err := http_range.ParseRange(rg, file.GetSize())
|
||||||
"Referer": []string{"https://pan.quark.cn"},
|
if err != nil {
|
||||||
"User-Agent": []string{ua},
|
return err
|
||||||
|
}
|
||||||
|
start, end = parseRange[0].Start, parseRange[0].Start+parseRange[0].Length
|
||||||
|
w.Header().Set("Content-Range", parseRange[0].ContentRange(file.GetSize()))
|
||||||
|
w.Header().Set("Content-Length", strconv.FormatInt(parseRange[0].Length, 10))
|
||||||
|
w.WriteHeader(http.StatusPartialContent)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
// request 10 MB at a time
|
||||||
|
chunkSize := int64(10 * 1024 * 1024)
|
||||||
|
for start < end {
|
||||||
|
_end := start + chunkSize
|
||||||
|
if _end > end {
|
||||||
|
_end = end
|
||||||
|
}
|
||||||
|
_range := "bytes=" + strconv.FormatInt(start, 10) + "-" + strconv.FormatInt(_end-1, 10)
|
||||||
|
start = _end
|
||||||
|
err = func() error {
|
||||||
|
req, err := http.NewRequest(r.Method, u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Range", _range)
|
||||||
|
req.Header.Set("User-Agent", ua)
|
||||||
|
req.Header.Set("Cookie", d.Cookie)
|
||||||
|
req.Header.Set("Referer", "https://pan.quark.cn")
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusPartialContent {
|
||||||
|
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(w, resp.Body)
|
||||||
|
return err
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@ type Addition struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
Name: "Quark",
|
Name: "Quark",
|
||||||
OnlyProxy: true,
|
OnlyLocal: true,
|
||||||
DefaultRoot: "0",
|
DefaultRoot: "0",
|
||||||
NoOverwriteUpload: true,
|
NoOverwriteUpload: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +85,7 @@ func InitialSettings() []model.SettingItem {
|
|||||||
{Key: "pagination_type", Value: "all", Type: conf.TypeSelect, Options: "all,pagination,load_more,auto_load_more", Group: model.SITE},
|
{Key: "pagination_type", Value: "all", Type: conf.TypeSelect, Options: "all,pagination,load_more,auto_load_more", Group: model.SITE},
|
||||||
{Key: "default_page_size", Value: "30", Type: conf.TypeNumber, Group: model.SITE},
|
{Key: "default_page_size", Value: "30", Type: conf.TypeNumber, Group: model.SITE},
|
||||||
{Key: conf.AllowIndexed, Value: "false", Type: conf.TypeBool, Group: model.SITE},
|
{Key: conf.AllowIndexed, Value: "false", Type: conf.TypeBool, Group: model.SITE},
|
||||||
|
{Key: conf.AllowMounted, Value: "true", Type: conf.TypeBool, Group: model.SITE},
|
||||||
// style settings
|
// style settings
|
||||||
{Key: conf.Logo, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeText, Group: model.STYLE},
|
{Key: conf.Logo, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeText, Group: model.STYLE},
|
||||||
{Key: conf.Favicon, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeString, Group: model.STYLE},
|
{Key: conf.Favicon, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeString, Group: model.STYLE},
|
||||||
@ -152,9 +153,13 @@ func InitialSettings() []model.SettingItem {
|
|||||||
|
|
||||||
// SSO settings
|
// SSO settings
|
||||||
{Key: conf.SSOLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.SSO, Flag: model.PUBLIC},
|
{Key: conf.SSOLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.SSO, Flag: model.PUBLIC},
|
||||||
{Key: conf.SSOLoginplatform, Type: conf.TypeSelect, Options: "Github,Microsoft,Google,Dingtalk", Group: model.SSO, Flag: model.PUBLIC},
|
{Key: conf.SSOLoginplatform, Type: conf.TypeSelect, Options: "Casdoor,Github,Microsoft,Google,Dingtalk", Group: model.SSO, Flag: model.PUBLIC},
|
||||||
{Key: conf.SSOClientId, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
{Key: conf.SSOClientId, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
{Key: conf.SSOClientSecret, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
{Key: conf.SSOClientSecret, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.SSOOrganizationName, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.SSOApplicationName, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.SSOEndpointName, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.SSOJwtPublicKey, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
|
|
||||||
// qbittorrent settings
|
// qbittorrent settings
|
||||||
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
|
@ -14,6 +14,7 @@ const (
|
|||||||
SiteTitle = "site_title"
|
SiteTitle = "site_title"
|
||||||
Announcement = "announcement"
|
Announcement = "announcement"
|
||||||
AllowIndexed = "allow_indexed"
|
AllowIndexed = "allow_indexed"
|
||||||
|
AllowMounted = "allow_mounted"
|
||||||
|
|
||||||
Logo = "logo"
|
Logo = "logo"
|
||||||
Favicon = "favicon"
|
Favicon = "favicon"
|
||||||
@ -55,10 +56,14 @@ const (
|
|||||||
IndexProgress = "index_progress"
|
IndexProgress = "index_progress"
|
||||||
|
|
||||||
//SSO
|
//SSO
|
||||||
SSOClientId = "sso_client_id"
|
SSOClientId = "sso_client_id"
|
||||||
SSOClientSecret = "sso_client_secret"
|
SSOClientSecret = "sso_client_secret"
|
||||||
SSOLoginEnabled = "sso_login_enabled"
|
SSOLoginEnabled = "sso_login_enabled"
|
||||||
SSOLoginplatform = "sso_login_platform"
|
SSOLoginplatform = "sso_login_platform"
|
||||||
|
SSOOrganizationName = "sso_organization_name"
|
||||||
|
SSOApplicationName = "sso_application_name"
|
||||||
|
SSOEndpointName = "sso_endpoint_name"
|
||||||
|
SSOJwtPublicKey = "sso_jwt_public_key"
|
||||||
|
|
||||||
// qbittorrent
|
// qbittorrent
|
||||||
QbittorrentUrl = "qbittorrent_url"
|
QbittorrentUrl = "qbittorrent_url"
|
||||||
|
@ -17,12 +17,13 @@ type LinkArgs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Link struct {
|
type Link struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Header http.Header `json:"header"` // needed header
|
Header http.Header `json:"header"` // needed header
|
||||||
Data io.ReadCloser // return file reader directly
|
Data io.ReadCloser // return file reader directly
|
||||||
Status int // status maybe 200 or 206, etc
|
Status int // status maybe 200 or 206, etc
|
||||||
FilePath *string // local file, return the filepath
|
FilePath *string // local file, return the filepath
|
||||||
Expiration *time.Duration // url expiration time
|
Expiration *time.Duration // url expiration time
|
||||||
|
Handle func(w http.ResponseWriter, r *http.Request) error `json:"-"` // custom handler
|
||||||
}
|
}
|
||||||
|
|
||||||
type OtherArgs struct {
|
type OtherArgs struct {
|
||||||
|
@ -134,6 +134,7 @@ func (c *client) AddFromLink(link string, savePath string, id string) error {
|
|||||||
addField("urls", link)
|
addField("urls", link)
|
||||||
addField("savepath", savePath)
|
addField("savepath", savePath)
|
||||||
addField("tags", "alist-"+id)
|
addField("tags", "alist-"+id)
|
||||||
|
addField("autoTMM", "false")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -72,17 +72,21 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
|||||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename)))
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename)))
|
||||||
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
|
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
|
||||||
return nil
|
return nil
|
||||||
|
} else if link.Handle != nil {
|
||||||
|
return link.Handle(w, r)
|
||||||
} else {
|
} else {
|
||||||
req, err := http.NewRequest(r.Method, link.URL, nil)
|
req, err := http.NewRequest(r.Method, link.URL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// client header
|
||||||
for h, val := range r.Header {
|
for h, val := range r.Header {
|
||||||
if utils.SliceContains(conf.SlicesMap[conf.ProxyIgnoreHeaders], strings.ToLower(h)) {
|
if utils.SliceContains(conf.SlicesMap[conf.ProxyIgnoreHeaders], strings.ToLower(h)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
req.Header[h] = val
|
req.Header[h] = val
|
||||||
}
|
}
|
||||||
|
// needed header
|
||||||
for h, val := range link.Header {
|
for h, val := range link.Header {
|
||||||
req.Header[h] = val
|
req.Header[h] = val
|
||||||
}
|
}
|
||||||
|
@ -150,11 +150,12 @@ func FsRecursiveMove(c *gin.Context) {
|
|||||||
for !movingFiles.IsEmpty() {
|
for !movingFiles.IsEmpty() {
|
||||||
|
|
||||||
movingFile := movingFiles.Pop()
|
movingFile := movingFiles.Pop()
|
||||||
movingFilePath := fmt.Sprintf("%s/%s", filePathMap[movingFile], movingFile.GetName())
|
movingFilePath := filePathMap[movingFile]
|
||||||
|
movingFileName := fmt.Sprintf("%s/%s", movingFilePath, movingFile.GetName())
|
||||||
if movingFile.IsDir() {
|
if movingFile.IsDir() {
|
||||||
// directory, recursive move
|
// directory, recursive move
|
||||||
subFilePath := movingFilePath
|
subFilePath := movingFileName
|
||||||
subFiles, err := fs.List(c, subFilePath, &fs.ListArgs{Refresh: true})
|
subFiles, err := fs.List(c, movingFileName, &fs.ListArgs{Refresh: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
@ -171,7 +172,7 @@ func FsRecursiveMove(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// move
|
// move
|
||||||
err := fs.Move(c, movingFilePath, dstDir, movingFiles.IsEmpty())
|
err := fs.Move(c, movingFileName, dstDir, movingFiles.IsEmpty())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/db"
|
"github.com/alist-org/alist/v3/internal/db"
|
||||||
@ -47,6 +48,11 @@ func SSOLoginRedirect(c *gin.Context) {
|
|||||||
urlValues.Add("scope", "openid")
|
urlValues.Add("scope", "openid")
|
||||||
urlValues.Add("prompt", "consent")
|
urlValues.Add("prompt", "consent")
|
||||||
urlValues.Add("response_type", "code")
|
urlValues.Add("response_type", "code")
|
||||||
|
case "Casdoor":
|
||||||
|
endpoint := strings.TrimSuffix(setting.GetStr(conf.SSOEndpointName), "/")
|
||||||
|
r_url = endpoint + "/login/oauth/authorize?"
|
||||||
|
urlValues.Add("scope", "read")
|
||||||
|
urlValues.Add("state", endpoint)
|
||||||
default:
|
default:
|
||||||
common.ErrorStrResp(c, "invalid platform", 400)
|
common.ErrorStrResp(c, "invalid platform", 400)
|
||||||
return
|
return
|
||||||
@ -94,6 +100,11 @@ func SSOLoginCallback(c *gin.Context) {
|
|||||||
url2 = "https://api.dingtalk.com/v1.0/contact/users/me"
|
url2 = "https://api.dingtalk.com/v1.0/contact/users/me"
|
||||||
authstring = "authCode"
|
authstring = "authCode"
|
||||||
idstring = "unionId"
|
idstring = "unionId"
|
||||||
|
case "Casdoor":
|
||||||
|
endpoint := strings.TrimSuffix(setting.GetStr(conf.SSOEndpointName), "/")
|
||||||
|
url1 = endpoint + "/api/login/oauth/access_token"
|
||||||
|
url2 = endpoint + "/account"
|
||||||
|
authstring = "code"
|
||||||
default:
|
default:
|
||||||
common.ErrorStrResp(c, "invalid platform", 400)
|
common.ErrorStrResp(c, "invalid platform", 400)
|
||||||
return
|
return
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/sign"
|
"github.com/alist-org/alist/v3/internal/sign"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
@ -237,6 +238,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
|
|||||||
}
|
}
|
||||||
err = common.Proxy(w, r, link, fi)
|
err = common.Proxy(w, r, link, fi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Errorf("webdav proxy error: %+v", err)
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
} else if storage.GetStorage().WebdavProxy() && downProxyUrl != "" {
|
} else if storage.GetStorage().WebdavProxy() && downProxyUrl != "" {
|
||||||
|
Reference in New Issue
Block a user