Compare commits
87 Commits
Author | SHA1 | Date | |
---|---|---|---|
a3446720a2 | |||
3c4c2ad4e0 | |||
077a525961 | |||
5be79eb26e | |||
ddc19ab699 | |||
ddfca5a29b | |||
c19166be1c | |||
daad61443c | |||
4b0c01158d | |||
f97f1d532e | |||
e15755fef0 | |||
ea88998325 | |||
74d971aa8a | |||
d41d868a8d | |||
555cc26cbf | |||
ab4215080b | |||
9502f5acd7 | |||
b03879403f | |||
ee4ac81677 | |||
b69fc8c306 | |||
ee6c31332d | |||
9fa16bd5fc | |||
c77ed5fcb0 | |||
822be17fb9 | |||
7e3b13ea2d | |||
f8fb48fb32 | |||
4bf46268da | |||
b7ea73b3c2 | |||
9fbc54314d | |||
cf8ab29a17 | |||
51cadd2d49 | |||
2bae8e129e | |||
9d55ad3af6 | |||
36cd504783 | |||
49f13b9b90 | |||
adb0739dfe | |||
340cb940e3 | |||
8711f2a1c5 | |||
7f35aab071 | |||
ecd167d2f9 | |||
220fd30830 | |||
5cba10446e | |||
a9bdb15205 | |||
c5f6a90f54 | |||
46f9aefb04 | |||
fdcad9c154 | |||
027025361a | |||
f1245153b9 | |||
570b8be022 | |||
86a773674a | |||
75fd0ee185 | |||
cc43238bd1 | |||
c0a6beecea | |||
c77eebb035 | |||
b1efb86b28 | |||
0707449c8f | |||
0f8a84f67e | |||
a475783b00 | |||
67413015e8 | |||
3a311a47af | |||
9ccd802126 | |||
0acba7cd22 | |||
3cdb8e7a81 | |||
d3efee2ea1 | |||
4ec274e748 | |||
3b07c72f88 | |||
0c5820a98f | |||
86beadc0ed | |||
be62d64dba | |||
112363031a | |||
48dc3552a6 | |||
663814c9ef | |||
bd892e6a63 | |||
4fd2c09845 | |||
0eab31bdf5 | |||
c6af22b97e | |||
b2a5110672 | |||
c628992ea6 | |||
c65d868e09 | |||
aeb48b2ecc | |||
cefec1a663 | |||
e7ad830aa8 | |||
b27eed265a | |||
3abe26473c | |||
023107226c | |||
8b109cfe40 | |||
b48e97d406 |
4
.github/stale.yml
vendored
4
.github/stale.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
# Number of days of inactivity before an issue becomes stale
|
||||||
daysUntilStale: 44
|
daysUntilStale: 44
|
||||||
# Number of days of inactivity before a stale issue is closed
|
# Number of days of inactivity before a stale issue is closed
|
||||||
daysUntilClose: 8
|
daysUntilClose: 10
|
||||||
# Issues with these labels will never be considered stale
|
# Issues with these labels will never be considered stale
|
||||||
exemptLabels:
|
exemptLabels:
|
||||||
- accepted
|
- accepted
|
||||||
@ -16,4 +16,4 @@ markComment: >
|
|||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
closeComment: >
|
closeComment: >
|
||||||
This issue was closed due to inactive more than 52 days. You can reopen or
|
This issue was closed due to inactive more than 52 days. You can reopen or
|
||||||
recreate it if you think it should continue. Thank you for your contributions again.
|
recreate it if you think it should continue. Thank you for your contributions again.
|
||||||
|
2
.github/workflows/auto_lang.yml
vendored
2
.github/workflows/auto_lang.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ ubuntu-latest ]
|
platform: [ ubuntu-latest ]
|
||||||
go-version: [ 1.19 ]
|
go-version: [ '1.20' ]
|
||||||
name: auto generate lang.json
|
name: auto generate lang.json
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ubuntu-latest]
|
platform: [ubuntu-latest]
|
||||||
go-version: [1.19]
|
go-version: [ '1.20' ]
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
|
8
.github/workflows/issue_close_question.yml
vendored
8
.github/workflows/issue_close_question.yml
vendored
@ -2,7 +2,7 @@ name: Close need info
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 */7 * *"
|
- cron: "0 0 */1 * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -15,8 +15,8 @@ jobs:
|
|||||||
actions: 'close-issues'
|
actions: 'close-issues'
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
labels: 'question'
|
labels: 'question'
|
||||||
inactive-day: 7
|
inactive-day: 3
|
||||||
close-reason: 'not_planned'
|
close-reason: 'not_planned'
|
||||||
body: |
|
body: |
|
||||||
Hello @${{ github.event.issue.user.login }}, this issue was closed due to no activities in 7 days.
|
Hello @${{ github.event.issue.user.login }}, this issue was closed due to no activities in 3 days.
|
||||||
你好 @${{ github.event.issue.user.login }},此issue因超过7天未回复被关闭。
|
你好 @${{ github.event.issue.user.login }},此issue因超过3天未回复被关闭。
|
21
.github/workflows/issue_close_stale.yml
vendored
Normal file
21
.github/workflows/issue_close_stale.yml
vendored
Normal 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.
|
4
.github/workflows/issue_question.yml
vendored
4
.github/workflows/issue_question.yml
vendored
@ -16,5 +16,5 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
body: |
|
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.
|
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模板填写, 并详细说明问题/复现步骤/复现链接/实现思路或提供更多信息等, 7天内未回复issue自动关闭。
|
你好 @${{ github.event.issue.user.login }},请按照issue模板填写, 并详细说明问题/日志记录/复现步骤/复现链接/实现思路或提供更多信息等, 3天内未回复issue自动关闭。
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -9,11 +9,11 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ ubuntu-latest ]
|
platform: [ ubuntu-latest ]
|
||||||
go-version: [ 1.19 ]
|
go-version: [ '1.20' ]
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- name: prerelease
|
- name: Prerelease
|
||||||
uses: irongut/EditRelease@v1.2.0
|
uses: irongut/EditRelease@v1.2.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MY_TOKEN }}
|
token: ${{ secrets.MY_TOKEN }}
|
||||||
@ -41,14 +41,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
bash build.sh release
|
bash build.sh release
|
||||||
|
|
||||||
- name: prerelease
|
- name: Release latest
|
||||||
uses: irongut/EditRelease@v1.2.0
|
uses: irongut/EditRelease@v1.2.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MY_TOKEN }}
|
token: ${{ secrets.MY_TOKEN }}
|
||||||
id: ${{ github.event.release.id }}
|
id: ${{ github.event.release.id }}
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
- name: Release
|
- name: Upload assets
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: build/compress/*
|
files: build/compress/*
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
- [git](https://nodejs.org/zh-cn/)
|
- [git](https://git-scm.com)
|
||||||
- [Go 1.19+](https://golang.org/doc/install)
|
- [Go 1.19+](https://golang.org/doc/install)
|
||||||
- [gcc](https://gcc.gnu.org/)
|
- [gcc](https://gcc.gnu.org/)
|
||||||
- [nodejs](https://nodejs.org/)
|
- [nodejs](https://nodejs.org/)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
FROM alpine:edge as builder
|
FROM alpine:3.17 as builder
|
||||||
LABEL stage=go-builder
|
LABEL stage=go-builder
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
COPY ./ ./
|
COPY ./ ./
|
||||||
RUN apk add --no-cache bash curl gcc git go musl-dev; \
|
RUN apk add --no-cache bash curl gcc git go musl-dev; \
|
||||||
bash build.sh release docker
|
bash build.sh release docker
|
||||||
|
|
||||||
FROM alpine:edge
|
FROM alpine:3.17
|
||||||
LABEL MAINTAINER="i@nn.ci"
|
LABEL MAINTAINER="i@nn.ci"
|
||||||
VOLUME /opt/alist/data/
|
VOLUME /opt/alist/data/
|
||||||
WORKDIR /opt/alist/
|
WORKDIR /opt/alist/
|
||||||
|
6
build.sh
6
build.sh
@ -59,7 +59,8 @@ BuildDev() {
|
|||||||
mv alist-* dist
|
mv alist-* dist
|
||||||
cd dist
|
cd dist
|
||||||
upx -9 ./alist-linux*
|
upx -9 ./alist-linux*
|
||||||
upx -9 ./alist-windows-amd64.exe
|
cp ./alist-windows-amd64.exe ./alist-windows-amd64-upx.exe
|
||||||
|
upx -9 ./alist-windows-amd64-upx.exe
|
||||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||||
cat md5.txt
|
cat md5.txt
|
||||||
}
|
}
|
||||||
@ -95,7 +96,8 @@ BuildRelease() {
|
|||||||
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||||
# why? Because some target platforms seem to have issues with upx compression
|
# why? Because some target platforms seem to have issues with upx compression
|
||||||
upx -9 ./alist-linux-amd64
|
upx -9 ./alist-linux-amd64
|
||||||
upx -9 ./alist-windows-amd64.exe
|
cp ./alist-windows-amd64.exe ./alist-windows-amd64-upx.exe
|
||||||
|
upx -9 ./alist-windows-amd64-upx.exe
|
||||||
mv alist-* build
|
mv alist-* build
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ var Cancel2FACmd = &cobra.Command{
|
|||||||
err := op.Cancel2FAByUser(admin)
|
err := op.Cancel2FAByUser(admin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log.Errorf("failed to cancel 2FA: %+v", err)
|
utils.Log.Errorf("failed to cancel 2FA: %+v", err)
|
||||||
|
} else {
|
||||||
|
utils.Log.Info("2FA canceled")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -44,7 +44,11 @@ func (d *Pan115) List(ctx context.Context, dir model.Obj, args model.ListArgs) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Pan115) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
func (d *Pan115) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
downloadInfo, err := d.client.Download(file.(driver115.File).PickCode)
|
downloadInfo, err := d.client.
|
||||||
|
SetUserAgent(driver115.UA115Browser).
|
||||||
|
Download(file.(driver115.File).PickCode)
|
||||||
|
// recover for upload
|
||||||
|
d.client.SetUserAgent(driver115.UA115Desktop)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/SheltonZhu/115driver/pkg/driver"
|
"github.com/SheltonZhu/115driver/pkg/driver"
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ func (d *Pan115) login() error {
|
|||||||
driver.UA(UserAgent),
|
driver.UA(UserAgent),
|
||||||
}
|
}
|
||||||
d.client = driver.New(opts...)
|
d.client = driver.New(opts...)
|
||||||
|
d.client.SetHttpClient(base.HttpClient)
|
||||||
cr := &driver.Credential{}
|
cr := &driver.Credential{}
|
||||||
if d.Addition.QRCodeToken != "" {
|
if d.Addition.QRCodeToken != "" {
|
||||||
s := &driver.QRCodeSession{
|
s := &driver.QRCodeSession{
|
||||||
|
@ -29,7 +29,6 @@ import (
|
|||||||
type Pan123 struct {
|
type Pan123 struct {
|
||||||
model.Storage
|
model.Storage
|
||||||
Addition
|
Addition
|
||||||
AccessToken string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Pan123) Config() driver.Config {
|
func (d *Pan123) Config() driver.Config {
|
||||||
@ -41,7 +40,8 @@ func (d *Pan123) GetAddition() driver.Additional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Pan123) Init(ctx context.Context) error {
|
func (d *Pan123) Init(ctx context.Context) error {
|
||||||
return d.login()
|
_, err := d.request(UserInfo, http.MethodGet, nil, nil)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Pan123) Drop(ctx context.Context) error {
|
func (d *Pan123) Drop(ctx context.Context) error {
|
||||||
@ -77,7 +77,7 @@ func (d *Pan123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
|||||||
"size": f.Size,
|
"size": f.Size,
|
||||||
"type": f.Type,
|
"type": f.Type,
|
||||||
}
|
}
|
||||||
resp, err := d.request("https://www.123pan.com/api/file/download_info", http.MethodPost, func(req *resty.Request) {
|
resp, err := d.request(DownloadInfo, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data).SetHeaders(headers)
|
req.SetBody(data).SetHeaders(headers)
|
||||||
}, nil)
|
}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -97,7 +97,8 @@ func (d *Pan123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
u_ := u.String()
|
u_ := u.String()
|
||||||
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Head(u_)
|
log.Debug("download url: ", u_)
|
||||||
|
res, err := base.NoRedirectClient.R().Get(u_)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -108,6 +109,8 @@ func (d *Pan123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
|||||||
log.Debugln("res code: ", res.StatusCode())
|
log.Debugln("res code: ", res.StatusCode())
|
||||||
if res.StatusCode() == 302 {
|
if res.StatusCode() == 302 {
|
||||||
link.URL = res.Header().Get("location")
|
link.URL = res.Header().Get("location")
|
||||||
|
} else if res.StatusCode() == 200 {
|
||||||
|
link.URL = utils.Json.Get(res.Body(), "data", "redirect_url").ToString()
|
||||||
}
|
}
|
||||||
return &link, nil
|
return &link, nil
|
||||||
} else {
|
} else {
|
||||||
@ -124,7 +127,7 @@ func (d *Pan123) MakeDir(ctx context.Context, parentDir model.Obj, dirName strin
|
|||||||
"size": 0,
|
"size": 0,
|
||||||
"type": 1,
|
"type": 1,
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/api/file/upload_request", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request(Mkdir, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data)
|
req.SetBody(data)
|
||||||
}, nil)
|
}, nil)
|
||||||
return err
|
return err
|
||||||
@ -135,7 +138,7 @@ func (d *Pan123) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|||||||
"fileIdList": []base.Json{{"FileId": srcObj.GetID()}},
|
"fileIdList": []base.Json{{"FileId": srcObj.GetID()}},
|
||||||
"parentFileId": dstDir.GetID(),
|
"parentFileId": dstDir.GetID(),
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/api/file/mod_pid", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request(Move, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data)
|
req.SetBody(data)
|
||||||
}, nil)
|
}, nil)
|
||||||
return err
|
return err
|
||||||
@ -147,7 +150,7 @@ func (d *Pan123) Rename(ctx context.Context, srcObj model.Obj, newName string) e
|
|||||||
"fileId": srcObj.GetID(),
|
"fileId": srcObj.GetID(),
|
||||||
"fileName": newName,
|
"fileName": newName,
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/api/file/rename", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request(Rename, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data)
|
req.SetBody(data)
|
||||||
}, nil)
|
}, nil)
|
||||||
return err
|
return err
|
||||||
@ -164,7 +167,7 @@ func (d *Pan123) Remove(ctx context.Context, obj model.Obj) error {
|
|||||||
"operation": true,
|
"operation": true,
|
||||||
"fileTrashInfoList": []File{f},
|
"fileTrashInfoList": []File{f},
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/b/api/file/trash", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request(Trash, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data)
|
req.SetBody(data)
|
||||||
}, nil)
|
}, nil)
|
||||||
return err
|
return err
|
||||||
@ -220,36 +223,41 @@ func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
"type": 0,
|
"type": 0,
|
||||||
}
|
}
|
||||||
var resp UploadResp
|
var resp UploadResp
|
||||||
_, err := d.request("https://www.123pan.com/a/api/file/upload_request", http.MethodPost, func(req *resty.Request) {
|
res, err := d.request(UploadRequest, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data).SetContext(ctx)
|
req.SetBody(data).SetContext(ctx)
|
||||||
}, &resp)
|
}, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Debugln("upload request res: ", string(res))
|
||||||
if resp.Data.Reuse || resp.Data.Key == "" {
|
if resp.Data.Reuse || resp.Data.Key == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cfg := &aws.Config{
|
if resp.Data.AccessKeyId == "" || resp.Data.SecretAccessKey == "" || resp.Data.SessionToken == "" {
|
||||||
Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken),
|
err = d.newUpload(ctx, &resp, stream, uploadFile, up)
|
||||||
Region: aws.String("123pan"),
|
} else {
|
||||||
Endpoint: aws.String("file.123pan.com"),
|
cfg := &aws.Config{
|
||||||
S3ForcePathStyle: aws.Bool(true),
|
Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken),
|
||||||
|
Region: aws.String("123pan"),
|
||||||
|
Endpoint: aws.String(resp.Data.EndPoint),
|
||||||
|
S3ForcePathStyle: aws.Bool(true),
|
||||||
|
}
|
||||||
|
s, err := session.NewSession(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uploader := s3manager.NewUploader(s)
|
||||||
|
input := &s3manager.UploadInput{
|
||||||
|
Bucket: &resp.Data.Bucket,
|
||||||
|
Key: &resp.Data.Key,
|
||||||
|
Body: uploadFile,
|
||||||
|
}
|
||||||
|
_, err = uploader.UploadWithContext(ctx, input)
|
||||||
}
|
}
|
||||||
s, err := session.NewSession(cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uploader := s3manager.NewUploader(s)
|
_, err = d.request(UploadComplete, http.MethodPost, func(req *resty.Request) {
|
||||||
input := &s3manager.UploadInput{
|
|
||||||
Bucket: &resp.Data.Bucket,
|
|
||||||
Key: &resp.Data.Key,
|
|
||||||
Body: uploadFile,
|
|
||||||
}
|
|
||||||
_, err = uploader.UploadWithContext(ctx, input)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = d.request("https://www.123pan.com/api/file/upload_complete", http.MethodPost, func(req *resty.Request) {
|
|
||||||
req.SetBody(base.Json{
|
req.SetBody(base.Json{
|
||||||
"fileId": resp.Data.FileId,
|
"fileId": resp.Data.FileId,
|
||||||
}).SetContext(ctx)
|
}).SetContext(ctx)
|
||||||
|
@ -6,14 +6,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
Username string `json:"username" required:"true"`
|
Username string `json:"username" required:"true"`
|
||||||
Password string `json:"password" required:"true"`
|
Password string `json:"password" required:"true"`
|
||||||
|
driver.RootID
|
||||||
OrderBy string `json:"order_by" type:"select" options:"file_name,size,update_at" default:"file_name"`
|
OrderBy string `json:"order_by" type:"select" options:"file_name,size,update_at" default:"file_name"`
|
||||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
|
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
|
||||||
driver.RootID
|
StreamUpload bool `json:"stream_upload"`
|
||||||
// define other
|
AccessToken string
|
||||||
StreamUpload bool `json:"stream_upload"`
|
|
||||||
//Field string `json:"field" type:"select" required:"true" options:"a,b,c" default:"a"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -74,5 +74,14 @@ type UploadResp struct {
|
|||||||
SessionToken string `json:"SessionToken"`
|
SessionToken string `json:"SessionToken"`
|
||||||
FileId int64 `json:"FileId"`
|
FileId int64 `json:"FileId"`
|
||||||
Reuse bool `json:"Reuse"`
|
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"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
127
drivers/123/upload.go
Normal file
127
drivers/123/upload.go
Normal 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
|
||||||
|
}
|
@ -14,9 +14,24 @@ import (
|
|||||||
|
|
||||||
// do others that not defined in Driver interface
|
// do others that not defined in Driver interface
|
||||||
|
|
||||||
|
const (
|
||||||
|
API = "https://www.123pan.com/b/api"
|
||||||
|
SignIn = API + "/user/sign_in"
|
||||||
|
UserInfo = API + "/user/info"
|
||||||
|
FileList = API + "/file/list/new"
|
||||||
|
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 {
|
func (d *Pan123) login() error {
|
||||||
var body base.Json
|
var body base.Json
|
||||||
url := "https://www.123pan.com/a/api/user/sign_in"
|
|
||||||
if utils.IsEmailFormat(d.Username) {
|
if utils.IsEmailFormat(d.Username) {
|
||||||
body = base.Json{
|
body = base.Json{
|
||||||
"mail": d.Username,
|
"mail": d.Username,
|
||||||
@ -30,7 +45,7 @@ func (d *Pan123) login() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
res, err := base.RestyClient.R().
|
res, err := base.RestyClient.R().
|
||||||
SetBody(body).Post(url)
|
SetBody(body).Post(SignIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -90,7 +105,7 @@ func (d *Pan123) getFiles(parentId string) ([]File, error) {
|
|||||||
"trashed": "false",
|
"trashed": "false",
|
||||||
"Page": strconv.Itoa(page),
|
"Page": strconv.Itoa(page),
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/api/file/list/new", http.MethodGet, func(req *resty.Request) {
|
_, err := d.request(FileList, http.MethodGet, func(req *resty.Request) {
|
||||||
req.SetQueryParams(query)
|
req.SetQueryParams(query)
|
||||||
}, &resp)
|
}, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -30,12 +30,9 @@ func (d *Cloud189) GetAddition() driver.Additional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Cloud189) Init(ctx context.Context) error {
|
func (d *Cloud189) Init(ctx context.Context) error {
|
||||||
d.client = resty.New().
|
d.client = base.NewRestyClient().
|
||||||
SetTimeout(base.DefaultTimeout).
|
SetHeader("Referer", "https://cloud.189.cn/")
|
||||||
SetRetryCount(3).
|
return d.newLogin()
|
||||||
SetHeader("Referer", "https://cloud.189.cn/").
|
|
||||||
SetHeader("User-Agent", base.UserAgent)
|
|
||||||
return d.login()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Cloud189) Drop(ctx context.Context) error {
|
func (d *Cloud189) Drop(ctx context.Context) error {
|
||||||
|
126
drivers/189/login.go
Normal file
126
drivers/189/login.go
Normal 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
|
||||||
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
type Addition struct {
|
type Addition struct {
|
||||||
Username string `json:"username" required:"true"`
|
Username string `json:"username" required:"true"`
|
||||||
Password string `json:"password" required:"true"`
|
Password string `json:"password" required:"true"`
|
||||||
|
Cookie string `json:"cookie" help:"Fill in the cookie if need captcha"`
|
||||||
driver.RootID
|
driver.RootID
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ var config = driver.Config{
|
|||||||
Name: "189Cloud",
|
Name: "189Cloud",
|
||||||
LocalSort: true,
|
LocalSort: true,
|
||||||
DefaultRoot: "-11",
|
DefaultRoot: "-11",
|
||||||
|
Alert: `info|You can try to use 189PC driver if this driver does not work.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -11,16 +11,13 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/setting"
|
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
myrand "github.com/alist-org/alist/v3/pkg/utils/random"
|
myrand "github.com/alist-org/alist/v3/pkg/utils/random"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
@ -30,118 +27,118 @@ import (
|
|||||||
|
|
||||||
// do others that not defined in Driver interface
|
// do others that not defined in Driver interface
|
||||||
|
|
||||||
func (d *Cloud189) login() error {
|
//func (d *Cloud189) login() error {
|
||||||
url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
|
// url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
|
||||||
b := ""
|
// b := ""
|
||||||
lt := ""
|
// lt := ""
|
||||||
ltText := regexp.MustCompile(`lt = "(.+?)"`)
|
// ltText := regexp.MustCompile(`lt = "(.+?)"`)
|
||||||
var res *resty.Response
|
// var res *resty.Response
|
||||||
var err error
|
// var err error
|
||||||
for i := 0; i < 3; i++ {
|
// for i := 0; i < 3; i++ {
|
||||||
res, err = d.client.R().Get(url)
|
// res, err = d.client.R().Get(url)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
// 已经登陆
|
// // 已经登陆
|
||||||
if res.RawResponse.Request.URL.String() == "https://cloud.189.cn/web/main" {
|
// if res.RawResponse.Request.URL.String() == "https://cloud.189.cn/web/main" {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
b = res.String()
|
// b = res.String()
|
||||||
ltTextArr := ltText.FindStringSubmatch(b)
|
// ltTextArr := ltText.FindStringSubmatch(b)
|
||||||
if len(ltTextArr) > 0 {
|
// if len(ltTextArr) > 0 {
|
||||||
lt = ltTextArr[1]
|
// lt = ltTextArr[1]
|
||||||
break
|
// break
|
||||||
} else {
|
// } else {
|
||||||
<-time.After(time.Second)
|
// <-time.After(time.Second)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if lt == "" {
|
// if lt == "" {
|
||||||
return fmt.Errorf("get page: %s \nstatus: %d \nrequest url: %s\nredirect url: %s",
|
// 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"))
|
// b, res.StatusCode(), res.RawResponse.Request.URL.String(), res.Header().Get("location"))
|
||||||
}
|
// }
|
||||||
captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
|
// captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
|
||||||
returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
|
// returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
|
||||||
paramId := regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(b)[1]
|
// paramId := regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(b)[1]
|
||||||
//reqId := regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(b)[1]
|
// //reqId := regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(b)[1]
|
||||||
jRsakey := regexp.MustCompile(`j_rsaKey" value="(\S+)"`).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]
|
// vCodeID := regexp.MustCompile(`picCaptcha\.do\?token\=([A-Za-z0-9\&\=]+)`).FindStringSubmatch(b)[1]
|
||||||
vCodeRS := ""
|
// vCodeRS := ""
|
||||||
if vCodeID != "" {
|
// if vCodeID != "" {
|
||||||
// need ValidateCode
|
// // need ValidateCode
|
||||||
log.Debugf("try to identify verification codes")
|
// log.Debugf("try to identify verification codes")
|
||||||
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
|
// timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
|
||||||
u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
|
// u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
|
||||||
imgRes, err := d.client.R().SetHeaders(map[string]string{
|
// 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",
|
// "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",
|
// "Referer": "https://open.e.189.cn/api/logbox/oauth2/unifyAccountLogin.do",
|
||||||
"Sec-Fetch-Dest": "image",
|
// "Sec-Fetch-Dest": "image",
|
||||||
"Sec-Fetch-Mode": "no-cors",
|
// "Sec-Fetch-Mode": "no-cors",
|
||||||
"Sec-Fetch-Site": "same-origin",
|
// "Sec-Fetch-Site": "same-origin",
|
||||||
}).Get(u)
|
// }).Get(u)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
// Enter the verification code manually
|
// // Enter the verification code manually
|
||||||
//err = message.GetMessenger().WaitSend(message.Message{
|
// //err = message.GetMessenger().WaitSend(message.Message{
|
||||||
// Type: "image",
|
// // Type: "image",
|
||||||
// Content: "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgRes.Body()),
|
// // Content: "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgRes.Body()),
|
||||||
//}, 10)
|
// //}, 10)
|
||||||
//if err != nil {
|
// //if err != nil {
|
||||||
// return err
|
// // return err
|
||||||
//}
|
// //}
|
||||||
//vCodeRS, err = message.GetMessenger().WaitReceive(30)
|
// //vCodeRS, err = message.GetMessenger().WaitReceive(30)
|
||||||
// use ocr api
|
// // use ocr api
|
||||||
vRes, err := base.RestyClient.R().SetMultipartField(
|
// vRes, err := base.RestyClient.R().SetMultipartField(
|
||||||
"image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
|
// "image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
|
||||||
Post(setting.GetStr(conf.OcrApi))
|
// Post(setting.GetStr(conf.OcrApi))
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
|
// if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
|
||||||
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
|
// return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
|
||||||
}
|
// }
|
||||||
vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
|
// vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
|
||||||
log.Debugln("code: ", vCodeRS)
|
// log.Debugln("code: ", vCodeRS)
|
||||||
}
|
// }
|
||||||
userRsa := RsaEncode([]byte(d.Username), jRsakey, true)
|
// userRsa := RsaEncode([]byte(d.Username), jRsakey, true)
|
||||||
passwordRsa := RsaEncode([]byte(d.Password), jRsakey, true)
|
// passwordRsa := RsaEncode([]byte(d.Password), jRsakey, true)
|
||||||
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
|
// url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
|
||||||
var loginResp LoginResp
|
// var loginResp LoginResp
|
||||||
res, err = d.client.R().
|
// res, err = d.client.R().
|
||||||
SetHeaders(map[string]string{
|
// SetHeaders(map[string]string{
|
||||||
"lt": lt,
|
// "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",
|
// "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/",
|
// "Referer": "https://open.e.189.cn/",
|
||||||
"accept": "application/json;charset=UTF-8",
|
// "accept": "application/json;charset=UTF-8",
|
||||||
}).SetFormData(map[string]string{
|
// }).SetFormData(map[string]string{
|
||||||
"appKey": "cloud",
|
// "appKey": "cloud",
|
||||||
"accountType": "01",
|
// "accountType": "01",
|
||||||
"userName": "{RSA}" + userRsa,
|
// "userName": "{RSA}" + userRsa,
|
||||||
"password": "{RSA}" + passwordRsa,
|
// "password": "{RSA}" + passwordRsa,
|
||||||
"validateCode": vCodeRS,
|
// "validateCode": vCodeRS,
|
||||||
"captchaToken": captchaToken,
|
// "captchaToken": captchaToken,
|
||||||
"returnUrl": returnUrl,
|
// "returnUrl": returnUrl,
|
||||||
"mailSuffix": "@pan.cn",
|
// "mailSuffix": "@pan.cn",
|
||||||
"paramId": paramId,
|
// "paramId": paramId,
|
||||||
"clientType": "10010",
|
// "clientType": "10010",
|
||||||
"dynamicCheck": "FALSE",
|
// "dynamicCheck": "FALSE",
|
||||||
"cb_SaveName": "1",
|
// "cb_SaveName": "1",
|
||||||
"isOauth2": "false",
|
// "isOauth2": "false",
|
||||||
}).Post(url)
|
// }).Post(url)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
err = utils.Json.Unmarshal(res.Body(), &loginResp)
|
// err = utils.Json.Unmarshal(res.Body(), &loginResp)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Error(err.Error())
|
// log.Error(err.Error())
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if loginResp.Result != 0 {
|
// if loginResp.Result != 0 {
|
||||||
return fmt.Errorf(loginResp.Msg)
|
// return fmt.Errorf(loginResp.Msg)
|
||||||
}
|
// }
|
||||||
_, err = d.client.R().Get(loginResp.ToUrl)
|
// _, err = d.client.R().Get(loginResp.ToUrl)
|
||||||
return err
|
// return err
|
||||||
}
|
//}
|
||||||
|
|
||||||
func (d *Cloud189) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
func (d *Cloud189) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||||
var e Error
|
var e Error
|
||||||
@ -163,7 +160,7 @@ func (d *Cloud189) request(url string, method string, callback base.ReqCallback,
|
|||||||
//log.Debug(res.String())
|
//log.Debug(res.String())
|
||||||
if e.ErrorCode != "" {
|
if e.ErrorCode != "" {
|
||||||
if e.ErrorCode == "InvalidSessionKey" {
|
if e.ErrorCode == "InvalidSessionKey" {
|
||||||
err = d.login()
|
err = d.newLogin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ func (d *Alias) getRootAndPath(path string) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Alias) get(ctx context.Context, path string, dst, sub string) (model.Obj, error) {
|
func (d *Alias) get(ctx context.Context, path string, dst, sub string) (model.Obj, error) {
|
||||||
obj, err := fs.Get(ctx, stdpath.Join(dst, sub))
|
obj, err := fs.Get(ctx, stdpath.Join(dst, sub), &fs.GetArgs{NoLog: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -65,29 +65,39 @@ func (d *Alias) get(ctx context.Context, path string, dst, sub string) (model.Ob
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Alias) list(ctx context.Context, dst, sub string) ([]model.Obj, error) {
|
func (d *Alias) list(ctx context.Context, dst, sub string) ([]model.Obj, error) {
|
||||||
objs, err := fs.List(ctx, stdpath.Join(dst, sub))
|
objs, err := fs.List(ctx, stdpath.Join(dst, sub), &fs.ListArgs{NoLog: true})
|
||||||
// the obj must implement the model.SetPath interface
|
// the obj must implement the model.SetPath interface
|
||||||
// return objs, err
|
// return objs, err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return utils.SliceConvert(objs, func(obj model.Obj) (model.Obj, error) {
|
return utils.SliceConvert(objs, func(obj model.Obj) (model.Obj, error) {
|
||||||
return &model.Object{
|
thumb, ok := model.GetThumb(obj)
|
||||||
|
objRes := model.Object{
|
||||||
Name: obj.GetName(),
|
Name: obj.GetName(),
|
||||||
Size: obj.GetSize(),
|
Size: obj.GetSize(),
|
||||||
Modified: obj.ModTime(),
|
Modified: obj.ModTime(),
|
||||||
IsFolder: obj.IsDir(),
|
IsFolder: obj.IsDir(),
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return &objRes, nil
|
||||||
|
}
|
||||||
|
return &model.ObjThumb{
|
||||||
|
Object: objRes,
|
||||||
|
Thumbnail: model.Thumbnail{
|
||||||
|
Thumbnail: thumb,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Alias) link(ctx context.Context, dst, sub string, args model.LinkArgs) (*model.Link, error) {
|
func (d *Alias) link(ctx context.Context, dst, sub string, args model.LinkArgs) (*model.Link, error) {
|
||||||
reqPath := stdpath.Join(dst, sub)
|
reqPath := stdpath.Join(dst, sub)
|
||||||
storage, err := fs.GetStorage(reqPath)
|
storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = fs.Get(ctx, reqPath)
|
_, err = fs.Get(ctx, reqPath, &fs.GetArgs{NoLog: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,19 @@ package alist_v3
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"fmt"
|
||||||
"io"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AListV3 struct {
|
type AListV3 struct {
|
||||||
@ -29,9 +32,39 @@ func (d *AListV3) GetAddition() driver.Additional {
|
|||||||
|
|
||||||
func (d *AListV3) Init(ctx context.Context) error {
|
func (d *AListV3) Init(ctx context.Context) error {
|
||||||
d.Addition.Address = strings.TrimSuffix(d.Addition.Address, "/")
|
d.Addition.Address = strings.TrimSuffix(d.Addition.Address, "/")
|
||||||
// TODO login / refresh token
|
var resp common.Resp[MeResp]
|
||||||
//op.MustSaveDriverStorage(d)
|
_, err := d.request("/me", http.MethodGet, func(req *resty.Request) {
|
||||||
return nil
|
req.SetResult(&resp)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// if the username is not empty and the username is not the same as the current username, then login again
|
||||||
|
if d.Username != "" && d.Username != resp.Data.Username {
|
||||||
|
err = d.login()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// re-get the user info
|
||||||
|
_, err = d.request("/me", http.MethodGet, func(req *resty.Request) {
|
||||||
|
req.SetResult(&resp)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.Data.Role == model.GUEST {
|
||||||
|
url := d.Address + "/api/public/settings"
|
||||||
|
res, err := base.RestyClient.R().Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
allowMounted := utils.Json.Get(res.Body(), "data", conf.AllowMounted).ToString() == "true"
|
||||||
|
if !allowMounted {
|
||||||
|
return fmt.Errorf("the site does not allow mounted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) Drop(ctx context.Context) error {
|
func (d *AListV3) Drop(ctx context.Context) error {
|
||||||
@ -39,26 +72,21 @@ func (d *AListV3) Drop(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
url := d.Address + "/api/fs/list"
|
|
||||||
var resp common.Resp[FsListResp]
|
var resp common.Resp[FsListResp]
|
||||||
_, err := base.RestyClient.R().
|
_, err := d.request("/fs/list", http.MethodPost, func(req *resty.Request) {
|
||||||
SetResult(&resp).
|
req.SetResult(&resp).SetBody(ListReq{
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(ListReq{
|
|
||||||
PageReq: model.PageReq{
|
PageReq: model.PageReq{
|
||||||
Page: 1,
|
Page: 1,
|
||||||
PerPage: 0,
|
PerPage: 0,
|
||||||
},
|
},
|
||||||
Path: dir.GetPath(),
|
Path: dir.GetPath(),
|
||||||
Password: d.Password,
|
Password: d.MetaPassword,
|
||||||
Refresh: false,
|
Refresh: false,
|
||||||
}).Post(url)
|
})
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.Code != 200 {
|
|
||||||
return nil, errors.New(resp.Message)
|
|
||||||
}
|
|
||||||
var files []model.Obj
|
var files []model.Obj
|
||||||
for _, f := range resp.Data.Content {
|
for _, f := range resp.Data.Content {
|
||||||
file := model.ObjThumb{
|
file := model.ObjThumb{
|
||||||
@ -76,107 +104,80 @@ func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
func (d *AListV3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
url := d.Address + "/api/fs/get"
|
|
||||||
var resp common.Resp[FsGetResp]
|
var resp common.Resp[FsGetResp]
|
||||||
_, err := base.RestyClient.R().
|
_, err := d.request("/fs/get", http.MethodPost, func(req *resty.Request) {
|
||||||
SetResult(&resp).
|
req.SetResult(&resp).SetBody(FsGetReq{
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(FsGetReq{
|
|
||||||
Path: file.GetPath(),
|
Path: file.GetPath(),
|
||||||
Password: d.Password,
|
Password: d.MetaPassword,
|
||||||
}).Post(url)
|
})
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.Code != 200 {
|
|
||||||
return nil, errors.New(resp.Message)
|
|
||||||
}
|
|
||||||
return &model.Link{
|
return &model.Link{
|
||||||
URL: resp.Data.RawURL,
|
URL: resp.Data.RawURL,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *AListV3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
url := d.Address + "/api/fs/mkdir"
|
_, err := d.request("/fs/mkdir", http.MethodPost, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetBody(MkdirOrLinkReq{
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(MkdirOrLinkReq{
|
|
||||||
Path: path.Join(parentDir.GetPath(), dirName),
|
Path: path.Join(parentDir.GetPath(), dirName),
|
||||||
}).Post(url)
|
})
|
||||||
return checkResp(resp, err)
|
})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
func (d *AListV3) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
url := d.Address + "/api/fs/move"
|
_, err := d.request("/fs/move", http.MethodPost, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetBody(MoveCopyReq{
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(MoveCopyReq{
|
|
||||||
SrcDir: path.Dir(srcObj.GetPath()),
|
SrcDir: path.Dir(srcObj.GetPath()),
|
||||||
DstDir: dstDir.GetPath(),
|
DstDir: dstDir.GetPath(),
|
||||||
Names: []string{srcObj.GetName()},
|
Names: []string{srcObj.GetName()},
|
||||||
}).Post(url)
|
})
|
||||||
return checkResp(resp, err)
|
})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
func (d *AListV3) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
url := d.Address + "/api/fs/rename"
|
_, err := d.request("/fs/rename", http.MethodPost, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetBody(RenameReq{
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(RenameReq{
|
|
||||||
Path: srcObj.GetPath(),
|
Path: srcObj.GetPath(),
|
||||||
Name: newName,
|
Name: newName,
|
||||||
}).Post(url)
|
})
|
||||||
return checkResp(resp, err)
|
})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
func (d *AListV3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
url := d.Address + "/api/fs/copy"
|
_, err := d.request("/fs/copy", http.MethodPost, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetBody(MoveCopyReq{
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(MoveCopyReq{
|
|
||||||
SrcDir: path.Dir(srcObj.GetPath()),
|
SrcDir: path.Dir(srcObj.GetPath()),
|
||||||
DstDir: dstDir.GetPath(),
|
DstDir: dstDir.GetPath(),
|
||||||
Names: []string{srcObj.GetName()},
|
Names: []string{srcObj.GetName()},
|
||||||
}).Post(url)
|
})
|
||||||
return checkResp(resp, err)
|
})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error {
|
func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
url := d.Address + "/api/fs/remove"
|
_, err := d.request("/fs/remove", http.MethodPost, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetBody(RemoveReq{
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetBody(RemoveReq{
|
|
||||||
Dir: path.Dir(obj.GetPath()),
|
Dir: path.Dir(obj.GetPath()),
|
||||||
Names: []string{obj.GetName()},
|
Names: []string{obj.GetName()},
|
||||||
}).Post(url)
|
})
|
||||||
return checkResp(resp, err)
|
})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AListV3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
func (d *AListV3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
url := d.Address + "/api/fs/put"
|
_, err := d.request("/fs/put", http.MethodPut, func(req *resty.Request) {
|
||||||
var resp common.Resp[interface{}]
|
req.SetHeader("File-Path", path.Join(dstDir.GetPath(), stream.GetName())).
|
||||||
fileBytes, err := io.ReadAll(stream.GetReadCloser())
|
SetHeader("Password", d.MetaPassword).
|
||||||
if err != nil {
|
SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)).
|
||||||
return nil
|
SetBody(stream.GetReadCloser())
|
||||||
}
|
})
|
||||||
_, err = base.RestyClient.R().SetContext(ctx).
|
return err
|
||||||
SetResult(&resp).
|
|
||||||
SetHeader("Authorization", d.AccessToken).
|
|
||||||
SetHeader("File-Path", path.Join(dstDir.GetPath(), stream.GetName())).
|
|
||||||
SetHeader("Password", d.Password).
|
|
||||||
SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)).
|
|
||||||
SetBody(fileBytes).Put(url)
|
|
||||||
return checkResp(resp, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
//func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
|
@ -7,15 +7,18 @@ import (
|
|||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
driver.RootPath
|
driver.RootPath
|
||||||
Address string `json:"url" required:"true"`
|
Address string `json:"url" required:"true"`
|
||||||
Password string `json:"password"`
|
MetaPassword string `json:"meta_password"`
|
||||||
AccessToken string `json:"access_token"`
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
Name: "AList V3",
|
Name: "AList V3",
|
||||||
LocalSort: true,
|
LocalSort: true,
|
||||||
DefaultRoot: "/",
|
DefaultRoot: "/",
|
||||||
|
CheckStatus: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -63,3 +63,19 @@ type RemoveReq struct {
|
|||||||
Dir string `json:"dir"`
|
Dir string `json:"dir"`
|
||||||
Names []string `json:"names"`
|
Names []string `json:"names"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoginResp struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MeResp struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
BasePath string `json:"base_path"`
|
||||||
|
Role int `json:"role"`
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
|
Permission int `json:"permission"`
|
||||||
|
SsoId string `json:"sso_id"`
|
||||||
|
Otp bool `json:"otp"`
|
||||||
|
}
|
||||||
|
@ -1,17 +1,56 @@
|
|||||||
package alist_v3
|
package alist_v3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkResp(resp common.Resp[interface{}], err error) error {
|
func (d *AListV3) login() error {
|
||||||
|
var resp common.Resp[LoginResp]
|
||||||
|
_, err := d.request("/auth/login", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetResult(&resp).SetBody(base.Json{
|
||||||
|
"username": d.Username,
|
||||||
|
"password": d.Password,
|
||||||
|
})
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.Code == 200 {
|
d.Token = resp.Data.Token
|
||||||
return nil
|
op.MustSaveDriverStorage(d)
|
||||||
}
|
return nil
|
||||||
return errors.New(resp.Message)
|
}
|
||||||
|
|
||||||
|
func (d *AListV3) request(api, method string, callback base.ReqCallback, retry ...bool) ([]byte, error) {
|
||||||
|
url := d.Address + "/api" + api
|
||||||
|
req := base.RestyClient.R()
|
||||||
|
req.SetHeader("Authorization", d.Token)
|
||||||
|
if callback != nil {
|
||||||
|
callback(req)
|
||||||
|
}
|
||||||
|
res, err := req.Execute(method, url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.StatusCode() >= 400 {
|
||||||
|
return nil, fmt.Errorf("request failed, status: %s", res.Status())
|
||||||
|
}
|
||||||
|
code := utils.Json.Get(res.Body(), "code").ToInt()
|
||||||
|
if code != 200 {
|
||||||
|
if (code == 401 || code == 403) && !utils.IsBool(retry...) {
|
||||||
|
err = d.login()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.request(api, method, callback, true)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed,code: %d, message: %s", code, utils.Json.Get(res.Body(), "message").ToString())
|
||||||
|
}
|
||||||
|
return res.Body(), nil
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
@ -20,8 +20,10 @@ type AliyundriveOpen struct {
|
|||||||
Addition
|
Addition
|
||||||
base string
|
base string
|
||||||
|
|
||||||
AccessToken string
|
DriveId 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 {
|
func (d *AliyundriveOpen) Config() driver.Config {
|
||||||
@ -33,15 +35,13 @@ func (d *AliyundriveOpen) GetAddition() driver.Additional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyundriveOpen) Init(ctx context.Context) error {
|
func (d *AliyundriveOpen) Init(ctx context.Context) error {
|
||||||
err := d.refreshToken()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res, err := d.request("/adrive/v1.0/user/getDriveInfo", http.MethodPost, nil)
|
res, err := d.request("/adrive/v1.0/user/getDriveInfo", http.MethodPost, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.DriveId = utils.Json.Get(res, "default_drive_id").ToString()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ func (d *AliyundriveOpen) Drop(ctx context.Context) error {
|
|||||||
return nil
|
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())
|
files, err := d.getFiles(dir.GetID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -59,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) {
|
res, err := d.request("/adrive/v1.0/openFile/getDownloadUrl", http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(base.Json{
|
req.SetBody(base.Json{
|
||||||
"drive_id": d.DriveId,
|
"drive_id": d.DriveId,
|
||||||
@ -71,11 +75,17 @@ func (d *AliyundriveOpen) Link(ctx context.Context, file model.Obj, args model.L
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
url := utils.Json.Get(res, "url").ToString()
|
url := utils.Json.Get(res, "url").ToString()
|
||||||
|
exp := time.Hour
|
||||||
return &model.Link{
|
return &model.Link{
|
||||||
URL: url,
|
URL: url,
|
||||||
|
Expiration: &exp,
|
||||||
}, nil
|
}, 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 {
|
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) {
|
_, err := d.request("/adrive/v1.0/openFile/create", http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(base.Json{
|
req.SetBody(base.Json{
|
||||||
@ -153,11 +163,7 @@ func (d *AliyundriveOpen) Put(ctx context.Context, dstDir model.Obj, stream mode
|
|||||||
count := 1
|
count := 1
|
||||||
if stream.GetSize() > DEFAULT {
|
if stream.GetSize() > DEFAULT {
|
||||||
count = int(math.Ceil(float64(stream.GetSize()) / float64(DEFAULT)))
|
count = int(math.Ceil(float64(stream.GetSize()) / float64(DEFAULT)))
|
||||||
partInfoList := make([]base.Json, 0, count)
|
createData["part_info_list"] = makePartInfos(count)
|
||||||
for i := 1; i <= count; i++ {
|
|
||||||
partInfoList = append(partInfoList, base.Json{"part_number": i})
|
|
||||||
}
|
|
||||||
createData["part_info_list"] = partInfoList
|
|
||||||
}
|
}
|
||||||
var createResp CreateResp
|
var createResp CreateResp
|
||||||
_, err := d.request("/adrive/v1.0/openFile/create", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request("/adrive/v1.0/openFile/create", http.MethodPost, func(req *resty.Request) {
|
||||||
@ -167,28 +173,26 @@ func (d *AliyundriveOpen) Put(ctx context.Context, dstDir model.Obj, stream mode
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// 2. upload
|
// 2. upload
|
||||||
for i, partInfo := range createResp.PartInfoList {
|
preTime := time.Now()
|
||||||
|
for i := 1; i <= len(createResp.PartInfoList); i++ {
|
||||||
if utils.IsCanceled(ctx) {
|
if utils.IsCanceled(ctx) {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
uploadUrl := partInfo.UploadUrl
|
err = d.uploadPart(ctx, i, count, utils.NewMultiReadable(io.LimitReader(stream, DEFAULT)), &createResp, true)
|
||||||
if d.InternalUpload {
|
|
||||||
//Replace a known public Host with an internal Host
|
|
||||||
uploadUrl = strings.ReplaceAll(uploadUrl, "https://cn-beijing-data.aliyundrive.net/", "http://ccp-bj29-bj-1592982087.oss-cn-beijing-internal.aliyuncs.com/")
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("PUT", uploadUrl, io.LimitReader(stream, DEFAULT))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req = req.WithContext(ctx)
|
|
||||||
res, err := base.HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res.Body.Close()
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
up(i * 100 / count)
|
up(i * 100 / count)
|
||||||
}
|
}
|
||||||
|
// refresh upload url if 50 minutes passed
|
||||||
|
if time.Since(preTime) > 50*time.Minute {
|
||||||
|
createResp.PartInfoList, err = d.getUploadUrl(count, createResp.FileId, createResp.UploadId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
preTime = time.Now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 3. complete
|
// 3. complete
|
||||||
_, err = d.request("/adrive/v1.0/openFile/complete", http.MethodPost, func(req *resty.Request) {
|
_, err = d.request("/adrive/v1.0/openFile/complete", http.MethodPost, func(req *resty.Request) {
|
||||||
|
@ -14,7 +14,8 @@ type Addition struct {
|
|||||||
ClientID string `json:"client_id" required:"false" help:"Keep it empty if you don't have one"`
|
ClientID string `json:"client_id" required:"false" help:"Keep it empty if you don't have one"`
|
||||||
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"`
|
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"`
|
||||||
RemoveWay string `json:"remove_way" required:"true" type:"select" options:"trash,delete"`
|
RemoveWay string `json:"remove_way" required:"true" type:"select" options:"trash,delete"`
|
||||||
InternalUpload bool `json:"internal_upload" help:"If you are using Aliyun ECS in Beijing, you can turn it on to boost the upload speed"`
|
InternalUpload bool `json:"internal_upload" help:"If you are using Aliyun ECS is located in Beijing, you can turn it on to boost the upload speed"`
|
||||||
|
AccessToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
@ -32,7 +33,7 @@ var config = driver.Config{
|
|||||||
func init() {
|
func init() {
|
||||||
op.RegisterDriver(func() driver.Driver {
|
op.RegisterDriver(func() driver.Driver {
|
||||||
return &AliyundriveOpen{
|
return &AliyundriveOpen{
|
||||||
base: "https://open.aliyundrive.com",
|
base: "https://openapi.aliyundrive.com",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,14 @@ func fileToObj(f File) *model.ObjThumb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PartInfo struct {
|
||||||
|
Etag interface{} `json:"etag"`
|
||||||
|
PartNumber int `json:"part_number"`
|
||||||
|
PartSize interface{} `json:"part_size"`
|
||||||
|
UploadUrl string `json:"upload_url"`
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateResp struct {
|
type CreateResp struct {
|
||||||
//Type string `json:"type"`
|
//Type string `json:"type"`
|
||||||
//ParentFileId string `json:"parent_file_id"`
|
//ParentFileId string `json:"parent_file_id"`
|
||||||
@ -56,12 +64,6 @@ type CreateResp struct {
|
|||||||
//FileName string `json:"file_name"`
|
//FileName string `json:"file_name"`
|
||||||
UploadId string `json:"upload_id"`
|
UploadId string `json:"upload_id"`
|
||||||
//Location string `json:"location"`
|
//Location string `json:"location"`
|
||||||
RapidUpload bool `json:"rapid_upload"`
|
RapidUpload bool `json:"rapid_upload"`
|
||||||
PartInfoList []struct {
|
PartInfoList []PartInfo `json:"part_info_list"`
|
||||||
Etag interface{} `json:"etag"`
|
|
||||||
PartNumber int `json:"part_number"`
|
|
||||||
PartSize interface{} `json:"part_size"`
|
|
||||||
UploadUrl string `json:"upload_url"`
|
|
||||||
ContentType string `json:"content_type"`
|
|
||||||
} `json:"part_info_list"`
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package aliyundrive_open
|
package aliyundrive_open
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,7 +65,7 @@ func (d *AliyundriveOpen) request(uri, method string, callback base.ReqCallback,
|
|||||||
}
|
}
|
||||||
isRetry := len(retry) > 0 && retry[0]
|
isRetry := len(retry) > 0 && retry[0]
|
||||||
if e.Code != "" {
|
if e.Code != "" {
|
||||||
if !isRetry && e.Code == "AccessTokenInvalid" {
|
if !isRetry && (utils.SliceContains([]string{"AccessTokenInvalid", "AccessTokenExpired", "I400JD"}, e.Code) || d.AccessToken == "") {
|
||||||
err = d.refreshToken()
|
err = d.refreshToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -106,3 +109,59 @@ func (d *AliyundriveOpen) getFiles(fileId string) ([]File, error) {
|
|||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makePartInfos(size int) []base.Json {
|
||||||
|
partInfoList := make([]base.Json, size)
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
partInfoList[i] = base.Json{"part_number": 1 + i}
|
||||||
|
}
|
||||||
|
return partInfoList
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) getUploadUrl(count int, fileId, uploadId string) ([]PartInfo, error) {
|
||||||
|
partInfoList := makePartInfos(count)
|
||||||
|
var resp CreateResp
|
||||||
|
_, err := d.request("/adrive/v1.0/openFile/getUploadUrl", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"file_id": fileId,
|
||||||
|
"part_info_list": partInfoList,
|
||||||
|
"upload_id": uploadId,
|
||||||
|
}).SetResult(&resp)
|
||||||
|
})
|
||||||
|
return resp.PartInfoList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) uploadPart(ctx context.Context, i, count int, reader *utils.MultiReadable, resp *CreateResp, retry bool) error {
|
||||||
|
partInfo := resp.PartInfoList[i-1]
|
||||||
|
uploadUrl := partInfo.UploadUrl
|
||||||
|
if d.InternalUpload {
|
||||||
|
uploadUrl = strings.ReplaceAll(uploadUrl, "https://cn-beijing-data.aliyundrive.net/", "http://ccp-bj29-bj-1592982087.oss-cn-beijing-internal.aliyuncs.com/")
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("PUT", uploadUrl, reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
res, err := base.HttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
if retry {
|
||||||
|
reader.Reset()
|
||||||
|
return d.uploadPart(ctx, i, count, reader, resp, false)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
if retry && res.StatusCode == http.StatusForbidden {
|
||||||
|
resp.PartInfoList, err = d.getUploadUrl(count, resp.FileId, resp.UploadId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reader.Reset()
|
||||||
|
return d.uploadPart(ctx, i, count, reader, resp, false)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusConflict {
|
||||||
|
return fmt.Errorf("upload status: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -19,11 +19,13 @@ import (
|
|||||||
_ "github.com/alist-org/alist/v3/drivers/ftp"
|
_ "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_drive"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/google_photo"
|
_ "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/lanzou"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/local"
|
_ "github.com/alist-org/alist/v3/drivers/local"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/mediatrack"
|
_ "github.com/alist-org/alist/v3/drivers/mediatrack"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/mega"
|
_ "github.com/alist-org/alist/v3/drivers/mega"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/onedrive_app"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/pikpak"
|
_ "github.com/alist-org/alist/v3/drivers/pikpak"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/pikpak_share"
|
_ "github.com/alist-org/alist/v3/drivers/pikpak_share"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/quark"
|
_ "github.com/alist-org/alist/v3/drivers/quark"
|
||||||
@ -35,6 +37,7 @@ import (
|
|||||||
_ "github.com/alist-org/alist/v3/drivers/terabox"
|
_ "github.com/alist-org/alist/v3/drivers/terabox"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/thunder"
|
_ "github.com/alist-org/alist/v3/drivers/thunder"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/trainbit"
|
_ "github.com/alist-org/alist/v3/drivers/trainbit"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/url_tree"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/uss"
|
_ "github.com/alist-org/alist/v3/drivers/uss"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/webdav"
|
_ "github.com/alist-org/alist/v3/drivers/webdav"
|
||||||
|
@ -249,7 +249,7 @@ func (d *BaiduPhoto) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
|
|||||||
"isdir": "0",
|
"isdir": "0",
|
||||||
"rtype": "1",
|
"rtype": "1",
|
||||||
"ctype": "11",
|
"ctype": "11",
|
||||||
"path": stream.GetName(),
|
"path": fmt.Sprintf("/%s", stream.GetName()),
|
||||||
"size": fmt.Sprint(stream.GetSize()),
|
"size": fmt.Sprint(stream.GetSize()),
|
||||||
"slice-md5": slice_md5,
|
"slice-md5": slice_md5,
|
||||||
"content-md5": content_md5,
|
"content-md5": content_md5,
|
||||||
|
@ -18,7 +18,7 @@ func getTid() string {
|
|||||||
|
|
||||||
// 检查名称
|
// 检查名称
|
||||||
func checkName(name string) bool {
|
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 {
|
func toTime(t int64) *time.Time {
|
||||||
|
@ -23,7 +23,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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) {
|
func (d *BaiduPhoto) Request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||||
|
@ -2,25 +2,30 @@ package baidu_share
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Xhofe/go-cache"
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaiduShare struct {
|
type BaiduShare struct {
|
||||||
model.Storage
|
model.Storage
|
||||||
Addition
|
Addition
|
||||||
config map[string]string
|
client *resty.Client
|
||||||
dlinkCache cache.ICache[string]
|
info struct {
|
||||||
|
Root string
|
||||||
|
Seckey string
|
||||||
|
Shareid string
|
||||||
|
Uk string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *BaiduShare) Config() driver.Config {
|
func (d *BaiduShare) Config() driver.Config {
|
||||||
@ -34,115 +39,208 @@ func (d *BaiduShare) GetAddition() driver.Additional {
|
|||||||
func (d *BaiduShare) Init(ctx context.Context) error {
|
func (d *BaiduShare) Init(ctx context.Context) error {
|
||||||
// TODO login / refresh token
|
// TODO login / refresh token
|
||||||
//op.MustSaveDriverStorage(d)
|
//op.MustSaveDriverStorage(d)
|
||||||
d.config = map[string]string{}
|
d.client = resty.New().
|
||||||
d.dlinkCache = cache.NewMemCache(cache.WithClearInterval[string](time.Duration(d.CacheExpiration) * time.Minute))
|
SetBaseURL("https://pan.baidu.com").
|
||||||
return nil
|
SetHeader("User-Agent", "netdisk").
|
||||||
|
SetCookie(&http.Cookie{Name: "BDUSS", Value: d.BDUSS}).
|
||||||
|
SetCookie(&http.Cookie{Name: "ndut_fmt"})
|
||||||
|
respJson := struct {
|
||||||
|
Errno int64 `json:"errno"`
|
||||||
|
Data struct {
|
||||||
|
List [1]struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
} `json:"list"`
|
||||||
|
Uk json.Number `json:"uk"`
|
||||||
|
Shareid json.Number `json:"shareid"`
|
||||||
|
Seckey string `json:"seckey"`
|
||||||
|
} `json:"data"`
|
||||||
|
}{}
|
||||||
|
resp, err := d.client.R().
|
||||||
|
SetBody(url.Values{
|
||||||
|
"pwd": {d.Pwd},
|
||||||
|
"root": {"1"},
|
||||||
|
"shorturl": {d.Surl},
|
||||||
|
}.Encode()).
|
||||||
|
SetResult(&respJson).
|
||||||
|
Post("share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
|
||||||
|
if err == nil {
|
||||||
|
if resp.IsSuccess() && respJson.Errno == 0 {
|
||||||
|
d.info.Root = path.Dir(respJson.Data.List[0].Path)
|
||||||
|
d.info.Seckey = respJson.Data.Seckey
|
||||||
|
d.info.Shareid = respJson.Data.Shareid.String()
|
||||||
|
d.info.Uk = respJson.Data.Uk.String()
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *BaiduShare) Drop(ctx context.Context) error {
|
func (d *BaiduShare) Drop(ctx context.Context) error {
|
||||||
d.dlinkCache.Clear()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *BaiduShare) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
func (d *BaiduShare) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
// TODO return the files list
|
// TODO return the files list, required
|
||||||
body := url.Values{
|
reqDir := dir.GetPath()
|
||||||
"shorturl": {d.Surl},
|
isRoot := "0"
|
||||||
"dir": {dir.GetPath()},
|
if reqDir == d.RootFolderPath {
|
||||||
"root": {"0"},
|
reqDir = path.Join(d.info.Root, reqDir)
|
||||||
"pwd": {d.Pwd},
|
|
||||||
"num": {"1000"},
|
|
||||||
"order": {"time"},
|
|
||||||
}
|
}
|
||||||
if body.Get("dir") == "" || body.Get("dir") == d.config["root"] {
|
if reqDir == d.info.Root {
|
||||||
body.Set("root", "1")
|
isRoot = "1"
|
||||||
}
|
}
|
||||||
res := []model.Obj{}
|
objs := []model.Obj{}
|
||||||
var err error
|
var err error
|
||||||
var page int64 = 1
|
var page uint64 = 1
|
||||||
more := true
|
more := true
|
||||||
for more {
|
for more && err == nil {
|
||||||
body.Set("page", strconv.FormatInt(page, 10))
|
respJson := struct {
|
||||||
req := base.RestyClient.R().
|
Errno int64 `json:"errno"`
|
||||||
SetCookies([]*http.Cookie{{Name: "BDUSS", Value: d.BDUSS}}).
|
Data struct {
|
||||||
SetBody(body.Encode())
|
More bool `json:"has_more"`
|
||||||
resp, e := req.Post("https://pan.baidu.com/share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
|
List []struct {
|
||||||
|
Fsid json.Number `json:"fs_id"`
|
||||||
|
Isdir json.Number `json:"isdir"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"server_filename"`
|
||||||
|
Mtime json.Number `json:"server_mtime"`
|
||||||
|
Size json.Number `json:"size"`
|
||||||
|
} `json:"list"`
|
||||||
|
} `json:"data"`
|
||||||
|
}{}
|
||||||
|
resp, e := d.client.R().
|
||||||
|
SetBody(url.Values{
|
||||||
|
"dir": {reqDir},
|
||||||
|
"num": {"1000"},
|
||||||
|
"order": {"time"},
|
||||||
|
"page": {fmt.Sprint(page)},
|
||||||
|
"pwd": {d.Pwd},
|
||||||
|
"root": {isRoot},
|
||||||
|
"shorturl": {d.Surl},
|
||||||
|
}.Encode()).
|
||||||
|
SetResult(&respJson).
|
||||||
|
Post("share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
|
||||||
err = e
|
err = e
|
||||||
jsonresp := jsonResp{}
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = base.RestyClient.JSONUnmarshal(resp.Body(), &jsonresp)
|
if resp.IsSuccess() && respJson.Errno == 0 {
|
||||||
}
|
page++
|
||||||
if err == nil && jsonresp.Errno == 0 {
|
more = respJson.Data.More
|
||||||
more = jsonresp.Data.More
|
for _, v := range respJson.Data.List {
|
||||||
page += 1
|
size, _ := v.Size.Int64()
|
||||||
for _, v := range jsonresp.Data.List {
|
mtime, _ := v.Mtime.Int64()
|
||||||
size, _ := v.Size.Int64()
|
objs = append(objs, &model.Object{
|
||||||
mtime, _ := v.Time.Int64()
|
ID: v.Fsid.String(),
|
||||||
res = append(res, &model.Object{
|
Path: v.Path,
|
||||||
ID: v.ID.String(),
|
Name: v.Name,
|
||||||
Path: v.Path,
|
Size: size,
|
||||||
Name: v.Name,
|
Modified: time.Unix(mtime, 0),
|
||||||
Size: size,
|
IsFolder: v.Isdir.String() == "1",
|
||||||
Modified: time.Unix(mtime, 0),
|
})
|
||||||
IsFolder: v.Dir.String() == "1",
|
}
|
||||||
})
|
} else {
|
||||||
d.dlinkCache.Set(v.Path, v.Dlink, cache.WithEx[string](time.Duration(d.CacheExpiration/2)*time.Minute))
|
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||||
}
|
}
|
||||||
if len(res) > 0 && body.Get("root") == "1" {
|
|
||||||
d.config["root"] = path.Dir(res[0].GetPath())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err == nil {
|
|
||||||
err = fmt.Errorf("errno:%d", jsonresp.Errno)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res, err
|
return objs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *BaiduShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
func (d *BaiduShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
// TODO return link of file
|
// TODO return link of file, required
|
||||||
var err error
|
link := model.Link{Header: d.client.Header}
|
||||||
req := base.RestyClient.R().SetCookies([]*http.Cookie{{Name: "BDUSS", Value: d.BDUSS}})
|
sign := ""
|
||||||
url, found := d.dlinkCache.Get(file.GetPath())
|
stamp := ""
|
||||||
if !found {
|
signJson := struct {
|
||||||
_, err = d.List(ctx, &model.Object{Path: path.Dir(file.GetPath())}, model.ListArgs{})
|
Errno int64 `json:"errno"`
|
||||||
url, found = d.dlinkCache.Get(file.GetPath())
|
Data struct {
|
||||||
if err == nil && !found {
|
Stamp json.Number `json:"timestamp"`
|
||||||
err = errs.NotSupport
|
Sign string `json:"sign"`
|
||||||
|
} `json:"data"`
|
||||||
|
}{}
|
||||||
|
resp, err := d.client.R().
|
||||||
|
SetQueryParam("surl", d.Surl).
|
||||||
|
SetResult(&signJson).
|
||||||
|
Get("share/tplconfig?fields=sign,timestamp&channel=chunlei&web=1&app_id=250528&clienttype=0")
|
||||||
|
if err == nil {
|
||||||
|
if resp.IsSuccess() && signJson.Errno == 0 {
|
||||||
|
stamp = signJson.Data.Stamp.String()
|
||||||
|
sign = signJson.Data.Sign
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &model.Link{URL: url, Header: req.Header}, err
|
if err == nil {
|
||||||
|
respJson := struct {
|
||||||
|
Errno int64 `json:"errno"`
|
||||||
|
List [1]struct {
|
||||||
|
Dlink string `json:"dlink"`
|
||||||
|
} `json:"list"`
|
||||||
|
}{}
|
||||||
|
resp, err = d.client.R().
|
||||||
|
SetQueryParam("sign", sign).
|
||||||
|
SetQueryParam("timestamp", stamp).
|
||||||
|
SetBody(url.Values{
|
||||||
|
"encrypt": {"0"},
|
||||||
|
"extra": {fmt.Sprintf(`{"sekey":"%s"}`, d.info.Seckey)},
|
||||||
|
"fid_list": {fmt.Sprintf("[%s]", file.GetID())},
|
||||||
|
"primaryid": {d.info.Shareid},
|
||||||
|
"product": {"share"},
|
||||||
|
"type": {"nolimit"},
|
||||||
|
"uk": {d.info.Uk},
|
||||||
|
}.Encode()).
|
||||||
|
SetResult(&respJson).
|
||||||
|
Post("api/sharedownload?app_id=250528&channel=chunlei&clienttype=12&web=1")
|
||||||
|
if err == nil {
|
||||||
|
if resp.IsSuccess() && respJson.Errno == 0 && respJson.List[0].Dlink != "" {
|
||||||
|
link.URL = respJson.List[0].Dlink
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
resp, err = d.client.R().
|
||||||
|
SetDoNotParseResponse(true).
|
||||||
|
Get(link.URL)
|
||||||
|
if err == nil {
|
||||||
|
defer resp.RawBody().Close()
|
||||||
|
if resp.IsError() {
|
||||||
|
byt, _ := io.ReadAll(resp.RawBody())
|
||||||
|
err = fmt.Errorf(" %s; %s; ", resp.Status(), byt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &link, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *BaiduShare) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *BaiduShare) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
// TODO create folder
|
// TODO create folder, optional
|
||||||
return errs.NotSupport
|
return errs.NotSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *BaiduShare) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
func (d *BaiduShare) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
// TODO move obj
|
// TODO move obj, optional
|
||||||
return errs.NotSupport
|
return errs.NotSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *BaiduShare) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
func (d *BaiduShare) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
// TODO rename obj
|
// TODO rename obj, optional
|
||||||
return errs.NotSupport
|
return errs.NotSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *BaiduShare) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
func (d *BaiduShare) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
// TODO copy obj
|
// TODO copy obj, optional
|
||||||
return errs.NotSupport
|
return errs.NotSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *BaiduShare) Remove(ctx context.Context, obj model.Obj) error {
|
func (d *BaiduShare) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
// TODO remove obj
|
// TODO remove obj, optional
|
||||||
return errs.NotSupport
|
return errs.NotSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *BaiduShare) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
func (d *BaiduShare) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
// TODO upload file
|
// TODO upload file, optional
|
||||||
return errs.NotSupport
|
return errs.NotSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,14 +17,17 @@ type Addition struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
Name: "BaiduShare",
|
Name: "BaiduShare",
|
||||||
LocalSort: true,
|
LocalSort: true,
|
||||||
OnlyLocal: false,
|
OnlyLocal: false,
|
||||||
OnlyProxy: false,
|
OnlyProxy: false,
|
||||||
NoCache: false,
|
NoCache: false,
|
||||||
NoUpload: true,
|
NoUpload: true,
|
||||||
NeedMs: false,
|
NeedMs: false,
|
||||||
DefaultRoot: "",
|
DefaultRoot: "/",
|
||||||
|
CheckStatus: false,
|
||||||
|
Alert: "",
|
||||||
|
NoOverwriteUpload: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -1,19 +1 @@
|
|||||||
package baidu_share
|
package baidu_share
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
type jsonResp struct {
|
|
||||||
Errno int64 `json:"errno"`
|
|
||||||
Data struct {
|
|
||||||
More bool `json:"has_more"`
|
|
||||||
List []struct {
|
|
||||||
ID json.Number `json:"fs_id"`
|
|
||||||
Dir json.Number `json:"isdir"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Name string `json:"server_filename"`
|
|
||||||
Time json.Number `json:"server_mtime"`
|
|
||||||
Size json.Number `json:"size"`
|
|
||||||
Dlink string `json:"dlink"`
|
|
||||||
} `json:"list"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
@ -1,31 +1,48 @@
|
|||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var NoRedirectClient *resty.Client
|
var (
|
||||||
var RestyClient = NewRestyClient()
|
NoRedirectClient *resty.Client
|
||||||
var HttpClient = &http.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 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
|
var DefaultTimeout = time.Second * 30
|
||||||
|
|
||||||
func init() {
|
func InitClient() {
|
||||||
NoRedirectClient = resty.New().SetRedirectPolicy(
|
NoRedirectClient = resty.New().SetRedirectPolicy(
|
||||||
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
}),
|
}),
|
||||||
)
|
).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify})
|
||||||
NoRedirectClient.SetHeader("user-agent", UserAgent)
|
NoRedirectClient.SetHeader("user-agent", UserAgent)
|
||||||
|
|
||||||
|
RestyClient = NewRestyClient()
|
||||||
|
HttpClient = NewHttpClient()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRestyClient() *resty.Client {
|
func NewRestyClient() *resty.Client {
|
||||||
client := resty.New().
|
client := resty.New().
|
||||||
SetHeader("user-agent", UserAgent).
|
SetHeader("user-agent", UserAgent).
|
||||||
SetRetryCount(3).
|
SetRetryCount(3).
|
||||||
SetTimeout(DefaultTimeout)
|
SetTimeout(DefaultTimeout).
|
||||||
|
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify})
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewHttpClient() *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Timeout: DefaultTimeout,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,21 +2,20 @@ package cloudreve
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Cloudreve struct {
|
type Cloudreve struct {
|
||||||
model.Storage
|
model.Storage
|
||||||
Addition
|
Addition
|
||||||
Cookie string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Cloudreve) Config() driver.Config {
|
func (d *Cloudreve) Config() driver.Config {
|
||||||
@ -28,6 +27,9 @@ func (d *Cloudreve) GetAddition() driver.Additional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Cloudreve) Init(ctx context.Context) error {
|
func (d *Cloudreve) Init(ctx context.Context) error {
|
||||||
|
if d.Cookie != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return d.login()
|
return d.login()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,8 +10,9 @@ type Addition struct {
|
|||||||
driver.RootPath
|
driver.RootPath
|
||||||
// define other
|
// define other
|
||||||
Address string `json:"address" required:"true"`
|
Address string `json:"address" required:"true"`
|
||||||
Username string `json:"username" required:"true"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password" required:"true"`
|
Password string `json:"password"`
|
||||||
|
Cookie string `json:"cookie"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package cloudreve
|
package cloudreve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resp struct {
|
type Resp struct {
|
||||||
@ -52,3 +53,8 @@ func objectToObj(f Object) *model.Object {
|
|||||||
IsFolder: f.Type == "dir",
|
IsFolder: f.Type == "dir",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
LoginCaptcha bool `json:"loginCaptcha"`
|
||||||
|
CaptchaType string `json:"captcha_type"`
|
||||||
|
}
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
package cloudreve
|
package cloudreve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/alist-org/alist/v3/pkg/cookie"
|
"github.com/alist-org/alist/v3/pkg/cookie"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
json "github.com/json-iterator/go"
|
json "github.com/json-iterator/go"
|
||||||
"net/http"
|
jsoniter "github.com/json-iterator/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// do others that not defined in Driver interface
|
// do others that not defined in Driver interface
|
||||||
@ -43,13 +49,15 @@ func (d *Cloudreve) request(method string, path string, callback base.ReqCallbac
|
|||||||
|
|
||||||
// 刷新 cookie
|
// 刷新 cookie
|
||||||
if r.Code == http.StatusUnauthorized && path != loginPath {
|
if r.Code == http.StatusUnauthorized && path != loginPath {
|
||||||
err = d.login()
|
if d.Username != "" && d.Password != "" {
|
||||||
if err != nil {
|
err = d.login()
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return d.request(method, path, callback, out)
|
||||||
}
|
}
|
||||||
return d.request(method, path, callback, out)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(r.Msg)
|
return errors.New(r.Msg)
|
||||||
}
|
}
|
||||||
sess := cookie.GetCookie(resp.Cookies(), "cloudreve-session")
|
sess := cookie.GetCookie(resp.Cookies(), "cloudreve-session")
|
||||||
@ -72,13 +80,57 @@ func (d *Cloudreve) request(method string, path string, callback base.ReqCallbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Cloudreve) login() error {
|
func (d *Cloudreve) login() error {
|
||||||
return d.request(http.MethodPost, loginPath, func(req *resty.Request) {
|
var siteConfig Config
|
||||||
|
err := d.request(http.MethodGet, "/site/config", nil, &siteConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
err = d.doLogin(siteConfig.LoginCaptcha)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil && err.Error() != "CAPTCHA not match." {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Cloudreve) doLogin(needCaptcha bool) error {
|
||||||
|
var captchaCode string
|
||||||
|
var err error
|
||||||
|
if needCaptcha {
|
||||||
|
var captcha string
|
||||||
|
err = d.request(http.MethodGet, "/site/captcha", nil, &captcha)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(captcha) == 0 {
|
||||||
|
return errors.New("can not get captcha")
|
||||||
|
}
|
||||||
|
i := strings.Index(captcha, ",")
|
||||||
|
dec := base64.NewDecoder(base64.StdEncoding, strings.NewReader(captcha[i+1:]))
|
||||||
|
vRes, err := base.RestyClient.R().SetMultipartField(
|
||||||
|
"image", "validateCode.png", "image/png", dec).
|
||||||
|
Post(setting.GetStr(conf.OcrApi))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
|
||||||
|
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
|
||||||
|
}
|
||||||
|
captchaCode = jsoniter.Get(vRes.Body(), "result").ToString()
|
||||||
|
}
|
||||||
|
var resp Resp
|
||||||
|
err = d.request(http.MethodPost, loginPath, func(req *resty.Request) {
|
||||||
req.SetBody(base.Json{
|
req.SetBody(base.Json{
|
||||||
"username": d.Addition.Username,
|
"username": d.Addition.Username,
|
||||||
"Password": d.Addition.Password,
|
"Password": d.Addition.Password,
|
||||||
"captchaCode": "",
|
"captchaCode": captchaCode,
|
||||||
})
|
})
|
||||||
}, nil)
|
}, &resp)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertSrc(obj model.Obj) map[string]interface{} {
|
func convertSrc(obj model.Obj) map[string]interface{} {
|
||||||
|
@ -17,7 +17,9 @@ import (
|
|||||||
type GoogleDrive struct {
|
type GoogleDrive struct {
|
||||||
model.Storage
|
model.Storage
|
||||||
Addition
|
Addition
|
||||||
AccessToken string
|
AccessToken string
|
||||||
|
ServiceAccountFile int
|
||||||
|
ServiceAccountFileList []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *GoogleDrive) Config() driver.Config {
|
func (d *GoogleDrive) Config() driver.Config {
|
||||||
|
@ -2,21 +2,134 @@ package google_drive
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// do others that not defined in Driver interface
|
// 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 {
|
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"
|
url := "https://www.googleapis.com/oauth2/v4/token"
|
||||||
var resp base.TokenResp
|
var resp base.TokenResp
|
||||||
var e TokenError
|
var e TokenError
|
||||||
|
128
drivers/ipfs_api/driver.go
Normal file
128
drivers/ipfs_api/driver.go
Normal 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
25
drivers/ipfs_api/meta.go
Normal 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{}
|
||||||
|
})
|
||||||
|
}
|
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
@ -15,8 +14,6 @@ import (
|
|||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var upClient = base.NewRestyClient().SetTimeout(120 * time.Second)
|
|
||||||
|
|
||||||
type LanZou struct {
|
type LanZou struct {
|
||||||
Addition
|
Addition
|
||||||
model.Storage
|
model.Storage
|
||||||
@ -209,11 +206,11 @@ func (d *LanZou) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
var resp RespText[[]FileOrFolder]
|
var resp RespText[[]FileOrFolder]
|
||||||
_, err := d._post(d.BaseUrl+"/fileup.php", func(req *resty.Request) {
|
_, err := d._post(d.BaseUrl+"/fileup.php", func(req *resty.Request) {
|
||||||
req.SetFormData(map[string]string{
|
req.SetFormData(map[string]string{
|
||||||
"task": "1",
|
"task": "1",
|
||||||
"vie": "2",
|
"vie": "2",
|
||||||
"ve": "2",
|
"ve": "2",
|
||||||
"id": "WU_FILE_0",
|
"id": "WU_FILE_0",
|
||||||
"name": stream.GetName(),
|
"name": stream.GetName(),
|
||||||
"folder_id_bb_n": dstDir.GetID(),
|
"folder_id_bb_n": dstDir.GetID(),
|
||||||
}).SetFileReader("upload_file", stream.GetName(), stream).SetContext(ctx)
|
}).SetFileReader("upload_file", stream.GetName(), stream).SetContext(ctx)
|
||||||
}, &resp, true)
|
}, &resp, true)
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
@ -16,6 +17,9 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var upClient *resty.Client
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
func (d *LanZou) doupload(callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
func (d *LanZou) doupload(callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||||
return d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
return d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
||||||
req.SetQueryParam("uid", d.uid)
|
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) {
|
func (d *LanZou) request(url string, method string, callback base.ReqCallback, up bool) ([]byte, error) {
|
||||||
var req *resty.Request
|
var req *resty.Request
|
||||||
if up {
|
if up {
|
||||||
|
once.Do(func() {
|
||||||
|
upClient = base.NewRestyClient().SetTimeout(120 * time.Second)
|
||||||
|
})
|
||||||
req = upClient.R()
|
req = upClient.R()
|
||||||
} else {
|
} else {
|
||||||
req = base.RestyClient.R()
|
req = base.RestyClient.R()
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
@ -21,7 +19,6 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/sign"
|
"github.com/alist-org/alist/v3/internal/sign"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/disintegration/imaging"
|
|
||||||
_ "golang.org/x/image/webp"
|
_ "golang.org/x/image/webp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,6 +52,12 @@ func (d *Local) Init(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
d.Addition.RootFolderPath = abs
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +71,7 @@ func (d *Local) GetAddition() driver.Additional {
|
|||||||
|
|
||||||
func (d *Local) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
func (d *Local) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
fullPath := dir.GetPath()
|
fullPath := dir.GetPath()
|
||||||
rawFiles, err := ioutil.ReadDir(fullPath)
|
rawFiles, err := readDir(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -136,36 +139,18 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
|||||||
fullPath := file.GetPath()
|
fullPath := file.GetPath()
|
||||||
var link model.Link
|
var link model.Link
|
||||||
if args.Type == "thumb" && utils.Ext(file.GetName()) != "svg" {
|
if args.Type == "thumb" && utils.Ext(file.GetName()) != "svg" {
|
||||||
var srcBuf *bytes.Buffer
|
buf, thumbPath, err := d.getThumb(file)
|
||||||
if utils.GetFileType(file.GetName()) == conf.VIDEO {
|
|
||||||
videoBuf, err := GetSnapshot(fullPath, 10)
|
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, err
|
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{
|
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 {
|
} else {
|
||||||
link.FilePath = &fullPath
|
link.FilePath = &fullPath
|
||||||
|
@ -7,9 +7,10 @@ import (
|
|||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
driver.RootPath
|
driver.RootPath
|
||||||
Thumbnail bool `json:"thumbnail" required:"true" help:"enable thumbnail"`
|
Thumbnail bool `json:"thumbnail" required:"true" help:"enable thumbnail"`
|
||||||
ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"`
|
ThumbCacheFolder string `json:"thumb_cache_folder"`
|
||||||
MkdirPerm string `json:"mkdir_perm" default:"777"`
|
ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"`
|
||||||
|
MkdirPerm string `json:"mkdir_perm" default:"777"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -3,10 +3,17 @@ package local
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isSymlinkDir(f fs.FileInfo, path string) bool {
|
func isSymlinkDir(f fs.FileInfo, path string) bool {
|
||||||
@ -39,3 +46,66 @@ func GetSnapshot(videoPath string, frameNum int) (imgData *bytes.Buffer, err err
|
|||||||
}
|
}
|
||||||
return srcBuf, nil
|
return srcBuf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readDir(dirname string) ([]fs.FileInfo, error) {
|
||||||
|
f, err := os.Open(dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list, err := f.Readdir(-1)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -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()))
|
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||||
finish += byteSize
|
finish += byteSize
|
||||||
res, err := base.HttpClient.Do(req)
|
res, err := base.HttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if res.StatusCode != 201 && res.StatusCode != 202 {
|
if res.StatusCode != 201 && res.StatusCode != 202 {
|
||||||
data, _ := io.ReadAll(res.Body)
|
data, _ := io.ReadAll(res.Body)
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
|
160
drivers/onedrive_app/driver.go
Normal file
160
drivers/onedrive_app/driver.go
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package onedrive_app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OnedriveAPP struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
AccessToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Init(ctx context.Context) error {
|
||||||
|
if d.ChunkSize < 1 {
|
||||||
|
d.ChunkSize = 5
|
||||||
|
}
|
||||||
|
return d.accessToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
files, err := d.getFiles(dir.GetPath())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
||||||
|
return fileToObj(src, dir.GetID()), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
f, err := d.GetFile(file.GetPath())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if f.File == nil {
|
||||||
|
return nil, errs.NotFile
|
||||||
|
}
|
||||||
|
return &model.Link{
|
||||||
|
URL: f.Url,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
|
url := d.GetMetaUrl(false, parentDir.GetPath()) + "/children"
|
||||||
|
data := base.Json{
|
||||||
|
"name": dirName,
|
||||||
|
"folder": base.Json{},
|
||||||
|
"@microsoft.graph.conflictBehavior": "rename",
|
||||||
|
}
|
||||||
|
_, err := d.Request(url, http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
parentPath := ""
|
||||||
|
if dstDir.GetID() == "" {
|
||||||
|
parentPath = dstDir.GetPath()
|
||||||
|
if utils.PathEqual(parentPath, "/") {
|
||||||
|
parentPath = path.Join("/drive/root", parentPath)
|
||||||
|
} else {
|
||||||
|
parentPath = path.Join("/drive/root:/", parentPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data := base.Json{
|
||||||
|
"parentReference": base.Json{
|
||||||
|
"id": dstDir.GetID(),
|
||||||
|
"path": parentPath,
|
||||||
|
},
|
||||||
|
"name": srcObj.GetName(),
|
||||||
|
}
|
||||||
|
url := d.GetMetaUrl(false, srcObj.GetPath())
|
||||||
|
_, err := d.Request(url, http.MethodPatch, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
|
var parentID string
|
||||||
|
if o, ok := srcObj.(*Object); ok {
|
||||||
|
parentID = o.ParentID
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("srcObj is not Object")
|
||||||
|
}
|
||||||
|
if parentID == "" {
|
||||||
|
parentID = "root"
|
||||||
|
}
|
||||||
|
data := base.Json{
|
||||||
|
"parentReference": base.Json{
|
||||||
|
"id": parentID,
|
||||||
|
},
|
||||||
|
"name": newName,
|
||||||
|
}
|
||||||
|
url := d.GetMetaUrl(false, srcObj.GetPath())
|
||||||
|
_, err := d.Request(url, http.MethodPatch, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
dst, err := d.GetFile(dstDir.GetPath())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data := base.Json{
|
||||||
|
"parentReference": base.Json{
|
||||||
|
"driveId": dst.ParentReference.DriveId,
|
||||||
|
"id": dst.Id,
|
||||||
|
},
|
||||||
|
"name": srcObj.GetName(),
|
||||||
|
}
|
||||||
|
url := d.GetMetaUrl(false, srcObj.GetPath()) + "/copy"
|
||||||
|
_, err = d.Request(url, http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
|
url := d.GetMetaUrl(false, obj.GetPath())
|
||||||
|
_, err := d.Request(url, http.MethodDelete, nil, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
var err error
|
||||||
|
if stream.GetSize() <= 4*1024*1024 {
|
||||||
|
err = d.upSmall(ctx, dstDir, stream)
|
||||||
|
} else {
|
||||||
|
err = d.upBig(ctx, dstDir, stream, up)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*OnedriveAPP)(nil)
|
28
drivers/onedrive_app/meta.go
Normal file
28
drivers/onedrive_app/meta.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package onedrive_app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
driver.RootPath
|
||||||
|
Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"`
|
||||||
|
ClientID string `json:"client_id" required:"true"`
|
||||||
|
ClientSecret string `json:"client_secret" required:"true"`
|
||||||
|
TenantID string `json:"tenant_id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "OnedriveAPP",
|
||||||
|
LocalSort: true,
|
||||||
|
DefaultRoot: "/",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &OnedriveAPP{}
|
||||||
|
})
|
||||||
|
}
|
74
drivers/onedrive_app/types.go
Normal file
74
drivers/onedrive_app/types.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package onedrive_app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Host struct {
|
||||||
|
Oauth string
|
||||||
|
Api string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenErr struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorDescription string `json:"error_description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RespErr struct {
|
||||||
|
Error struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
LastModifiedDateTime time.Time `json:"lastModifiedDateTime"`
|
||||||
|
Url string `json:"@microsoft.graph.downloadUrl"`
|
||||||
|
File *struct {
|
||||||
|
MimeType string `json:"mimeType"`
|
||||||
|
} `json:"file"`
|
||||||
|
Thumbnails []struct {
|
||||||
|
Medium struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
} `json:"medium"`
|
||||||
|
} `json:"thumbnails"`
|
||||||
|
ParentReference struct {
|
||||||
|
DriveId string `json:"driveId"`
|
||||||
|
} `json:"parentReference"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Object struct {
|
||||||
|
model.ObjThumb
|
||||||
|
ParentID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileToObj(f File, parentID string) *Object {
|
||||||
|
thumb := ""
|
||||||
|
if len(f.Thumbnails) > 0 {
|
||||||
|
thumb = f.Thumbnails[0].Medium.Url
|
||||||
|
}
|
||||||
|
return &Object{
|
||||||
|
ObjThumb: model.ObjThumb{
|
||||||
|
Object: model.Object{
|
||||||
|
ID: f.Id,
|
||||||
|
Name: f.Name,
|
||||||
|
Size: f.Size,
|
||||||
|
Modified: f.LastModifiedDateTime,
|
||||||
|
IsFolder: f.File == nil,
|
||||||
|
},
|
||||||
|
Thumbnail: model.Thumbnail{Thumbnail: thumb},
|
||||||
|
//Url: model.Url{Url: f.Url},
|
||||||
|
},
|
||||||
|
ParentID: parentID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Files struct {
|
||||||
|
Value []File `json:"value"`
|
||||||
|
NextLink string `json:"@odata.nextLink"`
|
||||||
|
}
|
199
drivers/onedrive_app/util.go
Normal file
199
drivers/onedrive_app/util.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
package onedrive_app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
stdpath "path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var onedriveHostMap = map[string]Host{
|
||||||
|
"global": {
|
||||||
|
Oauth: "https://login.microsoftonline.com",
|
||||||
|
Api: "https://graph.microsoft.com",
|
||||||
|
},
|
||||||
|
"cn": {
|
||||||
|
Oauth: "https://login.chinacloudapi.cn",
|
||||||
|
Api: "https://microsoftgraph.chinacloudapi.cn",
|
||||||
|
},
|
||||||
|
"us": {
|
||||||
|
Oauth: "https://login.microsoftonline.us",
|
||||||
|
Api: "https://graph.microsoft.us",
|
||||||
|
},
|
||||||
|
"de": {
|
||||||
|
Oauth: "https://login.microsoftonline.de",
|
||||||
|
Api: "https://graph.microsoft.de",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) GetMetaUrl(auth bool, path string) string {
|
||||||
|
host, _ := onedriveHostMap[d.Region]
|
||||||
|
path = utils.EncodePath(path, true)
|
||||||
|
if auth {
|
||||||
|
return host.Oauth
|
||||||
|
}
|
||||||
|
if path == "/" || path == "\\" {
|
||||||
|
return fmt.Sprintf("%s/v1.0/users/%s/drive/root", host.Api, d.Email)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/v1.0/users/%s/drive/root:%s:", host.Api, d.Email, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) accessToken() error {
|
||||||
|
var err error
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
err = d._accessToken()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) _accessToken() error {
|
||||||
|
url := d.GetMetaUrl(true, "") + "/" + d.TenantID + "/oauth2/token"
|
||||||
|
var resp base.TokenResp
|
||||||
|
var e TokenErr
|
||||||
|
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
|
||||||
|
"grant_type": "client_credentials",
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
"client_secret": d.ClientSecret,
|
||||||
|
"resource": "https://graph.microsoft.com/",
|
||||||
|
"scope": "https://graph.microsoft.com/.default",
|
||||||
|
}).Post(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Error != "" {
|
||||||
|
return fmt.Errorf("%s", e.ErrorDescription)
|
||||||
|
}
|
||||||
|
if resp.AccessToken == "" {
|
||||||
|
return errs.EmptyToken
|
||||||
|
}
|
||||||
|
d.AccessToken = resp.AccessToken
|
||||||
|
op.MustSaveDriverStorage(d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||||
|
req := base.RestyClient.R()
|
||||||
|
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
||||||
|
if callback != nil {
|
||||||
|
callback(req)
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
req.SetResult(resp)
|
||||||
|
}
|
||||||
|
var e RespErr
|
||||||
|
req.SetError(&e)
|
||||||
|
res, err := req.Execute(method, url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if e.Error.Code != "" {
|
||||||
|
if e.Error.Code == "InvalidAuthenticationToken" {
|
||||||
|
err = d.accessToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.Request(url, method, callback, resp)
|
||||||
|
}
|
||||||
|
return nil, errors.New(e.Error.Message)
|
||||||
|
}
|
||||||
|
return res.Body(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) getFiles(path string) ([]File, error) {
|
||||||
|
var res []File
|
||||||
|
nextLink := d.GetMetaUrl(false, path) + "/children?$top=5000&$expand=thumbnails($select=medium)&$select=id,name,size,lastModifiedDateTime,content.downloadUrl,file,parentReference"
|
||||||
|
for nextLink != "" {
|
||||||
|
var files Files
|
||||||
|
_, err := d.Request(nextLink, http.MethodGet, nil, &files)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, files.Value...)
|
||||||
|
nextLink = files.NextLink
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) GetFile(path string) (*File, error) {
|
||||||
|
var file File
|
||||||
|
u := d.GetMetaUrl(false, path)
|
||||||
|
_, err := d.Request(u, http.MethodGet, nil, &file)
|
||||||
|
return &file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) upSmall(ctx context.Context, dstDir model.Obj, stream model.FileStreamer) error {
|
||||||
|
url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetPath(), stream.GetName())) + "/content"
|
||||||
|
data, err := io.ReadAll(stream)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = d.Request(url, http.MethodPut, func(req *resty.Request) {
|
||||||
|
req.SetBody(data).SetContext(ctx)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) upBig(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetPath(), stream.GetName())) + "/createUploadSession"
|
||||||
|
res, err := d.Request(url, http.MethodPost, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
|
||||||
|
var finish int64 = 0
|
||||||
|
DEFAULT := d.ChunkSize * 1024 * 1024
|
||||||
|
for finish < stream.GetSize() {
|
||||||
|
if utils.IsCanceled(ctx) {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
log.Debugf("upload: %d", finish)
|
||||||
|
var byteSize int64 = DEFAULT
|
||||||
|
left := stream.GetSize() - finish
|
||||||
|
if left < DEFAULT {
|
||||||
|
byteSize = left
|
||||||
|
}
|
||||||
|
byteData := make([]byte, byteSize)
|
||||||
|
n, err := io.ReadFull(stream, byteData)
|
||||||
|
log.Debug(err, n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("PUT", uploadUrl, bytes.NewBuffer(byteData))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||||
|
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||||
|
finish += byteSize
|
||||||
|
res, err := base.HttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if res.StatusCode != 201 && res.StatusCode != 202 {
|
||||||
|
data, _ := io.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
return errors.New(string(data))
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
up(int(finish * 100 / stream.GetSize()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -5,15 +5,18 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -56,17 +59,66 @@ func (d *Quark) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
|||||||
"fids": []string{file.GetID()},
|
"fids": []string{file.GetID()},
|
||||||
}
|
}
|
||||||
var resp DownResp
|
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) {
|
_, err := d.request("/file/download", http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data)
|
req.SetHeader("User-Agent", ua).
|
||||||
|
SetBody(data)
|
||||||
}, &resp)
|
}, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
u := resp.Data[0].DownloadUrl
|
||||||
|
start, end := int64(0), file.GetSize()
|
||||||
return &model.Link{
|
return &model.Link{
|
||||||
URL: resp.Data[0].DownloadUrl,
|
Handle: func(w http.ResponseWriter, r *http.Request) error {
|
||||||
Header: http.Header{
|
if rg := r.Header.Get("Range"); rg != "" {
|
||||||
"Cookie": []string{d.Cookie},
|
parseRange, err := http_range.ParseRange(rg, file.GetSize())
|
||||||
"Referer": []string{"https://pan.quark.cn"},
|
if err != nil {
|
||||||
|
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
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@ type Addition struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
Name: "Quark",
|
Name: "Quark",
|
||||||
OnlyProxy: true,
|
OnlyLocal: true,
|
||||||
DefaultRoot: "0",
|
DefaultRoot: "0",
|
||||||
NoOverwriteUpload: true,
|
NoOverwriteUpload: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"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 != "" {
|
if d.CustomHost != "" {
|
||||||
err = req.Build()
|
err = req.Build()
|
||||||
link = req.HTTPRequest.URL.String()
|
link = req.HTTPRequest.URL.String()
|
||||||
|
if d.RemoveBucket {
|
||||||
|
link = strings.Replace(link, "/"+d.Bucket, "", 1)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
link, err = req.Presign(time.Hour * time.Duration(d.SignURLExpire))
|
link, err = req.Presign(time.Hour * time.Duration(d.SignURLExpire))
|
||||||
}
|
}
|
||||||
@ -128,6 +132,9 @@ func (d *S3) Remove(ctx context.Context, obj model.Obj) error {
|
|||||||
|
|
||||||
func (d *S3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
func (d *S3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
uploader := s3manager.NewUploader(d.Session)
|
uploader := s3manager.NewUploader(d.Session)
|
||||||
|
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||||
|
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||||
|
}
|
||||||
key := getKey(stdpath.Join(dstDir.GetPath(), stream.GetName()), false)
|
key := getKey(stdpath.Join(dstDir.GetPath(), stream.GetName()), false)
|
||||||
log.Debugln("key:", key)
|
log.Debugln("key:", key)
|
||||||
input := &s3manager.UploadInput{
|
input := &s3manager.UploadInput{
|
||||||
|
@ -17,6 +17,7 @@ type Addition struct {
|
|||||||
Placeholder string `json:"placeholder"`
|
Placeholder string `json:"placeholder"`
|
||||||
ForcePathStyle bool `json:"force_path_style"`
|
ForcePathStyle bool `json:"force_path_style"`
|
||||||
ListObjectVersion string `json:"list_object_version" type:"select" options:"v1,v2" default:"v1"`
|
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{
|
var config = driver.Config{
|
||||||
|
@ -15,10 +15,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SMB struct {
|
type SMB struct {
|
||||||
|
lastConnTime int64
|
||||||
model.Storage
|
model.Storage
|
||||||
Addition
|
Addition
|
||||||
fs *smb2.Share
|
fs *smb2.Share
|
||||||
lastConnTime int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SMB) Config() driver.Config {
|
func (d *SMB) Config() driver.Config {
|
||||||
|
@ -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 {
|
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)
|
res, err := d.request("/api/v2/users/me", http.MethodGet, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -10,8 +10,9 @@ type Addition struct {
|
|||||||
Cookie string `json:"cookie" required:"true"`
|
Cookie string `json:"cookie" required:"true"`
|
||||||
ProjectID string `json:"project_id" required:"true"`
|
ProjectID string `json:"project_id" required:"true"`
|
||||||
driver.RootID
|
driver.RootID
|
||||||
OrderBy string `json:"order_by" type:"select" options:"fileName,fileSize,updated,created" default:"fileName"`
|
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"`
|
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{
|
var config = driver.Config{
|
||||||
|
@ -66,3 +66,24 @@ type ChunkUpload struct {
|
|||||||
PreviewExt string `json:"previewExt"`
|
PreviewExt string `json:"previewExt"`
|
||||||
LastUploadTime interface{} `json:"lastUploadTime"`
|
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"`
|
||||||
|
}
|
||||||
|
@ -7,13 +7,16 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"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"
|
"github.com/go-resty/resty/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -210,17 +213,56 @@ func (d *Teambition) finishUpload(file *FileUpload, parentId string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBetweenStr(str, start, end string) string {
|
func (d *Teambition) newUpload(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
n := strings.Index(str, start)
|
var uploadToken UploadToken
|
||||||
if n == -1 {
|
_, err := d.request("/api/awos/upload-token", http.MethodPost, func(req *resty.Request) {
|
||||||
return ""
|
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)
|
cfg := &aws.Config{
|
||||||
str = string([]byte(str)[n:])
|
Credentials: credentials.NewStaticCredentials(
|
||||||
m := strings.Index(str, end)
|
uploadToken.Sdk.Credentials.AccessKeyId, uploadToken.Sdk.Credentials.SecretAccessKey, uploadToken.Sdk.Credentials.SessionToken),
|
||||||
if m == -1 {
|
Region: &uploadToken.Sdk.Region,
|
||||||
return ""
|
Endpoint: &uploadToken.Sdk.Endpoint,
|
||||||
|
S3ForcePathStyle: &uploadToken.Sdk.S3ForcePathStyle,
|
||||||
}
|
}
|
||||||
str = string([]byte(str)[:m])
|
ss, err := session.NewSession(cfg)
|
||||||
return str
|
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
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
@ -31,7 +32,7 @@ func (d *Trainbit) GetAddition() driver.Additional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Trainbit) Init(ctx context.Context) error {
|
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
|
return http.ErrUseLastResponse
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
@ -119,7 +120,7 @@ func (d *Trainbit) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
query := &url.Values{}
|
query := &url.Values{}
|
||||||
query.Add("q", strings.Split(dstDir.GetID(), "_")[1])
|
query.Add("q", strings.Split(dstDir.GetID(), "_")[1])
|
||||||
query.Add("guid", guid)
|
query.Add("guid", guid)
|
||||||
query.Add("name", url.QueryEscape(local2provider(stream.GetName(), false)))
|
query.Add("name", url.QueryEscape(local2provider(stream.GetName(), false)+"."))
|
||||||
endpoint.RawQuery = query.Encode()
|
endpoint.RawQuery = query.Encode()
|
||||||
var total int64
|
var total int64
|
||||||
total = 0
|
total = 0
|
||||||
@ -135,7 +136,7 @@ func (d *Trainbit) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "text/json; charset=UTF-8")
|
req.Header.Set("Content-Type", "text/json; charset=UTF-8")
|
||||||
_, err = http.DefaultClient.Do(req)
|
_, err = base.HttpClient.Do(req)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package trainbit
|
package trainbit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -8,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,7 +39,7 @@ func get(url string, apiKey string, AUSHELLPORTAL string) (*http.Response, error
|
|||||||
Value: apiKey,
|
Value: apiKey,
|
||||||
MaxAge: 2 * 60,
|
MaxAge: 2 * 60,
|
||||||
})
|
})
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := base.HttpClient.Do(req)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +66,7 @@ func postForm(endpoint string, data url.Values, apiExpiredate string, apiKey str
|
|||||||
Value: apiKey,
|
Value: apiKey,
|
||||||
MaxAge: 2 * 60,
|
MaxAge: 2 * 60,
|
||||||
})
|
})
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := base.HttpClient.Do(req)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,36 +90,19 @@ func getToken(apiKey string, AUSHELLPORTAL string) (string, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func local2provider(filename string, isFolder bool) string {
|
func local2provider(filename string, isFolder bool) string {
|
||||||
filename = strings.Replace(filename, "%", url.QueryEscape("%"), -1)
|
|
||||||
filename = strings.Replace(filename, "/", url.QueryEscape("/"), -1)
|
|
||||||
filename = strings.Replace(filename, ":", url.QueryEscape(":"), -1)
|
|
||||||
filename = strings.Replace(filename, "*", url.QueryEscape("*"), -1)
|
|
||||||
filename = strings.Replace(filename, "?", url.QueryEscape("?"), -1)
|
|
||||||
filename = strings.Replace(filename, "\"", url.QueryEscape("\""), -1)
|
|
||||||
filename = strings.Replace(filename, "<", url.QueryEscape("<"), -1)
|
|
||||||
filename = strings.Replace(filename, ">", url.QueryEscape(">"), -1)
|
|
||||||
filename = strings.Replace(filename, "|", url.QueryEscape("|"), -1)
|
|
||||||
if isFolder {
|
if isFolder {
|
||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
return strings.Join([]string{filename, ".delete_suffix."}, "")
|
return filename + ".delete_suffix"
|
||||||
}
|
}
|
||||||
|
|
||||||
func provider2local(filename string) string {
|
func provider2local(filename string) string {
|
||||||
index := strings.LastIndex(filename, ".delete_suffix.")
|
filename = html.UnescapeString(filename)
|
||||||
|
index := strings.LastIndex(filename, ".delete_suffix")
|
||||||
if index != -1 {
|
if index != -1 {
|
||||||
filename = filename[:index]
|
filename = filename[:index]
|
||||||
}
|
}
|
||||||
rawName := strings.Replace(filename, url.QueryEscape("/"), "/", -1)
|
return filename
|
||||||
rawName = strings.Replace(rawName, url.QueryEscape(":"), ":", -1)
|
|
||||||
rawName = strings.Replace(rawName, url.QueryEscape("*"), "*", -1)
|
|
||||||
rawName = strings.Replace(rawName, url.QueryEscape("?"), "?", -1)
|
|
||||||
rawName = strings.Replace(rawName, url.QueryEscape("\""), "\"", -1)
|
|
||||||
rawName = strings.Replace(rawName, url.QueryEscape("<"), "<", -1)
|
|
||||||
rawName = strings.Replace(rawName, url.QueryEscape(">"), ">", -1)
|
|
||||||
rawName = strings.Replace(rawName, url.QueryEscape("|"), "|", -1)
|
|
||||||
rawName = strings.Replace(rawName, url.QueryEscape("%"), "%", -1)
|
|
||||||
return rawName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRawFileObject(rawObject []any) ([]model.Obj, error) {
|
func parseRawFileObject(rawObject []any) ([]model.Obj, error) {
|
||||||
@ -129,10 +114,10 @@ func parseRawFileObject(rawObject []any) ([]model.Obj, error) {
|
|||||||
}
|
}
|
||||||
isFolder := int64(object["ty"].(float64)) == 1
|
isFolder := int64(object["ty"].(float64)) == 1
|
||||||
var name string
|
var name string
|
||||||
if isFolder {
|
if object["ext"].(string) != "" {
|
||||||
name = object["name"].(string)
|
|
||||||
} else {
|
|
||||||
name = strings.Join([]string{object["name"].(string), object["ext"].(string)}, ".")
|
name = strings.Join([]string{object["name"].(string), object["ext"].(string)}, ".")
|
||||||
|
} else {
|
||||||
|
name = object["name"].(string)
|
||||||
}
|
}
|
||||||
modified, err := time.Parse("2006/01/02 15:04:05", object["modified"].(string))
|
modified, err := time.Parse("2006/01/02 15:04:05", object["modified"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
79
drivers/url_tree/driver.go
Normal file
79
drivers/url_tree/driver.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package url_tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
stdpath "path"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Urls struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
root *Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) Init(ctx context.Context) error {
|
||||||
|
node, err := BuildTree(d.UrlStructure, d.HeadSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
node.calSize()
|
||||||
|
d.root = node
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||||
|
node := GetNodeFromRootByPath(d.root, path)
|
||||||
|
return nodeToObj(node, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
node := GetNodeFromRootByPath(d.root, dir.GetPath())
|
||||||
|
log.Debugf("path: %s, node: %+v", dir.GetPath(), node)
|
||||||
|
if node == nil {
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
if node.isFile() {
|
||||||
|
return nil, errs.NotFolder
|
||||||
|
}
|
||||||
|
return utils.SliceConvert(node.Children, func(node *Node) (model.Obj, error) {
|
||||||
|
return nodeToObj(node, stdpath.Join(dir.GetPath(), node.Name))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
node := GetNodeFromRootByPath(d.root, file.GetPath())
|
||||||
|
log.Debugf("path: %s, node: %+v", file.GetPath(), node)
|
||||||
|
if node == nil {
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
if node.isFile() {
|
||||||
|
return &model.Link{
|
||||||
|
URL: node.Url,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, errs.NotFile
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
|
// return nil, errs.NotSupport
|
||||||
|
//}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*Urls)(nil)
|
35
drivers/url_tree/meta.go
Normal file
35
drivers/url_tree/meta.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package url_tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
// Usually one of two
|
||||||
|
// driver.RootPath
|
||||||
|
// driver.RootID
|
||||||
|
// define other
|
||||||
|
UrlStructure string `json:"url_structure" type:"text" required:"true" default:"https://jsd.nn.ci/gh/alist-org/alist/README.md\nhttps://jsd.nn.ci/gh/alist-org/alist/README_cn.md\nfolder:\n CONTRIBUTING.md:1635:https://jsd.nn.ci/gh/alist-org/alist/CONTRIBUTING.md\n CODE_OF_CONDUCT.md:2093:https://jsd.nn.ci/gh/alist-org/alist/CODE_OF_CONDUCT.md" help:"structure:FolderName:\n [FileName:][FileSize:][Modified:]Url"`
|
||||||
|
HeadSize bool `json:"head_size" type:"bool" default:"false" help:"Use head method to get file size, but it may be failed."`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "UrlTree",
|
||||||
|
LocalSort: true,
|
||||||
|
OnlyLocal: false,
|
||||||
|
OnlyProxy: false,
|
||||||
|
NoCache: true,
|
||||||
|
NoUpload: true,
|
||||||
|
NeedMs: false,
|
||||||
|
DefaultRoot: "",
|
||||||
|
CheckStatus: true,
|
||||||
|
Alert: "",
|
||||||
|
NoOverwriteUpload: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &Urls{}
|
||||||
|
})
|
||||||
|
}
|
46
drivers/url_tree/types.go
Normal file
46
drivers/url_tree/types.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package url_tree
|
||||||
|
|
||||||
|
// Node is a node in the folder tree
|
||||||
|
type Node struct {
|
||||||
|
Url string
|
||||||
|
Name string
|
||||||
|
Level int
|
||||||
|
Modified int64
|
||||||
|
Size int64
|
||||||
|
Children []*Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *Node) getByPath(paths []string) *Node {
|
||||||
|
if len(paths) == 0 || node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if node.Name != paths[0] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(paths) == 1 {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
for _, child := range node.Children {
|
||||||
|
tmp := child.getByPath(paths[1:])
|
||||||
|
if tmp != nil {
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *Node) isFile() bool {
|
||||||
|
return node.Url != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *Node) calSize() int64 {
|
||||||
|
if node.isFile() {
|
||||||
|
return node.Size
|
||||||
|
}
|
||||||
|
var size int64 = 0
|
||||||
|
for _, child := range node.Children {
|
||||||
|
size += child.calSize()
|
||||||
|
}
|
||||||
|
node.Size = size
|
||||||
|
return size
|
||||||
|
}
|
47
drivers/url_tree/urls_test.go
Normal file
47
drivers/url_tree/urls_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package url_tree_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/url_tree"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testTree() (*url_tree.Node, error) {
|
||||||
|
text := `folder1:
|
||||||
|
name1:https://url1
|
||||||
|
http://url2
|
||||||
|
folder2:
|
||||||
|
http://url3
|
||||||
|
http://url4
|
||||||
|
http://url5
|
||||||
|
folder3:
|
||||||
|
http://url6
|
||||||
|
http://url7
|
||||||
|
http://url8`
|
||||||
|
return url_tree.BuildTree(text, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildTree(t *testing.T) {
|
||||||
|
node, err := testTree()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to build tree: %+v", err)
|
||||||
|
} else {
|
||||||
|
t.Logf("tree: %+v", node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNode(t *testing.T) {
|
||||||
|
root, err := testTree()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to build tree: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node := url_tree.GetNodeFromRootByPath(root, "/")
|
||||||
|
if node != root {
|
||||||
|
t.Errorf("got wrong node: %+v", node)
|
||||||
|
}
|
||||||
|
url3 := url_tree.GetNodeFromRootByPath(root, "/folder1/folder2/url3")
|
||||||
|
if url3 != root.Children[0].Children[2].Children[0] {
|
||||||
|
t.Errorf("got wrong node: %+v", url3)
|
||||||
|
}
|
||||||
|
}
|
192
drivers/url_tree/util.go
Normal file
192
drivers/url_tree/util.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package url_tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
stdpath "path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// build tree from text, text structure definition:
|
||||||
|
/**
|
||||||
|
* FolderName:
|
||||||
|
* [FileName:][FileSize:][Modified:]Url
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* For example:
|
||||||
|
* folder1:
|
||||||
|
* name1:url1
|
||||||
|
* url2
|
||||||
|
* folder2:
|
||||||
|
* url3
|
||||||
|
* url4
|
||||||
|
* url5
|
||||||
|
* folder3:
|
||||||
|
* url6
|
||||||
|
* url7
|
||||||
|
* url8
|
||||||
|
*/
|
||||||
|
// if there are no name, use the last segment of url as name
|
||||||
|
func BuildTree(text string, headSize bool) (*Node, error) {
|
||||||
|
lines := strings.Split(text, "\n")
|
||||||
|
var root = &Node{Level: -1, Name: "root"}
|
||||||
|
stack := []*Node{root}
|
||||||
|
for _, line := range lines {
|
||||||
|
// calculate indent
|
||||||
|
indent := 0
|
||||||
|
for i := 0; i < len(line); i++ {
|
||||||
|
if line[i] != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indent++
|
||||||
|
}
|
||||||
|
// if indent is not a multiple of 2, it is an error
|
||||||
|
if indent%2 != 0 {
|
||||||
|
return nil, fmt.Errorf("the line '%s' is not a multiple of 2", line)
|
||||||
|
}
|
||||||
|
// calculate level
|
||||||
|
level := indent / 2
|
||||||
|
line = strings.TrimSpace(line[indent:])
|
||||||
|
// if the line is empty, skip
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if level isn't greater than the level of the top of the stack
|
||||||
|
// it is not the child of the top of the stack
|
||||||
|
for level <= stack[len(stack)-1].Level {
|
||||||
|
// pop the top of the stack
|
||||||
|
stack = stack[:len(stack)-1]
|
||||||
|
}
|
||||||
|
// if the line is a folder
|
||||||
|
if isFolder(line) {
|
||||||
|
// create a new node
|
||||||
|
node := &Node{
|
||||||
|
Level: level,
|
||||||
|
Name: strings.TrimSuffix(line, ":"),
|
||||||
|
}
|
||||||
|
// add the node to the top of the stack
|
||||||
|
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
|
||||||
|
// push the node to the stack
|
||||||
|
stack = append(stack, node)
|
||||||
|
} else {
|
||||||
|
// if the line is a file
|
||||||
|
// create a new node
|
||||||
|
node, err := parseFileLine(line, headSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
node.Level = level
|
||||||
|
// add the node to the top of the stack
|
||||||
|
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFolder(line string) bool {
|
||||||
|
return strings.HasSuffix(line, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
// line definition:
|
||||||
|
// [FileName:][FileSize:][Modified:]Url
|
||||||
|
func parseFileLine(line string, headSize bool) (*Node, error) {
|
||||||
|
// if there is no url, it is an error
|
||||||
|
if !strings.Contains(line, "http://") && !strings.Contains(line, "https://") {
|
||||||
|
return nil, fmt.Errorf("invalid line: %s, because url is required for file", line)
|
||||||
|
}
|
||||||
|
index := strings.Index(line, "http://")
|
||||||
|
if index == -1 {
|
||||||
|
index = strings.Index(line, "https://")
|
||||||
|
}
|
||||||
|
url := line[index:]
|
||||||
|
info := line[:index]
|
||||||
|
node := &Node{
|
||||||
|
Url: url,
|
||||||
|
}
|
||||||
|
haveSize := false
|
||||||
|
if index > 0 {
|
||||||
|
if !strings.HasSuffix(info, ":") {
|
||||||
|
return nil, fmt.Errorf("invalid line: %s, because file info must end with ':'", line)
|
||||||
|
}
|
||||||
|
info = info[:len(info)-1]
|
||||||
|
if info == "" {
|
||||||
|
return nil, fmt.Errorf("invalid line: %s, because file name can't be empty", line)
|
||||||
|
}
|
||||||
|
infoParts := strings.Split(info, ":")
|
||||||
|
node.Name = infoParts[0]
|
||||||
|
if len(infoParts) > 1 {
|
||||||
|
size, err := strconv.ParseInt(infoParts[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid line: %s, because file size must be an integer", line)
|
||||||
|
}
|
||||||
|
node.Size = size
|
||||||
|
haveSize = true
|
||||||
|
if len(infoParts) > 2 {
|
||||||
|
modified, err := strconv.ParseInt(infoParts[2], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid line: %s, because file modified must be an unix timestamp", line)
|
||||||
|
}
|
||||||
|
node.Modified = modified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node.Name = stdpath.Base(url)
|
||||||
|
}
|
||||||
|
if !haveSize && headSize {
|
||||||
|
size, err := getSizeFromUrl(url)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get size from url error: %s", err)
|
||||||
|
} else {
|
||||||
|
node.Size = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitPath(path string) []string {
|
||||||
|
if path == "/" {
|
||||||
|
return []string{"root"}
|
||||||
|
}
|
||||||
|
parts := strings.Split(path, "/")
|
||||||
|
parts[0] = "root"
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeFromRootByPath(root *Node, path string) *Node {
|
||||||
|
return root.getByPath(splitPath(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeToObj(node *Node, path string) (model.Obj, error) {
|
||||||
|
if node == nil {
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
return &model.Object{
|
||||||
|
Name: node.Name,
|
||||||
|
Size: node.Size,
|
||||||
|
Modified: time.Unix(node.Modified, 0),
|
||||||
|
IsFolder: !node.isFile(),
|
||||||
|
Path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSizeFromUrl(url string) (int64, error) {
|
||||||
|
res, err := base.RestyClient.R().SetDoNotParseResponse(true).Head(url)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer res.RawResponse.Body.Close()
|
||||||
|
if res.StatusCode() >= 300 {
|
||||||
|
return 0, fmt.Errorf("get size from url %s failed, status code: %d", url, res.StatusCode())
|
||||||
|
}
|
||||||
|
size, err := strconv.ParseInt(res.Header().Get("Content-Length"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return size, nil
|
||||||
|
}
|
@ -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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
func (d *WebDav) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -185,7 +186,7 @@ func (ca *CookieAuth) getSPToken() (*SuccessResponse, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{}
|
client := base.HttpClient
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/webdav/odrvcookie"
|
"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"
|
"github.com/alist-org/alist/v3/pkg/gowebdav"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,3 +30,10 @@ func (d *WebDav) setClient() error {
|
|||||||
d.client = c
|
d.client = c
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPath(obj model.Obj) string {
|
||||||
|
if obj.IsDir() {
|
||||||
|
return obj.GetPath() + "/"
|
||||||
|
}
|
||||||
|
return obj.GetPath()
|
||||||
|
}
|
||||||
|
60
go.mod
60
go.mod
@ -1,12 +1,12 @@
|
|||||||
module github.com/alist-org/alist/v3
|
module github.com/alist-org/alist/v3
|
||||||
|
|
||||||
go 1.19
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/SheltonZhu/115driver v1.0.14
|
github.com/SheltonZhu/115driver v1.0.14
|
||||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||||
github.com/aws/aws-sdk-go v1.44.194
|
github.com/aws/aws-sdk-go v1.44.262
|
||||||
github.com/blevesearch/bleve/v2 v2.3.6
|
github.com/blevesearch/bleve/v2 v2.3.8
|
||||||
github.com/caarlos0/env/v7 v7.1.0
|
github.com/caarlos0/env/v7 v7.1.0
|
||||||
github.com/deckarep/golang-set/v2 v2.3.0
|
github.com/deckarep/golang-set/v2 v2.3.0
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
@ -18,6 +18,7 @@ require (
|
|||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/hirochachacha/go-smb2 v1.1.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/jlaffaye/ftp v0.1.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/maruel/natural v1.1.0
|
github.com/maruel/natural v1.1.0
|
||||||
@ -26,14 +27,14 @@ require (
|
|||||||
github.com/pkg/sftp v1.13.5
|
github.com/pkg/sftp v1.13.5
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca
|
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca
|
||||||
github.com/u2takey/ffmpeg-go v0.4.1
|
github.com/u2takey/ffmpeg-go v0.4.1
|
||||||
github.com/upyun/go-sdk/v3 v3.0.3
|
github.com/upyun/go-sdk/v3 v3.0.4
|
||||||
github.com/winfsp/cgofuse v1.5.0
|
github.com/winfsp/cgofuse v1.5.0
|
||||||
golang.org/x/crypto v0.7.0
|
golang.org/x/crypto v0.9.0
|
||||||
golang.org/x/image v0.6.0
|
golang.org/x/image v0.7.0
|
||||||
golang.org/x/net v0.8.0
|
golang.org/x/net v0.10.0
|
||||||
gorm.io/driver/mysql v1.4.7
|
gorm.io/driver/mysql v1.4.7
|
||||||
gorm.io/driver/postgres v1.4.8
|
gorm.io/driver/postgres v1.4.8
|
||||||
gorm.io/driver/sqlite v1.4.4
|
gorm.io/driver/sqlite v1.4.4
|
||||||
@ -46,26 +47,29 @@ require (
|
|||||||
github.com/aead/ecdh v0.2.0 // indirect
|
github.com/aead/ecdh v0.2.0 // indirect
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible // indirect
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible // indirect
|
||||||
github.com/andreburgaud/crypt2go v1.1.0 // 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/bits-and-blooms/bitset v1.2.0 // indirect
|
||||||
github.com/blevesearch/bleve_index_api v1.0.5 // indirect
|
github.com/blevesearch/bleve_index_api v1.0.5 // indirect
|
||||||
github.com/blevesearch/geo v0.1.16 // indirect
|
github.com/blevesearch/geo v0.1.17 // indirect
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.4 // indirect
|
github.com/blevesearch/scorch_segment_api/v2 v2.1.4 // indirect
|
||||||
github.com/blevesearch/segment v0.9.0 // indirect
|
github.com/blevesearch/segment v0.9.1 // indirect
|
||||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||||
github.com/blevesearch/upsidedown_store_api v1.0.1 // indirect
|
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||||
github.com/blevesearch/vellum v1.0.9 // indirect
|
github.com/blevesearch/vellum v1.0.9 // indirect
|
||||||
github.com/blevesearch/zapx/v11 v11.3.7 // indirect
|
github.com/blevesearch/zapx/v11 v11.3.7 // indirect
|
||||||
github.com/blevesearch/zapx/v12 v12.3.7 // indirect
|
github.com/blevesearch/zapx/v12 v12.3.7 // indirect
|
||||||
github.com/blevesearch/zapx/v13 v13.3.7 // indirect
|
github.com/blevesearch/zapx/v13 v13.3.7 // indirect
|
||||||
github.com/blevesearch/zapx/v14 v14.3.7 // indirect
|
github.com/blevesearch/zapx/v14 v14.3.7 // indirect
|
||||||
github.com/blevesearch/zapx/v15 v15.3.8 // indirect
|
github.com/blevesearch/zapx/v15 v15.3.10 // indirect
|
||||||
github.com/bluele/gcache v0.0.2 // indirect
|
github.com/bluele/gcache v0.0.2 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/bytedance/sonic v1.8.0 // indirect
|
github.com/bytedance/sonic v1.8.0 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // 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/gaoyb7/115drive-webdav v0.1.8 // indirect
|
||||||
github.com/geoffgarside/ber v1.1.0 // indirect
|
github.com/geoffgarside/ber v1.1.0 // indirect
|
||||||
github.com/gin-contrib/sse v0.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/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.0 // indirect
|
github.com/goccy/go-json v0.10.0 // indirect
|
||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||||
github.com/golang/protobuf v1.5.0 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/golang/snappy v0.0.1 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.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/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgx/v5 v5.3.0 // indirect
|
github.com/jackc/pgx/v5 v5.3.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // 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/kr/fs v0.1.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // 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-isatty v0.0.17 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.15 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // 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/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/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // 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/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/u2takey/go-utils v0.3.1 // indirect
|
github.com/u2takey/go-utils v0.3.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.9 // 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
|
go.etcd.io/bbolt v1.3.5 // indirect
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||||
golang.org/x/sys v0.6.0 // indirect
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
golang.org/x/text 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/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
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
lukechampine.com/blake3 v1.1.7 // indirect
|
||||||
)
|
)
|
||||||
|
136
go.sum
136
go.sum
@ -15,14 +15,20 @@ 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.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 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.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 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||||
github.com/blevesearch/bleve/v2 v2.3.6 h1:NlntUHcV5CSWIhpugx4d/BRMGCiaoI8ZZXrXlahzNq4=
|
github.com/blevesearch/bleve/v2 v2.3.7 h1:nIfIrhv28tvgBpbVF8Dq7/U1zW/YiwSqg/PBgE3x8bo=
|
||||||
github.com/blevesearch/bleve/v2 v2.3.6/go.mod h1:JM2legf1cKVkdV8Ehu7msKIOKC0McSw0Q16Fmv9vsW4=
|
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 h1:Lc986kpC4Z0/n1g3gg8ul7H+lxgOQPcXb9SxvQGu+tw=
|
||||||
github.com/blevesearch/bleve_index_api v1.0.5/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
github.com/blevesearch/bleve_index_api v1.0.5/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||||
github.com/blevesearch/geo v0.1.16 h1:unVaqUmlwprk56596OQRkGjtq1VZ8XFWSARj+h2cIBY=
|
github.com/blevesearch/geo v0.1.17 h1:AguzI6/5mHXapzB0gE9IKWo+wWPHZmXZoscHcjFgAFA=
|
||||||
github.com/blevesearch/geo v0.1.16/go.mod h1:a1OlySNE+oDQ5qY0vJGYNoLIsMpbKbx8dnmuRP8D7H0=
|
github.com/blevesearch/geo v0.1.17/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM=
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||||
@ -31,12 +37,12 @@ github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCD
|
|||||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.4 h1:LmGmo5twU3gV+natJbKmOktS9eMhokPGKWuR+jX84vk=
|
github.com/blevesearch/scorch_segment_api/v2 v2.1.4 h1:LmGmo5twU3gV+natJbKmOktS9eMhokPGKWuR+jX84vk=
|
||||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.4/go.mod h1:PgVnbbg/t1UkgezPDu8EHLi1BHQ17xUwsFdU6NnOYS0=
|
github.com/blevesearch/scorch_segment_api/v2 v2.1.4/go.mod h1:PgVnbbg/t1UkgezPDu8EHLi1BHQ17xUwsFdU6NnOYS0=
|
||||||
github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac=
|
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||||
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||||
github.com/blevesearch/upsidedown_store_api v1.0.1 h1:1SYRwyoFLwG3sj0ed89RLtM15amfX2pXlYbFOnF8zNU=
|
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
|
||||||
github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q=
|
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||||
github.com/blevesearch/vellum v1.0.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ=
|
github.com/blevesearch/vellum v1.0.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ=
|
||||||
github.com/blevesearch/vellum v1.0.9/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
github.com/blevesearch/vellum v1.0.9/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||||
github.com/blevesearch/zapx/v11 v11.3.7 h1:Y6yIAF/DVPiqZUA/jNgSLXmqewfzwHzuwfKyfdG+Xaw=
|
github.com/blevesearch/zapx/v11 v11.3.7 h1:Y6yIAF/DVPiqZUA/jNgSLXmqewfzwHzuwfKyfdG+Xaw=
|
||||||
@ -47,8 +53,10 @@ github.com/blevesearch/zapx/v13 v13.3.7 h1:igIQg5eKmjw168I7av0Vtwedf7kHnQro/M+ub
|
|||||||
github.com/blevesearch/zapx/v13 v13.3.7/go.mod h1:yyrB4kJ0OT75UPZwT/zS+Ru0/jYKorCOOSY5dBzAy+s=
|
github.com/blevesearch/zapx/v13 v13.3.7/go.mod h1:yyrB4kJ0OT75UPZwT/zS+Ru0/jYKorCOOSY5dBzAy+s=
|
||||||
github.com/blevesearch/zapx/v14 v14.3.7 h1:gfe+fbWslDWP/evHLtp/GOvmNM3sw1BbqD7LhycBX20=
|
github.com/blevesearch/zapx/v14 v14.3.7 h1:gfe+fbWslDWP/evHLtp/GOvmNM3sw1BbqD7LhycBX20=
|
||||||
github.com/blevesearch/zapx/v14 v14.3.7/go.mod h1:9J/RbOkqZ1KSjmkOes03AkETX7hrXT0sFMpWH4ewC4w=
|
github.com/blevesearch/zapx/v14 v14.3.7/go.mod h1:9J/RbOkqZ1KSjmkOes03AkETX7hrXT0sFMpWH4ewC4w=
|
||||||
github.com/blevesearch/zapx/v15 v15.3.8 h1:q4uMngBHzL1IIhRc8AJUEkj6dGOE3u1l3phLu7hq8uk=
|
github.com/blevesearch/zapx/v15 v15.3.9 h1:/s9zqKxFaZKQTTcMO2b/Tup0ch5MSztlvw+frVDfIBk=
|
||||||
github.com/blevesearch/zapx/v15 v15.3.8/go.mod h1:m7Y6m8soYUvS7MjN9eKlz1xrLCcmqfFadmu7GhWIrLY=
|
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 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
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=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
@ -58,18 +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/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 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
|
||||||
github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
|
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-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 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
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/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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/deckarep/golang-set/v2 v2.2.0 h1:2pMQd3Soi6qfw7E5MMKaEh5W5ES18bW3AbFFnGl6LgQ=
|
|
||||||
github.com/deckarep/golang-set/v2 v2.2.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
|
||||||
github.com/deckarep/golang-set/v2 v2.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g=
|
github.com/deckarep/golang-set/v2 v2.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g=
|
||||||
github.com/deckarep/golang-set/v2 v2.3.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
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 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
|
||||||
@ -111,14 +123,14 @@ 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-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 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
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/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
@ -132,8 +144,14 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
|
|||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
|
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
|
||||||
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
|
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
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 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
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=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
@ -152,14 +170,15 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
|||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
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/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/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.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 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@ -172,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/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 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
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 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
|
||||||
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
|
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=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
@ -179,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-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 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
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-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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@ -186,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.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
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/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 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
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 h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
|
||||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
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=
|
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 h1:dg/EaaJLPIg4xn2kaZil7Ax3wfoxcFXaBwyOTlcz5AI=
|
||||||
@ -217,9 +264,11 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
|
|||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
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 h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
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/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@ -234,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.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.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.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.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 h1:I9rVnNXdIkij4UvMT7OmKhH9sOIvS8iXkxfPdnn9wQA=
|
||||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA=
|
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=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
@ -248,9 +297,11 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
|
|||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
||||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/upyun/go-sdk/v3 v3.0.3 h1:2wUkNk2fyJReMYHMvJyav050D83rYwSjN7mEPR0Pp8Q=
|
github.com/upyun/go-sdk/v3 v3.0.4 h1:2DCJa/Yi7/3ZybT9UCPATSzvU3wpPPxhXinNlb1Hi8Q=
|
||||||
github.com/upyun/go-sdk/v3 v3.0.3/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
|
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/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 h1:MsBP7Mi/LiJf/7/F3O/7HjjR009ds6KCdqXzKpZSWxI=
|
||||||
github.com/winfsp/cgofuse v1.5.0/go.mod h1:h3awhoUOcn2VYVKCwDaYxSLlZwnyK+A8KaDoLUp2lbU=
|
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=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
@ -269,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.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.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.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
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.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.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
|
||||||
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
|
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.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/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@ -285,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.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.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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
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-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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -304,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-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-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-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-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-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.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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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-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.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.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.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.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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@ -323,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.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.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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
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-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-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
|
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
|
||||||
@ -336,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/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-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-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-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
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.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 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
@ -369,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.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||||
gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
|
gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
|
||||||
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
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=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -78,9 +77,7 @@ func InitConfig() {
|
|||||||
log.Fatalf("create temp dir error: %+v", err)
|
log.Fatalf("create temp dir error: %+v", err)
|
||||||
}
|
}
|
||||||
log.Debugf("config: %+v", conf.Conf)
|
log.Debugf("config: %+v", conf.Conf)
|
||||||
if conf.Conf.TlsInsecureSkipVerify {
|
base.InitClient()
|
||||||
base.RestyClient = base.RestyClient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
|
||||||
}
|
|
||||||
initURL()
|
initURL()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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: "pagination_type", Value: "all", Type: conf.TypeSelect, Options: "all,pagination,load_more,auto_load_more", Group: model.SITE},
|
||||||
{Key: "default_page_size", Value: "30", Type: conf.TypeNumber, Group: model.SITE},
|
{Key: "default_page_size", Value: "30", Type: conf.TypeNumber, Group: model.SITE},
|
||||||
{Key: conf.AllowIndexed, Value: "false", Type: conf.TypeBool, Group: model.SITE},
|
{Key: conf.AllowIndexed, Value: "false", Type: conf.TypeBool, Group: model.SITE},
|
||||||
|
{Key: conf.AllowMounted, Value: "true", Type: conf.TypeBool, Group: model.SITE},
|
||||||
|
{Key: conf.RobotsTxt, Value: "User-agent: *\nAllow: /", Type: conf.TypeText, Group: model.SITE},
|
||||||
// style settings
|
// style settings
|
||||||
{Key: conf.Logo, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeText, Group: model.STYLE},
|
{Key: conf.Logo, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeText, Group: model.STYLE},
|
||||||
{Key: conf.Favicon, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeString, Group: model.STYLE},
|
{Key: conf.Favicon, Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", Type: conf.TypeString, Group: model.STYLE},
|
||||||
@ -152,9 +154,13 @@ func InitialSettings() []model.SettingItem {
|
|||||||
|
|
||||||
// SSO settings
|
// SSO settings
|
||||||
{Key: conf.SSOLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.SSO, Flag: model.PUBLIC},
|
{Key: conf.SSOLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.SSO, Flag: model.PUBLIC},
|
||||||
{Key: conf.SSOLoginplatform, Type: conf.TypeSelect, Options: "Github,Microsoft,Google,Dingtalk", Group: model.SSO, Flag: model.PUBLIC},
|
{Key: conf.SSOLoginplatform, Type: conf.TypeSelect, Options: "Casdoor,Github,Microsoft,Google,Dingtalk", Group: model.SSO, Flag: model.PUBLIC},
|
||||||
{Key: conf.SSOClientId, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
{Key: conf.SSOClientId, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
{Key: conf.SSOClientSecret, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
{Key: conf.SSOClientSecret, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.SSOOrganizationName, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.SSOApplicationName, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.SSOEndpointName, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.SSOJwtPublicKey, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||||
|
|
||||||
// qbittorrent settings
|
// qbittorrent settings
|
||||||
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
|
@ -14,6 +14,8 @@ const (
|
|||||||
SiteTitle = "site_title"
|
SiteTitle = "site_title"
|
||||||
Announcement = "announcement"
|
Announcement = "announcement"
|
||||||
AllowIndexed = "allow_indexed"
|
AllowIndexed = "allow_indexed"
|
||||||
|
AllowMounted = "allow_mounted"
|
||||||
|
RobotsTxt = "robots_txt"
|
||||||
|
|
||||||
Logo = "logo"
|
Logo = "logo"
|
||||||
Favicon = "favicon"
|
Favicon = "favicon"
|
||||||
@ -55,10 +57,14 @@ const (
|
|||||||
IndexProgress = "index_progress"
|
IndexProgress = "index_progress"
|
||||||
|
|
||||||
//SSO
|
//SSO
|
||||||
SSOClientId = "sso_client_id"
|
SSOClientId = "sso_client_id"
|
||||||
SSOClientSecret = "sso_client_secret"
|
SSOClientSecret = "sso_client_secret"
|
||||||
SSOLoginEnabled = "sso_login_enabled"
|
SSOLoginEnabled = "sso_login_enabled"
|
||||||
SSOLoginplatform = "sso_login_platform"
|
SSOLoginplatform = "sso_login_platform"
|
||||||
|
SSOOrganizationName = "sso_organization_name"
|
||||||
|
SSOApplicationName = "sso_application_name"
|
||||||
|
SSOEndpointName = "sso_endpoint_name"
|
||||||
|
SSOJwtPublicKey = "sso_jwt_public_key"
|
||||||
|
|
||||||
// qbittorrent
|
// qbittorrent
|
||||||
QbittorrentUrl = "qbittorrent_url"
|
QbittorrentUrl = "qbittorrent_url"
|
||||||
|
@ -13,19 +13,32 @@ import (
|
|||||||
// So, the purpose of this package is to convert mount path to actual path
|
// So, the purpose of this package is to convert mount path to actual path
|
||||||
// then pass the actual path to the op package
|
// then pass the actual path to the op package
|
||||||
|
|
||||||
func List(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
|
type ListArgs struct {
|
||||||
res, err := list(ctx, path, refresh...)
|
Refresh bool
|
||||||
|
NoLog bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func List(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
|
||||||
|
res, err := list(ctx, path, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed list %s: %+v", path, err)
|
if !args.NoLog {
|
||||||
|
log.Errorf("failed list %s: %+v", path, err)
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(ctx context.Context, path string) (model.Obj, error) {
|
type GetArgs struct {
|
||||||
|
NoLog bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(ctx context.Context, path string, args *GetArgs) (model.Obj, error) {
|
||||||
res, err := get(ctx, path)
|
res, err := get(ctx, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed get %s: %+v", path, err)
|
if !args.NoLog {
|
||||||
|
log.Errorf("failed get %s: %+v", path, err)
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
@ -96,7 +109,10 @@ func PutAsTask(dstDirPath string, file *model.FileStream) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetStorage(path string) (driver.Driver, error) {
|
type GetStoragesArgs struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStorage(path string, args *GetStoragesArgs) (driver.Driver, error) {
|
||||||
storageDriver, _, err := op.GetStorageAndActualPath(path)
|
storageDriver, _, err := op.GetStorageAndActualPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -11,9 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// List files
|
// List files
|
||||||
func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
|
func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
|
||||||
meta := ctx.Value("meta").(*model.Meta)
|
meta, _ := ctx.Value("meta").(*model.Meta)
|
||||||
user := ctx.Value("user").(*model.User)
|
user, _ := ctx.Value("user").(*model.User)
|
||||||
virtualFiles := op.GetStorageVirtualFilesByPath(path)
|
virtualFiles := op.GetStorageVirtualFilesByPath(path)
|
||||||
storage, actualPath, err := op.GetStorageAndActualPath(path)
|
storage, actualPath, err := op.GetStorageAndActualPath(path)
|
||||||
if err != nil && len(virtualFiles) == 0 {
|
if err != nil && len(virtualFiles) == 0 {
|
||||||
@ -24,9 +24,11 @@ func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error
|
|||||||
if storage != nil {
|
if storage != nil {
|
||||||
_objs, err = op.List(ctx, storage, actualPath, model.ListArgs{
|
_objs, err = op.List(ctx, storage, actualPath, model.ListArgs{
|
||||||
ReqPath: path,
|
ReqPath: path,
|
||||||
}, refresh...)
|
}, args.Refresh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%+v", err)
|
if !args.NoLog {
|
||||||
|
log.Errorf("fs/list: %+v", err)
|
||||||
|
}
|
||||||
if len(virtualFiles) == 0 {
|
if len(virtualFiles) == 0 {
|
||||||
return nil, errors.WithMessage(err, "failed get objs")
|
return nil, errors.WithMessage(err, "failed get objs")
|
||||||
}
|
}
|
||||||
@ -43,7 +45,7 @@ func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error
|
|||||||
|
|
||||||
func whetherHide(user *model.User, meta *model.Meta, path string) bool {
|
func whetherHide(user *model.User, meta *model.Meta, path string) bool {
|
||||||
// if is admin, don't hide
|
// if is admin, don't hide
|
||||||
if user.CanSeeHides() {
|
if user == nil || user.CanSeeHides() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// if meta is nil, don't hide
|
// if meta is nil, don't hide
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
stdpath "path"
|
stdpath "path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
@ -33,8 +34,6 @@ func containsByName(files []model.Obj, file model.Obj) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpClient = &http.Client{}
|
|
||||||
|
|
||||||
func getFileStreamFromLink(file model.Obj, link *model.Link) (*model.FileStream, error) {
|
func getFileStreamFromLink(file model.Obj, link *model.Link) (*model.FileStream, error) {
|
||||||
var rc io.ReadCloser
|
var rc io.ReadCloser
|
||||||
mimetype := utils.GetMimeType(file.GetName())
|
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 {
|
for h, val := range link.Header {
|
||||||
req.Header[h] = val
|
req.Header[h] = val
|
||||||
}
|
}
|
||||||
res, err := httpClient.Do(req)
|
res, err := base.HttpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to get response for %s", link.URL)
|
return nil, errors.Wrapf(err, "failed to get response for %s", link.URL)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ func WalkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
|
|||||||
}
|
}
|
||||||
meta, _ := op.GetNearestMeta(name)
|
meta, _ := op.GetNearestMeta(name)
|
||||||
// Read directory names.
|
// Read directory names.
|
||||||
objs, err := List(context.WithValue(ctx, "meta", meta), name)
|
objs, err := List(context.WithValue(ctx, "meta", meta), name, &ListArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return walkFnErr
|
return walkFnErr
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,13 @@ type LinkArgs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Link struct {
|
type Link struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Header http.Header `json:"header"` // needed header
|
Header http.Header `json:"header"` // needed header
|
||||||
Data io.ReadCloser // return file reader directly
|
Data io.ReadCloser // return file reader directly
|
||||||
Status int // status maybe 200 or 206, etc
|
Status int // status maybe 200 or 206, etc
|
||||||
FilePath *string // local file, return the filepath
|
FilePath *string // local file, return the filepath
|
||||||
Expiration *time.Duration // url expiration time
|
Expiration *time.Duration // url expiration time
|
||||||
|
Handle func(w http.ResponseWriter, r *http.Request) error `json:"-"` // custom handler
|
||||||
}
|
}
|
||||||
|
|
||||||
type OtherArgs struct {
|
type OtherArgs struct {
|
||||||
|
@ -13,6 +13,7 @@ type Storage struct {
|
|||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
Modified time.Time `json:"modified"`
|
Modified time.Time `json:"modified"`
|
||||||
Disabled bool `json:"disabled"` // if disabled
|
Disabled bool `json:"disabled"` // if disabled
|
||||||
|
EnableSign bool `json:"enable_sign"`
|
||||||
Sort
|
Sort
|
||||||
Proxy
|
Proxy
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ func getMainItems(config driver.Config) []driver.Item {
|
|||||||
Name: "mount_path",
|
Name: "mount_path",
|
||||||
Type: conf.TypeString,
|
Type: conf.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
Help: "",
|
Help: "The path you want to mount to, it is unique and cannot be repeated",
|
||||||
}, {
|
}, {
|
||||||
Name: "order",
|
Name: "order",
|
||||||
Type: conf.TypeNumber,
|
Type: conf.TypeNumber,
|
||||||
@ -122,9 +122,14 @@ func getMainItems(config driver.Config) []driver.Item {
|
|||||||
Type: conf.TypeSelect,
|
Type: conf.TypeSelect,
|
||||||
Options: "front,back",
|
Options: "front,back",
|
||||||
})
|
})
|
||||||
|
items = append(items, driver.Item{
|
||||||
|
Name: "enable_sign",
|
||||||
|
Type: conf.TypeBool,
|
||||||
|
Default: "false",
|
||||||
|
Required: true,
|
||||||
|
})
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAdditionalItems(t reflect.Type, defaultRoot string) []driver.Item {
|
func getAdditionalItems(t reflect.Type, defaultRoot string) []driver.Item {
|
||||||
var items []driver.Item
|
var items []driver.Item
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
@ -134,6 +134,7 @@ func (c *client) AddFromLink(link string, savePath string, id string) error {
|
|||||||
addField("urls", link)
|
addField("urls", link)
|
||||||
addField("savepath", savePath)
|
addField("savepath", savePath)
|
||||||
addField("tags", "alist-"+id)
|
addField("tags", "alist-"+id)
|
||||||
|
addField("autoTMM", "false")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ func BuildIndex(ctx context.Context, indexPaths, ignorePaths []string, maxDepth
|
|||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fi, err = fs.Get(ctx, indexPath)
|
fi, err = fs.Get(ctx, indexPath, &fs.GetArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ func (c *client) AddURI(uris []string, options ...interface{}) (gid string, err
|
|||||||
// If a file with the same name already exists, it is overwritten!
|
// If a file with the same name already exists, it is overwritten!
|
||||||
// If the file cannot be saved successfully or --rpc-save-upload-metadata is false, the downloads added by this method are not saved by --save-session.
|
// If the file cannot be saved successfully or --rpc-save-upload-metadata is false, the downloads added by this method are not saved by --save-session.
|
||||||
func (c *client) AddTorrent(filename string, options ...interface{}) (gid string, err error) {
|
func (c *client) AddTorrent(filename string, options ...interface{}) (gid string, err error) {
|
||||||
co, err := ioutil.ReadFile(filename)
|
co, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ func (c *client) AddTorrent(filename string, options ...interface{}) (gid string
|
|||||||
// If a file with the same name already exists, it is overwritten!
|
// If a file with the same name already exists, it is overwritten!
|
||||||
// If the file cannot be saved successfully or --rpc-save-upload-metadata is false, the downloads added by this method are not saved by --save-session.
|
// If the file cannot be saved successfully or --rpc-save-upload-metadata is false, the downloads added by this method are not saved by --save-session.
|
||||||
func (c *client) AddMetalink(filename string, options ...interface{}) (gid []string, err error) {
|
func (c *client) AddMetalink(filename string, options ...interface{}) (gid []string, err error) {
|
||||||
co, err := ioutil.ReadFile(filename)
|
co, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -343,9 +343,9 @@ func (c *Client) Read(path string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Link(path string) (string, http.Header, error) {
|
func (c *Client) Link(path string) (string, http.Header, error) {
|
||||||
method := "HEAD"
|
method := "GET"
|
||||||
url := PathEscape(Join(c.root, path))
|
u := PathEscape(Join(c.root, path))
|
||||||
r, err := http.NewRequest(method, url, nil)
|
r, err := http.NewRequest(method, u, nil)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, newPathErrorErr("Link", path, err)
|
return "", nil, newPathErrorErr("Link", path, err)
|
||||||
@ -366,31 +366,6 @@ func (c *Client) Link(path string) (string, http.Header, error) {
|
|||||||
if c.interceptor != nil {
|
if c.interceptor != nil {
|
||||||
c.interceptor(method, r)
|
c.interceptor(method, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
rs, err := c.c.Do(r)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, newPathErrorErr("Link", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rs.StatusCode == 401 {
|
|
||||||
wwwAuthenticateHeader := strings.ToLower(rs.Header.Get("Www-Authenticate"))
|
|
||||||
if strings.Contains(wwwAuthenticateHeader, "digest") {
|
|
||||||
c.authMutex.Lock()
|
|
||||||
c.auth = &DigestAuth{auth.User(), auth.Pass(), digestParts(rs)}
|
|
||||||
c.auth.Authorize(r, method, path)
|
|
||||||
c.authMutex.Unlock()
|
|
||||||
} else if strings.Contains(wwwAuthenticateHeader, "basic") {
|
|
||||||
c.authMutex.Lock()
|
|
||||||
c.auth = &BasicAuth{auth.User(), auth.Pass()}
|
|
||||||
c.auth.Authorize(r, method, path)
|
|
||||||
c.authMutex.Unlock()
|
|
||||||
} else {
|
|
||||||
return "", nil, newPathError("Authorize", c.root, rs.StatusCode)
|
|
||||||
}
|
|
||||||
} else if rs.StatusCode > 400 {
|
|
||||||
return "", nil, newPathError("Authorize", path, rs.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.URL.String(), r.Header, nil
|
return r.URL.String(), r.Header, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (req *http.Response, err error) {
|
func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (req *http.Response, err error) {
|
||||||
var r *http.Request
|
var r *http.Request
|
||||||
var retryBuf io.Reader
|
var retryBuf io.Reader
|
||||||
|
canRetry := true
|
||||||
if body != nil {
|
if body != nil {
|
||||||
// If the authorization fails, we will need to restart reading
|
// If the authorization fails, we will need to restart reading
|
||||||
// from the passed body stream.
|
// from the passed body stream.
|
||||||
@ -25,6 +25,8 @@ func (c *Client) req(method, path string, body io.Reader, intercept func(*http.R
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
retryBuf = body
|
retryBuf = body
|
||||||
|
} else if method == http.MethodPut {
|
||||||
|
canRetry = false
|
||||||
} else {
|
} else {
|
||||||
buff := &bytes.Buffer{}
|
buff := &bytes.Buffer{}
|
||||||
retryBuf = buff
|
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
|
// retryBuf will be nil if body was nil initially so no check
|
||||||
// for body == nil is required here.
|
// for body == nil is required here.
|
||||||
return c.req(method, path, retryBuf, intercept)
|
if canRetry {
|
||||||
|
return c.req(method, path, retryBuf, intercept)
|
||||||
|
}
|
||||||
} else if rs.StatusCode == 401 {
|
} else if rs.StatusCode == 401 {
|
||||||
return rs, newPathError("Authorize", c.root, rs.StatusCode)
|
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) {
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package task
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -63,11 +64,17 @@ func (t Task[K]) GetErrMsg() string {
|
|||||||
return t.Error.Error()
|
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() {
|
func (t *Task[K]) run() {
|
||||||
t.state = RUNNING
|
t.state = RUNNING
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
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.Error = errors.Errorf("panic: %+v", err)
|
||||||
t.state = ERRORED
|
t.state = ERRORED
|
||||||
}
|
}
|
||||||
|
114
pkg/utils/fn_limiter.go
Normal file
114
pkg/utils/fn_limiter.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
59
pkg/utils/fn_limiter_test.go
Normal file
59
pkg/utils/fn_limiter_test.go
Normal 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
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
@ -91,3 +92,46 @@ func NewReadCloser(reader io.Reader, close CloseFunc) io.ReadCloser {
|
|||||||
func NewLimitReadCloser(reader io.Reader, close CloseFunc, limit int64) io.ReadCloser {
|
func NewLimitReadCloser(reader io.Reader, close CloseFunc, limit int64) io.ReadCloser {
|
||||||
return NewReadCloser(io.LimitReader(reader, limit), close)
|
return NewReadCloser(io.LimitReader(reader, limit), close)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MultiReadable struct {
|
||||||
|
originReader io.Reader
|
||||||
|
reader io.Reader
|
||||||
|
cache *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMultiReadable(reader io.Reader) *MultiReadable {
|
||||||
|
return &MultiReadable{
|
||||||
|
originReader: reader,
|
||||||
|
reader: reader,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr *MultiReadable) Read(p []byte) (int, error) {
|
||||||
|
n, err := mr.reader.Read(p)
|
||||||
|
if _, ok := mr.reader.(io.Seeker); !ok && n > 0 {
|
||||||
|
if mr.cache == nil {
|
||||||
|
mr.cache = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
mr.cache.Write(p[:n])
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr *MultiReadable) Reset() error {
|
||||||
|
if seeker, ok := mr.reader.(io.Seeker); ok {
|
||||||
|
_, err := seeker.Seek(0, io.SeekStart)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if mr.cache != nil && mr.cache.Len() > 0 {
|
||||||
|
mr.reader = io.MultiReader(mr.cache, mr.reader)
|
||||||
|
mr.cache = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr *MultiReadable) Close() error {
|
||||||
|
if closer, ok := mr.originReader.(io.Closer); ok {
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -8,9 +8,15 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func IsStorageSignEnabled(rawPath string) bool {
|
||||||
|
storage := op.GetBalancedStorage(rawPath)
|
||||||
|
return storage != nil && storage.GetStorage().EnableSign
|
||||||
|
}
|
||||||
|
|
||||||
func CanWrite(meta *model.Meta, path string) bool {
|
func CanWrite(meta *model.Meta, path string) bool {
|
||||||
if meta == nil || !meta.Write {
|
if meta == nil || !meta.Write {
|
||||||
return false
|
return false
|
||||||
|
@ -3,13 +3,14 @@ package common
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
@ -17,7 +18,22 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var HttpClient = &http.Client{}
|
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 {
|
func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.Obj) error {
|
||||||
// read data with native
|
// read data with native
|
||||||
@ -61,24 +77,29 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
filename := file.GetName()
|
||||||
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename)))
|
||||||
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
|
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
|
||||||
return nil
|
return nil
|
||||||
|
} else if link.Handle != nil {
|
||||||
|
return link.Handle(w, r)
|
||||||
} else {
|
} else {
|
||||||
req, err := http.NewRequest(r.Method, link.URL, nil)
|
req, err := http.NewRequest(r.Method, link.URL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// client header
|
||||||
for h, val := range r.Header {
|
for h, val := range r.Header {
|
||||||
if utils.SliceContains(conf.SlicesMap[conf.ProxyIgnoreHeaders], strings.ToLower(h)) {
|
if utils.SliceContains(conf.SlicesMap[conf.ProxyIgnoreHeaders], strings.ToLower(h)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
req.Header[h] = val
|
req.Header[h] = val
|
||||||
}
|
}
|
||||||
|
// needed header
|
||||||
for h, val := range link.Header {
|
for h, val := range link.Header {
|
||||||
req.Header[h] = val
|
req.Header[h] = val
|
||||||
}
|
}
|
||||||
res, err := HttpClient.Do(req)
|
res, err := HttpClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -93,7 +114,7 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
|||||||
}
|
}
|
||||||
w.WriteHeader(res.StatusCode)
|
w.WriteHeader(res.StatusCode)
|
||||||
if res.StatusCode >= 400 {
|
if res.StatusCode >= 400 {
|
||||||
all, _ := ioutil.ReadAll(res.Body)
|
all, _ := io.ReadAll(res.Body)
|
||||||
msg := string(all)
|
msg := string(all)
|
||||||
log.Debugln(msg)
|
log.Debugln(msg)
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
func Down(c *gin.Context) {
|
func Down(c *gin.Context) {
|
||||||
rawPath := c.MustGet("path").(string)
|
rawPath := c.MustGet("path").(string)
|
||||||
filename := stdpath.Base(rawPath)
|
filename := stdpath.Base(rawPath)
|
||||||
storage, err := fs.GetStorage(rawPath)
|
storage, err := fs.GetStorage(rawPath, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
@ -65,7 +65,7 @@ func Down(c *gin.Context) {
|
|||||||
func Proxy(c *gin.Context) {
|
func Proxy(c *gin.Context) {
|
||||||
rawPath := c.MustGet("path").(string)
|
rawPath := c.MustGet("path").(string)
|
||||||
filename := stdpath.Base(rawPath)
|
filename := stdpath.Base(rawPath)
|
||||||
storage, err := fs.GetStorage(rawPath)
|
storage, err := fs.GetStorage(rawPath, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user