Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
be62d64dba | |||
112363031a | |||
48dc3552a6 | |||
663814c9ef | |||
bd892e6a63 | |||
4fd2c09845 | |||
0eab31bdf5 | |||
c6af22b97e | |||
b2a5110672 | |||
c628992ea6 | |||
c65d868e09 | |||
aeb48b2ecc | |||
cefec1a663 | |||
e7ad830aa8 | |||
b27eed265a | |||
3abe26473c | |||
023107226c | |||
8b109cfe40 | |||
b48e97d406 |
2
.github/workflows/issue_question.yml
vendored
2
.github/workflows/issue_question.yml
vendored
@ -17,4 +17,4 @@ jobs:
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, please input issue by template and add detail. Issues labeled by `question` will be closed if no activities in 7 days.
|
||||
你好 @${{ github.event.issue.user.login }},请按照issue模板填写, 并详细说明问题/复现步骤/复现链接/实现思路或提供更多信息等, 7天内未回复issue自动关闭。
|
||||
你好 @${{ github.event.issue.user.login }},请按照issue模板填写, 并详细说明问题/日志记录/复现步骤/复现链接/实现思路或提供更多信息等, 7天内未回复issue自动关闭。
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: prerelease
|
||||
- name: Prerelease
|
||||
uses: irongut/EditRelease@v1.2.0
|
||||
with:
|
||||
token: ${{ secrets.MY_TOKEN }}
|
||||
@ -41,14 +41,14 @@ jobs:
|
||||
run: |
|
||||
bash build.sh release
|
||||
|
||||
- name: prerelease
|
||||
- name: Release latest
|
||||
uses: irongut/EditRelease@v1.2.0
|
||||
with:
|
||||
token: ${{ secrets.MY_TOKEN }}
|
||||
id: ${{ github.event.release.id }}
|
||||
prerelease: false
|
||||
|
||||
- name: Release
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: build/compress/*
|
||||
|
@ -22,6 +22,8 @@ var Cancel2FACmd = &cobra.Command{
|
||||
err := op.Cancel2FAByUser(admin)
|
||||
if err != nil {
|
||||
utils.Log.Errorf("failed to cancel 2FA: %+v", err)
|
||||
} else {
|
||||
utils.Log.Info("2FA canceled")
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -44,7 +44,11 @@ func (d *Pan115) List(ctx context.Context, dir model.Obj, args model.ListArgs) (
|
||||
}
|
||||
|
||||
func (d *Pan115) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
downloadInfo, err := d.client.Download(file.(driver115.File).PickCode)
|
||||
downloadInfo, err := d.client.
|
||||
SetUserAgent(driver115.UA115Browser).
|
||||
Download(file.(driver115.File).PickCode)
|
||||
// recover for upload
|
||||
d.client.SetUserAgent(driver115.UA115Desktop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -20,8 +20,7 @@ type AliyundriveOpen struct {
|
||||
Addition
|
||||
base string
|
||||
|
||||
AccessToken string
|
||||
DriveId string
|
||||
DriveId string
|
||||
}
|
||||
|
||||
func (d *AliyundriveOpen) Config() driver.Config {
|
||||
@ -33,10 +32,6 @@ func (d *AliyundriveOpen) GetAddition() driver.Additional {
|
||||
}
|
||||
|
||||
func (d *AliyundriveOpen) Init(ctx context.Context) error {
|
||||
err := d.refreshToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := d.request("/adrive/v1.0/user/getDriveInfo", http.MethodPost, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -15,6 +15,7 @@ type Addition struct {
|
||||
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"`
|
||||
RemoveWay string `json:"remove_way" required:"true" type:"select" options:"trash,delete"`
|
||||
InternalUpload bool `json:"internal_upload" help:"If you are using Aliyun ECS in Beijing, you can turn it on to boost the upload speed"`
|
||||
AccessToken string
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/mediatrack"
|
||||
_ "github.com/alist-org/alist/v3/drivers/mega"
|
||||
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
||||
_ "github.com/alist-org/alist/v3/drivers/onedrive_app"
|
||||
_ "github.com/alist-org/alist/v3/drivers/pikpak"
|
||||
_ "github.com/alist-org/alist/v3/drivers/pikpak_share"
|
||||
_ "github.com/alist-org/alist/v3/drivers/quark"
|
||||
@ -35,6 +36,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/terabox"
|
||||
_ "github.com/alist-org/alist/v3/drivers/thunder"
|
||||
_ "github.com/alist-org/alist/v3/drivers/trainbit"
|
||||
_ "github.com/alist-org/alist/v3/drivers/url_tree"
|
||||
_ "github.com/alist-org/alist/v3/drivers/uss"
|
||||
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
||||
_ "github.com/alist-org/alist/v3/drivers/webdav"
|
||||
|
@ -2,25 +2,30 @@ package baidu_share
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Xhofe/go-cache"
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type BaiduShare struct {
|
||||
model.Storage
|
||||
Addition
|
||||
config map[string]string
|
||||
dlinkCache cache.ICache[string]
|
||||
client *resty.Client
|
||||
info struct {
|
||||
Root string
|
||||
Seckey string
|
||||
Shareid string
|
||||
Uk string
|
||||
}
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Config() driver.Config {
|
||||
@ -34,115 +39,208 @@ func (d *BaiduShare) GetAddition() driver.Additional {
|
||||
func (d *BaiduShare) Init(ctx context.Context) error {
|
||||
// TODO login / refresh token
|
||||
//op.MustSaveDriverStorage(d)
|
||||
d.config = map[string]string{}
|
||||
d.dlinkCache = cache.NewMemCache(cache.WithClearInterval[string](time.Duration(d.CacheExpiration) * time.Minute))
|
||||
return nil
|
||||
d.client = resty.New().
|
||||
SetBaseURL("https://pan.baidu.com").
|
||||
SetHeader("User-Agent", "netdisk").
|
||||
SetCookie(&http.Cookie{Name: "BDUSS", Value: d.BDUSS}).
|
||||
SetCookie(&http.Cookie{Name: "ndut_fmt"})
|
||||
respJson := struct {
|
||||
Errno int64 `json:"errno"`
|
||||
Data struct {
|
||||
List [1]struct {
|
||||
Path string `json:"path"`
|
||||
} `json:"list"`
|
||||
Uk json.Number `json:"uk"`
|
||||
Shareid json.Number `json:"shareid"`
|
||||
Seckey string `json:"seckey"`
|
||||
} `json:"data"`
|
||||
}{}
|
||||
resp, err := d.client.R().
|
||||
SetBody(url.Values{
|
||||
"pwd": {d.Pwd},
|
||||
"root": {"1"},
|
||||
"shorturl": {d.Surl},
|
||||
}.Encode()).
|
||||
SetResult(&respJson).
|
||||
Post("share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
|
||||
if err == nil {
|
||||
if resp.IsSuccess() && respJson.Errno == 0 {
|
||||
d.info.Root = path.Dir(respJson.Data.List[0].Path)
|
||||
d.info.Seckey = respJson.Data.Seckey
|
||||
d.info.Shareid = respJson.Data.Shareid.String()
|
||||
d.info.Uk = respJson.Data.Uk.String()
|
||||
} else {
|
||||
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Drop(ctx context.Context) error {
|
||||
d.dlinkCache.Clear()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *BaiduShare) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
// TODO return the files list
|
||||
body := url.Values{
|
||||
"shorturl": {d.Surl},
|
||||
"dir": {dir.GetPath()},
|
||||
"root": {"0"},
|
||||
"pwd": {d.Pwd},
|
||||
"num": {"1000"},
|
||||
"order": {"time"},
|
||||
// TODO return the files list, required
|
||||
reqDir := dir.GetPath()
|
||||
isRoot := "0"
|
||||
if reqDir == d.RootFolderPath {
|
||||
reqDir = path.Join(d.info.Root, reqDir)
|
||||
}
|
||||
if body.Get("dir") == "" || body.Get("dir") == d.config["root"] {
|
||||
body.Set("root", "1")
|
||||
if reqDir == d.info.Root {
|
||||
isRoot = "1"
|
||||
}
|
||||
res := []model.Obj{}
|
||||
objs := []model.Obj{}
|
||||
var err error
|
||||
var page int64 = 1
|
||||
var page uint64 = 1
|
||||
more := true
|
||||
for more {
|
||||
body.Set("page", strconv.FormatInt(page, 10))
|
||||
req := base.RestyClient.R().
|
||||
SetCookies([]*http.Cookie{{Name: "BDUSS", Value: d.BDUSS}}).
|
||||
SetBody(body.Encode())
|
||||
resp, e := req.Post("https://pan.baidu.com/share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
|
||||
for more && err == nil {
|
||||
respJson := struct {
|
||||
Errno int64 `json:"errno"`
|
||||
Data struct {
|
||||
More bool `json:"has_more"`
|
||||
List []struct {
|
||||
Fsid json.Number `json:"fs_id"`
|
||||
Isdir json.Number `json:"isdir"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"server_filename"`
|
||||
Mtime json.Number `json:"server_mtime"`
|
||||
Size json.Number `json:"size"`
|
||||
} `json:"list"`
|
||||
} `json:"data"`
|
||||
}{}
|
||||
resp, e := d.client.R().
|
||||
SetBody(url.Values{
|
||||
"dir": {reqDir},
|
||||
"num": {"1000"},
|
||||
"order": {"time"},
|
||||
"page": {fmt.Sprint(page)},
|
||||
"pwd": {d.Pwd},
|
||||
"root": {isRoot},
|
||||
"shorturl": {d.Surl},
|
||||
}.Encode()).
|
||||
SetResult(&respJson).
|
||||
Post("share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
|
||||
err = e
|
||||
jsonresp := jsonResp{}
|
||||
if err == nil {
|
||||
err = base.RestyClient.JSONUnmarshal(resp.Body(), &jsonresp)
|
||||
}
|
||||
if err == nil && jsonresp.Errno == 0 {
|
||||
more = jsonresp.Data.More
|
||||
page += 1
|
||||
for _, v := range jsonresp.Data.List {
|
||||
size, _ := v.Size.Int64()
|
||||
mtime, _ := v.Time.Int64()
|
||||
res = append(res, &model.Object{
|
||||
ID: v.ID.String(),
|
||||
Path: v.Path,
|
||||
Name: v.Name,
|
||||
Size: size,
|
||||
Modified: time.Unix(mtime, 0),
|
||||
IsFolder: v.Dir.String() == "1",
|
||||
})
|
||||
d.dlinkCache.Set(v.Path, v.Dlink, cache.WithEx[string](time.Duration(d.CacheExpiration/2)*time.Minute))
|
||||
if resp.IsSuccess() && respJson.Errno == 0 {
|
||||
page++
|
||||
more = respJson.Data.More
|
||||
for _, v := range respJson.Data.List {
|
||||
size, _ := v.Size.Int64()
|
||||
mtime, _ := v.Mtime.Int64()
|
||||
objs = append(objs, &model.Object{
|
||||
ID: v.Fsid.String(),
|
||||
Path: v.Path,
|
||||
Name: v.Name,
|
||||
Size: size,
|
||||
Modified: time.Unix(mtime, 0),
|
||||
IsFolder: v.Isdir.String() == "1",
|
||||
})
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||
}
|
||||
if len(res) > 0 && body.Get("root") == "1" {
|
||||
d.config["root"] = path.Dir(res[0].GetPath())
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("errno:%d", jsonresp.Errno)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return res, err
|
||||
return objs, err
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
// TODO return link of file
|
||||
var err error
|
||||
req := base.RestyClient.R().SetCookies([]*http.Cookie{{Name: "BDUSS", Value: d.BDUSS}})
|
||||
url, found := d.dlinkCache.Get(file.GetPath())
|
||||
if !found {
|
||||
_, err = d.List(ctx, &model.Object{Path: path.Dir(file.GetPath())}, model.ListArgs{})
|
||||
url, found = d.dlinkCache.Get(file.GetPath())
|
||||
if err == nil && !found {
|
||||
err = errs.NotSupport
|
||||
// TODO return link of file, required
|
||||
link := model.Link{Header: d.client.Header}
|
||||
sign := ""
|
||||
stamp := ""
|
||||
signJson := struct {
|
||||
Errno int64 `json:"errno"`
|
||||
Data struct {
|
||||
Stamp json.Number `json:"timestamp"`
|
||||
Sign string `json:"sign"`
|
||||
} `json:"data"`
|
||||
}{}
|
||||
resp, err := d.client.R().
|
||||
SetQueryParam("surl", d.Surl).
|
||||
SetResult(&signJson).
|
||||
Get("share/tplconfig?fields=sign,timestamp&channel=chunlei&web=1&app_id=250528&clienttype=0")
|
||||
if err == nil {
|
||||
if resp.IsSuccess() && signJson.Errno == 0 {
|
||||
stamp = signJson.Data.Stamp.String()
|
||||
sign = signJson.Data.Sign
|
||||
} else {
|
||||
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||
}
|
||||
}
|
||||
return &model.Link{URL: url, Header: req.Header}, err
|
||||
if err == nil {
|
||||
respJson := struct {
|
||||
Errno int64 `json:"errno"`
|
||||
List [1]struct {
|
||||
Dlink string `json:"dlink"`
|
||||
} `json:"list"`
|
||||
}{}
|
||||
resp, err = d.client.R().
|
||||
SetQueryParam("sign", sign).
|
||||
SetQueryParam("timestamp", stamp).
|
||||
SetBody(url.Values{
|
||||
"encrypt": {"0"},
|
||||
"extra": {fmt.Sprintf(`{"sekey":"%s"}`, d.info.Seckey)},
|
||||
"fid_list": {fmt.Sprintf("[%s]", file.GetID())},
|
||||
"primaryid": {d.info.Shareid},
|
||||
"product": {"share"},
|
||||
"type": {"nolimit"},
|
||||
"uk": {d.info.Uk},
|
||||
}.Encode()).
|
||||
SetResult(&respJson).
|
||||
Post("api/sharedownload?app_id=250528&channel=chunlei&clienttype=12&web=1")
|
||||
if err == nil {
|
||||
if resp.IsSuccess() && respJson.Errno == 0 && respJson.List[0].Dlink != "" {
|
||||
link.URL = respJson.List[0].Dlink
|
||||
} else {
|
||||
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
resp, err = d.client.R().
|
||||
SetDoNotParseResponse(true).
|
||||
Get(link.URL)
|
||||
if err == nil {
|
||||
defer resp.RawBody().Close()
|
||||
if resp.IsError() {
|
||||
byt, _ := io.ReadAll(resp.RawBody())
|
||||
err = fmt.Errorf(" %s; %s; ", resp.Status(), byt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &link, err
|
||||
}
|
||||
|
||||
func (d *BaiduShare) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
// TODO create folder
|
||||
// TODO create folder, optional
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO move obj
|
||||
// TODO move obj, optional
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
// TODO rename obj
|
||||
// TODO rename obj, optional
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO copy obj
|
||||
// TODO copy obj, optional
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Remove(ctx context.Context, obj model.Obj) error {
|
||||
// TODO remove obj
|
||||
// TODO remove obj, optional
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
// TODO upload file
|
||||
// TODO upload file, optional
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
|
@ -17,14 +17,17 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "BaiduShare",
|
||||
LocalSort: true,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: true,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "",
|
||||
Name: "BaiduShare",
|
||||
LocalSort: true,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: true,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "/",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -1,19 +1 @@
|
||||
package baidu_share
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type jsonResp struct {
|
||||
Errno int64 `json:"errno"`
|
||||
Data struct {
|
||||
More bool `json:"has_more"`
|
||||
List []struct {
|
||||
ID json.Number `json:"fs_id"`
|
||||
Dir json.Number `json:"isdir"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"server_filename"`
|
||||
Time json.Number `json:"server_mtime"`
|
||||
Size json.Number `json:"size"`
|
||||
Dlink string `json:"dlink"`
|
||||
} `json:"list"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
@ -2,15 +2,15 @@ package cloudreve
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"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/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type Cloudreve struct {
|
||||
|
@ -1,8 +1,9 @@
|
||||
package cloudreve
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type Resp struct {
|
||||
@ -52,3 +53,8 @@ func objectToObj(f Object) *model.Object {
|
||||
IsFolder: f.Type == "dir",
|
||||
}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
LoginCaptcha bool `json:"loginCaptcha"`
|
||||
CaptchaType string `json:"captcha_type"`
|
||||
}
|
||||
|
@ -1,13 +1,19 @@
|
||||
package cloudreve
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/pkg/cookie"
|
||||
"github.com/go-resty/resty/v2"
|
||||
json "github.com/json-iterator/go"
|
||||
"net/http"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
// do others that not defined in Driver interface
|
||||
@ -72,13 +78,57 @@ func (d *Cloudreve) request(method string, path string, callback base.ReqCallbac
|
||||
}
|
||||
|
||||
func (d *Cloudreve) login() error {
|
||||
return d.request(http.MethodPost, loginPath, func(req *resty.Request) {
|
||||
var siteConfig Config
|
||||
err := d.request(http.MethodGet, "/site/config", nil, &siteConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
err = d.doLogin(siteConfig.LoginCaptcha)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if err != nil && err.Error() != "CAPTCHA not match." {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Cloudreve) doLogin(needCaptcha bool) error {
|
||||
var captchaCode string
|
||||
var err error
|
||||
if needCaptcha {
|
||||
var captcha string
|
||||
err = d.request(http.MethodGet, "/site/captcha", nil, &captcha)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(captcha) == 0 {
|
||||
return errors.New("can not get captcha")
|
||||
}
|
||||
i := strings.Index(captcha, ",")
|
||||
dec := base64.NewDecoder(base64.StdEncoding, strings.NewReader(captcha[i+1:]))
|
||||
vRes, err := base.RestyClient.R().SetMultipartField(
|
||||
"image", "validateCode.png", "image/png", dec).
|
||||
Post(setting.GetStr(conf.OcrApi))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
|
||||
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
|
||||
}
|
||||
captchaCode = jsoniter.Get(vRes.Body(), "result").ToString()
|
||||
}
|
||||
var resp Resp
|
||||
err = d.request(http.MethodPost, loginPath, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"username": d.Addition.Username,
|
||||
"Password": d.Addition.Password,
|
||||
"captchaCode": "",
|
||||
"captchaCode": captchaCode,
|
||||
})
|
||||
}, nil)
|
||||
}, &resp)
|
||||
return err
|
||||
}
|
||||
|
||||
func convertSrc(obj model.Obj) map[string]interface{} {
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
stdpath "path"
|
||||
@ -68,7 +67,7 @@ func (d *Local) GetAddition() driver.Additional {
|
||||
|
||||
func (d *Local) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
fullPath := dir.GetPath()
|
||||
rawFiles, err := ioutil.ReadDir(fullPath)
|
||||
rawFiles, err := readDir(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ package local
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||
)
|
||||
|
||||
func isSymlinkDir(f fs.FileInfo, path string) bool {
|
||||
@ -39,3 +41,17 @@ func GetSnapshot(videoPath string, frameNum int) (imgData *bytes.Buffer, err err
|
||||
}
|
||||
return srcBuf, nil
|
||||
}
|
||||
|
||||
func readDir(dirname string) ([]fs.FileInfo, error) {
|
||||
f, err := os.Open(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := f.Readdir(-1)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
|
||||
return list, nil
|
||||
}
|
||||
|
160
drivers/onedrive_app/driver.go
Normal file
160
drivers/onedrive_app/driver.go
Normal file
@ -0,0 +1,160 @@
|
||||
package onedrive_app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type OnedriveAPP struct {
|
||||
model.Storage
|
||||
Addition
|
||||
AccessToken string
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Init(ctx context.Context) error {
|
||||
if d.ChunkSize < 1 {
|
||||
d.ChunkSize = 5
|
||||
}
|
||||
return d.accessToken()
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
files, err := d.getFiles(dir.GetPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
||||
return fileToObj(src, dir.GetID()), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
f, err := d.GetFile(file.GetPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if f.File == nil {
|
||||
return nil, errs.NotFile
|
||||
}
|
||||
return &model.Link{
|
||||
URL: f.Url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
url := d.GetMetaUrl(false, parentDir.GetPath()) + "/children"
|
||||
data := base.Json{
|
||||
"name": dirName,
|
||||
"folder": base.Json{},
|
||||
"@microsoft.graph.conflictBehavior": "rename",
|
||||
}
|
||||
_, err := d.Request(url, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(data)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
parentPath := ""
|
||||
if dstDir.GetID() == "" {
|
||||
parentPath = dstDir.GetPath()
|
||||
if utils.PathEqual(parentPath, "/") {
|
||||
parentPath = path.Join("/drive/root", parentPath)
|
||||
} else {
|
||||
parentPath = path.Join("/drive/root:/", parentPath)
|
||||
}
|
||||
}
|
||||
data := base.Json{
|
||||
"parentReference": base.Json{
|
||||
"id": dstDir.GetID(),
|
||||
"path": parentPath,
|
||||
},
|
||||
"name": srcObj.GetName(),
|
||||
}
|
||||
url := d.GetMetaUrl(false, srcObj.GetPath())
|
||||
_, err := d.Request(url, http.MethodPatch, func(req *resty.Request) {
|
||||
req.SetBody(data)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
var parentID string
|
||||
if o, ok := srcObj.(*Object); ok {
|
||||
parentID = o.ParentID
|
||||
} else {
|
||||
return fmt.Errorf("srcObj is not Object")
|
||||
}
|
||||
if parentID == "" {
|
||||
parentID = "root"
|
||||
}
|
||||
data := base.Json{
|
||||
"parentReference": base.Json{
|
||||
"id": parentID,
|
||||
},
|
||||
"name": newName,
|
||||
}
|
||||
url := d.GetMetaUrl(false, srcObj.GetPath())
|
||||
_, err := d.Request(url, http.MethodPatch, func(req *resty.Request) {
|
||||
req.SetBody(data)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
dst, err := d.GetFile(dstDir.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"parentReference": base.Json{
|
||||
"driveId": dst.ParentReference.DriveId,
|
||||
"id": dst.Id,
|
||||
},
|
||||
"name": srcObj.GetName(),
|
||||
}
|
||||
url := d.GetMetaUrl(false, srcObj.GetPath()) + "/copy"
|
||||
_, err = d.Request(url, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(data)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Remove(ctx context.Context, obj model.Obj) error {
|
||||
url := d.GetMetaUrl(false, obj.GetPath())
|
||||
_, err := d.Request(url, http.MethodDelete, nil, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
var err error
|
||||
if stream.GetSize() <= 4*1024*1024 {
|
||||
err = d.upSmall(ctx, dstDir, stream)
|
||||
} else {
|
||||
err = d.upBig(ctx, dstDir, stream, up)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*OnedriveAPP)(nil)
|
28
drivers/onedrive_app/meta.go
Normal file
28
drivers/onedrive_app/meta.go
Normal file
@ -0,0 +1,28 @@
|
||||
package onedrive_app
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootPath
|
||||
Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"`
|
||||
ClientID string `json:"client_id" required:"true"`
|
||||
ClientSecret string `json:"client_secret" required:"true"`
|
||||
TenantID string `json:"tenant_id"`
|
||||
Email string `json:"email"`
|
||||
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "OnedriveAPP",
|
||||
LocalSort: true,
|
||||
DefaultRoot: "/",
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &OnedriveAPP{}
|
||||
})
|
||||
}
|
74
drivers/onedrive_app/types.go
Normal file
74
drivers/onedrive_app/types.go
Normal file
@ -0,0 +1,74 @@
|
||||
package onedrive_app
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type Host struct {
|
||||
Oauth string
|
||||
Api string
|
||||
}
|
||||
|
||||
type TokenErr struct {
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
}
|
||||
|
||||
type RespErr struct {
|
||||
Error struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
LastModifiedDateTime time.Time `json:"lastModifiedDateTime"`
|
||||
Url string `json:"@microsoft.graph.downloadUrl"`
|
||||
File *struct {
|
||||
MimeType string `json:"mimeType"`
|
||||
} `json:"file"`
|
||||
Thumbnails []struct {
|
||||
Medium struct {
|
||||
Url string `json:"url"`
|
||||
} `json:"medium"`
|
||||
} `json:"thumbnails"`
|
||||
ParentReference struct {
|
||||
DriveId string `json:"driveId"`
|
||||
} `json:"parentReference"`
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
model.ObjThumb
|
||||
ParentID string
|
||||
}
|
||||
|
||||
func fileToObj(f File, parentID string) *Object {
|
||||
thumb := ""
|
||||
if len(f.Thumbnails) > 0 {
|
||||
thumb = f.Thumbnails[0].Medium.Url
|
||||
}
|
||||
return &Object{
|
||||
ObjThumb: model.ObjThumb{
|
||||
Object: model.Object{
|
||||
ID: f.Id,
|
||||
Name: f.Name,
|
||||
Size: f.Size,
|
||||
Modified: f.LastModifiedDateTime,
|
||||
IsFolder: f.File == nil,
|
||||
},
|
||||
Thumbnail: model.Thumbnail{Thumbnail: thumb},
|
||||
//Url: model.Url{Url: f.Url},
|
||||
},
|
||||
ParentID: parentID,
|
||||
}
|
||||
}
|
||||
|
||||
type Files struct {
|
||||
Value []File `json:"value"`
|
||||
NextLink string `json:"@odata.nextLink"`
|
||||
}
|
196
drivers/onedrive_app/util.go
Normal file
196
drivers/onedrive_app/util.go
Normal file
@ -0,0 +1,196 @@
|
||||
package onedrive_app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
stdpath "path"
|
||||
"strconv"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"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"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var onedriveHostMap = map[string]Host{
|
||||
"global": {
|
||||
Oauth: "https://login.microsoftonline.com",
|
||||
Api: "https://graph.microsoft.com",
|
||||
},
|
||||
"cn": {
|
||||
Oauth: "https://login.chinacloudapi.cn",
|
||||
Api: "https://microsoftgraph.chinacloudapi.cn",
|
||||
},
|
||||
"us": {
|
||||
Oauth: "https://login.microsoftonline.us",
|
||||
Api: "https://graph.microsoft.us",
|
||||
},
|
||||
"de": {
|
||||
Oauth: "https://login.microsoftonline.de",
|
||||
Api: "https://graph.microsoft.de",
|
||||
},
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) GetMetaUrl(auth bool, path string) string {
|
||||
host, _ := onedriveHostMap[d.Region]
|
||||
path = utils.EncodePath(path, true)
|
||||
if auth {
|
||||
return host.Oauth
|
||||
}
|
||||
if path == "/" || path == "\\" {
|
||||
return fmt.Sprintf("%s/v1.0/users/%s/drive/root", host.Api, d.Email)
|
||||
}
|
||||
return fmt.Sprintf("%s/v1.0/users/%s/drive/root:%s:", host.Api, d.Email, path)
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) accessToken() error {
|
||||
var err error
|
||||
for i := 0; i < 3; i++ {
|
||||
err = d._accessToken()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) _accessToken() error {
|
||||
url := d.GetMetaUrl(true, "") + "/" + d.TenantID + "/oauth2/token"
|
||||
var resp base.TokenResp
|
||||
var e TokenErr
|
||||
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": d.ClientID,
|
||||
"client_secret": d.ClientSecret,
|
||||
"resource": "https://graph.microsoft.com/",
|
||||
"scope": "https://graph.microsoft.com/.default",
|
||||
}).Post(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Error != "" {
|
||||
return fmt.Errorf("%s", e.ErrorDescription)
|
||||
}
|
||||
if resp.AccessToken == "" {
|
||||
return errs.EmptyToken
|
||||
}
|
||||
d.AccessToken = resp.AccessToken
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
var e RespErr
|
||||
req.SetError(&e)
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Error.Code != "" {
|
||||
if e.Error.Code == "InvalidAuthenticationToken" {
|
||||
err = d.accessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Request(url, method, callback, resp)
|
||||
}
|
||||
return nil, errors.New(e.Error.Message)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) getFiles(path string) ([]File, error) {
|
||||
var res []File
|
||||
nextLink := d.GetMetaUrl(false, path) + "/children?$top=5000&$expand=thumbnails($select=medium)&$select=id,name,size,lastModifiedDateTime,content.downloadUrl,file,parentReference"
|
||||
for nextLink != "" {
|
||||
var files Files
|
||||
_, err := d.Request(nextLink, http.MethodGet, nil, &files)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, files.Value...)
|
||||
nextLink = files.NextLink
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) GetFile(path string) (*File, error) {
|
||||
var file File
|
||||
u := d.GetMetaUrl(false, path)
|
||||
_, err := d.Request(u, http.MethodGet, nil, &file)
|
||||
return &file, err
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) upSmall(ctx context.Context, dstDir model.Obj, stream model.FileStreamer) error {
|
||||
url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetPath(), stream.GetName())) + "/content"
|
||||
data, err := io.ReadAll(stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.Request(url, http.MethodPut, func(req *resty.Request) {
|
||||
req.SetBody(data).SetContext(ctx)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) upBig(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetPath(), stream.GetName())) + "/createUploadSession"
|
||||
res, err := d.Request(url, http.MethodPost, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
|
||||
var finish int64 = 0
|
||||
DEFAULT := d.ChunkSize * 1024 * 1024
|
||||
for finish < stream.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
log.Debugf("upload: %d", finish)
|
||||
var byteSize int64 = DEFAULT
|
||||
left := stream.GetSize() - finish
|
||||
if left < DEFAULT {
|
||||
byteSize = left
|
||||
}
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("PUT", uploadUrl, bytes.NewBuffer(byteData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||
finish += byteSize
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if res.StatusCode != 201 && res.StatusCode != 202 {
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return errors.New(string(data))
|
||||
}
|
||||
res.Body.Close()
|
||||
up(int(finish * 100 / stream.GetSize()))
|
||||
}
|
||||
return nil
|
||||
}
|
@ -15,10 +15,10 @@ import (
|
||||
)
|
||||
|
||||
type SMB struct {
|
||||
lastConnTime int64
|
||||
model.Storage
|
||||
Addition
|
||||
fs *smb2.Share
|
||||
lastConnTime int64
|
||||
fs *smb2.Share
|
||||
}
|
||||
|
||||
func (d *SMB) Config() driver.Config {
|
||||
|
@ -119,7 +119,7 @@ func (d *Trainbit) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
||||
query := &url.Values{}
|
||||
query.Add("q", strings.Split(dstDir.GetID(), "_")[1])
|
||||
query.Add("guid", guid)
|
||||
query.Add("name", url.QueryEscape(local2provider(stream.GetName(), false)))
|
||||
query.Add("name", url.QueryEscape(local2provider(stream.GetName(), false) + "."))
|
||||
endpoint.RawQuery = query.Encode()
|
||||
var total int64
|
||||
total = 0
|
||||
|
@ -1,6 +1,7 @@
|
||||
package trainbit
|
||||
|
||||
import (
|
||||
"html"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -88,36 +89,19 @@ func getToken(apiKey string, AUSHELLPORTAL string) (string, string, error) {
|
||||
}
|
||||
|
||||
func local2provider(filename string, isFolder bool) string {
|
||||
filename = strings.Replace(filename, "%", url.QueryEscape("%"), -1)
|
||||
filename = strings.Replace(filename, "/", url.QueryEscape("/"), -1)
|
||||
filename = strings.Replace(filename, ":", url.QueryEscape(":"), -1)
|
||||
filename = strings.Replace(filename, "*", url.QueryEscape("*"), -1)
|
||||
filename = strings.Replace(filename, "?", url.QueryEscape("?"), -1)
|
||||
filename = strings.Replace(filename, "\"", url.QueryEscape("\""), -1)
|
||||
filename = strings.Replace(filename, "<", url.QueryEscape("<"), -1)
|
||||
filename = strings.Replace(filename, ">", url.QueryEscape(">"), -1)
|
||||
filename = strings.Replace(filename, "|", url.QueryEscape("|"), -1)
|
||||
if isFolder {
|
||||
return filename
|
||||
}
|
||||
return strings.Join([]string{filename, ".delete_suffix."}, "")
|
||||
return filename + ".delete_suffix"
|
||||
}
|
||||
|
||||
func provider2local(filename string) string {
|
||||
index := strings.LastIndex(filename, ".delete_suffix.")
|
||||
filename = html.UnescapeString(filename)
|
||||
index := strings.LastIndex(filename, ".delete_suffix")
|
||||
if index != -1 {
|
||||
filename = filename[:index]
|
||||
}
|
||||
rawName := strings.Replace(filename, url.QueryEscape("/"), "/", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape(":"), ":", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("*"), "*", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("?"), "?", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("\""), "\"", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("<"), "<", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape(">"), ">", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("|"), "|", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("%"), "%", -1)
|
||||
return rawName
|
||||
return filename
|
||||
}
|
||||
|
||||
func parseRawFileObject(rawObject []any) ([]model.Obj, error) {
|
||||
@ -132,7 +116,7 @@ func parseRawFileObject(rawObject []any) ([]model.Obj, error) {
|
||||
if isFolder {
|
||||
name = object["name"].(string)
|
||||
} else {
|
||||
name = strings.Join([]string{object["name"].(string), object["ext"].(string)}, ".")
|
||||
name = object["name"].(string) + object["ext"].(string)
|
||||
}
|
||||
modified, err := time.Parse("2006/01/02 15:04:05", object["modified"].(string))
|
||||
if err != nil {
|
||||
|
79
drivers/url_tree/driver.go
Normal file
79
drivers/url_tree/driver.go
Normal file
@ -0,0 +1,79 @@
|
||||
package url_tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
stdpath "path"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Urls struct {
|
||||
model.Storage
|
||||
Addition
|
||||
root *Node
|
||||
}
|
||||
|
||||
func (d *Urls) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *Urls) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Urls) Init(ctx context.Context) error {
|
||||
node, err := BuildTree(d.UrlStructure, d.HeadSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
node.calSize()
|
||||
d.root = node
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Urls) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Urls) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||
node := GetNodeFromRootByPath(d.root, path)
|
||||
return nodeToObj(node, path)
|
||||
}
|
||||
|
||||
func (d *Urls) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
node := GetNodeFromRootByPath(d.root, dir.GetPath())
|
||||
log.Debugf("path: %s, node: %+v", dir.GetPath(), node)
|
||||
if node == nil {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
if node.isFile() {
|
||||
return nil, errs.NotFolder
|
||||
}
|
||||
return utils.SliceConvert(node.Children, func(node *Node) (model.Obj, error) {
|
||||
return nodeToObj(node, stdpath.Join(dir.GetPath(), node.Name))
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Urls) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
node := GetNodeFromRootByPath(d.root, file.GetPath())
|
||||
log.Debugf("path: %s, node: %+v", file.GetPath(), node)
|
||||
if node == nil {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
if node.isFile() {
|
||||
return &model.Link{
|
||||
URL: node.Url,
|
||||
}, nil
|
||||
}
|
||||
return nil, errs.NotFile
|
||||
}
|
||||
|
||||
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*Urls)(nil)
|
35
drivers/url_tree/meta.go
Normal file
35
drivers/url_tree/meta.go
Normal file
@ -0,0 +1,35 @@
|
||||
package url_tree
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
// Usually one of two
|
||||
// driver.RootPath
|
||||
// driver.RootID
|
||||
// define other
|
||||
UrlStructure string `json:"url_structure" type:"text" required:"true" default:"https://jsd.nn.ci/gh/alist-org/alist/README.md\nhttps://jsd.nn.ci/gh/alist-org/alist/README_cn.md\nfolder:\n CONTRIBUTING.md:1635:https://jsd.nn.ci/gh/alist-org/alist/CONTRIBUTING.md\n CODE_OF_CONDUCT.md:2093:https://jsd.nn.ci/gh/alist-org/alist/CODE_OF_CONDUCT.md" help:"structure:FolderName:\n [FileName:][FileSize:][Modified:]Url"`
|
||||
HeadSize bool `json:"head_size" type:"bool" default:"false" help:"Use head method to get file size, but it may be failed."`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "UrlTree",
|
||||
LocalSort: true,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: true,
|
||||
NoUpload: true,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "",
|
||||
CheckStatus: true,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Urls{}
|
||||
})
|
||||
}
|
46
drivers/url_tree/types.go
Normal file
46
drivers/url_tree/types.go
Normal file
@ -0,0 +1,46 @@
|
||||
package url_tree
|
||||
|
||||
// Node is a node in the folder tree
|
||||
type Node struct {
|
||||
Url string
|
||||
Name string
|
||||
Level int
|
||||
Modified int64
|
||||
Size int64
|
||||
Children []*Node
|
||||
}
|
||||
|
||||
func (node *Node) getByPath(paths []string) *Node {
|
||||
if len(paths) == 0 || node == nil {
|
||||
return nil
|
||||
}
|
||||
if node.Name != paths[0] {
|
||||
return nil
|
||||
}
|
||||
if len(paths) == 1 {
|
||||
return node
|
||||
}
|
||||
for _, child := range node.Children {
|
||||
tmp := child.getByPath(paths[1:])
|
||||
if tmp != nil {
|
||||
return tmp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node *Node) isFile() bool {
|
||||
return node.Url != ""
|
||||
}
|
||||
|
||||
func (node *Node) calSize() int64 {
|
||||
if node.isFile() {
|
||||
return node.Size
|
||||
}
|
||||
var size int64 = 0
|
||||
for _, child := range node.Children {
|
||||
size += child.calSize()
|
||||
}
|
||||
node.Size = size
|
||||
return size
|
||||
}
|
47
drivers/url_tree/urls_test.go
Normal file
47
drivers/url_tree/urls_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
package url_tree_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/url_tree"
|
||||
)
|
||||
|
||||
func testTree() (*url_tree.Node, error) {
|
||||
text := `folder1:
|
||||
name1:https://url1
|
||||
http://url2
|
||||
folder2:
|
||||
http://url3
|
||||
http://url4
|
||||
http://url5
|
||||
folder3:
|
||||
http://url6
|
||||
http://url7
|
||||
http://url8`
|
||||
return url_tree.BuildTree(text, false)
|
||||
}
|
||||
|
||||
func TestBuildTree(t *testing.T) {
|
||||
node, err := testTree()
|
||||
if err != nil {
|
||||
t.Errorf("failed to build tree: %+v", err)
|
||||
} else {
|
||||
t.Logf("tree: %+v", node)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNode(t *testing.T) {
|
||||
root, err := testTree()
|
||||
if err != nil {
|
||||
t.Errorf("failed to build tree: %+v", err)
|
||||
return
|
||||
}
|
||||
node := url_tree.GetNodeFromRootByPath(root, "/")
|
||||
if node != root {
|
||||
t.Errorf("got wrong node: %+v", node)
|
||||
}
|
||||
url3 := url_tree.GetNodeFromRootByPath(root, "/folder1/folder2/url3")
|
||||
if url3 != root.Children[0].Children[2].Children[0] {
|
||||
t.Errorf("got wrong node: %+v", url3)
|
||||
}
|
||||
}
|
192
drivers/url_tree/util.go
Normal file
192
drivers/url_tree/util.go
Normal file
@ -0,0 +1,192 @@
|
||||
package url_tree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
stdpath "path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// build tree from text, text structure definition:
|
||||
/**
|
||||
* FolderName:
|
||||
* [FileName:][FileSize:][Modified:]Url
|
||||
*/
|
||||
/**
|
||||
* For example:
|
||||
* folder1:
|
||||
* name1:url1
|
||||
* url2
|
||||
* folder2:
|
||||
* url3
|
||||
* url4
|
||||
* url5
|
||||
* folder3:
|
||||
* url6
|
||||
* url7
|
||||
* url8
|
||||
*/
|
||||
// if there are no name, use the last segment of url as name
|
||||
func BuildTree(text string, headSize bool) (*Node, error) {
|
||||
lines := strings.Split(text, "\n")
|
||||
var root = &Node{Level: -1, Name: "root"}
|
||||
stack := []*Node{root}
|
||||
for _, line := range lines {
|
||||
// calculate indent
|
||||
indent := 0
|
||||
for i := 0; i < len(line); i++ {
|
||||
if line[i] != ' ' {
|
||||
break
|
||||
}
|
||||
indent++
|
||||
}
|
||||
// if indent is not a multiple of 2, it is an error
|
||||
if indent%2 != 0 {
|
||||
return nil, fmt.Errorf("the line '%s' is not a multiple of 2", line)
|
||||
}
|
||||
// calculate level
|
||||
level := indent / 2
|
||||
line = strings.TrimSpace(line[indent:])
|
||||
// if the line is empty, skip
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
// if level isn't greater than the level of the top of the stack
|
||||
// it is not the child of the top of the stack
|
||||
for level <= stack[len(stack)-1].Level {
|
||||
// pop the top of the stack
|
||||
stack = stack[:len(stack)-1]
|
||||
}
|
||||
// if the line is a folder
|
||||
if isFolder(line) {
|
||||
// create a new node
|
||||
node := &Node{
|
||||
Level: level,
|
||||
Name: strings.TrimSuffix(line, ":"),
|
||||
}
|
||||
// add the node to the top of the stack
|
||||
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
|
||||
// push the node to the stack
|
||||
stack = append(stack, node)
|
||||
} else {
|
||||
// if the line is a file
|
||||
// create a new node
|
||||
node, err := parseFileLine(line, headSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node.Level = level
|
||||
// add the node to the top of the stack
|
||||
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
|
||||
}
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func isFolder(line string) bool {
|
||||
return strings.HasSuffix(line, ":")
|
||||
}
|
||||
|
||||
// line definition:
|
||||
// [FileName:][FileSize:][Modified:]Url
|
||||
func parseFileLine(line string, headSize bool) (*Node, error) {
|
||||
// if there is no url, it is an error
|
||||
if !strings.Contains(line, "http://") && !strings.Contains(line, "https://") {
|
||||
return nil, fmt.Errorf("invalid line: %s, because url is required for file", line)
|
||||
}
|
||||
index := strings.Index(line, "http://")
|
||||
if index == -1 {
|
||||
index = strings.Index(line, "https://")
|
||||
}
|
||||
url := line[index:]
|
||||
info := line[:index]
|
||||
node := &Node{
|
||||
Url: url,
|
||||
}
|
||||
haveSize := false
|
||||
if index > 0 {
|
||||
if !strings.HasSuffix(info, ":") {
|
||||
return nil, fmt.Errorf("invalid line: %s, because file info must end with ':'", line)
|
||||
}
|
||||
info = info[:len(info)-1]
|
||||
if info == "" {
|
||||
return nil, fmt.Errorf("invalid line: %s, because file name can't be empty", line)
|
||||
}
|
||||
infoParts := strings.Split(info, ":")
|
||||
node.Name = infoParts[0]
|
||||
if len(infoParts) > 1 {
|
||||
size, err := strconv.ParseInt(infoParts[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid line: %s, because file size must be an integer", line)
|
||||
}
|
||||
node.Size = size
|
||||
haveSize = true
|
||||
if len(infoParts) > 2 {
|
||||
modified, err := strconv.ParseInt(infoParts[2], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid line: %s, because file modified must be an unix timestamp", line)
|
||||
}
|
||||
node.Modified = modified
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.Name = stdpath.Base(url)
|
||||
}
|
||||
if !haveSize && headSize {
|
||||
size, err := getSizeFromUrl(url)
|
||||
if err != nil {
|
||||
log.Errorf("get size from url error: %s", err)
|
||||
} else {
|
||||
node.Size = size
|
||||
}
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func splitPath(path string) []string {
|
||||
if path == "/" {
|
||||
return []string{"root"}
|
||||
}
|
||||
parts := strings.Split(path, "/")
|
||||
parts[0] = "root"
|
||||
return parts
|
||||
}
|
||||
|
||||
func GetNodeFromRootByPath(root *Node, path string) *Node {
|
||||
return root.getByPath(splitPath(path))
|
||||
}
|
||||
|
||||
func nodeToObj(node *Node, path string) (model.Obj, error) {
|
||||
if node == nil {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
return &model.Object{
|
||||
Name: node.Name,
|
||||
Size: node.Size,
|
||||
Modified: time.Unix(node.Modified, 0),
|
||||
IsFolder: !node.isFile(),
|
||||
Path: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getSizeFromUrl(url string) (int64, error) {
|
||||
res, err := base.RestyClient.R().SetDoNotParseResponse(true).Head(url)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer res.RawResponse.Body.Close()
|
||||
if res.StatusCode() >= 300 {
|
||||
return 0, fmt.Errorf("get size from url %s failed, status code: %d", url, res.StatusCode())
|
||||
}
|
||||
size, err := strconv.ParseInt(res.Header().Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return size, nil
|
||||
}
|
12
go.mod
12
go.mod
@ -6,7 +6,7 @@ require (
|
||||
github.com/SheltonZhu/115driver v1.0.14
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||
github.com/aws/aws-sdk-go v1.44.194
|
||||
github.com/blevesearch/bleve/v2 v2.3.6
|
||||
github.com/blevesearch/bleve/v2 v2.3.7
|
||||
github.com/caarlos0/env/v7 v7.1.0
|
||||
github.com/deckarep/golang-set/v2 v2.3.0
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
@ -48,20 +48,20 @@ require (
|
||||
github.com/andreburgaud/crypt2go v1.1.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.0.5 // indirect
|
||||
github.com/blevesearch/geo v0.1.16 // indirect
|
||||
github.com/blevesearch/geo v0.1.17 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.4 // indirect
|
||||
github.com/blevesearch/segment v0.9.0 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.1 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
github.com/blevesearch/vellum v1.0.9 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.3.7 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.3.7 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.3.7 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.3.7 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.3.8 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.3.9 // indirect
|
||||
github.com/bluele/gcache v0.0.2 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bytedance/sonic v1.8.0 // indirect
|
||||
@ -76,7 +76,7 @@ require (
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/protobuf v1.5.0 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
|
28
go.sum
28
go.sum
@ -17,12 +17,12 @@ github.com/aws/aws-sdk-go v1.44.194 h1:1ZDK+QDcc5oRbZGgRZSz561eR8XVizXCeGpoZKo33
|
||||
github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/blevesearch/bleve/v2 v2.3.6 h1:NlntUHcV5CSWIhpugx4d/BRMGCiaoI8ZZXrXlahzNq4=
|
||||
github.com/blevesearch/bleve/v2 v2.3.6/go.mod h1:JM2legf1cKVkdV8Ehu7msKIOKC0McSw0Q16Fmv9vsW4=
|
||||
github.com/blevesearch/bleve/v2 v2.3.7 h1:nIfIrhv28tvgBpbVF8Dq7/U1zW/YiwSqg/PBgE3x8bo=
|
||||
github.com/blevesearch/bleve/v2 v2.3.7/go.mod h1:2tToYD6mDeseIA13jcZiEEqYrVLg6xdk0v6+F7dWquU=
|
||||
github.com/blevesearch/bleve_index_api v1.0.5 h1:Lc986kpC4Z0/n1g3gg8ul7H+lxgOQPcXb9SxvQGu+tw=
|
||||
github.com/blevesearch/bleve_index_api v1.0.5/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||
github.com/blevesearch/geo v0.1.16 h1:unVaqUmlwprk56596OQRkGjtq1VZ8XFWSARj+h2cIBY=
|
||||
github.com/blevesearch/geo v0.1.16/go.mod h1:a1OlySNE+oDQ5qY0vJGYNoLIsMpbKbx8dnmuRP8D7H0=
|
||||
github.com/blevesearch/geo v0.1.17 h1:AguzI6/5mHXapzB0gE9IKWo+wWPHZmXZoscHcjFgAFA=
|
||||
github.com/blevesearch/geo v0.1.17/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
@ -31,12 +31,12 @@ github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCD
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.4 h1:LmGmo5twU3gV+natJbKmOktS9eMhokPGKWuR+jX84vk=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.4/go.mod h1:PgVnbbg/t1UkgezPDu8EHLi1BHQ17xUwsFdU6NnOYS0=
|
||||
github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac=
|
||||
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.1 h1:1SYRwyoFLwG3sj0ed89RLtM15amfX2pXlYbFOnF8zNU=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||
github.com/blevesearch/vellum v1.0.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ=
|
||||
github.com/blevesearch/vellum v1.0.9/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||
github.com/blevesearch/zapx/v11 v11.3.7 h1:Y6yIAF/DVPiqZUA/jNgSLXmqewfzwHzuwfKyfdG+Xaw=
|
||||
@ -47,8 +47,8 @@ github.com/blevesearch/zapx/v13 v13.3.7 h1:igIQg5eKmjw168I7av0Vtwedf7kHnQro/M+ub
|
||||
github.com/blevesearch/zapx/v13 v13.3.7/go.mod h1:yyrB4kJ0OT75UPZwT/zS+Ru0/jYKorCOOSY5dBzAy+s=
|
||||
github.com/blevesearch/zapx/v14 v14.3.7 h1:gfe+fbWslDWP/evHLtp/GOvmNM3sw1BbqD7LhycBX20=
|
||||
github.com/blevesearch/zapx/v14 v14.3.7/go.mod h1:9J/RbOkqZ1KSjmkOes03AkETX7hrXT0sFMpWH4ewC4w=
|
||||
github.com/blevesearch/zapx/v15 v15.3.8 h1:q4uMngBHzL1IIhRc8AJUEkj6dGOE3u1l3phLu7hq8uk=
|
||||
github.com/blevesearch/zapx/v15 v15.3.8/go.mod h1:m7Y6m8soYUvS7MjN9eKlz1xrLCcmqfFadmu7GhWIrLY=
|
||||
github.com/blevesearch/zapx/v15 v15.3.9 h1:/s9zqKxFaZKQTTcMO2b/Tup0ch5MSztlvw+frVDfIBk=
|
||||
github.com/blevesearch/zapx/v15 v15.3.9/go.mod h1:m7Y6m8soYUvS7MjN9eKlz1xrLCcmqfFadmu7GhWIrLY=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
@ -66,8 +66,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set/v2 v2.2.0 h1:2pMQd3Soi6qfw7E5MMKaEh5W5ES18bW3AbFFnGl6LgQ=
|
||||
github.com/deckarep/golang-set/v2 v2.2.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/deckarep/golang-set/v2 v2.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g=
|
||||
github.com/deckarep/golang-set/v2 v2.3.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
@ -113,12 +111,11 @@ github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFig
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@ -152,7 +149,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
|
@ -13,6 +13,7 @@ type Storage struct {
|
||||
Remark string `json:"remark"`
|
||||
Modified time.Time `json:"modified"`
|
||||
Disabled bool `json:"disabled"` // if disabled
|
||||
EnableSign bool `json:"enable_sign"`
|
||||
Sort
|
||||
Proxy
|
||||
}
|
||||
|
@ -122,9 +122,14 @@ func getMainItems(config driver.Config) []driver.Item {
|
||||
Type: conf.TypeSelect,
|
||||
Options: "front,back",
|
||||
})
|
||||
items = append(items, driver.Item{
|
||||
Name: "enable_sign",
|
||||
Type: conf.TypeBool,
|
||||
Default: "false",
|
||||
Required: true,
|
||||
})
|
||||
return items
|
||||
}
|
||||
|
||||
func getAdditionalItems(t reflect.Type, defaultRoot string) []driver.Item {
|
||||
var items []driver.Item
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -89,7 +89,7 @@ func (c *client) AddURI(uris []string, options ...interface{}) (gid string, err
|
||||
// If a file with the same name already exists, it is overwritten!
|
||||
// If the file cannot be saved successfully or --rpc-save-upload-metadata is false, the downloads added by this method are not saved by --save-session.
|
||||
func (c *client) AddTorrent(filename string, options ...interface{}) (gid string, err error) {
|
||||
co, err := ioutil.ReadFile(filename)
|
||||
co, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -120,7 +120,7 @@ func (c *client) AddTorrent(filename string, options ...interface{}) (gid string
|
||||
// If a file with the same name already exists, it is overwritten!
|
||||
// If the file cannot be saved successfully or --rpc-save-upload-metadata is false, the downloads added by this method are not saved by --save-session.
|
||||
func (c *client) AddMetalink(filename string, options ...interface{}) (gid []string, err error) {
|
||||
co, err := ioutil.ReadFile(filename)
|
||||
co, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -8,9 +8,15 @@ import (
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
func IsStorageSignEnabled(rawPath string) bool {
|
||||
storage := op.GetBalancedStorage(rawPath)
|
||||
return storage != nil && storage.GetStorage().EnableSign
|
||||
}
|
||||
|
||||
func CanWrite(meta *model.Meta, path string) bool {
|
||||
if meta == nil || !meta.Write {
|
||||
return false
|
||||
|
@ -3,7 +3,6 @@ package common
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -61,7 +60,8 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
||||
filename := file.GetName()
|
||||
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)
|
||||
return nil
|
||||
} else {
|
||||
@ -93,7 +93,7 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
||||
}
|
||||
w.WriteHeader(res.StatusCode)
|
||||
if res.StatusCode >= 400 {
|
||||
all, _ := ioutil.ReadAll(res.Body)
|
||||
all, _ := io.ReadAll(res.Body)
|
||||
msg := string(all)
|
||||
log.Debugln(msg)
|
||||
return errors.New(msg)
|
||||
|
@ -165,6 +165,9 @@ func getReadme(meta *model.Meta, path string) string {
|
||||
}
|
||||
|
||||
func isEncrypt(meta *model.Meta, path string) bool {
|
||||
if common.IsStorageSignEnabled(path) {
|
||||
return true
|
||||
}
|
||||
if meta == nil || meta.Password == "" {
|
||||
return false
|
||||
}
|
||||
@ -260,16 +263,20 @@ func FsGet(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if storage.Config().MustProxy() || storage.GetStorage().WebProxy {
|
||||
query := ""
|
||||
if isEncrypt(meta, reqPath) {
|
||||
query = "?sign=" + sign.Sign(reqPath)
|
||||
}
|
||||
if storage.GetStorage().DownProxyUrl != "" {
|
||||
rawURL = fmt.Sprintf("%s%s?sign=%s",
|
||||
strings.Split(storage.GetStorage().DownProxyUrl, "\n")[0],
|
||||
utils.EncodePath(reqPath, true),
|
||||
sign.Sign(reqPath))
|
||||
} else {
|
||||
rawURL = fmt.Sprintf("%s/p%s?sign=%s",
|
||||
rawURL = fmt.Sprintf("%s/p%s%s",
|
||||
common.GetApiUrl(c.Request),
|
||||
utils.EncodePath(reqPath, true),
|
||||
sign.Sign(reqPath))
|
||||
query)
|
||||
}
|
||||
} else {
|
||||
// file have raw url
|
||||
@ -293,6 +300,7 @@ func FsGet(c *gin.Context) {
|
||||
related = filterRelated(sameLevelFiles, obj)
|
||||
}
|
||||
parentMeta, _ := op.GetNearestMeta(parentPath)
|
||||
thumb, _ := model.GetThumb(obj)
|
||||
common.SuccessResp(c, FsGetResp{
|
||||
ObjResp: ObjResp{
|
||||
Name: obj.GetName(),
|
||||
@ -301,6 +309,7 @@ func FsGet(c *gin.Context) {
|
||||
Modified: obj.ModTime(),
|
||||
Sign: common.Sign(obj, parentPath, isEncrypt(meta, reqPath)),
|
||||
Type: utils.GetFileType(obj.GetName()),
|
||||
Thumb: thumb,
|
||||
},
|
||||
RawURL: rawURL,
|
||||
Readme: getReadme(meta, reqPath),
|
||||
|
@ -4,10 +4,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/internal/sign"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
@ -49,6 +50,9 @@ func needSign(meta *model.Meta, path string) bool {
|
||||
if setting.GetBool(conf.SignAll) {
|
||||
return true
|
||||
}
|
||||
if common.IsStorageSignEnabled(path) {
|
||||
return true
|
||||
}
|
||||
if meta == nil || meta.Password == "" {
|
||||
return false
|
||||
}
|
||||
|
Reference in New Issue
Block a user