Compare commits

...

56 Commits

Author SHA1 Message Date
a3446720a2 fix: make TlsInsecureSkipVerify enable for all request (#4386) 2023-05-14 17:05:47 +08:00
3c4c2ad4e0 feat(teambition): support s3 upload method (close #4365) 2023-05-13 23:06:25 +08:00
077a525961 fix(189): adapt new login method (close #4378) 2023-05-13 17:28:40 +08:00
5be79eb26e feat: add robots.txt setting (close #4303) 2023-05-12 16:53:15 +08:00
ddc19ab699 fix(deps): update module github.com/blevesearch/bleve/v2 to v2.3.8 [skip ci] (#4322)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-12 16:34:25 +08:00
ddfca5a29b fix(deps): update module github.com/aws/aws-sdk-go to v1.44.262 (#3285)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-12 16:25:30 +08:00
c19166be1c feat(google_drive): support sa (close #3132 in #4360)
Co-authored-by: Andy Hsu <i@nn.ci>
2023-05-12 14:47:50 +08:00
daad61443c feat(local): support thumbnail cache (close #4216) 2023-05-11 19:57:24 +08:00
4b0c01158d fix: panic on nil pointer 2023-05-11 19:44:44 +08:00
f97f1d532e fix(webdav): don't retry for put if body isn't seeker (close #4149 close #4238) 2023-05-11 18:57:35 +08:00
e15755fef0 fix(189): enable TlsInsecureSkipVerify (close #4355) 2023-05-11 18:48:31 +08:00
ea88998325 docs: add help message for mount path (#4364)
Co-authored-by: Andy Hsu <i@nn.ci>
2023-05-11 18:40:56 +08:00
74d971aa8a docs: fix git address [skip ci] (#4366) 2023-05-11 15:05:33 +08:00
d41d868a8d fix(baidu_photo): change folder name length limit (close #4351 in #4353)
Co-authored-by: Andy Hsu <i@nn.ci>
2023-05-09 20:44:57 +08:00
555cc26cbf fix(deps): update module golang.org/x/crypto to v0.9.0 (#4350)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-09 20:28:52 +08:00
ab4215080b fix(deps): update module golang.org/x/net to v0.10.0 (#4347)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-09 16:31:17 +08:00
9502f5acd7 fix(cloudreve): skip init login when using cookie (#4341) 2023-05-08 19:25:36 +08:00
b03879403f feat(cloudreve): support use cookie to login (close #4324 in #4339)
Co-authored-by: Andy Hsu <i@nn.ci>
2023-05-08 15:19:51 +08:00
ee4ac81677 fix(webdav): can't rename on infini-cloud (close #4333) 2023-05-08 14:21:12 +08:00
b69fc8c306 ci: increase daysUntilClose to avoid use stale-bot [skip ci] 2023-05-07 21:07:31 +08:00
ee6c31332d feat(drivers): ipfs api (#4265)
Co-authored-by: Andy Hsu <i@nn.ci>
2023-05-05 17:42:22 +08:00
9fa16bd5fc ci: use github helper to close stale issue 2023-05-05 16:29:59 +08:00
c77ed5fcb0 feat(aliyundrive_open): limit rate for List and Link (close #4290) 2023-05-02 22:06:03 +08:00
822be17fb9 feat(aliyundrive_open): add expiration for link (close #4061) 2023-05-02 16:12:40 +08:00
7e3b13ea2d fix: fs/list interface conversion from copy alias (close #4279) 2023-05-01 15:45:45 +08:00
f8fb48fb32 fix: cannot connect to Casdoor SSO (close #4266 in #4274) 2023-05-01 15:32:34 +08:00
4bf46268da feat(alias): support thumbnail (close #4256) 2023-04-28 00:17:15 +08:00
b7ea73b3c2 fix(aliyundrive_open): can't refresh token if access_token is empty (#4255) 2023-04-28 00:01:47 +08:00
9fbc54314d chore(aliyundrive_open): change base url 2023-04-27 16:38:40 +08:00
cf8ab29a17 feat: optional allow be mounted (close #4218) 2023-04-27 16:33:01 +08:00
51cadd2d49 fix: ignore handle in json (close #4251 close #4252) 2023-04-27 15:39:32 +08:00
2bae8e129e feat: add Casdoor single sign-on (#4222) 2023-04-26 16:01:40 +08:00
9d55ad3af6 fix(123): get download url (close #4244) 2023-04-26 15:06:24 +08:00
36cd504783 fix(alist_v3): missed meta_password update
fix: adb0739dfe (commitcomment-110328033)
2023-04-24 20:56:46 +08:00
49f13b9b90 fix(baidu_photo): upload file has web prefix (close #4233 in #4235) 2023-04-24 19:13:33 +08:00
adb0739dfe feat!(alist_v3): support username & password login (close #4226)
Breaking changes:
- rename access_token to token
- rename old password to meta_password
2023-04-23 17:48:26 +08:00
340cb940e3 fix(qbittorrent): set autoTMM (#4217) 2023-04-22 13:33:54 +08:00
8711f2a1c5 feat(quark): shard request file (close #4175) 2023-04-17 15:33:38 +08:00
7f35aab071 revert(quark): remove preset range header 2023-04-17 14:39:21 +08:00
ecd167d2f9 feat(quark): add preset range header (close #4166) 2023-04-16 19:26:03 +08:00
220fd30830 fix: the recursive subdirectory moving bug (#4171) 2023-04-16 16:08:12 +08:00
5cba10446e fix(123): adapt new upload method (close #4141) 2023-04-14 15:48:39 +08:00
a9bdb15205 ci: fix golang version in auto_lang [skip ci] 2023-04-14 13:49:13 +08:00
c5f6a90f54 fix(quark): download file size limit (close #4140) 2023-04-14 13:47:05 +08:00
46f9aefb04 feat: empty folder clear API [ckip ci] (#4132)
* 增加清理空文件夹API

* 修复嵌套文件夹删除Bug

 Author:    varg247 <varg247@gmail.com>

---------

Co-authored-by: varg247 <varg247@qq.com>
2023-04-13 15:39:21 +08:00
fdcad9c154 fix(123): incorrect endpoint (close #4046) 2023-04-12 23:04:12 +08:00
027025361a ci: fixed version of alpine 2023-04-12 16:01:49 +08:00
f1245153b9 chore(deps): upgrade to go@1.20 2023-04-12 15:42:27 +08:00
570b8be022 fix(onedrive): error check in upBig 2023-04-11 22:52:42 +08:00
86a773674a feat(task): print stack trace if panic 2023-04-11 15:16:57 +08:00
75fd0ee185 feat(s3): optional remove bucket name from path (close #4069) 2023-04-09 19:25:52 +08:00
cc43238bd1 fix(alias): disable log completely (#4054) 2023-04-09 15:46:26 +08:00
c0a6beecea fix(alias): panic on nil pointer (close #4093) 2023-04-09 14:06:04 +08:00
c77eebb035 fix(deps): update module golang.org/x/image to v0.7.0 (#4065)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-08 21:51:51 +08:00
b1efb86b28 fix(deps): update module golang.org/x/net to v0.9.0 [skip ci] (#4066)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-08 21:20:19 +08:00
0707449c8f fix(deps): update module golang.org/x/crypto to v0.8.0 [skip ci] (#4076)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-08 21:18:39 +08:00
79 changed files with 1698 additions and 426 deletions

2
.github/stale.yml vendored
View File

@ -1,7 +1,7 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 44
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 8
daysUntilClose: 10
# Issues with these labels will never be considered stale
exemptLabels:
- accepted

View File

@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
platform: [ ubuntu-latest ]
go-version: [ 1.19 ]
go-version: [ '1.20' ]
name: auto generate lang.json
runs-on: ${{ matrix.platform }}
steps:

View File

@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
platform: [ubuntu-latest]
go-version: [1.19]
go-version: [ '1.20' ]
name: Build
runs-on: ${{ matrix.platform }}
steps:

View File

@ -2,7 +2,7 @@ name: Close need info
on:
schedule:
- cron: "0 0 */7 * *"
- cron: "0 0 */1 * *"
workflow_dispatch:
jobs:
@ -15,8 +15,8 @@ jobs:
actions: 'close-issues'
token: ${{ secrets.GITHUB_TOKEN }}
labels: 'question'
inactive-day: 7
inactive-day: 3
close-reason: 'not_planned'
body: |
Hello @${{ github.event.issue.user.login }}, this issue was closed due to no activities in 7 days.
你好 @${{ github.event.issue.user.login }}此issue因超过7天未回复被关闭。
Hello @${{ github.event.issue.user.login }}, this issue was closed due to no activities in 3 days.
你好 @${{ github.event.issue.user.login }}此issue因超过3天未回复被关闭。

21
.github/workflows/issue_close_stale.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: Close inactive
on:
schedule:
- cron: "0 0 */7 * *"
workflow_dispatch:
jobs:
close-inactive:
runs-on: ubuntu-latest
steps:
- name: close-issues
uses: actions-cool/issues-helper@v3
with:
actions: 'close-issues'
token: ${{ secrets.GITHUB_TOKEN }}
labels: 'stale'
inactive-day: 8
close-reason: 'not_planned'
body: |
Hello @${{ github.event.issue.user.login }}, this issue was closed due to inactive more than 52 days. You can reopen or recreate it if you think it should continue.

View File

@ -16,5 +16,5 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
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自动关闭。
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 3 days.
你好 @${{ github.event.issue.user.login }}请按照issue模板填写, 并详细说明问题/日志记录/复现步骤/复现链接/实现思路或提供更多信息等, 3天内未回复issue自动关闭。

View File

@ -9,7 +9,7 @@ jobs:
strategy:
matrix:
platform: [ ubuntu-latest ]
go-version: [ 1.19 ]
go-version: [ '1.20' ]
name: Release
runs-on: ${{ matrix.platform }}
steps:

View File

@ -6,7 +6,7 @@
Prerequisites:
- [git](https://nodejs.org/zh-cn/)
- [git](https://git-scm.com)
- [Go 1.19+](https://golang.org/doc/install)
- [gcc](https://gcc.gnu.org/)
- [nodejs](https://nodejs.org/)

View File

@ -1,11 +1,11 @@
FROM alpine:edge as builder
FROM alpine:3.17 as builder
LABEL stage=go-builder
WORKDIR /app/
COPY ./ ./
RUN apk add --no-cache bash curl gcc git go musl-dev; \
bash build.sh release docker
FROM alpine:edge
FROM alpine:3.17
LABEL MAINTAINER="i@nn.ci"
VOLUME /opt/alist/data/
WORKDIR /opt/alist/

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/SheltonZhu/115driver/pkg/driver"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/pkg/errors"
)
@ -15,6 +16,7 @@ func (d *Pan115) login() error {
driver.UA(UserAgent),
}
d.client = driver.New(opts...)
d.client.SetHttpClient(base.HttpClient)
cr := &driver.Credential{}
if d.Addition.QRCodeToken != "" {
s := &driver.QRCodeSession{

View File

@ -97,7 +97,8 @@ func (d *Pan123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
}
}
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 {
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())
if res.StatusCode() == 302 {
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
} else {
@ -220,19 +223,23 @@ func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
"type": 0,
}
var resp UploadResp
_, err := d.request(UploadRequest, http.MethodPost, func(req *resty.Request) {
res, err := d.request(UploadRequest, http.MethodPost, func(req *resty.Request) {
req.SetBody(data).SetContext(ctx)
}, &resp)
if err != nil {
return err
}
log.Debugln("upload request res: ", string(res))
if resp.Data.Reuse || resp.Data.Key == "" {
return nil
}
if resp.Data.AccessKeyId == "" || resp.Data.SecretAccessKey == "" || resp.Data.SessionToken == "" {
err = d.newUpload(ctx, &resp, stream, uploadFile, up)
} else {
cfg := &aws.Config{
Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken),
Region: aws.String("123pan"),
Endpoint: aws.String("file.123pan.com"),
Endpoint: aws.String(resp.Data.EndPoint),
S3ForcePathStyle: aws.Bool(true),
}
s, err := session.NewSession(cfg)
@ -246,6 +253,7 @@ func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
Body: uploadFile,
}
_, err = uploader.UploadWithContext(ctx, input)
}
if err != nil {
return err
}

View File

@ -74,5 +74,14 @@ type UploadResp struct {
SessionToken string `json:"SessionToken"`
FileId int64 `json:"FileId"`
Reuse bool `json:"Reuse"`
EndPoint string `json:"EndPoint"`
StorageNode string `json:"StorageNode"`
UploadId string `json:"UploadId"`
} `json:"data"`
}
type S3PreSignedURLs struct {
Data struct {
PreSignedUrls map[string]string `json:"presignedUrls"`
} `json:"data"`
}

127
drivers/123/upload.go Normal file
View File

@ -0,0 +1,127 @@
package _123
import (
"context"
"fmt"
"io"
"math"
"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"
)
func (d *Pan123) getS3PreSignedUrls(ctx context.Context, upReq *UploadResp, start, end int) (*S3PreSignedURLs, error) {
data := base.Json{
"bucket": upReq.Data.Bucket,
"key": upReq.Data.Key,
"partNumberEnd": end,
"partNumberStart": start,
"uploadId": upReq.Data.UploadId,
"StorageNode": upReq.Data.StorageNode,
}
var s3PreSignedUrls S3PreSignedURLs
_, err := d.request(S3PreSignedUrls, http.MethodPost, func(req *resty.Request) {
req.SetBody(data).SetContext(ctx)
}, &s3PreSignedUrls)
if err != nil {
return nil, err
}
return &s3PreSignedUrls, nil
}
func (d *Pan123) completeS3(ctx context.Context, upReq *UploadResp) error {
data := base.Json{
"bucket": upReq.Data.Bucket,
"key": upReq.Data.Key,
"uploadId": upReq.Data.UploadId,
"StorageNode": upReq.Data.StorageNode,
}
_, err := d.request(S3Complete, http.MethodPost, func(req *resty.Request) {
req.SetBody(data).SetContext(ctx)
}, nil)
return err
}
func (d *Pan123) newUpload(ctx context.Context, upReq *UploadResp, file model.FileStreamer, reader io.Reader, up driver.UpdateProgress) error {
chunkSize := int64(1024 * 1024 * 5)
// fetch s3 pre signed urls
chunkCount := int(math.Ceil(float64(file.GetSize()) / float64(chunkSize)))
// upload 10 chunks each batch
batchSize := 10
for i := 1; i <= chunkCount; i += batchSize {
if utils.IsCanceled(ctx) {
return ctx.Err()
}
start := i
end := i + batchSize
if end > chunkCount+1 {
end = chunkCount + 1
}
s3PreSignedUrls, err := d.getS3PreSignedUrls(ctx, upReq, start, end)
if err != nil {
return err
}
// upload each chunk
for j := start; j < end; j++ {
if utils.IsCanceled(ctx) {
return ctx.Err()
}
curSize := chunkSize
if j == chunkCount {
curSize = file.GetSize() - (int64(chunkCount)-1)*chunkSize
}
err = d.uploadS3Chunk(ctx, upReq, s3PreSignedUrls, j, end, io.LimitReader(reader, chunkSize), curSize, false)
if err != nil {
return err
}
up(j * 100 / chunkCount)
}
}
// complete s3 upload
return d.completeS3(ctx, upReq)
}
func (d *Pan123) uploadS3Chunk(ctx context.Context, upReq *UploadResp, s3PreSignedUrls *S3PreSignedURLs, cur, end int, reader io.Reader, curSize int64, retry bool) error {
uploadUrl := s3PreSignedUrls.Data.PreSignedUrls[strconv.Itoa(cur)]
if uploadUrl == "" {
return fmt.Errorf("upload url is empty, s3PreSignedUrls: %+v", s3PreSignedUrls)
}
req, err := http.NewRequest("PUT", uploadUrl, reader)
if err != nil {
return err
}
req = req.WithContext(ctx)
req.ContentLength = curSize
//req.Header.Set("Content-Length", strconv.FormatInt(curSize, 10))
res, err := base.HttpClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode == http.StatusForbidden {
if retry {
return fmt.Errorf("upload s3 chunk %d failed, status code: %d", cur, res.StatusCode)
}
// refresh s3 pre signed urls
newS3PreSignedUrls, err := d.getS3PreSignedUrls(ctx, upReq, cur, end)
if err != nil {
return err
}
s3PreSignedUrls.Data.PreSignedUrls = newS3PreSignedUrls.Data.PreSignedUrls
// retry
return d.uploadS3Chunk(ctx, upReq, s3PreSignedUrls, cur, end, reader, curSize, true)
}
if res.StatusCode != http.StatusOK {
body, err := io.ReadAll(res.Body)
if err != nil {
return err
}
return fmt.Errorf("upload s3 chunk %d failed, status code: %d, body: %s", cur, res.StatusCode, body)
}
return nil
}

View File

@ -19,13 +19,15 @@ const (
SignIn = API + "/user/sign_in"
UserInfo = API + "/user/info"
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"
Move = API + "/file/mod_pid"
Rename = API + "/file/rename"
Trash = API + "/file/trash"
UploadRequest = API + "/file/upload_request"
UploadComplete = API + "/file/upload_complete"
S3PreSignedUrls = API + "/file/s3_repare_upload_parts_batch"
S3Complete = API + "/file/s3_complete_multipart_upload"
)
func (d *Pan123) login() error {

View File

@ -30,12 +30,9 @@ func (d *Cloud189) GetAddition() driver.Additional {
}
func (d *Cloud189) Init(ctx context.Context) error {
d.client = resty.New().
SetTimeout(base.DefaultTimeout).
SetRetryCount(3).
SetHeader("Referer", "https://cloud.189.cn/").
SetHeader("User-Agent", base.UserAgent)
return d.login()
d.client = base.NewRestyClient().
SetHeader("Referer", "https://cloud.189.cn/")
return d.newLogin()
}
func (d *Cloud189) Drop(ctx context.Context) error {

126
drivers/189/login.go Normal file
View File

@ -0,0 +1,126 @@
package _189
import (
"errors"
"strconv"
"github.com/alist-org/alist/v3/pkg/utils"
log "github.com/sirupsen/logrus"
)
type AppConf struct {
Data struct {
AccountType string `json:"accountType"`
AgreementCheck string `json:"agreementCheck"`
AppKey string `json:"appKey"`
ClientType int `json:"clientType"`
IsOauth2 bool `json:"isOauth2"`
LoginSort string `json:"loginSort"`
MailSuffix string `json:"mailSuffix"`
PageKey string `json:"pageKey"`
ParamId string `json:"paramId"`
RegReturnUrl string `json:"regReturnUrl"`
ReqId string `json:"reqId"`
ReturnUrl string `json:"returnUrl"`
ShowFeedback string `json:"showFeedback"`
ShowPwSaveName string `json:"showPwSaveName"`
ShowQrSaveName string `json:"showQrSaveName"`
ShowSmsSaveName string `json:"showSmsSaveName"`
Sso string `json:"sso"`
} `json:"data"`
Msg string `json:"msg"`
Result string `json:"result"`
}
type EncryptConf struct {
Result int `json:"result"`
Data struct {
UpSmsOn string `json:"upSmsOn"`
Pre string `json:"pre"`
PreDomain string `json:"preDomain"`
PubKey string `json:"pubKey"`
} `json:"data"`
}
func (d *Cloud189) newLogin() error {
url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
res, err := d.client.R().Get(url)
if err != nil {
return err
}
// Is logged in
redirectURL := res.RawResponse.Request.URL
if redirectURL.String() == "https://cloud.189.cn/web/main" {
return nil
}
lt := redirectURL.Query().Get("lt")
reqId := redirectURL.Query().Get("reqId")
appId := redirectURL.Query().Get("appId")
headers := map[string]string{
"lt": lt,
"reqid": reqId,
"referer": redirectURL.String(),
"origin": "https://open.e.189.cn",
}
// get app Conf
var appConf AppConf
res, err = d.client.R().SetHeaders(headers).SetFormData(map[string]string{
"version": "2.0",
"appKey": appId,
}).SetResult(&appConf).Post("https://open.e.189.cn/api/logbox/oauth2/appConf.do")
if err != nil {
return err
}
log.Debugf("189 AppConf resp body: %s", res.String())
if appConf.Result != "0" {
return errors.New(appConf.Msg)
}
// get encrypt conf
var encryptConf EncryptConf
res, err = d.client.R().SetHeaders(headers).SetFormData(map[string]string{
"appId": appId,
}).Post("https://open.e.189.cn/api/logbox/config/encryptConf.do")
if err != nil {
return err
}
err = utils.Json.Unmarshal(res.Body(), &encryptConf)
if err != nil {
return err
}
log.Debugf("189 EncryptConf resp body: %s\n%+v", res.String(), encryptConf)
if encryptConf.Result != 0 {
return errors.New("get EncryptConf error:" + res.String())
}
// TODO: getUUID? needcaptcha
// login
loginData := map[string]string{
"version": "v2.0",
"apToken": "",
"appKey": appId,
"accountType": appConf.Data.AccountType,
"userName": encryptConf.Data.Pre + RsaEncode([]byte(d.Username), encryptConf.Data.PubKey, true),
"epd": encryptConf.Data.Pre + RsaEncode([]byte(d.Password), encryptConf.Data.PubKey, true),
"captchaType": "",
"validateCode": "",
"smsValidateCode": "",
"captchaToken": "",
"returnUrl": appConf.Data.ReturnUrl,
"mailSuffix": appConf.Data.MailSuffix,
"dynamicCheck": "FALSE",
"clientType": strconv.Itoa(appConf.Data.ClientType),
"cb_SaveName": "3",
"isOauth2": strconv.FormatBool(appConf.Data.IsOauth2),
"state": "",
"paramId": appConf.Data.ParamId,
}
res, err = d.client.R().SetHeaders(headers).SetFormData(loginData).Post("https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do")
if err != nil {
return err
}
log.Debugf("189 login resp body: %s", res.String())
loginResult := utils.Json.Get(res.Body(), "result").ToInt()
if loginResult != 0 {
return errors.New(utils.Json.Get(res.Body(), "msg").ToString())
}
return nil
}

View File

@ -8,6 +8,7 @@ import (
type Addition struct {
Username string `json:"username" required:"true"`
Password string `json:"password" required:"true"`
Cookie string `json:"cookie" help:"Fill in the cookie if need captcha"`
driver.RootID
}
@ -15,6 +16,7 @@ var config = driver.Config{
Name: "189Cloud",
LocalSort: true,
DefaultRoot: "-11",
Alert: `info|You can try to use 189PC driver if this driver does not work.`,
}
func init() {

View File

@ -11,16 +11,13 @@ import (
"io"
"math"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"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/model"
"github.com/alist-org/alist/v3/internal/setting"
"github.com/alist-org/alist/v3/pkg/utils"
myrand "github.com/alist-org/alist/v3/pkg/utils/random"
"github.com/go-resty/resty/v2"
@ -30,118 +27,118 @@ import (
// do others that not defined in Driver interface
func (d *Cloud189) login() error {
url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
b := ""
lt := ""
ltText := regexp.MustCompile(`lt = "(.+?)"`)
var res *resty.Response
var err error
for i := 0; i < 3; i++ {
res, err = d.client.R().Get(url)
if err != nil {
return err
}
// 已经登陆
if res.RawResponse.Request.URL.String() == "https://cloud.189.cn/web/main" {
return nil
}
b = res.String()
ltTextArr := ltText.FindStringSubmatch(b)
if len(ltTextArr) > 0 {
lt = ltTextArr[1]
break
} else {
<-time.After(time.Second)
}
}
if lt == "" {
return fmt.Errorf("get page: %s \nstatus: %d \nrequest url: %s\nredirect url: %s",
b, res.StatusCode(), res.RawResponse.Request.URL.String(), res.Header().Get("location"))
}
captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
paramId := regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(b)[1]
//reqId := regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(b)[1]
jRsakey := regexp.MustCompile(`j_rsaKey" value="(\S+)"`).FindStringSubmatch(b)[1]
vCodeID := regexp.MustCompile(`picCaptcha\.do\?token\=([A-Za-z0-9\&\=]+)`).FindStringSubmatch(b)[1]
vCodeRS := ""
if vCodeID != "" {
// need ValidateCode
log.Debugf("try to identify verification codes")
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
imgRes, err := d.client.R().SetHeaders(map[string]string{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0",
"Referer": "https://open.e.189.cn/api/logbox/oauth2/unifyAccountLogin.do",
"Sec-Fetch-Dest": "image",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-origin",
}).Get(u)
if err != nil {
return err
}
// Enter the verification code manually
//err = message.GetMessenger().WaitSend(message.Message{
// Type: "image",
// Content: "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgRes.Body()),
//}, 10)
//func (d *Cloud189) login() error {
// url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
// b := ""
// lt := ""
// ltText := regexp.MustCompile(`lt = "(.+?)"`)
// var res *resty.Response
// var err error
// for i := 0; i < 3; i++ {
// res, err = d.client.R().Get(url)
// if err != nil {
// return err
// }
//vCodeRS, err = message.GetMessenger().WaitReceive(30)
// use ocr api
vRes, err := base.RestyClient.R().SetMultipartField(
"image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
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())
}
vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
log.Debugln("code: ", vCodeRS)
}
userRsa := RsaEncode([]byte(d.Username), jRsakey, true)
passwordRsa := RsaEncode([]byte(d.Password), jRsakey, true)
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
var loginResp LoginResp
res, err = d.client.R().
SetHeaders(map[string]string{
"lt": lt,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
"Referer": "https://open.e.189.cn/",
"accept": "application/json;charset=UTF-8",
}).SetFormData(map[string]string{
"appKey": "cloud",
"accountType": "01",
"userName": "{RSA}" + userRsa,
"password": "{RSA}" + passwordRsa,
"validateCode": vCodeRS,
"captchaToken": captchaToken,
"returnUrl": returnUrl,
"mailSuffix": "@pan.cn",
"paramId": paramId,
"clientType": "10010",
"dynamicCheck": "FALSE",
"cb_SaveName": "1",
"isOauth2": "false",
}).Post(url)
if err != nil {
return err
}
err = utils.Json.Unmarshal(res.Body(), &loginResp)
if err != nil {
log.Error(err.Error())
return err
}
if loginResp.Result != 0 {
return fmt.Errorf(loginResp.Msg)
}
_, err = d.client.R().Get(loginResp.ToUrl)
return err
}
// // 已经登陆
// if res.RawResponse.Request.URL.String() == "https://cloud.189.cn/web/main" {
// return nil
// }
// b = res.String()
// ltTextArr := ltText.FindStringSubmatch(b)
// if len(ltTextArr) > 0 {
// lt = ltTextArr[1]
// break
// } else {
// <-time.After(time.Second)
// }
// }
// if lt == "" {
// return fmt.Errorf("get page: %s \nstatus: %d \nrequest url: %s\nredirect url: %s",
// b, res.StatusCode(), res.RawResponse.Request.URL.String(), res.Header().Get("location"))
// }
// captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
// returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
// paramId := regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(b)[1]
// //reqId := regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(b)[1]
// jRsakey := regexp.MustCompile(`j_rsaKey" value="(\S+)"`).FindStringSubmatch(b)[1]
// vCodeID := regexp.MustCompile(`picCaptcha\.do\?token\=([A-Za-z0-9\&\=]+)`).FindStringSubmatch(b)[1]
// vCodeRS := ""
// if vCodeID != "" {
// // need ValidateCode
// log.Debugf("try to identify verification codes")
// timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
// u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
// imgRes, err := d.client.R().SetHeaders(map[string]string{
// "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0",
// "Referer": "https://open.e.189.cn/api/logbox/oauth2/unifyAccountLogin.do",
// "Sec-Fetch-Dest": "image",
// "Sec-Fetch-Mode": "no-cors",
// "Sec-Fetch-Site": "same-origin",
// }).Get(u)
// if err != nil {
// return err
// }
// // Enter the verification code manually
// //err = message.GetMessenger().WaitSend(message.Message{
// // Type: "image",
// // Content: "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgRes.Body()),
// //}, 10)
// //if err != nil {
// // return err
// //}
// //vCodeRS, err = message.GetMessenger().WaitReceive(30)
// // use ocr api
// vRes, err := base.RestyClient.R().SetMultipartField(
// "image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
// 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())
// }
// vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
// log.Debugln("code: ", vCodeRS)
// }
// userRsa := RsaEncode([]byte(d.Username), jRsakey, true)
// passwordRsa := RsaEncode([]byte(d.Password), jRsakey, true)
// url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
// var loginResp LoginResp
// res, err = d.client.R().
// SetHeaders(map[string]string{
// "lt": lt,
// "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
// "Referer": "https://open.e.189.cn/",
// "accept": "application/json;charset=UTF-8",
// }).SetFormData(map[string]string{
// "appKey": "cloud",
// "accountType": "01",
// "userName": "{RSA}" + userRsa,
// "password": "{RSA}" + passwordRsa,
// "validateCode": vCodeRS,
// "captchaToken": captchaToken,
// "returnUrl": returnUrl,
// "mailSuffix": "@pan.cn",
// "paramId": paramId,
// "clientType": "10010",
// "dynamicCheck": "FALSE",
// "cb_SaveName": "1",
// "isOauth2": "false",
// }).Post(url)
// if err != nil {
// return err
// }
// err = utils.Json.Unmarshal(res.Body(), &loginResp)
// if err != nil {
// log.Error(err.Error())
// return err
// }
// if loginResp.Result != 0 {
// return fmt.Errorf(loginResp.Msg)
// }
// _, err = d.client.R().Get(loginResp.ToUrl)
// return err
//}
func (d *Cloud189) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
var e Error
@ -163,7 +160,7 @@ func (d *Cloud189) request(url string, method string, callback base.ReqCallback,
//log.Debug(res.String())
if e.ErrorCode != "" {
if e.ErrorCode == "InvalidSessionKey" {
err = d.login()
err = d.newLogin()
if err != nil {
return nil, err
}

View File

@ -72,18 +72,28 @@ func (d *Alias) list(ctx context.Context, dst, sub string) ([]model.Obj, error)
return nil, err
}
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(),
Size: obj.GetSize(),
Modified: obj.ModTime(),
IsFolder: obj.IsDir(),
}
if !ok {
return &objRes, nil
}
return &model.ObjThumb{
Object: objRes,
Thumbnail: model.Thumbnail{
Thumbnail: thumb,
},
}, nil
})
}
func (d *Alias) link(ctx context.Context, dst, sub string, args model.LinkArgs) (*model.Link, error) {
reqPath := stdpath.Join(dst, sub)
storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{NoLog: true})
storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{})
if err != nil {
return nil, err
}

View File

@ -2,16 +2,19 @@ package alist_v3
import (
"context"
"errors"
"io"
"fmt"
"net/http"
"path"
"strconv"
"strings"
"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/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
"github.com/go-resty/resty/v2"
)
type AListV3 struct {
@ -29,9 +32,39 @@ func (d *AListV3) GetAddition() driver.Additional {
func (d *AListV3) Init(ctx context.Context) error {
d.Addition.Address = strings.TrimSuffix(d.Addition.Address, "/")
// TODO login / refresh token
//op.MustSaveDriverStorage(d)
return nil
var resp common.Resp[MeResp]
_, err := d.request("/me", http.MethodGet, func(req *resty.Request) {
req.SetResult(&resp)
})
if 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 {
@ -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) {
url := d.Address + "/api/fs/list"
var resp common.Resp[FsListResp]
_, err := base.RestyClient.R().
SetResult(&resp).
SetHeader("Authorization", d.AccessToken).
SetBody(ListReq{
_, err := d.request("/fs/list", http.MethodPost, func(req *resty.Request) {
req.SetResult(&resp).SetBody(ListReq{
PageReq: model.PageReq{
Page: 1,
PerPage: 0,
},
Path: dir.GetPath(),
Password: d.Password,
Password: d.MetaPassword,
Refresh: false,
}).Post(url)
})
})
if err != nil {
return nil, err
}
if resp.Code != 200 {
return nil, errors.New(resp.Message)
}
var files []model.Obj
for _, f := range resp.Data.Content {
file := model.ObjThumb{
@ -76,107 +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) {
url := d.Address + "/api/fs/get"
var resp common.Resp[FsGetResp]
_, err := base.RestyClient.R().
SetResult(&resp).
SetHeader("Authorization", d.AccessToken).
SetBody(FsGetReq{
_, err := d.request("/fs/get", http.MethodPost, func(req *resty.Request) {
req.SetResult(&resp).SetBody(FsGetReq{
Path: file.GetPath(),
Password: d.Password,
}).Post(url)
Password: d.MetaPassword,
})
})
if err != nil {
return nil, err
}
if resp.Code != 200 {
return nil, errors.New(resp.Message)
}
return &model.Link{
URL: resp.Data.RawURL,
}, nil
}
func (d *AListV3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
url := d.Address + "/api/fs/mkdir"
var resp common.Resp[interface{}]
_, err := base.RestyClient.R().
SetResult(&resp).
SetHeader("Authorization", d.AccessToken).
SetBody(MkdirOrLinkReq{
_, err := d.request("/fs/mkdir", http.MethodPost, func(req *resty.Request) {
req.SetBody(MkdirOrLinkReq{
Path: path.Join(parentDir.GetPath(), dirName),
}).Post(url)
return checkResp(resp, err)
})
})
return err
}
func (d *AListV3) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
url := d.Address + "/api/fs/move"
var resp common.Resp[interface{}]
_, err := base.RestyClient.R().
SetResult(&resp).
SetHeader("Authorization", d.AccessToken).
SetBody(MoveCopyReq{
_, err := d.request("/fs/move", http.MethodPost, func(req *resty.Request) {
req.SetBody(MoveCopyReq{
SrcDir: path.Dir(srcObj.GetPath()),
DstDir: dstDir.GetPath(),
Names: []string{srcObj.GetName()},
}).Post(url)
return checkResp(resp, err)
})
})
return err
}
func (d *AListV3) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
url := d.Address + "/api/fs/rename"
var resp common.Resp[interface{}]
_, err := base.RestyClient.R().
SetResult(&resp).
SetHeader("Authorization", d.AccessToken).
SetBody(RenameReq{
_, err := d.request("/fs/rename", http.MethodPost, func(req *resty.Request) {
req.SetBody(RenameReq{
Path: srcObj.GetPath(),
Name: newName,
}).Post(url)
return checkResp(resp, err)
})
})
return err
}
func (d *AListV3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
url := d.Address + "/api/fs/copy"
var resp common.Resp[interface{}]
_, err := base.RestyClient.R().
SetResult(&resp).
SetHeader("Authorization", d.AccessToken).
SetBody(MoveCopyReq{
_, err := d.request("/fs/copy", http.MethodPost, func(req *resty.Request) {
req.SetBody(MoveCopyReq{
SrcDir: path.Dir(srcObj.GetPath()),
DstDir: dstDir.GetPath(),
Names: []string{srcObj.GetName()},
}).Post(url)
return checkResp(resp, err)
})
})
return err
}
func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error {
url := d.Address + "/api/fs/remove"
var resp common.Resp[interface{}]
_, err := base.RestyClient.R().
SetResult(&resp).
SetHeader("Authorization", d.AccessToken).
SetBody(RemoveReq{
_, err := d.request("/fs/remove", http.MethodPost, func(req *resty.Request) {
req.SetBody(RemoveReq{
Dir: path.Dir(obj.GetPath()),
Names: []string{obj.GetName()},
}).Post(url)
return checkResp(resp, err)
})
})
return err
}
func (d *AListV3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
url := d.Address + "/api/fs/put"
var resp common.Resp[interface{}]
fileBytes, err := io.ReadAll(stream.GetReadCloser())
if err != nil {
return nil
}
_, err = base.RestyClient.R().SetContext(ctx).
SetResult(&resp).
SetHeader("Authorization", d.AccessToken).
SetHeader("File-Path", path.Join(dstDir.GetPath(), stream.GetName())).
SetHeader("Password", d.Password).
_, err := d.request("/fs/put", http.MethodPut, func(req *resty.Request) {
req.SetHeader("File-Path", path.Join(dstDir.GetPath(), stream.GetName())).
SetHeader("Password", d.MetaPassword).
SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)).
SetBody(fileBytes).Put(url)
return checkResp(resp, err)
SetBody(stream.GetReadCloser())
})
return err
}
//func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {

View File

@ -8,14 +8,17 @@ import (
type Addition struct {
driver.RootPath
Address string `json:"url" required:"true"`
MetaPassword string `json:"meta_password"`
Username string `json:"username"`
Password string `json:"password"`
AccessToken string `json:"access_token"`
Token string `json:"token"`
}
var config = driver.Config{
Name: "AList V3",
LocalSort: true,
DefaultRoot: "/",
CheckStatus: true,
}
func init() {

View File

@ -63,3 +63,19 @@ type RemoveReq struct {
Dir string `json:"dir"`
Names []string `json:"names"`
}
type LoginResp struct {
Token string `json:"token"`
}
type MeResp struct {
Id int `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
BasePath string `json:"base_path"`
Role int `json:"role"`
Disabled bool `json:"disabled"`
Permission int `json:"permission"`
SsoId string `json:"sso_id"`
Otp bool `json:"otp"`
}

View File

@ -1,17 +1,56 @@
package alist_v3
import (
"errors"
"fmt"
"net/http"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
"github.com/go-resty/resty/v2"
)
func checkResp(resp common.Resp[interface{}], err error) error {
func (d *AListV3) login() error {
var resp common.Resp[LoginResp]
_, err := d.request("/auth/login", http.MethodPost, func(req *resty.Request) {
req.SetResult(&resp).SetBody(base.Json{
"username": d.Username,
"password": d.Password,
})
})
if err != nil {
return err
}
if resp.Code == 200 {
d.Token = resp.Data.Token
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
}

View File

@ -21,6 +21,9 @@ type AliyundriveOpen struct {
base string
DriveId string
limitList func(ctx context.Context, dir model.Obj) ([]model.Obj, error)
limitLink func(ctx context.Context, file model.Obj) (*model.Link, error)
}
func (d *AliyundriveOpen) Config() driver.Config {
@ -37,6 +40,8 @@ func (d *AliyundriveOpen) Init(ctx context.Context) error {
return err
}
d.DriveId = utils.Json.Get(res, "default_drive_id").ToString()
d.limitList = utils.LimitRateCtx(d.list, time.Second/4)
d.limitLink = utils.LimitRateCtx(d.link, time.Second)
return nil
}
@ -44,7 +49,7 @@ func (d *AliyundriveOpen) Drop(ctx context.Context) error {
return nil
}
func (d *AliyundriveOpen) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
func (d *AliyundriveOpen) list(ctx context.Context, dir model.Obj) ([]model.Obj, error) {
files, err := d.getFiles(dir.GetID())
if err != nil {
return nil, err
@ -54,7 +59,11 @@ func (d *AliyundriveOpen) List(ctx context.Context, dir model.Obj, args model.Li
})
}
func (d *AliyundriveOpen) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
func (d *AliyundriveOpen) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
return d.limitList(ctx, dir)
}
func (d *AliyundriveOpen) link(ctx context.Context, file model.Obj) (*model.Link, error) {
res, err := d.request("/adrive/v1.0/openFile/getDownloadUrl", http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{
"drive_id": d.DriveId,
@ -66,11 +75,17 @@ func (d *AliyundriveOpen) Link(ctx context.Context, file model.Obj, args model.L
return nil, err
}
url := utils.Json.Get(res, "url").ToString()
exp := time.Hour
return &model.Link{
URL: url,
Expiration: &exp,
}, nil
}
func (d *AliyundriveOpen) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
return d.limitLink(ctx, file)
}
func (d *AliyundriveOpen) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
_, err := d.request("/adrive/v1.0/openFile/create", http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{

View File

@ -33,7 +33,7 @@ var config = driver.Config{
func init() {
op.RegisterDriver(func() driver.Driver {
return &AliyundriveOpen{
base: "https://open.aliyundrive.com",
base: "https://openapi.aliyundrive.com",
}
})
}

View File

@ -65,7 +65,7 @@ func (d *AliyundriveOpen) request(uri, method string, callback base.ReqCallback,
}
isRetry := len(retry) > 0 && retry[0]
if e.Code != "" {
if !isRetry && e.Code == "AccessTokenInvalid" {
if !isRetry && (utils.SliceContains([]string{"AccessTokenInvalid", "AccessTokenExpired", "I400JD"}, e.Code) || d.AccessToken == "") {
err = d.refreshToken()
if err != nil {
return nil, err

View File

@ -19,6 +19,7 @@ import (
_ "github.com/alist-org/alist/v3/drivers/ftp"
_ "github.com/alist-org/alist/v3/drivers/google_drive"
_ "github.com/alist-org/alist/v3/drivers/google_photo"
_ "github.com/alist-org/alist/v3/drivers/ipfs_api"
_ "github.com/alist-org/alist/v3/drivers/lanzou"
_ "github.com/alist-org/alist/v3/drivers/local"
_ "github.com/alist-org/alist/v3/drivers/mediatrack"

View File

@ -249,7 +249,7 @@ func (d *BaiduPhoto) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
"isdir": "0",
"rtype": "1",
"ctype": "11",
"path": stream.GetName(),
"path": fmt.Sprintf("/%s", stream.GetName()),
"size": fmt.Sprint(stream.GetSize()),
"slice-md5": slice_md5,
"content-md5": content_md5,

View File

@ -18,7 +18,7 @@ func getTid() string {
// 检查名称
func checkName(name string) bool {
return len(name) <= 20 && regexp.MustCompile("[\u4e00-\u9fa5A-Za-z0-9_-]").MatchString(name)
return len(name) <= 50 && regexp.MustCompile("[\u4e00-\u9fa5A-Za-z0-9_-]").MatchString(name)
}
func toTime(t int64) *time.Time {

View File

@ -23,7 +23,7 @@ const (
)
var (
ErrNotSupportName = errors.New("only chinese and english, numbers and underscores are supported, and the length is no more than 20")
ErrNotSupportName = errors.New("only chinese and english, numbers and underscores are supported, and the length is no more than 50")
)
func (d *BaiduPhoto) Request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {

View File

@ -1,31 +1,48 @@
package base
import (
"crypto/tls"
"net/http"
"time"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/go-resty/resty/v2"
)
var NoRedirectClient *resty.Client
var RestyClient = NewRestyClient()
var HttpClient = &http.Client{}
var (
NoRedirectClient *resty.Client
RestyClient *resty.Client
HttpClient *http.Client
)
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
var DefaultTimeout = time.Second * 30
func init() {
func InitClient() {
NoRedirectClient = resty.New().SetRedirectPolicy(
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}),
)
).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify})
NoRedirectClient.SetHeader("user-agent", UserAgent)
RestyClient = NewRestyClient()
HttpClient = NewHttpClient()
}
func NewRestyClient() *resty.Client {
client := resty.New().
SetHeader("user-agent", UserAgent).
SetRetryCount(3).
SetTimeout(DefaultTimeout)
SetTimeout(DefaultTimeout).
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify})
return client
}
func NewHttpClient() *http.Client {
return &http.Client{
Timeout: DefaultTimeout,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify},
},
}
}

View File

@ -16,7 +16,6 @@ import (
type Cloudreve struct {
model.Storage
Addition
Cookie string
}
func (d *Cloudreve) Config() driver.Config {
@ -28,6 +27,9 @@ func (d *Cloudreve) GetAddition() driver.Additional {
}
func (d *Cloudreve) Init(ctx context.Context) error {
if d.Cookie != "" {
return nil
}
return d.login()
}

View File

@ -10,8 +10,9 @@ type Addition struct {
driver.RootPath
// define other
Address string `json:"address" required:"true"`
Username string `json:"username" required:"true"`
Password string `json:"password" required:"true"`
Username string `json:"username"`
Password string `json:"password"`
Cookie string `json:"cookie"`
}
var config = driver.Config{

View File

@ -49,12 +49,14 @@ func (d *Cloudreve) request(method string, path string, callback base.ReqCallbac
// 刷新 cookie
if r.Code == http.StatusUnauthorized && path != loginPath {
if d.Username != "" && d.Password != "" {
err = d.login()
if err != nil {
return err
}
return d.request(method, path, callback, out)
}
}
return errors.New(r.Msg)
}

View File

@ -18,6 +18,8 @@ type GoogleDrive struct {
model.Storage
Addition
AccessToken string
ServiceAccountFile int
ServiceAccountFileList []string
}
func (d *GoogleDrive) Config() driver.Config {

View File

@ -2,21 +2,134 @@ package google_drive
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"regexp"
"strconv"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
"github.com/golang-jwt/jwt/v4"
log "github.com/sirupsen/logrus"
)
// do others that not defined in Driver interface
type googleDriveServiceAccount struct {
//Type string `json:"type"`
//ProjectID string `json:"project_id"`
//PrivateKeyID string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
ClientEMail string `json:"client_email"`
//ClientID string `json:"client_id"`
//AuthURI string `json:"auth_uri"`
TokenURI string `json:"token_uri"`
//AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"`
//ClientX509CertURL string `json:"client_x509_cert_url"`
}
func (d *GoogleDrive) refreshToken() error {
// googleDriveServiceAccountFile gdsaFile
gdsaFile, gdsaFileErr := os.Stat(d.RefreshToken)
if gdsaFileErr == nil {
gdsaFileThis := d.RefreshToken
if gdsaFile.IsDir() {
if len(d.ServiceAccountFileList) <= 0 {
gdsaReadDir, gdsaDirErr := ioutil.ReadDir(d.RefreshToken)
if gdsaDirErr != nil {
log.Error("read dir fail")
return gdsaDirErr
}
var gdsaFileList []string
for _, fi := range gdsaReadDir {
if !fi.IsDir() {
match, _ := regexp.MatchString("^.*\\.json$", fi.Name())
if !match {
continue
}
gdsaDirText := d.RefreshToken
if d.RefreshToken[len(d.RefreshToken)-1:] != "/" {
gdsaDirText = d.RefreshToken + "/"
}
gdsaFileList = append(gdsaFileList, gdsaDirText+fi.Name())
}
}
d.ServiceAccountFileList = gdsaFileList
gdsaFileThis = d.ServiceAccountFileList[d.ServiceAccountFile]
d.ServiceAccountFile++
} else {
if d.ServiceAccountFile < len(d.ServiceAccountFileList) {
d.ServiceAccountFile++
} else {
d.ServiceAccountFile = 0
}
gdsaFileThis = d.ServiceAccountFileList[d.ServiceAccountFile]
}
}
gdsaFileThisContent, err := ioutil.ReadFile(gdsaFileThis)
if err != nil {
return err
}
// Now let's unmarshal the data into `payload`
var jsonData googleDriveServiceAccount
err = utils.Json.Unmarshal(gdsaFileThisContent, &jsonData)
if err != nil {
return err
}
gdsaScope := "https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.metadata https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/drive.scripts"
timeNow := time.Now()
var timeStart int64 = timeNow.Unix()
var timeEnd int64 = timeNow.Add(time.Minute * 60).Unix()
// load private key from string
privateKeyPem, _ := pem.Decode([]byte(jsonData.PrivateKey))
privateKey, _ := x509.ParsePKCS8PrivateKey(privateKeyPem.Bytes)
jwtToken := jwt.NewWithClaims(jwt.SigningMethodRS256,
jwt.MapClaims{
"iss": jsonData.ClientEMail,
"scope": gdsaScope,
"aud": jsonData.TokenURI,
"exp": timeEnd,
"iat": timeStart,
})
assertion, err := jwtToken.SignedString(privateKey)
if err != nil {
return err
}
var resp base.TokenResp
var e TokenError
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"assertion": assertion,
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
}).Post(jsonData.TokenURI)
if err != nil {
return err
}
log.Debug(res.String())
if e.Error != "" {
return fmt.Errorf(e.Error)
}
d.AccessToken = resp.AccessToken
return nil
}
if gdsaFileErr != nil && os.IsExist(gdsaFileErr) {
return gdsaFileErr
}
url := "https://www.googleapis.com/oauth2/v4/token"
var resp base.TokenResp
var e TokenError

128
drivers/ipfs_api/driver.go Normal file
View File

@ -0,0 +1,128 @@
package ipfs
import (
"context"
"fmt"
"net/url"
stdpath "path"
"path/filepath"
"strings"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model"
shell "github.com/ipfs/go-ipfs-api"
)
type IPFS struct {
model.Storage
Addition
sh *shell.Shell
gateURL *url.URL
}
func (d *IPFS) Config() driver.Config {
return config
}
func (d *IPFS) GetAddition() driver.Additional {
return &d.Addition
}
func (d *IPFS) Init(ctx context.Context) error {
d.sh = shell.NewShell(d.Endpoint)
gateURL, err := url.Parse(d.Gateway)
if err != nil {
return err
}
d.gateURL = gateURL
return nil
}
func (d *IPFS) Drop(ctx context.Context) error {
return nil
}
func (d *IPFS) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
path := dir.GetPath()
if path[len(path):] != "/" {
path += "/"
}
path_cid, err := d.sh.FilesStat(ctx, path)
if err != nil {
return nil, err
}
dirs, err := d.sh.List(path_cid.Hash)
if err != nil {
return nil, err
}
objlist := []model.Obj{}
for _, file := range dirs {
gateurl := *d.gateURL
gateurl.Path = "ipfs/" + file.Hash
gateurl.RawQuery = "filename=" + file.Name
objlist = append(objlist, &model.ObjectURL{
Object: model.Object{ID: file.Hash, Name: file.Name, Size: int64(file.Size), IsFolder: file.Type == 1},
Url: model.Url{Url: gateurl.String()},
})
}
return objlist, nil
}
func (d *IPFS) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
link := d.Gateway + "/ipfs/" + file.GetID() + "/?filename=" + file.GetName()
return &model.Link{URL: link}, nil
}
func (d *IPFS) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
path := parentDir.GetPath()
if path[len(path):] != "/" {
path += "/"
}
return d.sh.FilesMkdir(ctx, path+dirName)
}
func (d *IPFS) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
return d.sh.FilesMv(ctx, srcObj.GetPath(), dstDir.GetPath())
}
func (d *IPFS) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
newFileName := filepath.Dir(srcObj.GetPath()) + "/" + newName
return d.sh.FilesMv(ctx, srcObj.GetPath(), strings.ReplaceAll(newFileName, "\\", "/"))
}
func (d *IPFS) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
// TODO copy obj, optional
fmt.Println(srcObj.GetPath())
fmt.Println(dstDir.GetPath())
newFileName := dstDir.GetPath() + "/" + filepath.Base(srcObj.GetPath())
fmt.Println(newFileName)
return d.sh.FilesCp(ctx, srcObj.GetPath(), strings.ReplaceAll(newFileName, "\\", "/"))
}
func (d *IPFS) Remove(ctx context.Context, obj model.Obj) error {
// TODO remove obj, optional
return d.sh.FilesRm(ctx, obj.GetPath(), true)
}
func (d *IPFS) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
// TODO upload file, optional
_, err := d.sh.Add(stream, ToFiles(stdpath.Join(dstDir.GetPath(), stream.GetName())))
return err
}
func ToFiles(dstDir string) shell.AddOpts {
return func(rb *shell.RequestBuilder) error {
rb.Option("to-files", dstDir)
return nil
}
}
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
// return nil, errs.NotSupport
//}
var _ driver.Driver = (*IPFS)(nil)

25
drivers/ipfs_api/meta.go Normal file
View File

@ -0,0 +1,25 @@
package ipfs
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
Endpoint string `json:"endpoint" default:"http://127.0.0.1:5001"`
Gateway string `json:"gateway" default:"https://ipfs.io"`
}
var config = driver.Config{
Name: "IPFS API",
DefaultRoot: "/",
LocalSort: true,
}
func init() {
op.RegisterDriver(func() driver.Driver {
return &IPFS{}
})
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"net/http"
"regexp"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/driver"
@ -15,8 +14,6 @@ import (
"github.com/go-resty/resty/v2"
)
var upClient = base.NewRestyClient().SetTimeout(120 * time.Second)
type LanZou struct {
Addition
model.Storage

View File

@ -7,6 +7,7 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/alist-org/alist/v3/drivers/base"
@ -16,6 +17,9 @@ import (
log "github.com/sirupsen/logrus"
)
var upClient *resty.Client
var once sync.Once
func (d *LanZou) doupload(callback base.ReqCallback, resp interface{}) ([]byte, error) {
return d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
req.SetQueryParam("uid", d.uid)
@ -64,6 +68,9 @@ func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{},
func (d *LanZou) request(url string, method string, callback base.ReqCallback, up bool) ([]byte, error) {
var req *resty.Request
if up {
once.Do(func() {
upClient = base.NewRestyClient().SetTimeout(120 * time.Second)
})
req = upClient.R()
} else {
req = base.RestyClient.R()

View File

@ -1,7 +1,6 @@
package local
import (
"bytes"
"context"
"errors"
"fmt"
@ -20,7 +19,6 @@ import (
"github.com/alist-org/alist/v3/internal/sign"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
"github.com/disintegration/imaging"
_ "golang.org/x/image/webp"
)
@ -54,6 +52,12 @@ func (d *Local) Init(ctx context.Context) error {
}
d.Addition.RootFolderPath = abs
}
if d.ThumbCacheFolder != "" && !utils.Exists(d.ThumbCacheFolder) {
err := os.MkdirAll(d.ThumbCacheFolder, os.FileMode(d.mkdirPerm))
if err != nil {
return err
}
}
return nil
}
@ -135,36 +139,18 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
fullPath := file.GetPath()
var link model.Link
if args.Type == "thumb" && utils.Ext(file.GetName()) != "svg" {
var srcBuf *bytes.Buffer
if utils.GetFileType(file.GetName()) == conf.VIDEO {
videoBuf, err := GetSnapshot(fullPath, 10)
buf, thumbPath, err := d.getThumb(file)
if err != nil {
return nil, err
}
srcBuf = videoBuf
} else {
imgData, err := os.ReadFile(fullPath)
if err != nil {
return nil, err
}
imgBuf := bytes.NewBuffer(imgData)
srcBuf = imgBuf
}
image, err := imaging.Decode(srcBuf)
if err != nil {
return nil, err
}
thumbImg := imaging.Resize(image, 144, 0, imaging.Lanczos)
var buf bytes.Buffer
err = imaging.Encode(&buf, thumbImg, imaging.PNG)
if err != nil {
return nil, err
}
size := buf.Len()
link.Data = io.NopCloser(&buf)
link.Header = http.Header{
"Content-Length": []string{strconv.Itoa(size)},
"Content-Type": []string{"image/png"},
}
if thumbPath != nil {
link.FilePath = thumbPath
} else {
link.Data = io.NopCloser(buf)
link.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
}
} else {
link.FilePath = &fullPath

View File

@ -8,6 +8,7 @@ import (
type Addition struct {
driver.RootPath
Thumbnail bool `json:"thumbnail" required:"true" help:"enable thumbnail"`
ThumbCacheFolder string `json:"thumb_cache_folder"`
ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"`
MkdirPerm string `json:"mkdir_perm" default:"777"`
}

View File

@ -7,7 +7,12 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/disintegration/imaging"
ffmpeg "github.com/u2takey/ffmpeg-go"
)
@ -55,3 +60,52 @@ func readDir(dirname string) ([]fs.FileInfo, error) {
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
return list, nil
}
func (d *Local) getThumb(file model.Obj) (*bytes.Buffer, *string, error) {
fullPath := file.GetPath()
thumbPrefix := "alist_thumb_"
thumbName := thumbPrefix + utils.GetMD5Encode(fullPath) + ".png"
if d.ThumbCacheFolder != "" {
// skip if the file is a thumbnail
if strings.HasPrefix(file.GetName(), thumbPrefix) {
return nil, &fullPath, nil
}
thumbPath := filepath.Join(d.ThumbCacheFolder, thumbName)
if utils.Exists(thumbPath) {
return nil, &thumbPath, nil
}
}
var srcBuf *bytes.Buffer
if utils.GetFileType(file.GetName()) == conf.VIDEO {
videoBuf, err := GetSnapshot(fullPath, 10)
if err != nil {
return nil, nil, err
}
srcBuf = videoBuf
} else {
imgData, err := os.ReadFile(fullPath)
if err != nil {
return nil, nil, err
}
imgBuf := bytes.NewBuffer(imgData)
srcBuf = imgBuf
}
image, err := imaging.Decode(srcBuf)
if err != nil {
return nil, nil, err
}
thumbImg := imaging.Resize(image, 144, 0, imaging.Lanczos)
var buf bytes.Buffer
err = imaging.Encode(&buf, thumbImg, imaging.PNG)
if err != nil {
return nil, nil, err
}
if d.ThumbCacheFolder != "" {
err = os.WriteFile(filepath.Join(d.ThumbCacheFolder, thumbName), buf.Bytes(), 0666)
if err != nil {
return nil, nil, err
}
}
return &buf, nil, nil
}

View File

@ -193,6 +193,9 @@ func (d *Onedrive) upBig(ctx context.Context, dstDir model.Obj, stream model.Fil
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 err != nil {
return err
}
if res.StatusCode != 201 && res.StatusCode != 202 {
data, _ := io.ReadAll(res.Body)
res.Body.Close()

View File

@ -184,6 +184,9 @@ func (d *OnedriveAPP) upBig(ctx context.Context, dstDir model.Obj, stream model.
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 err != nil {
return err
}
if res.StatusCode != 201 && res.StatusCode != 202 {
data, _ := io.ReadAll(res.Body)
res.Body.Close()

View File

@ -5,15 +5,18 @@ import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
"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/http_range"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
@ -56,17 +59,66 @@ func (d *Quark) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
"fids": []string{file.GetID()},
}
var resp DownResp
ua := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch"
_, err := d.request("/file/download", http.MethodPost, func(req *resty.Request) {
req.SetBody(data)
req.SetHeader("User-Agent", ua).
SetBody(data)
}, &resp)
if err != nil {
return nil, err
}
u := resp.Data[0].DownloadUrl
start, end := int64(0), file.GetSize()
return &model.Link{
URL: resp.Data[0].DownloadUrl,
Header: http.Header{
"Cookie": []string{d.Cookie},
"Referer": []string{"https://pan.quark.cn"},
Handle: func(w http.ResponseWriter, r *http.Request) error {
if rg := r.Header.Get("Range"); rg != "" {
parseRange, err := http_range.ParseRange(rg, file.GetSize())
if err != nil {
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 := base.HttpClient.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
}

View File

@ -14,7 +14,7 @@ type Addition struct {
var config = driver.Config{
Name: "Quark",
OnlyProxy: true,
OnlyLocal: true,
DefaultRoot: "0",
NoOverwriteUpload: true,
}

View File

@ -7,6 +7,7 @@ import (
"io"
"net/url"
stdpath "path"
"strings"
"time"
"github.com/alist-org/alist/v3/internal/driver"
@ -75,6 +76,9 @@ func (d *S3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*mo
if d.CustomHost != "" {
err = req.Build()
link = req.HTTPRequest.URL.String()
if d.RemoveBucket {
link = strings.Replace(link, "/"+d.Bucket, "", 1)
}
} else {
link, err = req.Presign(time.Hour * time.Duration(d.SignURLExpire))
}

View File

@ -17,6 +17,7 @@ type Addition struct {
Placeholder string `json:"placeholder"`
ForcePathStyle bool `json:"force_path_style"`
ListObjectVersion string `json:"list_object_version" type:"select" options:"v1,v2" default:"v1"`
RemoveBucket bool `json:"remove_bucket" help:"Remove bucket name from path when using custom host."`
}
var config = driver.Config{

View File

@ -125,6 +125,9 @@ func (d *Teambition) Remove(ctx context.Context, obj model.Obj) error {
}
func (d *Teambition) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
if d.UseS3UploadMethod {
return d.newUpload(ctx, dstDir, stream, up)
}
res, err := d.request("/api/v2/users/me", http.MethodGet, nil, nil)
if err != nil {
return err

View File

@ -12,6 +12,7 @@ type Addition struct {
driver.RootID
OrderBy string `json:"order_by" type:"select" options:"fileName,fileSize,updated,created" default:"fileName"`
OrderDirection string `json:"order_direction" type:"select" options:"Asc,Desc" default:"Asc"`
UseS3UploadMethod bool `json:"use_s3_upload_method" default:"true"`
}
var config = driver.Config{

View File

@ -66,3 +66,24 @@ type ChunkUpload struct {
PreviewExt string `json:"previewExt"`
LastUploadTime interface{} `json:"lastUploadTime"`
}
type UploadToken struct {
Sdk struct {
Endpoint string `json:"endpoint"`
Region string `json:"region"`
S3ForcePathStyle bool `json:"s3ForcePathStyle"`
Credentials struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
SessionToken string `json:"sessionToken"`
} `json:"credentials"`
} `json:"sdk"`
Upload struct {
Bucket string `json:"Bucket"`
Key string `json:"Key"`
ContentDisposition string `json:"ContentDisposition"`
ContentType string `json:"ContentType"`
} `json:"upload"`
Token string `json:"token"`
DownloadUrl string `json:"downloadUrl"`
}

View File

@ -7,13 +7,16 @@ import (
"io"
"net/http"
"strconv"
"strings"
"time"
"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/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
@ -210,17 +213,56 @@ func (d *Teambition) finishUpload(file *FileUpload, parentId string) error {
return err
}
func getBetweenStr(str, start, end string) string {
n := strings.Index(str, start)
if n == -1 {
return ""
func (d *Teambition) newUpload(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
var uploadToken UploadToken
_, err := d.request("/api/awos/upload-token", http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{
"category": "work",
"fileName": stream.GetName(),
"fileSize": stream.GetSize(),
"fileType": stream.GetMimetype(),
"payload": base.Json{
"involveMembers": []struct{}{},
"visible": "members",
},
"scope": "project:" + d.ProjectID,
})
}, &uploadToken)
if err != nil {
return err
}
n = n + len(start)
str = string([]byte(str)[n:])
m := strings.Index(str, end)
if m == -1 {
return ""
cfg := &aws.Config{
Credentials: credentials.NewStaticCredentials(
uploadToken.Sdk.Credentials.AccessKeyId, uploadToken.Sdk.Credentials.SecretAccessKey, uploadToken.Sdk.Credentials.SessionToken),
Region: &uploadToken.Sdk.Region,
Endpoint: &uploadToken.Sdk.Endpoint,
S3ForcePathStyle: &uploadToken.Sdk.S3ForcePathStyle,
}
str = string([]byte(str)[:m])
return str
ss, err := session.NewSession(cfg)
if err != nil {
return err
}
uploader := s3manager.NewUploader(ss)
input := &s3manager.UploadInput{
Bucket: &uploadToken.Upload.Bucket,
Key: &uploadToken.Upload.Key,
ContentDisposition: &uploadToken.Upload.ContentDisposition,
ContentType: &uploadToken.Upload.ContentType,
Body: stream,
}
_, err = uploader.UploadWithContext(ctx, input)
if err != nil {
return err
}
// finish upload
_, err = d.request("/api/works", http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{
"fileTokens": []string{uploadToken.Token},
"involveMembers": []struct{}{},
"visible": "members",
"works": []struct{}{},
"_parentId": dstDir.GetID(),
})
}, nil)
return err
}

View File

@ -10,6 +10,7 @@ import (
"net/url"
"strings"
"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"
@ -31,7 +32,7 @@ func (d *Trainbit) GetAddition() driver.Additional {
}
func (d *Trainbit) Init(ctx context.Context) error {
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
base.HttpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
var err error
@ -135,7 +136,7 @@ func (d *Trainbit) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
return err
}
req.Header.Set("Content-Type", "text/json; charset=UTF-8")
_, err = http.DefaultClient.Do(req)
_, err = base.HttpClient.Do(req)
return err
}

View File

@ -9,6 +9,7 @@ import (
"strings"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/model"
)
@ -38,7 +39,7 @@ func get(url string, apiKey string, AUSHELLPORTAL string) (*http.Response, error
Value: apiKey,
MaxAge: 2 * 60,
})
res, err := http.DefaultClient.Do(req)
res, err := base.HttpClient.Do(req)
return res, err
}
@ -65,7 +66,7 @@ func postForm(endpoint string, data url.Values, apiExpiredate string, apiKey str
Value: apiKey,
MaxAge: 2 * 60,
})
res, err := http.DefaultClient.Do(req)
res, err := base.HttpClient.Do(req)
return res, err
}

View File

@ -78,19 +78,19 @@ func (d *WebDav) MakeDir(ctx context.Context, parentDir model.Obj, dirName strin
}
func (d *WebDav) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
return d.client.Rename(srcObj.GetPath(), path.Join(dstDir.GetPath(), srcObj.GetName()), true)
return d.client.Rename(getPath(srcObj), path.Join(dstDir.GetPath(), srcObj.GetName()), true)
}
func (d *WebDav) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
return d.client.Rename(srcObj.GetPath(), path.Join(path.Dir(srcObj.GetPath()), newName), true)
return d.client.Rename(getPath(srcObj), path.Join(path.Dir(srcObj.GetPath()), newName), true)
}
func (d *WebDav) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
return d.client.Copy(srcObj.GetPath(), path.Join(dstDir.GetPath(), srcObj.GetName()), true)
return d.client.Copy(getPath(srcObj), path.Join(dstDir.GetPath(), srcObj.GetName()), true)
}
func (d *WebDav) Remove(ctx context.Context, obj model.Obj) error {
return d.client.RemoveAll(obj.GetPath())
return d.client.RemoveAll(getPath(obj))
}
func (d *WebDav) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {

View File

@ -12,6 +12,7 @@ import (
"strings"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"golang.org/x/net/publicsuffix"
)
@ -185,7 +186,7 @@ func (ca *CookieAuth) getSPToken() (*SuccessResponse, error) {
return nil, err
}
client := &http.Client{}
client := base.HttpClient
resp, err := client.Do(req)
if err != nil {
return nil, err

View File

@ -4,6 +4,7 @@ import (
"net/http"
"github.com/alist-org/alist/v3/drivers/webdav/odrvcookie"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/gowebdav"
)
@ -29,3 +30,10 @@ func (d *WebDav) setClient() error {
d.client = c
return nil
}
func getPath(obj model.Obj) string {
if obj.IsDir() {
return obj.GetPath() + "/"
}
return obj.GetPath()
}

48
go.mod
View File

@ -1,12 +1,12 @@
module github.com/alist-org/alist/v3
go 1.19
go 1.20
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.7
github.com/aws/aws-sdk-go v1.44.262
github.com/blevesearch/bleve/v2 v2.3.8
github.com/caarlos0/env/v7 v7.1.0
github.com/deckarep/golang-set/v2 v2.3.0
github.com/disintegration/imaging v1.6.2
@ -18,6 +18,7 @@ require (
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/hirochachacha/go-smb2 v1.1.0
github.com/ipfs/go-ipfs-api v0.6.0
github.com/jlaffaye/ftp v0.1.0
github.com/json-iterator/go v1.1.12
github.com/maruel/natural v1.1.0
@ -31,9 +32,9 @@ require (
github.com/u2takey/ffmpeg-go v0.4.1
github.com/upyun/go-sdk/v3 v3.0.4
github.com/winfsp/cgofuse v1.5.0
golang.org/x/crypto v0.7.0
golang.org/x/image v0.6.0
golang.org/x/net v0.8.0
golang.org/x/crypto v0.9.0
golang.org/x/image v0.7.0
golang.org/x/net v0.10.0
gorm.io/driver/mysql v1.4.7
gorm.io/driver/postgres v1.4.8
gorm.io/driver/sqlite v1.4.4
@ -46,6 +47,7 @@ require (
github.com/aead/ecdh v0.2.0 // indirect
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible // indirect
github.com/andreburgaud/crypt2go v1.1.0 // indirect
github.com/benbjohnson/clock v1.3.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.17 // indirect
@ -61,11 +63,13 @@ require (
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.9 // indirect
github.com/blevesearch/zapx/v15 v15.3.10 // 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
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/gaoyb7/115drive-webdav v0.1.8 // indirect
github.com/geoffgarside/ber v1.1.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
@ -75,40 +79,58 @@ require (
github.com/go-sql-driver/mysql v1.7.0 // indirect
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.3 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/ipfs/boxo v0.8.0 // indirect
github.com/ipfs/go-cid v0.4.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.3.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
github.com/libp2p/go-libp2p v0.26.3 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr v0.8.0 // indirect
github.com/multiformats/go-multibase v0.1.1 // indirect
github.com/multiformats/go-multicodec v0.8.1 // indirect
github.com/multiformats/go-multihash v0.2.1 // indirect
github.com/multiformats/go-multistream v0.4.1 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/u2takey/go-utils v0.3.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c // indirect
go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.1.7 // indirect
)

100
go.sum
View File

@ -15,10 +15,16 @@ github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9
github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.44.194 h1:1ZDK+QDcc5oRbZGgRZSz561eR8XVizXCeGpoZKo33NU=
github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I=
github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
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.7 h1:nIfIrhv28tvgBpbVF8Dq7/U1zW/YiwSqg/PBgE3x8bo=
github.com/blevesearch/bleve/v2 v2.3.7/go.mod h1:2tToYD6mDeseIA13jcZiEEqYrVLg6xdk0v6+F7dWquU=
github.com/blevesearch/bleve/v2 v2.3.8 h1:IqFyMJ73n4gY8AmVqM8Sa6EtAZ5beE8yramVqCvs2kQ=
github.com/blevesearch/bleve/v2 v2.3.8/go.mod h1:Lh9aZEHrLKxwPnW4z4lsBEGnflZQ1V/aWP/t+htsiDw=
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.17 h1:AguzI6/5mHXapzB0gE9IKWo+wWPHZmXZoscHcjFgAFA=
@ -49,6 +55,8 @@ github.com/blevesearch/zapx/v14 v14.3.7 h1:gfe+fbWslDWP/evHLtp/GOvmNM3sw1BbqD7Lh
github.com/blevesearch/zapx/v14 v14.3.7/go.mod h1:9J/RbOkqZ1KSjmkOes03AkETX7hrXT0sFMpWH4ewC4w=
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/blevesearch/zapx/v15 v15.3.10 h1:bQ9ZxJCj6rKp873EuVJu2JPxQ+EWQZI1cjJGeroovaQ=
github.com/blevesearch/zapx/v15 v15.3.10/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=
@ -58,16 +66,22 @@ github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg=
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g=
github.com/deckarep/golang-set/v2 v2.3.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
@ -109,10 +123,11 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
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.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/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=
@ -131,6 +146,12 @@ github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRK
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ipfs/boxo v0.8.0 h1:UdjAJmHzQHo/j3g3b1bAcAXCj/GM6iTwvSlBDvPBNBs=
github.com/ipfs/boxo v0.8.0/go.mod h1:RIsi4CnTyQ7AUsNn5gXljJYZlQrHBMnJp94p73liFiA=
github.com/ipfs/go-cid v0.4.0 h1:a4pdZq0sx6ZSxbCizebnKiMCx/xI/aBBFlB73IgH4rA=
github.com/ipfs/go-cid v0.4.0/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
github.com/ipfs/go-ipfs-api v0.6.0 h1:JARgG0VTbjyVhO5ZfesnbXv9wTcMvoKRBLF1SzJqzmg=
github.com/ipfs/go-ipfs-api v0.6.0/go.mod h1:iDC2VMwN9LUpQV/GzEeZ2zNqd8NUdRmWcFM+K/6odf0=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
@ -154,8 +175,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -168,6 +191,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM=
github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro=
github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM=
github.com/libp2p/go-libp2p v0.26.3/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8=
github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
@ -175,6 +204,10 @@ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPn
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -182,8 +215,26 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU=
github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs=
github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI=
github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8=
github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8=
github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108=
github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc=
github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo=
github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 h1:dg/EaaJLPIg4xn2kaZil7Ax3wfoxcFXaBwyOTlcz5AI=
@ -213,6 +264,8 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
@ -230,8 +283,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca h1:I9rVnNXdIkij4UvMT7OmKhH9sOIvS8iXkxfPdnn9wQA=
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@ -247,6 +300,8 @@ github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZg
github.com/upyun/go-sdk/v3 v3.0.4 h1:2DCJa/Yi7/3ZybT9UCPATSzvU3wpPPxhXinNlb1Hi8Q=
github.com/upyun/go-sdk/v3 v3.0.4/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c h1:GGsyl0dZ2jJgVT+VvWBf/cNijrHRhkrTjkmp5wg7li0=
github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c/go.mod h1:xxcJeBb7SIUl/Wzkz1eVKJE/CB34YNrqX2TQI6jY9zs=
github.com/winfsp/cgofuse v1.5.0 h1:MsBP7Mi/LiJf/7/F3O/7HjjR009ds6KCdqXzKpZSWxI=
github.com/winfsp/cgofuse v1.5.0/go.mod h1:h3awhoUOcn2VYVKCwDaYxSLlZwnyK+A8KaDoLUp2lbU=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@ -265,11 +320,13 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -281,8 +338,10 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -300,18 +359,21 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -319,8 +381,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
@ -332,9 +394,9 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
@ -365,5 +427,7 @@ gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@ -1,7 +1,6 @@
package bootstrap
import (
"crypto/tls"
"net/url"
"os"
"path/filepath"
@ -78,9 +77,7 @@ func InitConfig() {
log.Fatalf("create temp dir error: %+v", err)
}
log.Debugf("config: %+v", conf.Conf)
if conf.Conf.TlsInsecureSkipVerify {
base.RestyClient = base.RestyClient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}
base.InitClient()
initURL()
}

View File

@ -85,6 +85,8 @@ func InitialSettings() []model.SettingItem {
{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: conf.AllowIndexed, Value: "false", Type: conf.TypeBool, Group: model.SITE},
{Key: conf.AllowMounted, Value: "true", Type: conf.TypeBool, Group: model.SITE},
{Key: conf.RobotsTxt, Value: "User-agent: *\nAllow: /", Type: conf.TypeText, Group: model.SITE},
// 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.Favicon, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeString, Group: model.STYLE},
@ -152,9 +154,13 @@ func InitialSettings() []model.SettingItem {
// SSO settings
{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.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
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},

View File

@ -14,6 +14,8 @@ const (
SiteTitle = "site_title"
Announcement = "announcement"
AllowIndexed = "allow_indexed"
AllowMounted = "allow_mounted"
RobotsTxt = "robots_txt"
Logo = "logo"
Favicon = "favicon"
@ -59,6 +61,10 @@ const (
SSOClientSecret = "sso_client_secret"
SSOLoginEnabled = "sso_login_enabled"
SSOLoginplatform = "sso_login_platform"
SSOOrganizationName = "sso_organization_name"
SSOApplicationName = "sso_application_name"
SSOEndpointName = "sso_endpoint_name"
SSOJwtPublicKey = "sso_jwt_public_key"
// qbittorrent
QbittorrentUrl = "qbittorrent_url"

View File

@ -21,7 +21,9 @@ type ListArgs struct {
func List(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
res, err := list(ctx, path, args)
if err != nil {
if !args.NoLog {
log.Errorf("failed list %s: %+v", path, err)
}
return nil, err
}
return res, nil
@ -33,8 +35,10 @@ type GetArgs struct {
func Get(ctx context.Context, path string, args *GetArgs) (model.Obj, error) {
res, err := get(ctx, path)
if err != nil && !args.NoLog {
if err != nil {
if !args.NoLog {
log.Errorf("failed get %s: %+v", path, err)
}
return nil, err
}
return res, nil
@ -106,12 +110,11 @@ func PutAsTask(dstDirPath string, file *model.FileStream) error {
}
type GetStoragesArgs struct {
NoLog bool
}
func GetStorage(path string, args *GetStoragesArgs) (driver.Driver, error) {
storageDriver, _, err := op.GetStorageAndActualPath(path)
if err != nil && !args.NoLog {
if err != nil {
return nil, err
}
return storageDriver, nil

View File

@ -12,8 +12,8 @@ import (
// List files
func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
meta := ctx.Value("meta").(*model.Meta)
user := ctx.Value("user").(*model.User)
meta, _ := ctx.Value("meta").(*model.Meta)
user, _ := ctx.Value("user").(*model.User)
virtualFiles := op.GetStorageVirtualFilesByPath(path)
storage, actualPath, err := op.GetStorageAndActualPath(path)
if err != nil && len(virtualFiles) == 0 {
@ -26,7 +26,9 @@ func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error)
ReqPath: path,
}, args.Refresh)
if err != nil {
log.Errorf("%+v", err)
if !args.NoLog {
log.Errorf("fs/list: %+v", err)
}
if len(virtualFiles) == 0 {
return nil, errors.WithMessage(err, "failed get objs")
}
@ -43,7 +45,7 @@ func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error)
func whetherHide(user *model.User, meta *model.Meta, path string) bool {
// if is admin, don't hide
if user.CanSeeHides() {
if user == nil || user.CanSeeHides() {
return false
}
// if meta is nil, don't hide

View File

@ -8,6 +8,7 @@ import (
stdpath "path"
"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/op"
@ -33,8 +34,6 @@ func containsByName(files []model.Obj, file model.Obj) bool {
return false
}
var httpClient = &http.Client{}
func getFileStreamFromLink(file model.Obj, link *model.Link) (*model.FileStream, error) {
var rc io.ReadCloser
mimetype := utils.GetMimeType(file.GetName())
@ -60,7 +59,7 @@ func getFileStreamFromLink(file model.Obj, link *model.Link) (*model.FileStream,
for h, val := range link.Header {
req.Header[h] = val
}
res, err := httpClient.Do(req)
res, err := base.HttpClient.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "failed to get response for %s", link.URL)
}

View File

@ -23,6 +23,7 @@ type Link struct {
Status int // status maybe 200 or 206, etc
FilePath *string // local file, return the filepath
Expiration *time.Duration // url expiration time
Handle func(w http.ResponseWriter, r *http.Request) error `json:"-"` // custom handler
}
type OtherArgs struct {

View File

@ -63,7 +63,7 @@ func getMainItems(config driver.Config) []driver.Item {
Name: "mount_path",
Type: conf.TypeString,
Required: true,
Help: "",
Help: "The path you want to mount to, it is unique and cannot be repeated",
}, {
Name: "order",
Type: conf.TypeNumber,

View File

@ -134,6 +134,7 @@ func (c *client) AddFromLink(link string, savePath string, id string) error {
addField("urls", link)
addField("savepath", savePath)
addField("tags", "alist-"+id)
addField("autoTMM", "false")
if err != nil {
return err
}

View File

@ -12,7 +12,7 @@ import (
func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (req *http.Response, err error) {
var r *http.Request
var retryBuf io.Reader
canRetry := true
if body != nil {
// If the authorization fails, we will need to restart reading
// from the passed body stream.
@ -25,6 +25,8 @@ func (c *Client) req(method, path string, body io.Reader, intercept func(*http.R
return
}
retryBuf = body
} else if method == http.MethodPut {
canRetry = false
} else {
buff := &bytes.Buffer{}
retryBuf = buff
@ -83,7 +85,9 @@ func (c *Client) req(method, path string, body io.Reader, intercept func(*http.R
// retryBuf will be nil if body was nil initially so no check
// for body == nil is required here.
if canRetry {
return c.req(method, path, retryBuf, intercept)
}
} else if rs.StatusCode == 401 {
return rs, newPathError("Authorize", c.root, rs.StatusCode)
}
@ -193,7 +197,7 @@ func (c *Client) copymove(method string, oldpath string, newpath string, overwri
}
func (c *Client) put(path string, stream io.Reader, callback func(r *http.Request)) (status int, err error) {
rs, err := c.req("PUT", path, stream, callback)
rs, err := c.req(http.MethodPut, path, stream, callback)
if err != nil {
return
}

View File

@ -3,6 +3,7 @@ package task
import (
"context"
"runtime"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
@ -63,11 +64,17 @@ func (t Task[K]) GetErrMsg() string {
return t.Error.Error()
}
func getCurrentGoroutineStack() string {
buf := make([]byte, 1<<16)
n := runtime.Stack(buf, false)
return string(buf[:n])
}
func (t *Task[K]) run() {
t.state = RUNNING
defer func() {
if err := recover(); err != nil {
log.Errorf("error [%+v] while run task [%s]", err, t.Name)
log.Errorf("error [%s] while run task [%s],stack trace:\n%s", err, t.Name, getCurrentGoroutineStack())
t.Error = errors.Errorf("panic: %+v", err)
t.state = ERRORED
}

114
pkg/utils/fn_limiter.go Normal file
View File

@ -0,0 +1,114 @@
package utils
import (
"context"
"reflect"
"time"
)
func LimitRateReflect(f interface{}, interval time.Duration) func(...interface{}) []interface{} {
// Use closures to save the time of the last function call
var lastCall time.Time
fValue := reflect.ValueOf(f)
fType := fValue.Type()
if fType.Kind() != reflect.Func {
panic("f must be a function")
}
//if fType.NumOut() == 0 {
// panic("f must have at least one output parameter")
//}
outCount := fType.NumOut()
outTypes := make([]reflect.Type, outCount)
for i := 0; i < outCount; i++ {
outTypes[i] = fType.Out(i)
}
// Returns a new function, which is used to limit the function to be called only once at a specified time interval
return func(args ...interface{}) []interface{} {
// Calculate the time interval since the last function call
elapsed := time.Since(lastCall)
// If the interval is less than the specified time, wait for the remaining time
if elapsed < interval {
time.Sleep(interval - elapsed)
}
// Update the time of the last function call
lastCall = time.Now()
inCount := fType.NumIn()
in := make([]reflect.Value, inCount)
if len(args) != inCount {
panic("wrong number of arguments")
}
for i := 0; i < inCount; i++ {
in[i] = reflect.ValueOf(args[i])
}
out := fValue.Call(in)
if len(out) != outCount {
panic("function returned wrong number of values")
}
result := make([]interface{}, outCount)
for i := 0; i < outCount; i++ {
result[i] = out[i].Interface()
}
return result
}
}
type Fn[T any, R any] func(T) (R, error)
type FnCtx[T any, R any] func(context.Context, T) (R, error)
func LimitRate[T any, R any](f Fn[T, R], interval time.Duration) Fn[T, R] {
// Use closures to save the time of the last function call
var lastCall time.Time
// Returns a new function, which is used to limit the function to be called only once at a specified time interval
return func(t T) (R, error) {
// Calculate the time interval since the last function call
elapsed := time.Since(lastCall)
// If the interval is less than the specified time, wait for the remaining time
if elapsed < interval {
time.Sleep(interval - elapsed)
}
// Update the time of the last function call
lastCall = time.Now()
// Execute the function that needs to be limited
return f(t)
}
}
func LimitRateCtx[T any, R any](f FnCtx[T, R], interval time.Duration) FnCtx[T, R] {
// Use closures to save the time of the last function call
var lastCall time.Time
// Returns a new function, which is used to limit the function to be called only once at a specified time interval
return func(ctx context.Context, t T) (R, error) {
// Calculate the time interval since the last function call
elapsed := time.Since(lastCall)
// If the interval is less than the specified time, wait for the remaining time
if elapsed < interval {
t := time.NewTimer(interval - elapsed)
select {
case <-ctx.Done():
t.Stop()
var zero R
return zero, ctx.Err()
case <-t.C:
}
}
// Update the time of the last function call
lastCall = time.Now()
// Execute the function that needs to be limited
return f(ctx, t)
}
}

View File

@ -0,0 +1,59 @@
package utils_test
import (
"context"
"testing"
"time"
"github.com/alist-org/alist/v3/pkg/utils"
)
func myFunction(a int) (int, error) {
// do something
return a + 1, nil
}
func TestLimitRate(t *testing.T) {
myLimitedFunction := utils.LimitRate(myFunction, time.Second)
result, _ := myLimitedFunction(1)
t.Log(result) // Output: 2
result, _ = myLimitedFunction(2)
t.Log(result) // Output: 3
}
type Test struct {
limitFn func(string) (string, error)
}
func (t *Test) myFunction(a string) (string, error) {
// do something
return a + " world", nil
}
func TestLimitRateStruct(t *testing.T) {
test := &Test{}
test.limitFn = utils.LimitRate(test.myFunction, time.Second)
result, _ := test.limitFn("hello")
t.Log(result) // Output: hello world
result, _ = test.limitFn("hi")
t.Log(result) // Output: hi world
}
func myFunctionCtx(ctx context.Context, a int) (int, error) {
// do something
return a + 1, nil
}
func TestLimitRateCtx(t *testing.T) {
myLimitedFunction := utils.LimitRateCtx(myFunctionCtx, time.Second)
result, _ := myLimitedFunction(context.Background(), 1)
t.Log(result) // Output: 2
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(500 * time.Millisecond)
cancel()
}()
result, err := myLimitedFunction(ctx, 2)
t.Log(result, err) // Output: 0 context canceled
result, _ = myLimitedFunction(context.Background(), 3)
t.Log(result) // Output: 4
}

View File

@ -8,7 +8,9 @@ import (
"os"
"strconv"
"strings"
"sync"
"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/pkg/utils"
@ -16,15 +18,22 @@ import (
log "github.com/sirupsen/logrus"
)
var HttpClient = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
func HttpClient() *http.Client {
once.Do(func() {
httpClient = base.NewHttpClient()
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
req.Header.Del("Referer")
return nil
},
}
})
return httpClient
}
var once sync.Once
var httpClient *http.Client
func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.Obj) error {
// read data with native
@ -72,21 +81,25 @@ 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)))
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
return nil
} else if link.Handle != nil {
return link.Handle(w, r)
} else {
req, err := http.NewRequest(r.Method, link.URL, nil)
if err != nil {
return err
}
// client header
for h, val := range r.Header {
if utils.SliceContains(conf.SlicesMap[conf.ProxyIgnoreHeaders], strings.ToLower(h)) {
continue
}
req.Header[h] = val
}
// needed header
for h, val := range link.Header {
req.Header[h] = val
}
res, err := HttpClient.Do(req)
res, err := HttpClient().Do(req)
if err != nil {
return err
}

View File

@ -150,11 +150,12 @@ func FsRecursiveMove(c *gin.Context) {
for !movingFiles.IsEmpty() {
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() {
// directory, recursive move
subFilePath := movingFilePath
subFiles, err := fs.List(c, subFilePath, &fs.ListArgs{Refresh: true})
subFilePath := movingFileName
subFiles, err := fs.List(c, movingFileName, &fs.ListArgs{Refresh: true})
if err != nil {
common.ErrorResp(c, err, 500)
return
@ -171,7 +172,7 @@ func FsRecursiveMove(c *gin.Context) {
}
// move
err := fs.Move(c, movingFilePath, dstDir, movingFiles.IsEmpty())
err := fs.Move(c, movingFileName, dstDir, movingFiles.IsEmpty())
if err != nil {
common.ErrorResp(c, err, 500)
return
@ -351,6 +352,105 @@ func FsRemove(c *gin.Context) {
common.SuccessResp(c)
}
type RemoveEmptyDirectoryReq struct {
SrcDir string `json:"src_dir"`
}
func FsRemoveEmptyDirectory(c *gin.Context) {
var req RemoveEmptyDirectoryReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
user := c.MustGet("user").(*model.User)
if !user.CanRemove() {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
srcDir, err := user.JoinPath(req.SrcDir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
meta, err := op.GetNearestMeta(srcDir)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
common.ErrorResp(c, err, 500, true)
return
}
}
c.Set("meta", meta)
rootFiles, err := fs.List(c, srcDir, &fs.ListArgs{})
if err != nil {
common.ErrorResp(c, err, 500)
return
}
// record the file path
filePathMap := make(map[model.Obj]string)
// record the parent file
fileParentMap := make(map[model.Obj]model.Obj)
// removing files
removingFiles := generic.NewQueue[model.Obj]()
// removed files
removedFiles := make(map[string]bool)
for _, file := range rootFiles {
if !file.IsDir() {
continue
}
removingFiles.Push(file)
filePathMap[file] = srcDir
}
for !removingFiles.IsEmpty() {
removingFile := removingFiles.Pop()
removingFilePath := fmt.Sprintf("%s/%s", filePathMap[removingFile], removingFile.GetName())
if removedFiles[removingFilePath] {
continue
}
subFiles, err := fs.List(c, removingFilePath, &fs.ListArgs{Refresh: true})
if err != nil {
common.ErrorResp(c, err, 500)
return
}
if len(subFiles) == 0 {
// remove empty directory
err = fs.Remove(c, removingFilePath)
removedFiles[removingFilePath] = true
if err != nil {
common.ErrorResp(c, err, 500)
return
}
// recheck parent folder
parentFile, exist := fileParentMap[removingFile]
if exist {
removingFiles.Push(parentFile)
}
} else {
// recursive remove
for _, subFile := range subFiles {
if !subFile.IsDir() {
continue
}
removingFiles.Push(subFile)
filePathMap[subFile] = removingFilePath
fileParentMap[subFile] = removingFile
}
}
}
common.SuccessResp(c)
}
// Link return real link, just for proxy program, it may contain cookie, so just allowed for admin
func Link(c *gin.Context) {
var req MkdirOrLinkReq

View File

@ -16,6 +16,10 @@ func Favicon(c *gin.Context) {
c.Redirect(302, setting.GetStr(conf.Favicon))
}
func Robots(c *gin.Context) {
c.String(200, setting.GetStr(conf.RobotsTxt))
}
func Plist(c *gin.Context) {
linkNameB64 := strings.TrimSuffix(c.Param("link_name"), ".plist")
linkName, err := utils.SafeAtob(linkNameB64)

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"net/url"
"strings"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
@ -47,6 +48,11 @@ func SSOLoginRedirect(c *gin.Context) {
urlValues.Add("scope", "openid")
urlValues.Add("prompt", "consent")
urlValues.Add("response_type", "code")
case "Casdoor":
endpoint := strings.TrimSuffix(setting.GetStr(conf.SSOEndpointName), "/")
r_url = endpoint + "/login/oauth/authorize?"
urlValues.Add("scope", "profile")
urlValues.Add("state", endpoint)
default:
common.ErrorStrResp(c, "invalid platform", 400)
return
@ -94,6 +100,14 @@ func SSOLoginCallback(c *gin.Context) {
url2 = "https://api.dingtalk.com/v1.0/contact/users/me"
authstring = "authCode"
idstring = "unionId"
case "Casdoor":
endpoint := strings.TrimSuffix(setting.GetStr(conf.SSOEndpointName), "/")
url1 = endpoint + "/api/login/oauth/access_token"
url2 = endpoint + "/api/userinfo"
additionalbody = "&grant_type=authorization_code"
scope = "profile"
authstring = "code"
idstring = "preferred_username"
default:
common.ErrorStrResp(c, "invalid platform", 400)
return

View File

@ -24,6 +24,9 @@ func Init(e *gin.Engine) {
g.Any("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
g.GET("/favicon.ico", handles.Favicon)
g.GET("/robots.txt", handles.Robots)
g.GET("/i/:link_name", handles.Plist)
common.SecretKey = []byte(conf.Conf.JwtSecret)
g.Use(middlewares.StoragesLoaded)
if conf.Conf.MaxConnections > 0 {
@ -31,8 +34,6 @@ func Init(e *gin.Engine) {
}
WebDav(g.Group("/dav"))
g.GET("/favicon.ico", handles.Favicon)
g.GET("/i/:link_name", handles.Plist)
g.GET("/d/*path", middlewares.Down, handles.Down)
g.GET("/p/*path", middlewares.Down, handles.Proxy)
@ -131,6 +132,7 @@ func _fs(g *gin.RouterGroup) {
g.POST("/recursive_move", handles.FsRecursiveMove)
g.POST("/copy", handles.FsCopy)
g.POST("/remove", handles.FsRemove)
g.POST("/remove_empty_directory", handles.FsRemoveEmptyDirectory)
g.PUT("/put", middlewares.FsUp, handles.FsStream)
g.PUT("/form", middlewares.FsUp, handles.FsForm)
g.POST("/link", middlewares.AuthAdmin, handles.Link)

View File

@ -21,6 +21,7 @@ import (
"github.com/alist-org/alist/v3/internal/sign"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
log "github.com/sirupsen/logrus"
)
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)
if err != nil {
log.Errorf("webdav proxy error: %+v", err)
return http.StatusInternalServerError, err
}
} else if storage.GetStorage().WebdavProxy() && downProxyUrl != "" {