Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
ae6984714d | |||
d0f88bd1cb | |||
f8b1f87a5f | |||
71e4e1ab6e | |||
7e6522c81e | |||
94a80bccfe | |||
e66abb3f58 | |||
742335f80e | |||
f1979a8bbc | |||
1f835502ba | |||
424ab2d0c0 | |||
858ba19670 | |||
0c7e47a76c | |||
53926d5cd0 | |||
47f4b05517 | |||
6d85f1b0c0 | |||
e49fda3e2a | |||
da5e35578a | |||
812f58ae6d | |||
9bd3c87bcc | |||
c82866975e | |||
aef952ae68 | |||
9222510d8d | |||
d88b54d98a | |||
85a28d9822 | |||
4f7761fe2c | |||
a8c900d09e | |||
8bccb69e8d | |||
0f29a811bf | |||
442c2f77ea | |||
ce06f394f1 | |||
e3e790f461 | |||
f0e8c0e886 | |||
86b35ae5cf | |||
4930f85b90 | |||
85fe65951d | |||
1381e8fb27 | |||
292bbe94ee | |||
bb6747de4e | |||
555ef0eb1a | |||
bff56ffd0f | |||
34b73b94f7 | |||
434892f135 |
36
.github/workflows/build_docker.yml
vendored
36
.github/workflows/build_docker.yml
vendored
@ -18,6 +18,20 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: xhofe/alist
|
||||||
|
|
||||||
|
- name: Docker meta with ffmpeg
|
||||||
|
id: meta-ffmpeg
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: xhofe/alist
|
||||||
|
flavor: |
|
||||||
|
suffix=-ffmpeg,onlatest=true
|
||||||
|
|
||||||
- uses: actions/setup-go@v4
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: 'stable'
|
go-version: 'stable'
|
||||||
@ -25,12 +39,6 @@ jobs:
|
|||||||
- name: Build go binary
|
- name: Build go binary
|
||||||
run: bash build.sh dev docker-multiplatform
|
run: bash build.sh dev docker-multiplatform
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: xhofe/alist
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
@ -55,6 +63,20 @@ jobs:
|
|||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
||||||
|
|
||||||
|
- name: Replace dockerfile tag
|
||||||
|
run: |
|
||||||
|
sed -i -e "s/latest/main/g" Dockerfile.ffmpeg
|
||||||
|
|
||||||
|
- name: Build and push with ffmpeg
|
||||||
|
id: docker_build_ffmpeg
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
file: Dockerfile.ffmpeg
|
||||||
|
push: ${{ github.event_name == 'push' }}
|
||||||
|
tags: ${{ steps.meta-ffmpeg.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta-ffmpeg.outputs.labels }}
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
||||||
|
|
||||||
build_docker_with_aria2:
|
build_docker_with_aria2:
|
||||||
needs: build_docker
|
needs: build_docker
|
||||||
name: Build docker with aria2
|
name: Build docker with aria2
|
||||||
@ -80,4 +102,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.MY_TOKEN }}
|
github_token: ${{ secrets.MY_TOKEN }}
|
||||||
branch: main
|
branch: main
|
||||||
repository: alist-org/with_aria2
|
repository: alist-org/with_aria2
|
||||||
|
34
.github/workflows/release_android.yml
vendored
Normal file
34
.github/workflows/release_android.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: release_android
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [ published ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release_android:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [ ubuntu-latest ]
|
||||||
|
go-version: [ '1.21' ]
|
||||||
|
name: Release
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
bash build.sh release android
|
||||||
|
|
||||||
|
- name: Upload assets
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: build/compress/*
|
19
.github/workflows/release_docker.yml
vendored
19
.github/workflows/release_docker.yml
vendored
@ -49,6 +49,25 @@ jobs:
|
|||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
||||||
|
|
||||||
|
- name: Docker meta with ffmpeg
|
||||||
|
id: meta-ffmpeg
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: xhofe/alist
|
||||||
|
flavor: |
|
||||||
|
latest=true
|
||||||
|
suffix=-ffmpeg,onlatest=true
|
||||||
|
|
||||||
|
- name: Build and push with ffmpeg
|
||||||
|
id: docker_build_ffmpeg
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
file: Dockerfile.ffmpeg
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta-ffmpeg.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta-ffmpeg.outputs.labels }}
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
||||||
|
|
||||||
release_docker_with_aria2:
|
release_docker_with_aria2:
|
||||||
needs: release_docker
|
needs: release_docker
|
||||||
name: Release docker with aria2
|
name: Release docker with aria2
|
||||||
|
4
Dockerfile.ffmpeg
Normal file
4
Dockerfile.ffmpeg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
FROM xhofe/alist:latest
|
||||||
|
RUN apk update && \
|
||||||
|
apk add --no-cache ffmpeg \
|
||||||
|
rm -rf /var/cache/apk/*
|
@ -75,6 +75,7 @@ English | [中文](./README_cn.md)| [日本語](./README_ja.md) | [Contributing]
|
|||||||
- [x] [115](https://115.com/)
|
- [x] [115](https://115.com/)
|
||||||
- [X] Cloudreve
|
- [X] Cloudreve
|
||||||
- [x] [Dropbox](https://www.dropbox.com/)
|
- [x] [Dropbox](https://www.dropbox.com/)
|
||||||
|
- [x] [FeijiPan](https://www.feijipan.com/)
|
||||||
- [x] Easy to deploy and out-of-the-box
|
- [x] Easy to deploy and out-of-the-box
|
||||||
- [x] File preview (PDF, markdown, code, plain text, ...)
|
- [x] File preview (PDF, markdown, code, plain text, ...)
|
||||||
- [x] Image preview in gallery mode
|
- [x] Image preview in gallery mode
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
- [x] [115](https://115.com/)
|
- [x] [115](https://115.com/)
|
||||||
- [X] Cloudreve
|
- [X] Cloudreve
|
||||||
- [x] [Dropbox](https://www.dropbox.com/)
|
- [x] [Dropbox](https://www.dropbox.com/)
|
||||||
|
- [x] [飞机盘](https://www.feijipan.com/)
|
||||||
- [x] 部署方便,开箱即用
|
- [x] 部署方便,开箱即用
|
||||||
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
||||||
- [x] 画廊模式下的图像预览
|
- [x] 画廊模式下的图像预览
|
||||||
|
@ -75,6 +75,7 @@
|
|||||||
- [x] [115](https://115.com/)
|
- [x] [115](https://115.com/)
|
||||||
- [X] Cloudreve
|
- [X] Cloudreve
|
||||||
- [x] [Dropbox](https://www.dropbox.com/)
|
- [x] [Dropbox](https://www.dropbox.com/)
|
||||||
|
- [x] [FeijiPan](https://www.feijipan.com/)
|
||||||
- [x] デプロイが簡単で、すぐに使える
|
- [x] デプロイが簡単で、すぐに使える
|
||||||
- [x] ファイルプレビュー (PDF, マークダウン, コード, プレーンテキスト, ...)
|
- [x] ファイルプレビュー (PDF, マークダウン, コード, プレーンテキスト, ...)
|
||||||
- [x] ギャラリーモードでの画像プレビュー
|
- [x] ギャラリーモードでの画像プレビュー
|
||||||
|
29
build.sh
29
build.sh
@ -211,6 +211,27 @@ BuildReleaseLinuxMuslArm() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BuildReleaseAndroid() {
|
||||||
|
rm -rf .git/
|
||||||
|
mkdir -p "build"
|
||||||
|
wget https://dl.google.com/android/repository/android-ndk-r26b-linux.zip
|
||||||
|
unzip android-ndk-r26b-linux.zip
|
||||||
|
rm android-ndk-r26b-linux.zip
|
||||||
|
OS_ARCHES=(amd64 arm64 386 arm)
|
||||||
|
CGO_ARGS=(x86_64-linux-android24-clang aarch64-linux-android24-clang i686-linux-android24-clang armv7a-linux-androideabi24-clang)
|
||||||
|
for i in "${!OS_ARCHES[@]}"; do
|
||||||
|
os_arch=${OS_ARCHES[$i]}
|
||||||
|
cgo_cc=$(realpath android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/${CGO_ARGS[$i]})
|
||||||
|
echo building for android-${os_arch}
|
||||||
|
export GOOS=android
|
||||||
|
export GOARCH=${os_arch##*-}
|
||||||
|
export CC=${cgo_cc}
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
go build -o ./build/$appName-android-$os_arch -ldflags="$ldflags" -tags=jsoniter .
|
||||||
|
android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip ./build/$appName-android-$os_arch
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
MakeRelease() {
|
MakeRelease() {
|
||||||
cd build
|
cd build
|
||||||
mkdir compress
|
mkdir compress
|
||||||
@ -218,6 +239,11 @@ MakeRelease() {
|
|||||||
cp "$i" alist
|
cp "$i" alist
|
||||||
tar -czvf compress/"$i".tar.gz alist
|
tar -czvf compress/"$i".tar.gz alist
|
||||||
rm -f alist
|
rm -f alist
|
||||||
|
done
|
||||||
|
for i in $(find . -type f -name "$appName-android-*"); do
|
||||||
|
cp "$i" alist
|
||||||
|
tar -czvf compress/"$i".tar.gz alist
|
||||||
|
rm -f alist
|
||||||
done
|
done
|
||||||
for i in $(find . -type f -name "$appName-darwin-*"); do
|
for i in $(find . -type f -name "$appName-darwin-*"); do
|
||||||
cp "$i" alist
|
cp "$i" alist
|
||||||
@ -256,6 +282,9 @@ elif [ "$1" = "release" ]; then
|
|||||||
elif [ "$2" = "linux_musl" ]; then
|
elif [ "$2" = "linux_musl" ]; then
|
||||||
BuildReleaseLinuxMusl
|
BuildReleaseLinuxMusl
|
||||||
MakeRelease "md5-linux-musl.txt"
|
MakeRelease "md5-linux-musl.txt"
|
||||||
|
elif [ "$2" = "android" ]; then
|
||||||
|
BuildReleaseAndroid
|
||||||
|
MakeRelease "md5-android.txt"
|
||||||
else
|
else
|
||||||
BuildRelease
|
BuildRelease
|
||||||
MakeRelease "md5.txt"
|
MakeRelease "md5.txt"
|
||||||
|
@ -6,10 +6,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
|
Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
|
||||||
QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
|
QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
|
||||||
PageSize int64 `json:"page_size" type:"number" default:"56" help:"list api per page size of 115 driver"`
|
QRCodeSource string `json:"qrcode_source" type:"select" options:"web,android,ios,linux,mac,windows,tv" default:"linux" help:"select the QR code device, default linux"`
|
||||||
LimitRate float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
|
PageSize int64 `json:"page_size" type:"number" default:"56" help:"list api per page size of 115 driver"`
|
||||||
|
LimitRate float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
|
||||||
driver.RootID
|
driver.RootID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ func (d *Pan115) login() error {
|
|||||||
s := &driver115.QRCodeSession{
|
s := &driver115.QRCodeSession{
|
||||||
UID: d.Addition.QRCodeToken,
|
UID: d.Addition.QRCodeToken,
|
||||||
}
|
}
|
||||||
if cr, err = d.client.QRCodeLogin(s); err != nil {
|
if cr, err = d.client.QRCodeLoginWithApp(s, driver115.LoginApp(d.QRCodeSource)); err != nil {
|
||||||
return errors.Wrap(err, "failed to login by qrcode")
|
return errors.Wrap(err, "failed to login by qrcode")
|
||||||
}
|
}
|
||||||
d.Addition.Cookie = fmt.Sprintf("UID=%s;CID=%s;SEID=%s", cr.UID, cr.CID, cr.SEID)
|
d.Addition.Cookie = fmt.Sprintf("UID=%s;CID=%s;SEID=%s", cr.UID, cr.CID, cr.SEID)
|
||||||
|
@ -6,12 +6,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
|
Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
|
||||||
QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
|
QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
|
||||||
PageSize int64 `json:"page_size" type:"number" default:"20" help:"list api per page size of 115 driver"`
|
QRCodeSource string `json:"qrcode_source" type:"select" options:"web,android,ios,linux,mac,windows,tv" default:"linux" help:"select the QR code device, default linux"`
|
||||||
LimitRate float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
|
PageSize int64 `json:"page_size" type:"number" default:"20" help:"list api per page size of 115 driver"`
|
||||||
ShareCode string `json:"share_code" type:"text" required:"true" help:"share code of 115 share link"`
|
LimitRate float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
|
||||||
ReceiveCode string `json:"receive_code" type:"text" required:"true" help:"receive code of 115 share link"`
|
ShareCode string `json:"share_code" type:"text" required:"true" help:"share code of 115 share link"`
|
||||||
|
ReceiveCode string `json:"receive_code" type:"text" required:"true" help:"receive code of 115 share link"`
|
||||||
driver.RootID
|
driver.RootID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ func (d *Pan115Share) login() error {
|
|||||||
s := &driver115.QRCodeSession{
|
s := &driver115.QRCodeSession{
|
||||||
UID: d.QRCodeToken,
|
UID: d.QRCodeToken,
|
||||||
}
|
}
|
||||||
if cr, err = d.client.QRCodeLogin(s); err != nil {
|
if cr, err = d.client.QRCodeLoginWithApp(s, driver115.LoginApp(d.QRCodeSource)); err != nil {
|
||||||
return errors.Wrap(err, "failed to login by qrcode")
|
return errors.Wrap(err, "failed to login by qrcode")
|
||||||
}
|
}
|
||||||
d.Cookie = fmt.Sprintf("UID=%s;CID=%s;SEID=%s", cr.UID, cr.CID, cr.SEID)
|
d.Cookie = fmt.Sprintf("UID=%s;CID=%s;SEID=%s", cr.UID, cr.CID, cr.SEID)
|
||||||
|
@ -6,6 +6,13 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"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"
|
||||||
@ -17,14 +24,12 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
"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"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Pan123 struct {
|
type Pan123 struct {
|
||||||
model.Storage
|
model.Storage
|
||||||
Addition
|
Addition
|
||||||
|
apiRateLimit sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Pan123) Config() driver.Config {
|
func (d *Pan123) Config() driver.Config {
|
||||||
@ -232,6 +237,9 @@ func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uploader := s3manager.NewUploader(s)
|
uploader := s3manager.NewUploader(s)
|
||||||
|
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||||
|
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||||
|
}
|
||||||
input := &s3manager.UploadInput{
|
input := &s3manager.UploadInput{
|
||||||
Bucket: &resp.Data.Bucket,
|
Bucket: &resp.Data.Bucket,
|
||||||
Key: &resp.Data.Key,
|
Key: &resp.Data.Key,
|
||||||
@ -250,4 +258,11 @@ func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Pan123) APIRateLimit(api string) bool {
|
||||||
|
limiter, _ := d.apiRateLimit.LoadOrStore(api,
|
||||||
|
rate.NewLimiter(rate.Every(time.Millisecond*700), 1))
|
||||||
|
ins := limiter.(*rate.Limiter)
|
||||||
|
return ins.Allow()
|
||||||
|
}
|
||||||
|
|
||||||
var _ driver.Driver = (*Pan123)(nil)
|
var _ driver.Driver = (*Pan123)(nil)
|
||||||
|
@ -160,7 +160,7 @@ func (d *Pan123) login() error {
|
|||||||
SetHeaders(map[string]string{
|
SetHeaders(map[string]string{
|
||||||
"origin": "https://www.123pan.com",
|
"origin": "https://www.123pan.com",
|
||||||
"referer": "https://www.123pan.com/",
|
"referer": "https://www.123pan.com/",
|
||||||
"user-agent": "Dart/2.19(dart:io)",
|
"user-agent": "Dart/2.19(dart:io)-alist",
|
||||||
"platform": "web",
|
"platform": "web",
|
||||||
"app-version": "3",
|
"app-version": "3",
|
||||||
//"user-agent": base.UserAgent,
|
//"user-agent": base.UserAgent,
|
||||||
@ -197,7 +197,7 @@ func (d *Pan123) request(url string, method string, callback base.ReqCallback, r
|
|||||||
"origin": "https://www.123pan.com",
|
"origin": "https://www.123pan.com",
|
||||||
"referer": "https://www.123pan.com/",
|
"referer": "https://www.123pan.com/",
|
||||||
"authorization": "Bearer " + d.AccessToken,
|
"authorization": "Bearer " + d.AccessToken,
|
||||||
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0",
|
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) alist-client",
|
||||||
"platform": "web",
|
"platform": "web",
|
||||||
"app-version": "3",
|
"app-version": "3",
|
||||||
//"user-agent": base.UserAgent,
|
//"user-agent": base.UserAgent,
|
||||||
@ -235,7 +235,12 @@ func (d *Pan123) request(url string, method string, callback base.ReqCallback, r
|
|||||||
func (d *Pan123) getFiles(parentId string) ([]File, error) {
|
func (d *Pan123) getFiles(parentId string) ([]File, error) {
|
||||||
page := 1
|
page := 1
|
||||||
res := make([]File, 0)
|
res := make([]File, 0)
|
||||||
|
// 2024-02-06 fix concurrency by 123pan
|
||||||
for {
|
for {
|
||||||
|
if !d.APIRateLimit(FileList) {
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
continue
|
||||||
|
}
|
||||||
var resp Files
|
var resp Files
|
||||||
query := map[string]string{
|
query := map[string]string{
|
||||||
"driveId": "0",
|
"driveId": "0",
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
_ "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_uc"
|
_ "github.com/alist-org/alist/v3/drivers/quark_uc"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/quqi"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/s3"
|
_ "github.com/alist-org/alist/v3/drivers/s3"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/seafile"
|
_ "github.com/alist-org/alist/v3/drivers/seafile"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/sftp"
|
_ "github.com/alist-org/alist/v3/drivers/sftp"
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package chaoxing
|
package chaoxing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
@ -88,44 +90,59 @@ type UserAuth struct {
|
|||||||
} `json:"operationAuth"`
|
} `json:"operationAuth"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 手机端学习通上传的文件的json内容(content字段)与网页端上传的有所不同
|
||||||
|
// 网页端json `"puid": 54321, "size": 12345`
|
||||||
|
// 手机端json `"puid": "54321". "size": "12345"`
|
||||||
|
type int_str int
|
||||||
|
|
||||||
|
// json 字符串数字和纯数字解析
|
||||||
|
func (ios *int_str) UnmarshalJSON(data []byte) error {
|
||||||
|
intValue, err := strconv.Atoi(string(bytes.Trim(data, "\"")))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*ios = int_str(intValue)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
Cataid int `json:"cataid"`
|
Cataid int `json:"cataid"`
|
||||||
Cfid int `json:"cfid"`
|
Cfid int `json:"cfid"`
|
||||||
Content struct {
|
Content struct {
|
||||||
Cfid int `json:"cfid"`
|
Cfid int `json:"cfid"`
|
||||||
Pid int `json:"pid"`
|
Pid int `json:"pid"`
|
||||||
FolderName string `json:"folderName"`
|
FolderName string `json:"folderName"`
|
||||||
ShareType int `json:"shareType"`
|
ShareType int `json:"shareType"`
|
||||||
Preview string `json:"preview"`
|
Preview string `json:"preview"`
|
||||||
Filetype string `json:"filetype"`
|
Filetype string `json:"filetype"`
|
||||||
PreviewURL string `json:"previewUrl"`
|
PreviewURL string `json:"previewUrl"`
|
||||||
IsImg bool `json:"isImg"`
|
IsImg bool `json:"isImg"`
|
||||||
ParentPath string `json:"parentPath"`
|
ParentPath string `json:"parentPath"`
|
||||||
Icon string `json:"icon"`
|
Icon string `json:"icon"`
|
||||||
Suffix string `json:"suffix"`
|
Suffix string `json:"suffix"`
|
||||||
Duration int `json:"duration"`
|
Duration int `json:"duration"`
|
||||||
Pantype string `json:"pantype"`
|
Pantype string `json:"pantype"`
|
||||||
Puid int `json:"puid"`
|
Puid int_str `json:"puid"`
|
||||||
Filepath string `json:"filepath"`
|
Filepath string `json:"filepath"`
|
||||||
Crc string `json:"crc"`
|
Crc string `json:"crc"`
|
||||||
Isfile bool `json:"isfile"`
|
Isfile bool `json:"isfile"`
|
||||||
Residstr string `json:"residstr"`
|
Residstr string `json:"residstr"`
|
||||||
ObjectID string `json:"objectId"`
|
ObjectID string `json:"objectId"`
|
||||||
Extinfo string `json:"extinfo"`
|
Extinfo string `json:"extinfo"`
|
||||||
Thumbnail string `json:"thumbnail"`
|
Thumbnail string `json:"thumbnail"`
|
||||||
Creator int `json:"creator"`
|
Creator int `json:"creator"`
|
||||||
ResTypeValue int `json:"resTypeValue"`
|
ResTypeValue int `json:"resTypeValue"`
|
||||||
UploadDateFormat string `json:"uploadDateFormat"`
|
UploadDateFormat string `json:"uploadDateFormat"`
|
||||||
DisableOpt bool `json:"disableOpt"`
|
DisableOpt bool `json:"disableOpt"`
|
||||||
DownPath string `json:"downPath"`
|
DownPath string `json:"downPath"`
|
||||||
Sort int `json:"sort"`
|
Sort int `json:"sort"`
|
||||||
Topsort int `json:"topsort"`
|
Topsort int `json:"topsort"`
|
||||||
Restype string `json:"restype"`
|
Restype string `json:"restype"`
|
||||||
Size int `json:"size"`
|
Size int_str `json:"size"`
|
||||||
UploadDate string `json:"uploadDate"`
|
UploadDate int64 `json:"uploadDate"`
|
||||||
FileSize string `json:"fileSize"`
|
FileSize string `json:"fileSize"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
FileID string `json:"fileId"`
|
FileID string `json:"fileId"`
|
||||||
} `json:"content"`
|
} `json:"content"`
|
||||||
CreatorID int `json:"creatorId"`
|
CreatorID int `json:"creatorId"`
|
||||||
DesID string `json:"des_id"`
|
DesID string `json:"des_id"`
|
||||||
@ -204,7 +221,6 @@ type UploadFileDataRsp struct {
|
|||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type UploadDoneParam struct {
|
type UploadDoneParam struct {
|
||||||
Cataid string `json:"cataid"`
|
Cataid string `json:"cataid"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
@ -249,10 +265,7 @@ func fileToObj(f File) *model.Object {
|
|||||||
IsFolder: true,
|
IsFolder: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
paserTime, err := time.Parse("2006-01-02 15:04", f.Content.UploadDate)
|
paserTime := time.UnixMilli(f.Content.UploadDate)
|
||||||
if err != nil {
|
|
||||||
paserTime = time.Now()
|
|
||||||
}
|
|
||||||
return &model.Object{
|
return &model.Object{
|
||||||
ID: fmt.Sprintf("%d$%s", f.ID, f.Content.FileID),
|
ID: fmt.Sprintf("%d$%s", f.ID, f.Content.FileID),
|
||||||
Name: f.Content.Name,
|
Name: f.Content.Name,
|
||||||
|
@ -79,7 +79,7 @@ func (d *ChaoXing) GetFiles(parent string) ([]File, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.Result != 1 {
|
if resp.Result != 1 {
|
||||||
msg:=fmt.Sprintf("error code is:%d", resp.Result)
|
msg := fmt.Sprintf("error code is:%d", resp.Result)
|
||||||
return nil, errors.New(msg)
|
return nil, errors.New(msg)
|
||||||
}
|
}
|
||||||
if len(resp.List) > 0 {
|
if len(resp.List) > 0 {
|
||||||
@ -97,8 +97,12 @@ func (d *ChaoXing) GetFiles(parent string) ([]File, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(resps.List) > 0 {
|
for _, file := range resps.List {
|
||||||
files = append(files, resps.List...)
|
// 手机端超星上传的文件没有fileID字段,但ObjectID与fileID相同,可代替
|
||||||
|
if file.Content.FileID == "" {
|
||||||
|
file.Content.FileID = file.Content.ObjectID
|
||||||
|
}
|
||||||
|
files = append(files, file)
|
||||||
}
|
}
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,25 @@ func (d *Dropbox) Init(ctx context.Context) error {
|
|||||||
if result != query {
|
if result != query {
|
||||||
return fmt.Errorf("failed to check user: %s", string(res))
|
return fmt.Errorf("failed to check user: %s", string(res))
|
||||||
}
|
}
|
||||||
return nil
|
d.RootNamespaceId, err = d.GetRootNamespaceId(ctx)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dropbox) GetRootNamespaceId(ctx context.Context) (string, error) {
|
||||||
|
res, err := d.request("/2/users/get_current_account", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(nil)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var currentAccountResp CurrentAccountResp
|
||||||
|
err = utils.Json.Unmarshal(res, ¤tAccountResp)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
rootNamespaceId := currentAccountResp.RootInfo.RootNamespaceId
|
||||||
|
return rootNamespaceId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dropbox) Drop(ctx context.Context) error {
|
func (d *Dropbox) Drop(ctx context.Context) error {
|
||||||
|
@ -17,7 +17,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"`
|
||||||
|
|
||||||
AccessToken string
|
AccessToken string
|
||||||
|
RootNamespaceId string
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -23,6 +23,13 @@ type RefreshTokenErrorResp struct {
|
|||||||
ErrorDescription string `json:"error_description"`
|
ErrorDescription string `json:"error_description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CurrentAccountResp struct {
|
||||||
|
RootInfo struct {
|
||||||
|
RootNamespaceId string `json:"root_namespace_id"`
|
||||||
|
HomeNamespaceId string `json:"home_namespace_id"`
|
||||||
|
} `json:"root_info"`
|
||||||
|
}
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
Tag string `json:".tag"`
|
Tag string `json:".tag"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -46,12 +46,22 @@ func (d *Dropbox) refreshToken() error {
|
|||||||
func (d *Dropbox) request(uri, method string, callback base.ReqCallback, retry ...bool) ([]byte, error) {
|
func (d *Dropbox) request(uri, method string, callback base.ReqCallback, retry ...bool) ([]byte, error) {
|
||||||
req := base.RestyClient.R()
|
req := base.RestyClient.R()
|
||||||
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
||||||
if method == http.MethodPost {
|
if d.RootNamespaceId != "" {
|
||||||
req.SetHeader("Content-Type", "application/json")
|
apiPathRootJson, err := utils.Json.MarshalToString(map[string]interface{}{
|
||||||
|
".tag": "root",
|
||||||
|
"root": d.RootNamespaceId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.SetHeader("Dropbox-API-Path-Root", apiPathRootJson)
|
||||||
}
|
}
|
||||||
if callback != nil {
|
if callback != nil {
|
||||||
callback(req)
|
callback(req)
|
||||||
}
|
}
|
||||||
|
if method == http.MethodPost && req.Body != nil {
|
||||||
|
req.SetHeader("Content-Type", "application/json")
|
||||||
|
}
|
||||||
var e ErrorResp
|
var e ErrorResp
|
||||||
req.SetError(&e)
|
req.SetError(&e)
|
||||||
res, err := req.Execute(method, d.base+uri)
|
res, err := req.Execute(method, d.base+uri)
|
||||||
|
@ -58,9 +58,33 @@ func (d *GooglePhoto) Link(ctx context.Context, file model.Obj, args model.LinkA
|
|||||||
URL: f.BaseURL + "=d",
|
URL: f.BaseURL + "=d",
|
||||||
}, nil
|
}, nil
|
||||||
} else if strings.Contains(f.MimeType, "video/") {
|
} else if strings.Contains(f.MimeType, "video/") {
|
||||||
return &model.Link{
|
var width, height int
|
||||||
URL: f.BaseURL + "=dv",
|
|
||||||
}, nil
|
fmt.Sscanf(f.MediaMetadata.Width, "%d", &width)
|
||||||
|
fmt.Sscanf(f.MediaMetadata.Height, "%d", &height)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// 1080P
|
||||||
|
case width == 1920 && height == 1080:
|
||||||
|
return &model.Link{
|
||||||
|
URL: f.BaseURL + "=m37",
|
||||||
|
}, nil
|
||||||
|
// 720P
|
||||||
|
case width == 1280 && height == 720:
|
||||||
|
return &model.Link{
|
||||||
|
URL: f.BaseURL + "=m22",
|
||||||
|
}, nil
|
||||||
|
// 360P
|
||||||
|
case width == 640 && height == 360:
|
||||||
|
return &model.Link{
|
||||||
|
URL: f.BaseURL + "=m18",
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return &model.Link{
|
||||||
|
URL: f.BaseURL + "=dv",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return &model.Link{}, nil
|
return &model.Link{}, nil
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ func (d *GooglePhoto) getMedia(id string) (MediaItem, error) {
|
|||||||
var resp MediaItem
|
var resp MediaItem
|
||||||
|
|
||||||
query := map[string]string{
|
query := map[string]string{
|
||||||
"fields": "baseUrl,mimeType",
|
"fields": "mediaMetadata,baseUrl,mimeType",
|
||||||
}
|
}
|
||||||
_, err := d.request(fmt.Sprintf("https://photoslibrary.googleapis.com/v1/mediaItems/%s", id), http.MethodGet, func(req *resty.Request) {
|
_, err := d.request(fmt.Sprintf("https://photoslibrary.googleapis.com/v1/mediaItems/%s", id), http.MethodGet, func(req *resty.Request) {
|
||||||
req.SetQueryParams(query)
|
req.SetQueryParams(query)
|
||||||
|
@ -30,10 +30,12 @@ type ILanZou struct {
|
|||||||
userID string
|
userID string
|
||||||
account string
|
account string
|
||||||
upClient *resty.Client
|
upClient *resty.Client
|
||||||
|
conf Conf
|
||||||
|
config driver.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ILanZou) Config() driver.Config {
|
func (d *ILanZou) Config() driver.Config {
|
||||||
return config
|
return d.config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ILanZou) GetAddition() driver.Additional {
|
func (d *ILanZou) GetAddition() driver.Additional {
|
||||||
@ -112,7 +114,7 @@ func (d *ILanZou) List(ctx context.Context, dir model.Obj, args model.ListArgs)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *ILanZou) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
func (d *ILanZou) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
u, err := url.Parse("https://api.ilanzou.com/unproved/file/redirect")
|
u, err := url.Parse(d.conf.base + "/" + d.conf.unproved + "/file/redirect")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -121,27 +123,38 @@ func (d *ILanZou) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
|||||||
query.Set("devType", "6")
|
query.Set("devType", "6")
|
||||||
query.Set("devCode", d.UUID)
|
query.Set("devCode", d.UUID)
|
||||||
query.Set("devModel", "chrome")
|
query.Set("devModel", "chrome")
|
||||||
query.Set("devVersion", "120")
|
query.Set("devVersion", d.conf.devVersion)
|
||||||
query.Set("appVersion", "")
|
query.Set("appVersion", "")
|
||||||
ts, err := getTimestamp()
|
ts, err := getTimestamp(d.conf.secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
query.Set("timestamp", ts)
|
query.Set("timestamp", ts)
|
||||||
//query.Set("appToken", d.Token)
|
query.Set("appToken", d.Token)
|
||||||
query.Set("enable", "1")
|
query.Set("enable", "1")
|
||||||
downloadId, err := mopan.AesEncrypt([]byte(fmt.Sprintf("%s|%s", file.GetID(), d.userID)), AesSecret)
|
downloadId, err := mopan.AesEncrypt([]byte(fmt.Sprintf("%s|%s", file.GetID(), d.userID)), d.conf.secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
query.Set("downloadId", hex.EncodeToString(downloadId))
|
query.Set("downloadId", hex.EncodeToString(downloadId))
|
||||||
auth, err := mopan.AesEncrypt([]byte(fmt.Sprintf("%s|%d", file.GetID(), time.Now().UnixMilli())), AesSecret)
|
auth, err := mopan.AesEncrypt([]byte(fmt.Sprintf("%s|%d", file.GetID(), time.Now().UnixMilli())), d.conf.secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
query.Set("auth", hex.EncodeToString(auth))
|
query.Set("auth", hex.EncodeToString(auth))
|
||||||
u.RawQuery = query.Encode()
|
u.RawQuery = query.Encode()
|
||||||
link := model.Link{URL: u.String()}
|
realURL := u.String()
|
||||||
|
// get the url after redirect
|
||||||
|
res, err := base.NoRedirectClient.R().Get(realURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.StatusCode() == 302 {
|
||||||
|
realURL = res.Header().Get("location")
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("redirect failed, status: %d", res.StatusCode())
|
||||||
|
}
|
||||||
|
link := model.Link{URL: realURL}
|
||||||
return &link, nil
|
return &link, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,7 +294,7 @@ func (d *ILanZou) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
key := fmt.Sprintf("disk/%d/%d/%d/%s/%016d", now.Year(), now.Month(), now.Day(), d.account, now.UnixMilli())
|
key := fmt.Sprintf("disk/%d/%d/%d/%s/%016d", now.Year(), now.Month(), now.Day(), d.account, now.UnixMilli())
|
||||||
var token string
|
var token string
|
||||||
if stream.GetSize() > DefaultPartSize {
|
if stream.GetSize() <= DefaultPartSize {
|
||||||
res, err := d.upClient.R().SetMultipartFormData(map[string]string{
|
res, err := d.upClient.R().SetMultipartFormData(map[string]string{
|
||||||
"token": upToken,
|
"token": upToken,
|
||||||
"key": key,
|
"key": key,
|
||||||
@ -294,7 +307,7 @@ func (d *ILanZou) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
|
|||||||
token = utils.Json.Get(res.Body(), "token").ToString()
|
token = utils.Json.Get(res.Body(), "token").ToString()
|
||||||
} else {
|
} else {
|
||||||
keyBase64 := base64.URLEncoding.EncodeToString([]byte(key))
|
keyBase64 := base64.URLEncoding.EncodeToString([]byte(key))
|
||||||
res, err := d.upClient.R().Post(fmt.Sprintf("https://upload.qiniup.com/buckets/wpanstore-lanzou/objects/%s/uploads", keyBase64))
|
res, err := d.upClient.R().SetHeader("Authorization", "UpToken "+upToken).Post(fmt.Sprintf("https://upload.qiniup.com/buckets/%s/objects/%s/uploads", d.conf.bucket, keyBase64))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -302,8 +315,8 @@ func (d *ILanZou) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
|
|||||||
parts := make([]Part, 0)
|
parts := make([]Part, 0)
|
||||||
partNum := (stream.GetSize() + DefaultPartSize - 1) / DefaultPartSize
|
partNum := (stream.GetSize() + DefaultPartSize - 1) / DefaultPartSize
|
||||||
for i := 1; i <= int(partNum); i++ {
|
for i := 1; i <= int(partNum); i++ {
|
||||||
u := fmt.Sprintf("https://upload.qiniup.com/buckets/wpanstore-lanzou/objects/%s/uploads/%s/%d", keyBase64, uploadId, i)
|
u := fmt.Sprintf("https://upload.qiniup.com/buckets/%s/objects/%s/uploads/%s/%d", d.conf.bucket, keyBase64, uploadId, i)
|
||||||
res, err = d.upClient.R().SetBody(io.LimitReader(tempFile, DefaultPartSize)).Put(u)
|
res, err = d.upClient.R().SetHeader("Authorization", "UpToken "+upToken).SetBody(io.LimitReader(tempFile, DefaultPartSize)).Put(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -313,10 +326,10 @@ func (d *ILanZou) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
|
|||||||
ETag: etag,
|
ETag: etag,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
res, err = d.upClient.R().SetBody(base.Json{
|
res, err = d.upClient.R().SetHeader("Authorization", "UpToken "+upToken).SetBody(base.Json{
|
||||||
"fnmae": stream.GetName(),
|
"fnmae": stream.GetName(),
|
||||||
"parts": parts,
|
"parts": parts,
|
||||||
}).Post(fmt.Sprintf("https://upload.qiniup.com/buckets/wpanstore-lanzou/objects/%s/uploads/%s", keyBase64, uploadId))
|
}).Post(fmt.Sprintf("https://upload.qiniup.com/buckets/%s/objects/%s/uploads/%s", d.conf.bucket, keyBase64, uploadId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -14,22 +14,64 @@ type Addition struct {
|
|||||||
UUID string
|
UUID string
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
type Conf struct {
|
||||||
Name: "ILanZou",
|
base string
|
||||||
LocalSort: false,
|
secret []byte
|
||||||
OnlyLocal: false,
|
bucket string
|
||||||
OnlyProxy: false,
|
unproved string
|
||||||
NoCache: false,
|
proved string
|
||||||
NoUpload: false,
|
devVersion string
|
||||||
NeedMs: false,
|
|
||||||
DefaultRoot: "0",
|
|
||||||
CheckStatus: false,
|
|
||||||
Alert: "",
|
|
||||||
NoOverwriteUpload: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
op.RegisterDriver(func() driver.Driver {
|
op.RegisterDriver(func() driver.Driver {
|
||||||
return &ILanZou{}
|
return &ILanZou{
|
||||||
|
config: driver.Config{
|
||||||
|
Name: "ILanZou",
|
||||||
|
LocalSort: false,
|
||||||
|
OnlyLocal: false,
|
||||||
|
OnlyProxy: false,
|
||||||
|
NoCache: false,
|
||||||
|
NoUpload: false,
|
||||||
|
NeedMs: false,
|
||||||
|
DefaultRoot: "0",
|
||||||
|
CheckStatus: false,
|
||||||
|
Alert: "",
|
||||||
|
NoOverwriteUpload: false,
|
||||||
|
},
|
||||||
|
conf: Conf{
|
||||||
|
base: "https://api.ilanzou.com",
|
||||||
|
secret: []byte("lanZouY-disk-app"),
|
||||||
|
bucket: "wpanstore-lanzou",
|
||||||
|
unproved: "unproved",
|
||||||
|
proved: "proved",
|
||||||
|
devVersion: "120",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &ILanZou{
|
||||||
|
config: driver.Config{
|
||||||
|
Name: "FeijiPan",
|
||||||
|
LocalSort: false,
|
||||||
|
OnlyLocal: false,
|
||||||
|
OnlyProxy: false,
|
||||||
|
NoCache: false,
|
||||||
|
NoUpload: false,
|
||||||
|
NeedMs: false,
|
||||||
|
DefaultRoot: "0",
|
||||||
|
CheckStatus: false,
|
||||||
|
Alert: "",
|
||||||
|
NoOverwriteUpload: false,
|
||||||
|
},
|
||||||
|
conf: Conf{
|
||||||
|
base: "https://api.feijipan.com",
|
||||||
|
secret: []byte("dingHao-disk-app"),
|
||||||
|
bucket: "wpanstore",
|
||||||
|
unproved: "ws",
|
||||||
|
proved: "app",
|
||||||
|
devVersion: "121",
|
||||||
|
},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,6 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
Base = "https://api.ilanzou.com"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
AesSecret = []byte("lanZouY-disk-app")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *ILanZou) login() error {
|
func (d *ILanZou) login() error {
|
||||||
res, err := d.unproved("/login", http.MethodPost, func(req *resty.Request) {
|
res, err := d.unproved("/login", http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(base.Json{
|
req.SetBody(base.Json{
|
||||||
@ -39,10 +31,10 @@ func (d *ILanZou) login() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTimestamp() (string, error) {
|
func getTimestamp(secret []byte) (string, error) {
|
||||||
ts := time.Now().UnixMilli()
|
ts := time.Now().UnixMilli()
|
||||||
tsStr := strconv.FormatInt(ts, 10)
|
tsStr := strconv.FormatInt(ts, 10)
|
||||||
res, err := mopan.AesEncrypt([]byte(tsStr), AesSecret)
|
res, err := mopan.AesEncrypt([]byte(tsStr), secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -51,7 +43,7 @@ func getTimestamp() (string, error) {
|
|||||||
|
|
||||||
func (d *ILanZou) request(pathname, method string, callback base.ReqCallback, proved bool, retry ...bool) ([]byte, error) {
|
func (d *ILanZou) request(pathname, method string, callback base.ReqCallback, proved bool, retry ...bool) ([]byte, error) {
|
||||||
req := base.RestyClient.R()
|
req := base.RestyClient.R()
|
||||||
ts, err := getTimestamp()
|
ts, err := getTimestamp(d.conf.secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -72,7 +64,7 @@ func (d *ILanZou) request(pathname, method string, callback base.ReqCallback, pr
|
|||||||
if callback != nil {
|
if callback != nil {
|
||||||
callback(req)
|
callback(req)
|
||||||
}
|
}
|
||||||
res, err := req.Execute(method, Base+pathname)
|
res, err := req.Execute(method, d.conf.base+pathname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if res != nil {
|
if res != nil {
|
||||||
log.Errorf("[iLanZou] request error: %s", res.String())
|
log.Errorf("[iLanZou] request error: %s", res.String())
|
||||||
@ -97,9 +89,9 @@ func (d *ILanZou) request(pathname, method string, callback base.ReqCallback, pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *ILanZou) unproved(pathname, method string, callback base.ReqCallback) ([]byte, error) {
|
func (d *ILanZou) unproved(pathname, method string, callback base.ReqCallback) ([]byte, error) {
|
||||||
return d.request("/unproved"+pathname, method, callback, false)
|
return d.request("/"+d.conf.unproved+pathname, method, callback, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ILanZou) proved(pathname, method string, callback base.ReqCallback) ([]byte, error) {
|
func (d *ILanZou) proved(pathname, method string, callback base.ReqCallback) ([]byte, error) {
|
||||||
return d.request("/proved"+pathname, method, callback, true)
|
return d.request("/"+d.conf.proved+pathname, method, callback, true)
|
||||||
}
|
}
|
||||||
|
@ -257,10 +257,18 @@ func (d *Local) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|||||||
|
|
||||||
func (d *Local) Remove(ctx context.Context, obj model.Obj) error {
|
func (d *Local) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
var err error
|
var err error
|
||||||
if obj.IsDir() {
|
if utils.SliceContains([]string{"", "delete permanently"}, d.RecycleBinPath) {
|
||||||
err = os.RemoveAll(obj.GetPath())
|
if obj.IsDir() {
|
||||||
|
err = os.RemoveAll(obj.GetPath())
|
||||||
|
} else {
|
||||||
|
err = os.Remove(obj.GetPath())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = os.Remove(obj.GetPath())
|
dstPath := filepath.Join(d.RecycleBinPath, obj.GetName())
|
||||||
|
if utils.Exists(dstPath) {
|
||||||
|
dstPath = filepath.Join(d.RecycleBinPath, obj.GetName()+"_"+time.Now().Format("20060102150405"))
|
||||||
|
}
|
||||||
|
err = os.Rename(obj.GetPath(), dstPath)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -11,6 +11,7 @@ type Addition struct {
|
|||||||
ThumbCacheFolder string `json:"thumb_cache_folder"`
|
ThumbCacheFolder string `json:"thumb_cache_folder"`
|
||||||
ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"`
|
ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"`
|
||||||
MkdirPerm string `json:"mkdir_perm" default:"777"`
|
MkdirPerm string `json:"mkdir_perm" default:"777"`
|
||||||
|
RecycleBinPath string `json:"recycle_bin_path" default:"delete permanently" help:"path to recycle bin, delete permanently if empty or keep 'delete permanently'"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -188,6 +188,9 @@ func (d *MediaTrack) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
|
|||||||
_ = tempFile.Close()
|
_ = tempFile.Close()
|
||||||
}()
|
}()
|
||||||
uploader := s3manager.NewUploader(s)
|
uploader := s3manager.NewUploader(s)
|
||||||
|
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||||
|
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||||
|
}
|
||||||
input := &s3manager.UploadInput{
|
input := &s3manager.UploadInput{
|
||||||
Bucket: &resp.Data.Bucket,
|
Bucket: &resp.Data.Bucket,
|
||||||
Key: &resp.Data.Object,
|
Key: &resp.Data.Object,
|
||||||
|
@ -43,23 +43,31 @@ func (d *MoPan) Init(ctx context.Context) error {
|
|||||||
if d.uploadThread < 1 || d.uploadThread > 32 {
|
if d.uploadThread < 1 || d.uploadThread > 32 {
|
||||||
d.uploadThread, d.UploadThread = 3, "3"
|
d.uploadThread, d.UploadThread = 3, "3"
|
||||||
}
|
}
|
||||||
login := func() error {
|
|
||||||
data, err := d.client.Login(d.Phone, d.Password)
|
defer func() { d.SMSCode = "" }()
|
||||||
|
|
||||||
|
login := func() (err error) {
|
||||||
|
var loginData *mopan.LoginResp
|
||||||
|
if d.SMSCode != "" {
|
||||||
|
loginData, err = d.client.LoginBySmsStep2(d.Phone, d.SMSCode)
|
||||||
|
} else {
|
||||||
|
loginData, err = d.client.Login(d.Phone, d.Password)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.client.SetAuthorization(data.Token)
|
d.client.SetAuthorization(loginData.Token)
|
||||||
|
|
||||||
info, err := d.client.GetUserInfo()
|
info, err := d.client.GetUserInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.userID = info.UserID
|
d.userID = info.UserID
|
||||||
log.Debugf("[mopan] Phone: %s UserCloudStorageRelations: %+v", d.Phone, data.UserCloudStorageRelations)
|
log.Debugf("[mopan] Phone: %s UserCloudStorageRelations: %+v", d.Phone, loginData.UserCloudStorageRelations)
|
||||||
cloudCircleApp, _ := d.client.QueryAllCloudCircleApp()
|
cloudCircleApp, _ := d.client.QueryAllCloudCircleApp()
|
||||||
log.Debugf("[mopan] Phone: %s CloudCircleApp: %+v", d.Phone, cloudCircleApp)
|
log.Debugf("[mopan] Phone: %s CloudCircleApp: %+v", d.Phone, cloudCircleApp)
|
||||||
if d.RootFolderID == "" {
|
if d.RootFolderID == "" {
|
||||||
for _, userCloudStorage := range data.UserCloudStorageRelations {
|
for _, userCloudStorage := range loginData.UserCloudStorageRelations {
|
||||||
if userCloudStorage.Path == "/文件" {
|
if userCloudStorage.Path == "/文件" {
|
||||||
d.RootFolderID = userCloudStorage.FolderID
|
d.RootFolderID = userCloudStorage.FolderID
|
||||||
}
|
}
|
||||||
@ -76,8 +84,20 @@ func (d *MoPan) Init(ctx context.Context) error {
|
|||||||
op.MustSaveDriverStorage(d)
|
op.MustSaveDriverStorage(d)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}).SetDeviceInfo(d.DeviceInfo)
|
})
|
||||||
d.DeviceInfo = d.client.GetDeviceInfo()
|
|
||||||
|
var deviceInfo mopan.DeviceInfo
|
||||||
|
if strings.TrimSpace(d.DeviceInfo) != "" && utils.Json.UnmarshalFromString(d.DeviceInfo, &deviceInfo) == nil {
|
||||||
|
d.client.SetDeviceInfo(&deviceInfo)
|
||||||
|
}
|
||||||
|
d.DeviceInfo, _ = utils.Json.MarshalToString(d.client.GetDeviceInfo())
|
||||||
|
|
||||||
|
if strings.Contains(d.SMSCode, "send") {
|
||||||
|
if _, err := d.client.LoginBySms(d.Phone); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return errors.New("please enter the SMS code")
|
||||||
|
}
|
||||||
return login()
|
return login()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
type Addition struct {
|
type Addition struct {
|
||||||
Phone string `json:"phone" required:"true"`
|
Phone string `json:"phone" required:"true"`
|
||||||
Password string `json:"password" required:"true"`
|
Password string `json:"password" required:"true"`
|
||||||
|
SMSCode string `json:"sms_code" help:"input 'send' send sms "`
|
||||||
|
|
||||||
RootFolderID string `json:"root_folder_id" default:""`
|
RootFolderID string `json:"root_folder_id" default:""`
|
||||||
|
|
||||||
|
@ -172,6 +172,9 @@ func (d *PikPak) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uploader := s3manager.NewUploader(ss)
|
uploader := s3manager.NewUploader(ss)
|
||||||
|
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||||
|
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||||
|
}
|
||||||
input := &s3manager.UploadInput{
|
input := &s3manager.UploadInput{
|
||||||
Bucket: ¶ms.Bucket,
|
Bucket: ¶ms.Bucket,
|
||||||
Key: ¶ms.Key,
|
Key: ¶ms.Key,
|
||||||
|
437
drivers/quqi/driver.go
Normal file
437
drivers/quqi/driver.go
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
package quqi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/alist-org/alist/v3/pkg/utils/random"
|
||||||
|
"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"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Quqi struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
Cookie string // Cookie
|
||||||
|
GroupID string // 私人云群组ID
|
||||||
|
ClientID string // 随机生成客户端ID 经过测试,部分接口调用若不携带client id会出现错误
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) Init(ctx context.Context) error {
|
||||||
|
// 登录
|
||||||
|
if err := d.login(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成随机client id (与网页端生成逻辑一致)
|
||||||
|
d.ClientID = "quqipc_" + random.String(10)
|
||||||
|
|
||||||
|
// 获取私人云ID (暂时仅获取私人云)
|
||||||
|
groupResp := &GroupRes{}
|
||||||
|
if _, err := d.request("group.quqi.com", "/v1/group/list", resty.MethodGet, nil, groupResp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, groupInfo := range groupResp.Data {
|
||||||
|
if groupInfo == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if groupInfo.Type == 2 {
|
||||||
|
d.GroupID = strconv.Itoa(groupInfo.ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d.GroupID == "" {
|
||||||
|
return errs.StorageNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
var (
|
||||||
|
listResp = &ListRes{}
|
||||||
|
files []model.Obj
|
||||||
|
)
|
||||||
|
|
||||||
|
if _, err := d.request("", "/api/dir/ls", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"quqi_id": d.GroupID,
|
||||||
|
"tree_id": "1",
|
||||||
|
"node_id": dir.GetID(),
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
})
|
||||||
|
}, listResp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if listResp.Data == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirs
|
||||||
|
for _, dirInfo := range listResp.Data.Dir {
|
||||||
|
if dirInfo == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files = append(files, &model.Object{
|
||||||
|
ID: strconv.FormatInt(dirInfo.NodeID, 10),
|
||||||
|
Name: dirInfo.Name,
|
||||||
|
Modified: time.Unix(dirInfo.UpdateTime, 0),
|
||||||
|
Ctime: time.Unix(dirInfo.AddTime, 0),
|
||||||
|
IsFolder: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// files
|
||||||
|
for _, fileInfo := range listResp.Data.File {
|
||||||
|
if fileInfo == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fileInfo.EXT != "" {
|
||||||
|
fileInfo.Name = strings.Join([]string{fileInfo.Name, fileInfo.EXT}, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
files = append(files, &model.Object{
|
||||||
|
ID: strconv.FormatInt(fileInfo.NodeID, 10),
|
||||||
|
Name: fileInfo.Name,
|
||||||
|
Size: fileInfo.Size,
|
||||||
|
Modified: time.Unix(fileInfo.UpdateTime, 0),
|
||||||
|
Ctime: time.Unix(fileInfo.AddTime, 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
if d.CDN {
|
||||||
|
link, err := d.linkFromCDN(file.GetID())
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
} else {
|
||||||
|
return link, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := d.linkFromPreview(file.GetID())
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
} else {
|
||||||
|
return link, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err = d.linkFromDownload(file.GetID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return link, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
|
||||||
|
var (
|
||||||
|
makeDirRes = &MakeDirRes{}
|
||||||
|
timeNow = time.Now()
|
||||||
|
)
|
||||||
|
|
||||||
|
if _, err := d.request("", "/api/dir/mkDir", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"quqi_id": d.GroupID,
|
||||||
|
"tree_id": "1",
|
||||||
|
"parent_id": parentDir.GetID(),
|
||||||
|
"name": dirName,
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
})
|
||||||
|
}, makeDirRes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Object{
|
||||||
|
ID: strconv.FormatInt(makeDirRes.Data.NodeID, 10),
|
||||||
|
Name: dirName,
|
||||||
|
Modified: timeNow,
|
||||||
|
Ctime: timeNow,
|
||||||
|
IsFolder: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||||
|
var moveRes = &MoveRes{}
|
||||||
|
|
||||||
|
if _, err := d.request("", "/api/dir/mvDir", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"quqi_id": d.GroupID,
|
||||||
|
"tree_id": "1",
|
||||||
|
"node_id": dstDir.GetID(),
|
||||||
|
"source_quqi_id": d.GroupID,
|
||||||
|
"source_tree_id": "1",
|
||||||
|
"source_node_id": srcObj.GetID(),
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
})
|
||||||
|
}, moveRes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Object{
|
||||||
|
ID: strconv.FormatInt(moveRes.Data.NodeID, 10),
|
||||||
|
Name: moveRes.Data.NodeName,
|
||||||
|
Size: srcObj.GetSize(),
|
||||||
|
Modified: time.Now(),
|
||||||
|
Ctime: srcObj.CreateTime(),
|
||||||
|
IsFolder: srcObj.IsDir(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
|
||||||
|
var realName = newName
|
||||||
|
|
||||||
|
if !srcObj.IsDir() {
|
||||||
|
srcExt, newExt := utils.Ext(srcObj.GetName()), utils.Ext(newName)
|
||||||
|
|
||||||
|
// 曲奇网盘的文件名称由文件名和扩展名组成,若存在扩展名,则重命名时仅支持更改文件名,扩展名在曲奇服务端保留
|
||||||
|
if srcExt != "" && srcExt == newExt {
|
||||||
|
parts := strings.Split(newName, ".")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
realName = strings.Join(parts[:len(parts)-1], ".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := d.request("", "/api/dir/renameDir", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"quqi_id": d.GroupID,
|
||||||
|
"tree_id": "1",
|
||||||
|
"node_id": srcObj.GetID(),
|
||||||
|
"rename": realName,
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
})
|
||||||
|
}, nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Object{
|
||||||
|
ID: srcObj.GetID(),
|
||||||
|
Name: newName,
|
||||||
|
Size: srcObj.GetSize(),
|
||||||
|
Modified: time.Now(),
|
||||||
|
Ctime: srcObj.CreateTime(),
|
||||||
|
IsFolder: srcObj.IsDir(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||||
|
// 无法从曲奇接口响应中直接获取复制后的文件信息
|
||||||
|
if _, err := d.request("", "/api/node/copy", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"quqi_id": d.GroupID,
|
||||||
|
"tree_id": "1",
|
||||||
|
"node_id": dstDir.GetID(),
|
||||||
|
"source_quqi_id": d.GroupID,
|
||||||
|
"source_tree_id": "1",
|
||||||
|
"source_node_id": srcObj.GetID(),
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
})
|
||||||
|
}, nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
|
// 暂时不做直接删除,默认都放到回收站。直接删除方法:先调用删除接口放入回收站,在通过回收站接口删除文件
|
||||||
|
if _, err := d.request("", "/api/node/del", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"quqi_id": d.GroupID,
|
||||||
|
"tree_id": "1",
|
||||||
|
"node_id": obj.GetID(),
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
})
|
||||||
|
}, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
|
||||||
|
// base info
|
||||||
|
sizeStr := strconv.FormatInt(stream.GetSize(), 10)
|
||||||
|
f, err := stream.CacheFullInTempFile()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
md5, err := utils.HashFile(utils.MD5, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sha, err := utils.HashFile(utils.SHA256, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// init upload
|
||||||
|
var uploadInitResp UploadInitResp
|
||||||
|
_, err = d.request("", "/api/upload/v1/file/init", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"quqi_id": d.GroupID,
|
||||||
|
"tree_id": "1",
|
||||||
|
"parent_id": dstDir.GetID(),
|
||||||
|
"size": sizeStr,
|
||||||
|
"file_name": stream.GetName(),
|
||||||
|
"md5": md5,
|
||||||
|
"sha": sha,
|
||||||
|
"is_slice": "true",
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
})
|
||||||
|
}, &uploadInitResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// check exist
|
||||||
|
// if the file already exists in Quqi server, there is no need to actually upload it
|
||||||
|
if uploadInitResp.Data.Exist {
|
||||||
|
// the file name returned by Quqi does not include the extension name
|
||||||
|
nodeName, nodeExt := uploadInitResp.Data.NodeName, rawExt(stream.GetName())
|
||||||
|
if nodeExt != "" {
|
||||||
|
nodeName = nodeName + "." + nodeExt
|
||||||
|
}
|
||||||
|
return &model.Object{
|
||||||
|
ID: strconv.FormatInt(uploadInitResp.Data.NodeID, 10),
|
||||||
|
Name: nodeName,
|
||||||
|
Size: stream.GetSize(),
|
||||||
|
Modified: stream.ModTime(),
|
||||||
|
Ctime: stream.CreateTime(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
// listParts
|
||||||
|
_, err = d.request("upload.quqi.com:20807", "/upload/v1/listParts", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"token": uploadInitResp.Data.Token,
|
||||||
|
"task_id": uploadInitResp.Data.TaskID,
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
})
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// get temp key
|
||||||
|
var tempKeyResp TempKeyResp
|
||||||
|
_, err = d.request("upload.quqi.com:20807", "/upload/v1/tempKey", resty.MethodGet, func(req *resty.Request) {
|
||||||
|
req.SetQueryParams(map[string]string{
|
||||||
|
"token": uploadInitResp.Data.Token,
|
||||||
|
"task_id": uploadInitResp.Data.TaskID,
|
||||||
|
})
|
||||||
|
}, &tempKeyResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// upload
|
||||||
|
// u, err := url.Parse(fmt.Sprintf("https://%s.cos.ap-shanghai.myqcloud.com", uploadInitResp.Data.Bucket))
|
||||||
|
// b := &cos.BaseURL{BucketURL: u}
|
||||||
|
// client := cos.NewClient(b, &http.Client{
|
||||||
|
// Transport: &cos.CredentialTransport{
|
||||||
|
// Credential: cos.NewTokenCredential(tempKeyResp.Data.Credentials.TmpSecretID, tempKeyResp.Data.Credentials.TmpSecretKey, tempKeyResp.Data.Credentials.SessionToken),
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// partSize := int64(1024 * 1024 * 2)
|
||||||
|
// partCount := (stream.GetSize() + partSize - 1) / partSize
|
||||||
|
// for i := 1; i <= int(partCount); i++ {
|
||||||
|
// length := partSize
|
||||||
|
// if i == int(partCount) {
|
||||||
|
// length = stream.GetSize() - (int64(i)-1)*partSize
|
||||||
|
// }
|
||||||
|
// _, err := client.Object.UploadPart(
|
||||||
|
// ctx, uploadInitResp.Data.Key, uploadInitResp.Data.UploadID, i, io.LimitReader(f, partSize), &cos.ObjectUploadPartOptions{
|
||||||
|
// ContentLength: length,
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
cfg := &aws.Config{
|
||||||
|
Credentials: credentials.NewStaticCredentials(tempKeyResp.Data.Credentials.TmpSecretID, tempKeyResp.Data.Credentials.TmpSecretKey, tempKeyResp.Data.Credentials.SessionToken),
|
||||||
|
Region: aws.String("ap-shanghai"),
|
||||||
|
Endpoint: aws.String("cos.ap-shanghai.myqcloud.com"),
|
||||||
|
}
|
||||||
|
s, err := session.NewSession(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
uploader := s3manager.NewUploader(s)
|
||||||
|
buf := make([]byte, 1024*1024*2)
|
||||||
|
for partNumber := int64(1); ; partNumber++ {
|
||||||
|
n, err := io.ReadFull(f, buf)
|
||||||
|
if err != nil && err != io.ErrUnexpectedEOF {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = uploader.S3.UploadPartWithContext(ctx, &s3.UploadPartInput{
|
||||||
|
UploadId: &uploadInitResp.Data.UploadID,
|
||||||
|
Key: &uploadInitResp.Data.Key,
|
||||||
|
Bucket: &uploadInitResp.Data.Bucket,
|
||||||
|
PartNumber: aws.Int64(partNumber),
|
||||||
|
Body: bytes.NewReader(buf[:n]),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// finish upload
|
||||||
|
var uploadFinishResp UploadFinishResp
|
||||||
|
_, err = d.request("", "/api/upload/v1/file/finish", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"token": uploadInitResp.Data.Token,
|
||||||
|
"task_id": uploadInitResp.Data.TaskID,
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
})
|
||||||
|
}, &uploadFinishResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// the file name returned by Quqi does not include the extension name
|
||||||
|
nodeName, nodeExt := uploadFinishResp.Data.NodeName, rawExt(stream.GetName())
|
||||||
|
if nodeExt != "" {
|
||||||
|
nodeName = nodeName + "." + nodeExt
|
||||||
|
}
|
||||||
|
return &model.Object{
|
||||||
|
ID: strconv.FormatInt(uploadFinishResp.Data.NodeID, 10),
|
||||||
|
Name: nodeName,
|
||||||
|
Size: stream.GetSize(),
|
||||||
|
Modified: stream.ModTime(),
|
||||||
|
Ctime: stream.CreateTime(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
|
// return nil, errs.NotSupport
|
||||||
|
//}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*Quqi)(nil)
|
28
drivers/quqi/meta.go
Normal file
28
drivers/quqi/meta.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package quqi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
driver.RootID
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Cookie string `json:"cookie" help:"Cookie can be used on multiple clients at the same time"`
|
||||||
|
CDN bool `json:"cdn" help:"If you enable this option, the download speed can be increased, but there will be some performance loss"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "Quqi",
|
||||||
|
OnlyLocal: true,
|
||||||
|
LocalSort: true,
|
||||||
|
//NoUpload: true,
|
||||||
|
DefaultRoot: "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &Quqi{}
|
||||||
|
})
|
||||||
|
}
|
197
drivers/quqi/types.go
Normal file
197
drivers/quqi/types.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package quqi
|
||||||
|
|
||||||
|
type BaseReqQuery struct {
|
||||||
|
ID string `json:"quqiid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseReq struct {
|
||||||
|
GroupID string `json:"quqi_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseRes struct {
|
||||||
|
//Data interface{} `json:"data"`
|
||||||
|
Code int `json:"err"`
|
||||||
|
Message string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupRes struct {
|
||||||
|
BaseRes
|
||||||
|
Data []*Group `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListRes struct {
|
||||||
|
BaseRes
|
||||||
|
Data *List `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDocRes struct {
|
||||||
|
BaseRes
|
||||||
|
Data struct {
|
||||||
|
OriginPath string `json:"origin_path"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDownloadResp struct {
|
||||||
|
BaseRes
|
||||||
|
Data struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MakeDirRes struct {
|
||||||
|
BaseRes
|
||||||
|
Data struct {
|
||||||
|
IsRoot bool `json:"is_root"`
|
||||||
|
NodeID int64 `json:"node_id"`
|
||||||
|
ParentID int64 `json:"parent_id"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MoveRes struct {
|
||||||
|
BaseRes
|
||||||
|
Data struct {
|
||||||
|
NodeChildNum int64 `json:"node_child_num"`
|
||||||
|
NodeID int64 `json:"node_id"`
|
||||||
|
NodeName string `json:"node_name"`
|
||||||
|
ParentID int64 `json:"parent_id"`
|
||||||
|
GroupID int64 `json:"quqi_id"`
|
||||||
|
TreeID int64 `json:"tree_id"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenameRes struct {
|
||||||
|
BaseRes
|
||||||
|
Data struct {
|
||||||
|
NodeID int64 `json:"node_id"`
|
||||||
|
GroupID int64 `json:"quqi_id"`
|
||||||
|
Rename string `json:"rename"`
|
||||||
|
TreeID int64 `json:"tree_id"`
|
||||||
|
UpdateTime int64 `json:"updatetime"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CopyRes struct {
|
||||||
|
BaseRes
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoveRes struct {
|
||||||
|
BaseRes
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
ID int `json:"quqi_id"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
IsAdministrator int `json:"is_administrator"`
|
||||||
|
Role int `json:"role"`
|
||||||
|
Avatar string `json:"avatar_url"`
|
||||||
|
IsStick int `json:"is_stick"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type List struct {
|
||||||
|
ListDir
|
||||||
|
Dir []*ListDir `json:"dir"`
|
||||||
|
File []*ListFile `json:"file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListItem struct {
|
||||||
|
AddTime int64 `json:"add_time"`
|
||||||
|
IsDir int `json:"is_dir"`
|
||||||
|
IsExpand int `json:"is_expand"`
|
||||||
|
IsFinalize int `json:"is_finalize"`
|
||||||
|
LastEditorName string `json:"last_editor_name"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
NodeID int64 `json:"nid"`
|
||||||
|
ParentID int64 `json:"parent_id"`
|
||||||
|
Permission int `json:"permission"`
|
||||||
|
TreeID int64 `json:"tid"`
|
||||||
|
UpdateCNT int64 `json:"update_cnt"`
|
||||||
|
UpdateTime int64 `json:"update_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListDir struct {
|
||||||
|
ListItem
|
||||||
|
ChildDocNum int64 `json:"child_doc_num"`
|
||||||
|
DirDetail string `json:"dir_detail"`
|
||||||
|
DirType int `json:"dir_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListFile struct {
|
||||||
|
ListItem
|
||||||
|
BroadDocType string `json:"broad_doc_type"`
|
||||||
|
CanDisplay bool `json:"can_display"`
|
||||||
|
Detail string `json:"detail"`
|
||||||
|
EXT string `json:"ext"`
|
||||||
|
Filetype string `json:"filetype"`
|
||||||
|
HasMobileThumbnail bool `json:"has_mobile_thumbnail"`
|
||||||
|
HasThumbnail bool `json:"has_thumbnail"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadInitResp struct {
|
||||||
|
Data struct {
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
Exist bool `json:"exist"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
TaskID string `json:"task_id"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
UploadID string `json:"upload_id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
NodeID int64 `json:"node_id"`
|
||||||
|
NodeName string `json:"node_name"`
|
||||||
|
ParentID int64 `json:"parent_id"`
|
||||||
|
} `json:"data"`
|
||||||
|
Err int `json:"err"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TempKeyResp struct {
|
||||||
|
Err int `json:"err"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data struct {
|
||||||
|
ExpiredTime int `json:"expiredTime"`
|
||||||
|
Expiration string `json:"expiration"`
|
||||||
|
Credentials struct {
|
||||||
|
SessionToken string `json:"sessionToken"`
|
||||||
|
TmpSecretID string `json:"tmpSecretId"`
|
||||||
|
TmpSecretKey string `json:"tmpSecretKey"`
|
||||||
|
} `json:"credentials"`
|
||||||
|
RequestID string `json:"requestId"`
|
||||||
|
StartTime int `json:"startTime"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadFinishResp struct {
|
||||||
|
Data struct {
|
||||||
|
NodeID int64 `json:"node_id"`
|
||||||
|
NodeName string `json:"node_name"`
|
||||||
|
ParentID int64 `json:"parent_id"`
|
||||||
|
QuqiID int64 `json:"quqi_id"`
|
||||||
|
TreeID int64 `json:"tree_id"`
|
||||||
|
} `json:"data"`
|
||||||
|
Err int `json:"err"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UrlExchangeResp struct {
|
||||||
|
BaseRes
|
||||||
|
Data struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Mime string `json:"mime"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
DownloadType int `json:"download_type"`
|
||||||
|
ChannelType int `json:"channel_type"`
|
||||||
|
ChannelID int `json:"channel_id"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
ExpiredTime int64 `json:"expired_time"`
|
||||||
|
IsEncrypted bool `json:"is_encrypted"`
|
||||||
|
EncryptedSize int64 `json:"encrypted_size"`
|
||||||
|
EncryptedAlg string `json:"encrypted_alg"`
|
||||||
|
EncryptedKey string `json:"encrypted_key"`
|
||||||
|
PassportID int64 `json:"passport_id"`
|
||||||
|
RequestExpiredTime int64 `json:"request_expired_time"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
316
drivers/quqi/util.go
Normal file
316
drivers/quqi/util.go
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
package quqi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
stdpath "path"
|
||||||
|
"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"
|
||||||
|
"github.com/alist-org/alist/v3/internal/stream"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/minio/sio"
|
||||||
|
)
|
||||||
|
|
||||||
|
// do others that not defined in Driver interface
|
||||||
|
func (d *Quqi) request(host string, path string, method string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
|
||||||
|
var (
|
||||||
|
reqUrl = url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "quqi.com",
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
req = base.RestyClient.R()
|
||||||
|
result BaseRes
|
||||||
|
)
|
||||||
|
|
||||||
|
if host != "" {
|
||||||
|
reqUrl.Host = host
|
||||||
|
}
|
||||||
|
req.SetHeaders(map[string]string{
|
||||||
|
"Origin": "https://quqi.com",
|
||||||
|
"Cookie": d.Cookie,
|
||||||
|
})
|
||||||
|
|
||||||
|
if d.GroupID != "" {
|
||||||
|
req.SetQueryParam("quqiid", d.GroupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if callback != nil {
|
||||||
|
callback(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := req.Execute(method, reqUrl.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// resty.Request.SetResult cannot parse result correctly sometimes
|
||||||
|
err = utils.Json.Unmarshal(res.Body(), &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if result.Code != 0 {
|
||||||
|
return nil, errors.New(result.Message)
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
err = utils.Json.Unmarshal(res.Body(), resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) login() error {
|
||||||
|
if d.Addition.Cookie != "" {
|
||||||
|
d.Cookie = d.Addition.Cookie
|
||||||
|
}
|
||||||
|
if d.checkLogin() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if d.Cookie != "" {
|
||||||
|
return errors.New("cookie is invalid")
|
||||||
|
}
|
||||||
|
if d.Phone == "" {
|
||||||
|
return errors.New("phone number is empty")
|
||||||
|
}
|
||||||
|
if d.Password == "" {
|
||||||
|
return errs.EmptyPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := d.request("", "/auth/person/v2/login/password", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"phone": d.Phone,
|
||||||
|
"password": base64.StdEncoding.EncodeToString([]byte(d.Password)),
|
||||||
|
})
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookies []string
|
||||||
|
for _, cookie := range resp.RawResponse.Cookies() {
|
||||||
|
cookies = append(cookies, fmt.Sprintf("%s=%s", cookie.Name, cookie.Value))
|
||||||
|
}
|
||||||
|
d.Cookie = strings.Join(cookies, ";")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) checkLogin() bool {
|
||||||
|
if _, err := d.request("", "/auth/account/baseInfo", resty.MethodGet, nil, nil); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawExt 保留扩展名大小写
|
||||||
|
func rawExt(name string) string {
|
||||||
|
ext := stdpath.Ext(name)
|
||||||
|
if strings.HasPrefix(ext, ".") {
|
||||||
|
ext = ext[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ext
|
||||||
|
}
|
||||||
|
|
||||||
|
// decryptKey 获取密码
|
||||||
|
func decryptKey(encodeKey string) []byte {
|
||||||
|
// 移除非法字符
|
||||||
|
u := strings.ReplaceAll(encodeKey, "[^A-Za-z0-9+\\/]", "")
|
||||||
|
|
||||||
|
// 计算输出字节数组的长度
|
||||||
|
o := len(u)
|
||||||
|
a := 32
|
||||||
|
|
||||||
|
// 创建输出字节数组
|
||||||
|
c := make([]byte, a)
|
||||||
|
|
||||||
|
// 编码循环
|
||||||
|
s := uint32(0) // 累加器
|
||||||
|
f := 0 // 输出数组索引
|
||||||
|
for l := 0; l < o; l++ {
|
||||||
|
r := l & 3 // 取模4,得到当前字符在四字节块中的位置
|
||||||
|
i := u[l] // 当前字符的ASCII码
|
||||||
|
|
||||||
|
// 编码当前字符
|
||||||
|
switch {
|
||||||
|
case i >= 65 && i < 91: // 大写字母
|
||||||
|
s |= uint32(i-65) << uint32(6*(3-r))
|
||||||
|
case i >= 97 && i < 123: // 小写字母
|
||||||
|
s |= uint32(i-71) << uint32(6*(3-r))
|
||||||
|
case i >= 48 && i < 58: // 数字
|
||||||
|
s |= uint32(i+4) << uint32(6*(3-r))
|
||||||
|
case i == 43: // 加号
|
||||||
|
s |= uint32(62) << uint32(6*(3-r))
|
||||||
|
case i == 47: // 斜杠
|
||||||
|
s |= uint32(63) << uint32(6*(3-r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果累加器已经包含了四个字符,或者是最后一个字符,则写入输出数组
|
||||||
|
if r == 3 || l == o-1 {
|
||||||
|
for e := 0; e < 3 && f < a; e, f = e+1, f+1 {
|
||||||
|
c[f] = byte(s >> (16 >> e & 24) & 255)
|
||||||
|
}
|
||||||
|
s = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) linkFromPreview(id string) (*model.Link, error) {
|
||||||
|
var getDocResp GetDocRes
|
||||||
|
if _, err := d.request("", "/api/doc/getDoc", resty.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetFormData(map[string]string{
|
||||||
|
"quqi_id": d.GroupID,
|
||||||
|
"tree_id": "1",
|
||||||
|
"node_id": id,
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
})
|
||||||
|
}, &getDocResp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if getDocResp.Data.OriginPath == "" {
|
||||||
|
return nil, errors.New("cannot get link from preview")
|
||||||
|
}
|
||||||
|
return &model.Link{
|
||||||
|
URL: getDocResp.Data.OriginPath,
|
||||||
|
Header: http.Header{
|
||||||
|
"Origin": []string{"https://quqi.com"},
|
||||||
|
"Cookie": []string{d.Cookie},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) linkFromDownload(id string) (*model.Link, error) {
|
||||||
|
var getDownloadResp GetDownloadResp
|
||||||
|
if _, err := d.request("", "/api/doc/getDownload", resty.MethodGet, func(req *resty.Request) {
|
||||||
|
req.SetQueryParams(map[string]string{
|
||||||
|
"quqi_id": d.GroupID,
|
||||||
|
"tree_id": "1",
|
||||||
|
"node_id": id,
|
||||||
|
"url_type": "undefined",
|
||||||
|
"entry_type": "undefined",
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
"no_redirect": "1",
|
||||||
|
})
|
||||||
|
}, &getDownloadResp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if getDownloadResp.Data.Url == "" {
|
||||||
|
return nil, errors.New("cannot get link from download")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Link{
|
||||||
|
URL: getDownloadResp.Data.Url,
|
||||||
|
Header: http.Header{
|
||||||
|
"Origin": []string{"https://quqi.com"},
|
||||||
|
"Cookie": []string{d.Cookie},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Quqi) linkFromCDN(id string) (*model.Link, error) {
|
||||||
|
downloadLink, err := d.linkFromDownload(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var urlExchangeResp UrlExchangeResp
|
||||||
|
if _, err = d.request("api.quqi.com", "/preview/downloadInfo/url/exchange", resty.MethodGet, func(req *resty.Request) {
|
||||||
|
req.SetQueryParam("url", downloadLink.URL)
|
||||||
|
}, &urlExchangeResp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if urlExchangeResp.Data.Url == "" {
|
||||||
|
return nil, errors.New("cannot get link from cdn")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 假设存在未加密的情况
|
||||||
|
if !urlExchangeResp.Data.IsEncrypted {
|
||||||
|
return &model.Link{
|
||||||
|
URL: urlExchangeResp.Data.Url,
|
||||||
|
Header: http.Header{
|
||||||
|
"Origin": []string{"https://quqi.com"},
|
||||||
|
"Cookie": []string{d.Cookie},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据sio(https://github.com/minio/sio/blob/master/DARE.md)描述及实际测试,得出以下结论:
|
||||||
|
// 1. 加密后大小(encrypted_size)-原始文件大小(size) = 加密包的头大小+身份验证标识 = (16+16) * N -> N为加密包的数量
|
||||||
|
// 2. 原始文件大小(size)+64*1024-1 / (64*1024) = N -> 每个包的有效负载为64K
|
||||||
|
remoteClosers := utils.EmptyClosers()
|
||||||
|
payloadSize := int64(1 << 16)
|
||||||
|
expiration := time.Until(time.Unix(urlExchangeResp.Data.ExpiredTime, 0))
|
||||||
|
resultRangeReader := func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
||||||
|
encryptedOffset := httpRange.Start / payloadSize * (payloadSize + 32)
|
||||||
|
decryptedOffset := httpRange.Start % payloadSize
|
||||||
|
encryptedLength := (httpRange.Length+httpRange.Start+payloadSize-1)/payloadSize*(payloadSize+32) - encryptedOffset
|
||||||
|
if httpRange.Length < 0 {
|
||||||
|
encryptedLength = httpRange.Length
|
||||||
|
} else {
|
||||||
|
if httpRange.Length+httpRange.Start >= urlExchangeResp.Data.Size || encryptedLength+encryptedOffset >= urlExchangeResp.Data.EncryptedSize {
|
||||||
|
encryptedLength = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//log.Debugf("size: %d\tencrypted_size: %d", urlExchangeResp.Data.Size, urlExchangeResp.Data.EncryptedSize)
|
||||||
|
//log.Debugf("http range offset: %d, length: %d", httpRange.Start, httpRange.Length)
|
||||||
|
//log.Debugf("encrypted offset: %d, length: %d, decrypted offset: %d", encryptedOffset, encryptedLength, decryptedOffset)
|
||||||
|
|
||||||
|
rrc, err := stream.GetRangeReadCloserFromLink(urlExchangeResp.Data.EncryptedSize, &model.Link{
|
||||||
|
URL: urlExchangeResp.Data.Url,
|
||||||
|
Header: http.Header{
|
||||||
|
"Origin": []string{"https://quqi.com"},
|
||||||
|
"Cookie": []string{d.Cookie},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := rrc.RangeRead(ctx, http_range.Range{Start: encryptedOffset, Length: encryptedLength})
|
||||||
|
remoteClosers.AddClosers(rrc.GetClosers())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptReader, err := sio.DecryptReader(rc, sio.Config{
|
||||||
|
MinVersion: sio.Version10,
|
||||||
|
MaxVersion: sio.Version20,
|
||||||
|
CipherSuites: []byte{sio.CHACHA20_POLY1305, sio.AES_256_GCM},
|
||||||
|
Key: decryptKey(urlExchangeResp.Data.EncryptedKey),
|
||||||
|
SequenceNumber: uint32(httpRange.Start / payloadSize),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bufferReader := bufio.NewReader(decryptReader)
|
||||||
|
bufferReader.Discard(int(decryptedOffset))
|
||||||
|
|
||||||
|
return utils.NewReadCloser(bufferReader, func() error {
|
||||||
|
return nil
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Link{
|
||||||
|
Header: http.Header{
|
||||||
|
"Origin": []string{"https://quqi.com"},
|
||||||
|
"Cookie": []string{d.Cookie},
|
||||||
|
},
|
||||||
|
RangeReadCloser: &model.RangeReadCloser{RangeReader: resultRangeReader, Closers: remoteClosers},
|
||||||
|
Expiration: &expiration,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -11,6 +11,7 @@ type Addition struct {
|
|||||||
PrivateKey string `json:"private_key" type:"text"`
|
PrivateKey string `json:"private_key" type:"text"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
driver.RootPath
|
driver.RootPath
|
||||||
|
IgnoreSymlinkError bool `json:"ignore_symlink_error" default:"false" info:"Ignore symlink error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -30,6 +30,14 @@ func (d *SFTP) fileToObj(f os.FileInfo, dir string) (model.Obj, error) {
|
|||||||
}
|
}
|
||||||
_f, err := d.client.Stat(target)
|
_f, err := d.client.Stat(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if d.IgnoreSymlinkError {
|
||||||
|
return &model.Object{
|
||||||
|
Name: f.Name(),
|
||||||
|
Size: f.Size(),
|
||||||
|
Modified: f.ModTime(),
|
||||||
|
IsFolder: f.IsDir(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// set basic info
|
// set basic info
|
||||||
|
@ -244,6 +244,9 @@ func (d *Teambition) newUpload(ctx context.Context, dstDir model.Obj, stream mod
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uploader := s3manager.NewUploader(ss)
|
uploader := s3manager.NewUploader(ss)
|
||||||
|
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||||
|
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||||
|
}
|
||||||
input := &s3manager.UploadInput{
|
input := &s3manager.UploadInput{
|
||||||
Bucket: &uploadToken.Upload.Bucket,
|
Bucket: &uploadToken.Upload.Bucket,
|
||||||
Key: &uploadToken.Upload.Key,
|
Key: &uploadToken.Upload.Key,
|
||||||
|
@ -373,7 +373,11 @@ func (xc *XunLeiCommon) Put(ctx context.Context, dstDir model.Obj, stream model.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = s3manager.NewUploader(s).UploadWithContext(ctx, &s3manager.UploadInput{
|
uploader := s3manager.NewUploader(s)
|
||||||
|
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||||
|
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||||
|
}
|
||||||
|
_, err = uploader.UploadWithContext(ctx, &s3manager.UploadInput{
|
||||||
Bucket: aws.String(param.Bucket),
|
Bucket: aws.String(param.Bucket),
|
||||||
Key: aws.String(param.Key),
|
Key: aws.String(param.Key),
|
||||||
Expires: aws.Time(param.Expiration),
|
Expires: aws.Time(param.Expiration),
|
||||||
|
@ -272,6 +272,9 @@ func (d *Vtencent) FileUpload(ctx context.Context, dstDir model.Obj, stream mode
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uploader := s3manager.NewUploader(ss)
|
uploader := s3manager.NewUploader(ss)
|
||||||
|
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||||
|
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||||
|
}
|
||||||
input := &s3manager.UploadInput{
|
input := &s3manager.UploadInput{
|
||||||
Bucket: aws.String(fmt.Sprintf("%s-%d", params.StorageBucket, params.StorageAppID)),
|
Bucket: aws.String(fmt.Sprintf("%s-%d", params.StorageBucket, params.StorageAppID)),
|
||||||
Key: ¶ms.Video.StoragePath,
|
Key: ¶ms.Video.StoragePath,
|
||||||
|
40
go.mod
40
go.mod
@ -3,13 +3,14 @@ module github.com/alist-org/alist/v3
|
|||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/SheltonZhu/115driver v1.0.21
|
github.com/Mikubill/gofakes3 v0.0.3-0.20230622102024-284c0f988700
|
||||||
|
github.com/SheltonZhu/115driver v1.0.22
|
||||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||||
github.com/Xhofe/rateg v0.0.0-20230728072201-251a4e1adad4
|
github.com/Xhofe/rateg v0.0.0-20230728072201-251a4e1adad4
|
||||||
github.com/Xhofe/wopan-sdk-go v0.1.2
|
github.com/Xhofe/wopan-sdk-go v0.1.2
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.10+incompatible
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||||
github.com/avast/retry-go v3.0.0+incompatible
|
github.com/avast/retry-go v3.0.0+incompatible
|
||||||
github.com/aws/aws-sdk-go v1.49.15
|
github.com/aws/aws-sdk-go v1.50.24
|
||||||
github.com/blevesearch/bleve/v2 v2.3.10
|
github.com/blevesearch/bleve/v2 v2.3.10
|
||||||
github.com/caarlos0/env/v9 v9.0.0
|
github.com/caarlos0/env/v9 v9.0.0
|
||||||
github.com/charmbracelet/bubbles v0.17.1
|
github.com/charmbracelet/bubbles v0.17.1
|
||||||
@ -21,7 +22,7 @@ require (
|
|||||||
github.com/djherbis/times v1.6.0
|
github.com/djherbis/times v1.6.0
|
||||||
github.com/dlclark/regexp2 v1.10.0
|
github.com/dlclark/regexp2 v1.10.0
|
||||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564
|
||||||
github.com/foxxorcat/mopan-sdk-go v0.1.4
|
github.com/foxxorcat/mopan-sdk-go v0.1.5
|
||||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3
|
github.com/foxxorcat/weiyun-sdk-go v0.1.3
|
||||||
github.com/gaoyb7/115drive-webdav v0.1.8
|
github.com/gaoyb7/115drive-webdav v0.1.8
|
||||||
github.com/gin-contrib/cors v1.5.0
|
github.com/gin-contrib/cors v1.5.0
|
||||||
@ -36,7 +37,10 @@ require (
|
|||||||
github.com/jlaffaye/ftp v0.2.0
|
github.com/jlaffaye/ftp v0.2.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/maruel/natural v1.1.1
|
github.com/maruel/natural v1.1.1
|
||||||
|
github.com/meilisearch/meilisearch-go v0.26.1
|
||||||
|
github.com/minio/sio v0.3.0
|
||||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||||
|
github.com/ncw/swift/v2 v2.0.2
|
||||||
github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831
|
github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pkg/sftp v1.13.6
|
github.com/pkg/sftp v1.13.6
|
||||||
@ -50,11 +54,11 @@ require (
|
|||||||
github.com/upyun/go-sdk/v3 v3.0.4
|
github.com/upyun/go-sdk/v3 v3.0.4
|
||||||
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5
|
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5
|
||||||
github.com/xhofe/tache v0.1.1
|
github.com/xhofe/tache v0.1.1
|
||||||
golang.org/x/crypto v0.17.0
|
golang.org/x/crypto v0.18.0
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc
|
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e
|
||||||
golang.org/x/image v0.15.0
|
golang.org/x/image v0.15.0
|
||||||
golang.org/x/net v0.19.0
|
golang.org/x/net v0.20.0
|
||||||
golang.org/x/oauth2 v0.15.0
|
golang.org/x/oauth2 v0.16.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.5.0
|
||||||
google.golang.org/appengine v1.6.8
|
google.golang.org/appengine v1.6.8
|
||||||
gopkg.in/ldap.v3 v3.1.0
|
gopkg.in/ldap.v3 v3.1.0
|
||||||
@ -72,6 +76,7 @@ require (
|
|||||||
github.com/abbot/go-http-auth v0.4.0 // indirect
|
github.com/abbot/go-http-auth v0.4.0 // indirect
|
||||||
github.com/aead/ecdh v0.2.0 // indirect
|
github.com/aead/ecdh v0.2.0 // indirect
|
||||||
github.com/andreburgaud/crypt2go v1.2.0 // indirect
|
github.com/andreburgaud/crypt2go v1.2.0 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
@ -132,8 +137,10 @@ require (
|
|||||||
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/josharian/intern v1.0.0 // indirect
|
||||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 // indirect
|
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||||
@ -141,13 +148,14 @@ require (
|
|||||||
github.com/libp2p/go-libp2p v0.27.8 // indirect
|
github.com/libp2p/go-libp2p v0.27.8 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
@ -166,7 +174,6 @@ require (
|
|||||||
github.com/multiformats/go-multihash v0.2.3 // indirect
|
github.com/multiformats/go-multihash v0.2.3 // indirect
|
||||||
github.com/multiformats/go-multistream v0.4.1 // indirect
|
github.com/multiformats/go-multistream v0.4.1 // indirect
|
||||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||||
github.com/ncw/swift/v2 v2.0.2 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
@ -178,6 +185,8 @@ require (
|
|||||||
github.com/prometheus/procfs v0.11.1 // indirect
|
github.com/prometheus/procfs v0.11.1 // indirect
|
||||||
github.com/rfjakob/eme v1.1.2 // indirect
|
github.com/rfjakob/eme v1.1.2 // indirect
|
||||||
github.com/rivo/uniseg v0.4.4 // indirect
|
github.com/rivo/uniseg v0.4.4 // indirect
|
||||||
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
|
||||||
|
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect
|
||||||
github.com/shirou/gopsutil/v3 v3.23.7 // indirect
|
github.com/shirou/gopsutil/v3 v3.23.7 // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||||
@ -188,15 +197,18 @@ require (
|
|||||||
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.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25 // indirect
|
github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
go.etcd.io/bbolt v1.3.7 // indirect
|
go.etcd.io/bbolt v1.3.7 // indirect
|
||||||
golang.org/x/arch v0.5.0 // indirect
|
golang.org/x/arch v0.5.0 // indirect
|
||||||
golang.org/x/sync v0.3.0 // indirect
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
golang.org/x/sys v0.15.0 // indirect
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
golang.org/x/term v0.15.0 // indirect
|
golang.org/x/term v0.16.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
golang.org/x/tools v0.16.0 // indirect
|
||||||
google.golang.org/api v0.134.0 // indirect
|
google.golang.org/api v0.134.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect
|
||||||
google.golang.org/grpc v1.57.0 // indirect
|
google.golang.org/grpc v1.57.0 // indirect
|
||||||
|
145
go.sum
145
go.sum
@ -7,10 +7,12 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
|
|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE=
|
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE=
|
||||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc=
|
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc=
|
||||||
|
github.com/Mikubill/gofakes3 v0.0.3-0.20230622102024-284c0f988700 h1:r3fp2/Ro+0RtpjNY0/wsbN7vRmCW//dXTOZDQTct25Q=
|
||||||
|
github.com/Mikubill/gofakes3 v0.0.3-0.20230622102024-284c0f988700/go.mod h1:OSXqXEGUe9CmPiwLMMnVrbXonMf4BeLBkBdLufxxiyY=
|
||||||
github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY=
|
github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY=
|
||||||
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
|
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
|
||||||
github.com/SheltonZhu/115driver v1.0.21 h1:Pz6r14VwIiuSyHj+OmJe57FHhbmWB/6IfnXAFL2iXbU=
|
github.com/SheltonZhu/115driver v1.0.22 h1:Wp8pN7/gK3YwEO5P18ggbIOHM++lo9eP/pBhuvXfI6U=
|
||||||
github.com/SheltonZhu/115driver v1.0.21/go.mod h1:e3fPOBANbH/FsTya8FquJwOR3ErhCQgEab3q6CVY2k4=
|
github.com/SheltonZhu/115driver v1.0.22/go.mod h1:e3fPOBANbH/FsTya8FquJwOR3ErhCQgEab3q6CVY2k4=
|
||||||
github.com/Unknwon/goconfig v1.0.0 h1:9IAu/BYbSLQi8puFjUQApZTxIHqSwrj5d8vpP8vTq4A=
|
github.com/Unknwon/goconfig v1.0.0 h1:9IAu/BYbSLQi8puFjUQApZTxIHqSwrj5d8vpP8vTq4A=
|
||||||
github.com/Unknwon/goconfig v1.0.0/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw=
|
github.com/Unknwon/goconfig v1.0.0/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw=
|
||||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE=
|
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE=
|
||||||
@ -23,21 +25,17 @@ github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0E
|
|||||||
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
|
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
|
||||||
github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
|
github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
|
||||||
github.com/aead/ecdh v0.2.0/go.mod h1:a9HHtXuSo8J1Js1MwLQx2mBhkXMT6YwUmVVEY4tTB8U=
|
github.com/aead/ecdh v0.2.0/go.mod h1:a9HHtXuSo8J1Js1MwLQx2mBhkXMT6YwUmVVEY4tTB8U=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiwbXTpUEinBpHsN7mG21Rc2k=
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.10+incompatible h1:ROMcuN61gI8SfQ+AEMh4d7GZ3gwTZLIhPjtd05TQCG4=
|
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.10+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
|
||||||
github.com/andreburgaud/crypt2go v1.2.0 h1:oly/ENAodeqTYpUafgd4r3v+VKLQnmOKUyfpj+TxHbE=
|
github.com/andreburgaud/crypt2go v1.2.0 h1:oly/ENAodeqTYpUafgd4r3v+VKLQnmOKUyfpj+TxHbE=
|
||||||
github.com/andreburgaud/crypt2go v1.2.0/go.mod h1:kKRqlrX/3Q9Ki7HdUsoh0cX1Urq14/Hcta4l4VrIXrI=
|
github.com/andreburgaud/crypt2go v1.2.0/go.mod h1:kKRqlrX/3Q9Ki7HdUsoh0cX1Urq14/Hcta4l4VrIXrI=
|
||||||
|
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||||
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||||
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.46.7 h1:IjvAWeiJZlbETOemOwvheN5L17CvKvKW0T1xOC6d3Sc=
|
github.com/aws/aws-sdk-go v1.50.24 h1:3o2Pg7mOoVL0jv54vWtuafoZqAeEXLhm1tltWA2GcEw=
|
||||||
github.com/aws/aws-sdk-go v1.46.7/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
github.com/aws/aws-sdk-go v1.50.24/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||||
github.com/aws/aws-sdk-go v1.49.13 h1:f4mGztsgnx2dR9r8FQYa9YW/RsKb+N7bgef4UGrOW1Y=
|
|
||||||
github.com/aws/aws-sdk-go v1.49.13/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
|
||||||
github.com/aws/aws-sdk-go v1.49.15 h1:aH9bSV4kL4ziH0AMtuYbukGIVebXddXBL0cKZ1zj15k=
|
|
||||||
github.com/aws/aws-sdk-go v1.49.15/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||||
@ -85,8 +83,6 @@ github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0
|
|||||||
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=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
|
||||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
|
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
|
||||||
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
@ -94,12 +90,8 @@ github.com/caarlos0/env/v9 v9.0.0 h1:SI6JNsOA+y5gj9njpgybykATIylrRMklbs5ch6wO6pc
|
|||||||
github.com/caarlos0/env/v9 v9.0.0/go.mod h1:ye5mlCVMYh6tZ+vCgrs/B95sj88cg5Tlnc0XIzgZ020=
|
github.com/caarlos0/env/v9 v9.0.0/go.mod h1:ye5mlCVMYh6tZ+vCgrs/B95sj88cg5Tlnc0XIzgZ020=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
|
||||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
|
||||||
github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4=
|
github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4=
|
||||||
github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o=
|
github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o=
|
||||||
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
|
|
||||||
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
|
||||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||||
@ -107,7 +99,6 @@ github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9
|
|||||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
|
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
|
||||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||||
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/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||||
@ -119,7 +110,6 @@ github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjs
|
|||||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/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 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg=
|
||||||
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE=
|
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE=
|
||||||
@ -127,8 +117,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.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.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A=
|
|
||||||
github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
|
||||||
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
||||||
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||||
@ -137,16 +125,14 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1
|
|||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
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/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU=
|
|
||||||
github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0=
|
|
||||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
|
||||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564/go.mod h1:yekO+3ZShy19S+bsmnERmznGy9Rfg6dWWWpiGJjNAz8=
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564/go.mod h1:yekO+3ZShy19S+bsmnERmznGy9Rfg6dWWWpiGJjNAz8=
|
||||||
github.com/foxxorcat/mopan-sdk-go v0.1.4 h1:6utvPiBv8KDRDVKB7A4FERdrVxcHKZd2fBFCNuKcXzU=
|
github.com/foxxorcat/mopan-sdk-go v0.1.5 h1:N3LqOvk2aWWxszsFIkArP5udIv74uTei/bH2jM3tfSc=
|
||||||
github.com/foxxorcat/mopan-sdk-go v0.1.4/go.mod h1:iWHA2JFhzmKR28ySp1ON0g6DjLaYtvb5jhTqPVTDW9A=
|
github.com/foxxorcat/mopan-sdk-go v0.1.5/go.mod h1:iWHA2JFhzmKR28ySp1ON0g6DjLaYtvb5jhTqPVTDW9A=
|
||||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3 h1:I5c5nfGErhq9DBumyjCVCggRA74jhgriMqRRFu5jeeY=
|
github.com/foxxorcat/weiyun-sdk-go v0.1.3 h1:I5c5nfGErhq9DBumyjCVCggRA74jhgriMqRRFu5jeeY=
|
||||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3/go.mod h1:TPxzN0d2PahweUEHlOBWlwZSA+rELSUlGYMWgXRn9ps=
|
github.com/foxxorcat/weiyun-sdk-go v0.1.3/go.mod h1:TPxzN0d2PahweUEHlOBWlwZSA+rELSUlGYMWgXRn9ps=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@ -158,8 +144,6 @@ github.com/gaoyb7/115drive-webdav v0.1.8 h1:EJt4PSmcbvBY4KUh2zSo5p6fN9LZFNkIzuKe
|
|||||||
github.com/gaoyb7/115drive-webdav v0.1.8/go.mod h1:BKbeY6j8SKs3+rzBFFALznGxbPmefEm3vA+dGhqgOGU=
|
github.com/gaoyb7/115drive-webdav v0.1.8/go.mod h1:BKbeY6j8SKs3+rzBFFALznGxbPmefEm3vA+dGhqgOGU=
|
||||||
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
|
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
|
||||||
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
|
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
|
||||||
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
|
|
||||||
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
|
|
||||||
github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk=
|
github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk=
|
||||||
github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI=
|
github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
@ -183,23 +167,15 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
|
||||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
|
||||||
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
|
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
|
||||||
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||||
github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo=
|
|
||||||
github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
|
||||||
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
||||||
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/go-webauthn/webauthn v0.9.4 h1:YxvHSqgUyc5AK2pZbqkWWR55qKeDPhP8zLDr6lpIc2g=
|
|
||||||
github.com/go-webauthn/webauthn v0.9.4/go.mod h1:LqupCtzSef38FcxzaklmOn7AykGKhAhr9xlRbdbgnTw=
|
|
||||||
github.com/go-webauthn/webauthn v0.10.0 h1:yuW2e1tXnRAwAvKrR4q4LQmc6XtCMH639/ypZGhZCwk=
|
github.com/go-webauthn/webauthn v0.10.0 h1:yuW2e1tXnRAwAvKrR4q4LQmc6XtCMH639/ypZGhZCwk=
|
||||||
github.com/go-webauthn/webauthn v0.10.0/go.mod h1:l0NiauXhL6usIKqNLCUM3Qir43GK7ORg8ggold0Uv/Y=
|
github.com/go-webauthn/webauthn v0.10.0/go.mod h1:l0NiauXhL6usIKqNLCUM3Qir43GK7ORg8ggold0Uv/Y=
|
||||||
github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0=
|
|
||||||
github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8cukqY=
|
|
||||||
github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ=
|
github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ=
|
||||||
github.com/go-webauthn/x v0.1.6/go.mod h1:W8dFVZ79o4f+nY1eOUICy/uq5dhrRl7mxQkYhXTo0FA=
|
github.com/go-webauthn/x v0.1.6/go.mod h1:W8dFVZ79o4f+nY1eOUICy/uq5dhrRl7mxQkYhXTo0FA=
|
||||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
@ -215,7 +191,6 @@ github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgR
|
|||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
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/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
@ -232,16 +207,12 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
|||||||
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||||
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.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
|
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
|
github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@ -280,6 +251,8 @@ 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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
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=
|
||||||
@ -287,10 +260,13 @@ github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC
|
|||||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c=
|
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c=
|
||||||
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.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
|
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
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.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
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=
|
||||||
@ -317,8 +293,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
|||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||||
github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
||||||
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
@ -336,8 +312,12 @@ github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOj
|
|||||||
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/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
github.com/meilisearch/meilisearch-go v0.26.1 h1:3bmo2uLijX7kvBmiZ9LupVfC95TFcRJDgrRTzbOoE4A=
|
||||||
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
github.com/meilisearch/meilisearch-go v0.26.1/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0=
|
||||||
|
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||||
|
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||||
|
github.com/minio/sio v0.3.0 h1:syEFBewzOMOYVzSTFpp1MqpSZk8rUNbz8VIIc+PNzus=
|
||||||
|
github.com/minio/sio v0.3.0/go.mod h1:8b0yPp2avGThviy/+OCJBI6OMpvxoUuiLvE6F1lebhw=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
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/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
@ -385,8 +365,6 @@ github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831 h1:K3T3eu
|
|||||||
github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831/go.mod h1:lSHD4lC4zlMl+zcoysdJcd5KFzsWwOD8BJbyg1Ws9Ng=
|
github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831/go.mod h1:lSHD4lC4zlMl+zcoysdJcd5KFzsWwOD8BJbyg1Ws9Ng=
|
||||||
github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
|
github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
||||||
@ -429,6 +407,10 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
|||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
||||||
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||||
|
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 h1:WnNuhiq+FOY3jNj6JXFT+eLN3CQ/oPIsDPRanvwsmbI=
|
||||||
|
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500/go.mod h1:+njLrG5wSeoG4Ds61rFgEzKvenR2UHbjMoDHsczxly0=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
@ -445,8 +427,6 @@ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:s
|
|||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
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.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
|
||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
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=
|
||||||
@ -465,7 +445,6 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
|
|||||||
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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca h1:I9rVnNXdIkij4UvMT7OmKhH9sOIvS8iXkxfPdnn9wQA=
|
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca h1:I9rVnNXdIkij4UvMT7OmKhH9sOIvS8iXkxfPdnn9wQA=
|
||||||
@ -487,8 +466,13 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
|
|||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/upyun/go-sdk/v3 v3.0.4 h1:2DCJa/Yi7/3ZybT9UCPATSzvU3wpPPxhXinNlb1Hi8Q=
|
github.com/upyun/go-sdk/v3 v3.0.4 h1:2DCJa/Yi7/3ZybT9UCPATSzvU3wpPPxhXinNlb1Hi8Q=
|
||||||
github.com/upyun/go-sdk/v3 v3.0.4/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
|
github.com/upyun/go-sdk/v3 v3.0.4/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d h1:xS9QTPgKl9ewGsAOPc+xW7DeStJDqYPfisDmeSCcbco=
|
||||||
|
github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
|
||||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5 h1:jxZvjx8Ve5sOXorZG0KzTxbp0Cr1n3FEegfmyd9br1k=
|
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5 h1:jxZvjx8Ve5sOXorZG0KzTxbp0Cr1n3FEegfmyd9br1k=
|
||||||
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5/go.mod h1:uxjoF2jEYT3+x+vC2KJddEGdk/LU8pRowXmyVMHSV5I=
|
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5/go.mod h1:uxjoF2jEYT3+x+vC2KJddEGdk/LU8pRowXmyVMHSV5I=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
@ -506,64 +490,52 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
|||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
|
||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
||||||
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
||||||
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
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.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
|
||||||
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
|
|
||||||
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
|
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
|
||||||
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.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
|
||||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
|
||||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
|
||||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
|
||||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||||
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=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||||
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
|
|
||||||
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-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=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -576,6 +548,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/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-20220227234510-4e6760a101f9/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-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/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=
|
||||||
@ -589,16 +563,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
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=
|
||||||
@ -608,27 +582,26 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|||||||
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.9.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/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
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.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190829051458-42f498d34c4d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
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/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
|
||||||
|
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||||
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=
|
||||||
google.golang.org/api v0.134.0 h1:ktL4Goua+UBgoP1eL1/60LwZJqa1sIzkLmvoR3hR6Gw=
|
google.golang.org/api v0.134.0 h1:ktL4Goua+UBgoP1eL1/60LwZJqa1sIzkLmvoR3hR6Gw=
|
||||||
google.golang.org/api v0.134.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk=
|
google.golang.org/api v0.134.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk=
|
||||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg=
|
||||||
|
@ -21,7 +21,6 @@ func initSettings() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log.Fatalf("failed get settings: %+v", err)
|
utils.Log.Fatalf("failed get settings: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range settings {
|
for i := range settings {
|
||||||
if !isActive(settings[i].Key) && settings[i].Flag != model.DEPRECATED {
|
if !isActive(settings[i].Key) && settings[i].Flag != model.DEPRECATED {
|
||||||
settings[i].Flag = model.DEPRECATED
|
settings[i].Flag = model.DEPRECATED
|
||||||
@ -42,7 +41,7 @@ func initSettings() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// save
|
// save
|
||||||
if stored != nil && item.Key != conf.VERSION {
|
if stored != nil && item.Key != conf.VERSION && stored.Value != item.DeprecatedValue {
|
||||||
item.Value = stored.Value
|
item.Value = stored.Value
|
||||||
}
|
}
|
||||||
if stored == nil || *item != *stored {
|
if stored == nil || *item != *stored {
|
||||||
@ -129,7 +128,7 @@ func InitialSettings() []model.SettingItem {
|
|||||||
// global settings
|
// global settings
|
||||||
{Key: conf.HideFiles, Value: "/\\/README.md/i", Type: conf.TypeText, Group: model.GLOBAL},
|
{Key: conf.HideFiles, Value: "/\\/README.md/i", Type: conf.TypeText, Group: model.GLOBAL},
|
||||||
{Key: "package_download", Value: "true", Type: conf.TypeBool, Group: model.GLOBAL},
|
{Key: "package_download", Value: "true", Type: conf.TypeBool, Group: model.GLOBAL},
|
||||||
{Key: conf.CustomizeHead, Value: `<script src="https://polyfill.io/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>`, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
{Key: conf.CustomizeHead, DeprecatedValue: `<script src="https://polyfill.io/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>`, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: conf.CustomizeBody, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
{Key: conf.CustomizeBody, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: conf.LinkExpiration, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
{Key: conf.LinkExpiration, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: conf.SignAll, Value: "true", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
|
{Key: conf.SignAll, Value: "true", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
@ -145,7 +144,7 @@ func InitialSettings() []model.SettingItem {
|
|||||||
|
|
||||||
// single settings
|
// single settings
|
||||||
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
{Key: conf.SearchIndex, Value: "none", Type: conf.TypeSelect, Options: "database,database_non_full_text,bleve,none", Group: model.INDEX},
|
{Key: conf.SearchIndex, Value: "none", Type: conf.TypeSelect, Options: "database,database_non_full_text,bleve,meilisearch,none", Group: model.INDEX},
|
||||||
{Key: conf.AutoUpdateIndex, Value: "false", Type: conf.TypeBool, Group: model.INDEX},
|
{Key: conf.AutoUpdateIndex, Value: "false", Type: conf.TypeBool, Group: model.INDEX},
|
||||||
{Key: conf.IgnorePaths, Value: "", Type: conf.TypeText, Group: model.INDEX, Flag: model.PRIVATE, Help: `one path per line`},
|
{Key: conf.IgnorePaths, Value: "", Type: conf.TypeText, Group: model.INDEX, Flag: model.PRIVATE, Help: `one path per line`},
|
||||||
{Key: conf.MaxIndexDepth, Value: "20", Type: conf.TypeNumber, Group: model.INDEX, Flag: model.PRIVATE, Help: `max depth of index`},
|
{Key: conf.MaxIndexDepth, Value: "20", Type: conf.TypeNumber, Group: model.INDEX, Flag: model.PRIVATE, Help: `max depth of index`},
|
||||||
@ -176,6 +175,12 @@ func InitialSettings() []model.SettingItem {
|
|||||||
{Key: conf.LdapDefaultDir, Value: "/", Type: conf.TypeString, Group: model.LDAP, Flag: model.PRIVATE},
|
{Key: conf.LdapDefaultDir, Value: "/", Type: conf.TypeString, Group: model.LDAP, Flag: model.PRIVATE},
|
||||||
{Key: conf.LdapDefaultPermission, Value: "0", Type: conf.TypeNumber, Group: model.LDAP, Flag: model.PRIVATE},
|
{Key: conf.LdapDefaultPermission, Value: "0", Type: conf.TypeNumber, Group: model.LDAP, Flag: model.PRIVATE},
|
||||||
{Key: conf.LdapLoginTips, Value: "login with ldap", Type: conf.TypeString, Group: model.LDAP, Flag: model.PUBLIC},
|
{Key: conf.LdapLoginTips, Value: "login with ldap", Type: conf.TypeString, Group: model.LDAP, Flag: model.PUBLIC},
|
||||||
|
|
||||||
|
//s3 settings
|
||||||
|
{Key: conf.S3Enabled, Value: "false", Type: conf.TypeBool, Group: model.S3, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.S3AccessKeyId, Value: "", Type: conf.TypeString, Group: model.S3, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.S3SecretAccessKey, Value: "", Type: conf.TypeString, Group: model.S3, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.S3Buckets, Value: "[]", Type: conf.TypeString, Group: model.S3, Flag: model.PRIVATE},
|
||||||
}
|
}
|
||||||
initialSettingItems = append(initialSettingItems, tool.Tools.Items()...)
|
initialSettingItems = append(initialSettingItems, tool.Tools.Items()...)
|
||||||
if flags.Dev {
|
if flags.Dev {
|
||||||
|
@ -48,6 +48,7 @@ func initUser() {
|
|||||||
guest = &model.User{
|
guest = &model.User{
|
||||||
Username: "guest",
|
Username: "guest",
|
||||||
PwdHash: model.TwoHashPwd("guest", salt),
|
PwdHash: model.TwoHashPwd("guest", salt),
|
||||||
|
Salt: salt,
|
||||||
Role: model.GUEST,
|
Role: model.GUEST,
|
||||||
BasePath: "/",
|
BasePath: "/",
|
||||||
Permission: 0,
|
Permission: 0,
|
||||||
|
@ -56,14 +56,21 @@ func InitDB() {
|
|||||||
}
|
}
|
||||||
case "mysql":
|
case "mysql":
|
||||||
{
|
{
|
||||||
|
//[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
|
||||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
|
||||||
database.User, database.Password, database.Host, database.Port, database.Name, database.SSLMode)
|
database.User, database.Password, database.Host, database.Port, database.Name, database.SSLMode)
|
||||||
|
if database.DSN != "" {
|
||||||
|
dsn = database.DSN
|
||||||
|
}
|
||||||
dB, err = gorm.Open(mysql.Open(dsn), gormConfig)
|
dB, err = gorm.Open(mysql.Open(dsn), gormConfig)
|
||||||
}
|
}
|
||||||
case "postgres":
|
case "postgres":
|
||||||
{
|
{
|
||||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=Asia/Shanghai",
|
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=Asia/Shanghai",
|
||||||
database.Host, database.User, database.Password, database.Name, database.Port, database.SSLMode)
|
database.Host, database.User, database.Password, database.Name, database.Port, database.SSLMode)
|
||||||
|
if database.DSN != "" {
|
||||||
|
dsn = database.DSN
|
||||||
|
}
|
||||||
dB, err = gorm.Open(postgres.Open(dsn), gormConfig)
|
dB, err = gorm.Open(postgres.Open(dsn), gormConfig)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/cmd/flags"
|
"github.com/alist-org/alist/v3/cmd/flags"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils/random"
|
"github.com/alist-org/alist/v3/pkg/utils/random"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
@ -17,6 +16,13 @@ type Database struct {
|
|||||||
DBFile string `json:"db_file" env:"FILE"`
|
DBFile string `json:"db_file" env:"FILE"`
|
||||||
TablePrefix string `json:"table_prefix" env:"TABLE_PREFIX"`
|
TablePrefix string `json:"table_prefix" env:"TABLE_PREFIX"`
|
||||||
SSLMode string `json:"ssl_mode" env:"SSL_MODE"`
|
SSLMode string `json:"ssl_mode" env:"SSL_MODE"`
|
||||||
|
DSN string `json:"dsn" env:"DSN"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Meilisearch struct {
|
||||||
|
Host string `json:"host" env:"HOST"`
|
||||||
|
APIKey string `json:"api_key" env:"API_KEY"`
|
||||||
|
IndexPrefix string `json:"index_prefix" env:"INDEX_PREFIX"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Scheme struct {
|
type Scheme struct {
|
||||||
@ -64,6 +70,7 @@ type Config struct {
|
|||||||
JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`
|
JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`
|
||||||
TokenExpiresIn int `json:"token_expires_in" env:"TOKEN_EXPIRES_IN"`
|
TokenExpiresIn int `json:"token_expires_in" env:"TOKEN_EXPIRES_IN"`
|
||||||
Database Database `json:"database" envPrefix:"DB_"`
|
Database Database `json:"database" envPrefix:"DB_"`
|
||||||
|
Meilisearch Meilisearch `json:"meilisearch" env:"MEILISEARCH"`
|
||||||
Scheme Scheme `json:"scheme"`
|
Scheme Scheme `json:"scheme"`
|
||||||
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
||||||
BleveDir string `json:"bleve_dir" env:"BLEVE_DIR"`
|
BleveDir string `json:"bleve_dir" env:"BLEVE_DIR"`
|
||||||
@ -100,6 +107,9 @@ func DefaultConfig() *Config {
|
|||||||
TablePrefix: "x_",
|
TablePrefix: "x_",
|
||||||
DBFile: dbPath,
|
DBFile: dbPath,
|
||||||
},
|
},
|
||||||
|
Meilisearch: Meilisearch{
|
||||||
|
Host: "http://localhost:7700",
|
||||||
|
},
|
||||||
BleveDir: indexDir,
|
BleveDir: indexDir,
|
||||||
Log: LogConfig{
|
Log: LogConfig{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
|
@ -84,6 +84,12 @@ const (
|
|||||||
LdapDefaultDir = "ldap_default_dir"
|
LdapDefaultDir = "ldap_default_dir"
|
||||||
LdapLoginTips = "ldap_login_tips"
|
LdapLoginTips = "ldap_login_tips"
|
||||||
|
|
||||||
|
//s3
|
||||||
|
S3Enabled = "s3_enabled"
|
||||||
|
S3AccessKeyId = "s3_access_key_id"
|
||||||
|
S3SecretAccessKey = "s3_secret_access_key"
|
||||||
|
S3Buckets = "s3_buckets"
|
||||||
|
|
||||||
// qbittorrent
|
// qbittorrent
|
||||||
QbittorrentUrl = "qbittorrent_url"
|
QbittorrentUrl = "qbittorrent_url"
|
||||||
QbittorrentSeedtime = "qbittorrent_seedtime"
|
QbittorrentSeedtime = "qbittorrent_seedtime"
|
||||||
|
@ -10,6 +10,7 @@ const (
|
|||||||
INDEX
|
INDEX
|
||||||
SSO
|
SSO
|
||||||
LDAP
|
LDAP
|
||||||
|
S3
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -20,13 +21,14 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SettingItem struct {
|
type SettingItem struct {
|
||||||
Key string `json:"key" gorm:"primaryKey" binding:"required"` // unique key
|
Key string `json:"key" gorm:"primaryKey" binding:"required"` // unique key
|
||||||
Value string `json:"value"` // value
|
Value string `json:"value"` // value
|
||||||
Help string `json:"help"` // help message
|
DeprecatedValue string `json:"deprecated_value" gorm:"-:all"` // deprecated value
|
||||||
Type string `json:"type"` // string, number, bool, select
|
Help string `json:"help"` // help message
|
||||||
Options string `json:"options"` // values for select
|
Type string `json:"type"` // string, number, bool, select
|
||||||
Group int `json:"group"` // use to group setting in frontend
|
Options string `json:"options"` // values for select
|
||||||
Flag int `json:"flag"` // 0 = public, 1 = private, 2 = readonly, 3 = deprecated, etc.
|
Group int `json:"group"` // use to group setting in frontend
|
||||||
|
Flag int `json:"flag"` // 0 = public, 1 = private, 2 = readonly, 3 = deprecated, etc.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SettingItem) IsDeprecated() bool {
|
func (s SettingItem) IsDeprecated() bool {
|
||||||
|
@ -3,10 +3,11 @@ package aria2
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
|
||||||
"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/offline_download/tool"
|
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||||
|
@ -2,13 +2,14 @@ package tool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/setting"
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/xhofe/tache"
|
"github.com/xhofe/tache"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DownloadTask struct {
|
type DownloadTask struct {
|
||||||
|
@ -211,14 +211,15 @@ func Update(parent string, objs []model.Obj) {
|
|||||||
}
|
}
|
||||||
for i := range objs {
|
for i := range objs {
|
||||||
if toAdd.Contains(objs[i].GetName()) {
|
if toAdd.Contains(objs[i].GetName()) {
|
||||||
log.Debugf("add index: %s", path.Join(parent, objs[i].GetName()))
|
if !objs[i].IsDir() {
|
||||||
err = Index(ctx, parent, objs[i])
|
log.Debugf("add index: %s", path.Join(parent, objs[i].GetName()))
|
||||||
if err != nil {
|
err = Index(ctx, parent, objs[i])
|
||||||
log.Errorf("update search index error while index new node: %+v", err)
|
if err != nil {
|
||||||
return
|
log.Errorf("update search index error while index new node: %+v", err)
|
||||||
}
|
return
|
||||||
// build index if it's a folder
|
}
|
||||||
if objs[i].IsDir() {
|
} else {
|
||||||
|
// build index if it's a folder
|
||||||
dir := path.Join(parent, objs[i].GetName())
|
dir := path.Join(parent, objs[i].GetName())
|
||||||
err = BuildIndex(ctx,
|
err = BuildIndex(ctx,
|
||||||
[]string{dir},
|
[]string{dir},
|
||||||
|
@ -4,4 +4,5 @@ import (
|
|||||||
_ "github.com/alist-org/alist/v3/internal/search/bleve"
|
_ "github.com/alist-org/alist/v3/internal/search/bleve"
|
||||||
_ "github.com/alist-org/alist/v3/internal/search/db"
|
_ "github.com/alist-org/alist/v3/internal/search/db"
|
||||||
_ "github.com/alist-org/alist/v3/internal/search/db_non_full_text"
|
_ "github.com/alist-org/alist/v3/internal/search/db_non_full_text"
|
||||||
|
_ "github.com/alist-org/alist/v3/internal/search/meilisearch"
|
||||||
)
|
)
|
||||||
|
89
internal/search/meilisearch/init.go
Normal file
89
internal/search/meilisearch/init.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package meilisearch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/search/searcher"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/meilisearch/meilisearch-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
var config = searcher.Config{
|
||||||
|
Name: "meilisearch",
|
||||||
|
AutoUpdate: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
searcher.RegisterSearcher(config, func() (searcher.Searcher, error) {
|
||||||
|
m := Meilisearch{
|
||||||
|
Client: meilisearch.NewClient(meilisearch.ClientConfig{
|
||||||
|
Host: conf.Conf.Meilisearch.Host,
|
||||||
|
APIKey: conf.Conf.Meilisearch.APIKey,
|
||||||
|
}),
|
||||||
|
IndexUid: conf.Conf.Meilisearch.IndexPrefix + "alist",
|
||||||
|
FilterableAttributes: []string{"parent", "is_dir", "name"},
|
||||||
|
SearchableAttributes: []string{"name"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := m.Client.GetIndex(m.IndexUid)
|
||||||
|
if err != nil {
|
||||||
|
var mErr *meilisearch.Error
|
||||||
|
ok := errors.As(err, &mErr)
|
||||||
|
if ok && mErr.MeilisearchApiError.Code == "index_not_found" {
|
||||||
|
task, err := m.Client.CreateIndex(&meilisearch.IndexConfig{
|
||||||
|
Uid: m.IndexUid,
|
||||||
|
PrimaryKey: "id",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
forTask, err := m.Client.WaitForTask(task.TaskUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if forTask.Status != meilisearch.TaskStatusSucceeded {
|
||||||
|
return nil, fmt.Errorf("index creation failed, task status is %s", forTask.Status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attributes, err := m.Client.Index(m.IndexUid).GetFilterableAttributes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if attributes == nil || !utils.SliceAllContains(*attributes, m.FilterableAttributes...) {
|
||||||
|
_, err = m.Client.Index(m.IndexUid).UpdateFilterableAttributes(&m.FilterableAttributes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes, err = m.Client.Index(m.IndexUid).GetSearchableAttributes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if attributes == nil || !utils.SliceAllContains(*attributes, m.SearchableAttributes...) {
|
||||||
|
_, err = m.Client.Index(m.IndexUid).UpdateSearchableAttributes(&m.SearchableAttributes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pagination, err := m.Client.Index(m.IndexUid).GetPagination()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pagination.MaxTotalHits != int64(model.MaxInt) {
|
||||||
|
_, err := m.Client.Index(m.IndexUid).UpdatePagination(&meilisearch.Pagination{
|
||||||
|
MaxTotalHits: int64(model.MaxInt),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &m, nil
|
||||||
|
})
|
||||||
|
}
|
227
internal/search/meilisearch/search.go
Normal file
227
internal/search/meilisearch/search.go
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
package meilisearch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/search/searcher"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/meilisearch/meilisearch-go"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type searchDocument struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
model.SearchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
type Meilisearch struct {
|
||||||
|
Client *meilisearch.Client
|
||||||
|
IndexUid string
|
||||||
|
FilterableAttributes []string
|
||||||
|
SearchableAttributes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) Config() searcher.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) {
|
||||||
|
mReq := &meilisearch.SearchRequest{
|
||||||
|
AttributesToSearchOn: m.SearchableAttributes,
|
||||||
|
Page: int64(req.Page),
|
||||||
|
HitsPerPage: int64(req.PerPage),
|
||||||
|
}
|
||||||
|
if req.Scope != 0 {
|
||||||
|
mReq.Filter = fmt.Sprintf("is_dir = %v", req.Scope == 1)
|
||||||
|
}
|
||||||
|
search, err := m.Client.Index(m.IndexUid).Search(req.Keywords, mReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
nodes, err := utils.SliceConvert(search.Hits, func(src any) (model.SearchNode, error) {
|
||||||
|
srcMap := src.(map[string]any)
|
||||||
|
return model.SearchNode{
|
||||||
|
Parent: srcMap["parent"].(string),
|
||||||
|
Name: srcMap["name"].(string),
|
||||||
|
IsDir: srcMap["is_dir"].(bool),
|
||||||
|
Size: int64(srcMap["size"].(float64)),
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return nodes, search.TotalHits, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) Index(ctx context.Context, node model.SearchNode) error {
|
||||||
|
return m.BatchIndex(ctx, []model.SearchNode{node})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) BatchIndex(ctx context.Context, nodes []model.SearchNode) error {
|
||||||
|
documents, _ := utils.SliceConvert(nodes, func(src model.SearchNode) (*searchDocument, error) {
|
||||||
|
|
||||||
|
return &searchDocument{
|
||||||
|
ID: uuid.NewString(),
|
||||||
|
SearchNode: src,
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := m.Client.Index(m.IndexUid).AddDocuments(documents)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//// Wait for the task to complete and check
|
||||||
|
//forTask, err := m.Client.WaitForTask(task.TaskUID, meilisearch.WaitParams{
|
||||||
|
// Context: ctx,
|
||||||
|
// Interval: time.Millisecond * 50,
|
||||||
|
//})
|
||||||
|
//if err != nil {
|
||||||
|
// return err
|
||||||
|
//}
|
||||||
|
//if forTask.Status != meilisearch.TaskStatusSucceeded {
|
||||||
|
// return fmt.Errorf("BatchIndex failed, task status is %s", forTask.Status)
|
||||||
|
//}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) getDocumentsByParent(ctx context.Context, parent string) ([]*searchDocument, error) {
|
||||||
|
var result meilisearch.DocumentsResult
|
||||||
|
err := m.Client.Index(m.IndexUid).GetDocuments(&meilisearch.DocumentsQuery{
|
||||||
|
Filter: fmt.Sprintf("parent = '%s'", strings.ReplaceAll(parent, "'", "\\'")),
|
||||||
|
Limit: int64(model.MaxInt),
|
||||||
|
}, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return utils.SliceConvert(result.Results, func(src map[string]any) (*searchDocument, error) {
|
||||||
|
return &searchDocument{
|
||||||
|
ID: src["id"].(string),
|
||||||
|
SearchNode: model.SearchNode{
|
||||||
|
Parent: src["parent"].(string),
|
||||||
|
Name: src["name"].(string),
|
||||||
|
IsDir: src["is_dir"].(bool),
|
||||||
|
Size: int64(src["size"].(float64)),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) Get(ctx context.Context, parent string) ([]model.SearchNode, error) {
|
||||||
|
result, err := m.getDocumentsByParent(ctx, parent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return utils.SliceConvert(result, func(src *searchDocument) (model.SearchNode, error) {
|
||||||
|
return src.SearchNode, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) getParentsByPrefix(ctx context.Context, parent string) ([]string, error) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
parents := []string{parent}
|
||||||
|
get, err := m.getDocumentsByParent(ctx, parent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, node := range get {
|
||||||
|
if node.IsDir {
|
||||||
|
arr, err := m.getParentsByPrefix(ctx, path.Join(node.Parent, node.Name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
parents = append(parents, arr...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parents, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) DelDirChild(ctx context.Context, prefix string) error {
|
||||||
|
dfs, err := m.getParentsByPrefix(ctx, utils.FixAndCleanPath(prefix))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
utils.SliceReplace(dfs, func(src string) string {
|
||||||
|
return "'" + strings.ReplaceAll(src, "'", "\\'") + "'"
|
||||||
|
})
|
||||||
|
s := fmt.Sprintf("parent IN [%s]", strings.Join(dfs, ","))
|
||||||
|
task, err := m.Client.Index(m.IndexUid).DeleteDocumentsByFilter(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
taskStatus, err := m.getTaskStatus(ctx, task.TaskUID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if taskStatus != meilisearch.TaskStatusSucceeded {
|
||||||
|
return fmt.Errorf("DelDir failed, task status is %s", taskStatus)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) Del(ctx context.Context, prefix string) error {
|
||||||
|
prefix = utils.FixAndCleanPath(prefix)
|
||||||
|
dir, name := path.Split(prefix)
|
||||||
|
get, err := m.getDocumentsByParent(ctx, dir[:len(dir)-1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var document *searchDocument
|
||||||
|
for _, v := range get {
|
||||||
|
if v.Name == name {
|
||||||
|
document = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if document == nil {
|
||||||
|
// Defensive programming. Document may be the folder, try deleting Child
|
||||||
|
return m.DelDirChild(ctx, prefix)
|
||||||
|
}
|
||||||
|
if document.IsDir {
|
||||||
|
err = m.DelDirChild(ctx, prefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
task, err := m.Client.Index(m.IndexUid).DeleteDocument(document.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
taskStatus, err := m.getTaskStatus(ctx, task.TaskUID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if taskStatus != meilisearch.TaskStatusSucceeded {
|
||||||
|
return fmt.Errorf("DelDir failed, task status is %s", taskStatus)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) Release(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) Clear(ctx context.Context) error {
|
||||||
|
_, err := m.Client.Index(m.IndexUid).DeleteAllDocuments()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meilisearch) getTaskStatus(ctx context.Context, taskUID int64) (meilisearch.TaskStatus, error) {
|
||||||
|
forTask, err := m.Client.WaitForTask(taskUID, meilisearch.WaitParams{
|
||||||
|
Context: ctx,
|
||||||
|
Interval: time.Second,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return meilisearch.TaskStatusUnknown, err
|
||||||
|
}
|
||||||
|
return forTask.Status, nil
|
||||||
|
}
|
@ -29,6 +29,20 @@ func SliceContains[T comparable](arr []T, v T) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SliceAllContains check if slice all contains elements
|
||||||
|
func SliceAllContains[T comparable](arr []T, vs ...T) bool {
|
||||||
|
vsMap := make(map[T]struct{})
|
||||||
|
for _, v := range arr {
|
||||||
|
vsMap[v] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, v := range vs {
|
||||||
|
if _, ok := vsMap[v]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// SliceConvert convert slice to another type slice
|
// SliceConvert convert slice to another type slice
|
||||||
func SliceConvert[S any, D any](srcS []S, convert func(src S) (D, error)) ([]D, error) {
|
func SliceConvert[S any, D any](srcS []S, convert func(src S) (D, error)) ([]D, error) {
|
||||||
res := make([]D, 0, len(srcS))
|
res := make([]D, 0, len(srcS))
|
||||||
@ -79,3 +93,9 @@ func SliceFilter[T any](arr []T, filter func(src T) bool) []T {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SliceReplace[T any](arr []T, replace func(src T) T) {
|
||||||
|
for i, src := range arr {
|
||||||
|
arr[i] = replace(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,5 +2,5 @@ package public
|
|||||||
|
|
||||||
import "embed"
|
import "embed"
|
||||||
|
|
||||||
//go:embed dist
|
//go:embed all:dist
|
||||||
var Public embed.FS
|
var Public embed.FS
|
||||||
|
@ -51,6 +51,7 @@ func UpdateIndex(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
if !search.Config(c).AutoUpdate {
|
if !search.Config(c).AutoUpdate {
|
||||||
common.ErrorStrResp(c, "update is not supported for current index", 400)
|
common.ErrorStrResp(c, "update is not supported for current index", 400)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -50,31 +50,13 @@ func loginLdap(c *gin.Context, req *LoginReq) {
|
|||||||
ldapUserSearchBase := setting.GetStr(conf.LdapUserSearchBase)
|
ldapUserSearchBase := setting.GetStr(conf.LdapUserSearchBase)
|
||||||
ldapUserSearchFilter := setting.GetStr(conf.LdapUserSearchFilter) // (uid=%s)
|
ldapUserSearchFilter := setting.GetStr(conf.LdapUserSearchFilter) // (uid=%s)
|
||||||
|
|
||||||
var tlsEnabled bool = false
|
// Connect to LdapServer
|
||||||
if strings.HasPrefix(ldapServer, "ldaps://") {
|
l, err := dial(ldapServer)
|
||||||
tlsEnabled = true
|
|
||||||
ldapServer = strings.TrimPrefix(ldapServer, "ldaps://")
|
|
||||||
} else if strings.HasPrefix(ldapServer, "ldap://") {
|
|
||||||
ldapServer = strings.TrimPrefix(ldapServer, "ldap://")
|
|
||||||
}
|
|
||||||
|
|
||||||
l, err := ldap.Dial("tcp", ldapServer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log.Errorf("failed to connect to LDAP: %v", err)
|
utils.Log.Errorf("failed to connect to LDAP: %v", err)
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer l.Close()
|
|
||||||
|
|
||||||
if tlsEnabled {
|
|
||||||
// Reconnect with TLS
|
|
||||||
err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
|
|
||||||
if err != nil {
|
|
||||||
utils.Log.Errorf("failed to start tls: %v", err)
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// First bind with a read only user
|
// First bind with a read only user
|
||||||
if ldapManagerDN != "" && ldapManagerPassword != "" {
|
if ldapManagerDN != "" && ldapManagerPassword != "" {
|
||||||
@ -157,3 +139,19 @@ func ladpRegister(username string) (*model.User, error) {
|
|||||||
}
|
}
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dial(ldapServer string) (*ldap.Conn, error) {
|
||||||
|
var tlsEnabled bool = false
|
||||||
|
if strings.HasPrefix(ldapServer, "ldaps://") {
|
||||||
|
tlsEnabled = true
|
||||||
|
ldapServer = strings.TrimPrefix(ldapServer, "ldaps://")
|
||||||
|
} else if strings.HasPrefix(ldapServer, "ldap://") {
|
||||||
|
ldapServer = strings.TrimPrefix(ldapServer, "ldap://")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsEnabled {
|
||||||
|
return ldap.DialTLS("tcp", ldapServer, &tls.Config{InsecureSkipVerify: true})
|
||||||
|
} else {
|
||||||
|
return ldap.Dial("tcp", ldapServer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package handles
|
package handles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/fs"
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
@ -23,12 +25,17 @@ func getTaskInfo[T tache.TaskWithInfo](task T) TaskInfo {
|
|||||||
if task.GetErr() != nil {
|
if task.GetErr() != nil {
|
||||||
errMsg = task.GetErr().Error()
|
errMsg = task.GetErr().Error()
|
||||||
}
|
}
|
||||||
|
progress := task.GetProgress()
|
||||||
|
// if progress is NaN, set it to 100
|
||||||
|
if math.IsNaN(progress) {
|
||||||
|
progress = 100
|
||||||
|
}
|
||||||
return TaskInfo{
|
return TaskInfo{
|
||||||
ID: task.GetID(),
|
ID: task.GetID(),
|
||||||
Name: task.GetName(),
|
Name: task.GetName(),
|
||||||
State: task.GetState(),
|
State: task.GetState(),
|
||||||
Status: task.GetStatus(),
|
Status: task.GetStatus(),
|
||||||
Progress: task.GetProgress(),
|
Progress: progress,
|
||||||
Error: errMsg,
|
Error: errMsg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ func Init(e *gin.Engine) {
|
|||||||
g.Use(middlewares.MaxAllowed(conf.Conf.MaxConnections))
|
g.Use(middlewares.MaxAllowed(conf.Conf.MaxConnections))
|
||||||
}
|
}
|
||||||
WebDav(g.Group("/dav"))
|
WebDav(g.Group("/dav"))
|
||||||
|
S3(g.Group("/s3"))
|
||||||
|
|
||||||
g.GET("/d/*path", middlewares.Down, handles.Down)
|
g.GET("/d/*path", middlewares.Down, handles.Down)
|
||||||
g.GET("/p/*path", middlewares.Down, handles.Proxy)
|
g.GET("/p/*path", middlewares.Down, handles.Proxy)
|
||||||
|
29
server/s3.go
Normal file
29
server/s3.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/alist-org/alist/v3/server/s3"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func S3(g *gin.RouterGroup) {
|
||||||
|
if !setting.GetBool(conf.S3Enabled) {
|
||||||
|
g.Any("/*path", func(c *gin.Context) {
|
||||||
|
common.ErrorStrResp(c, "S3 server is not enabled", 403)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h, _ := s3.NewServer(context.Background(), []string{setting.GetStr(conf.S3AccessKeyId) + "," + setting.GetStr(conf.S3SecretAccessKey)})
|
||||||
|
|
||||||
|
g.Any("/*path", func(c *gin.Context) {
|
||||||
|
adjustedPath := strings.TrimPrefix(c.Request.URL.Path, path.Join(conf.URL.Path, "/s3"))
|
||||||
|
c.Request.URL.Path = adjustedPath
|
||||||
|
gin.WrapH(h)(c)
|
||||||
|
})
|
||||||
|
}
|
432
server/s3/backend.go
Normal file
432
server/s3/backend.go
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||||
|
// Package s3 implements a fake s3 server for alist
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Mikubill/gofakes3"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/internal/stream"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/ncw/swift/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
emptyPrefix = &gofakes3.Prefix{}
|
||||||
|
timeFormat = "Mon, 2 Jan 2006 15:04:05.999999999 GMT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// s3Backend implements the gofacess3.Backend interface to make an S3
|
||||||
|
// backend for gofakes3
|
||||||
|
type s3Backend struct {
|
||||||
|
meta *sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBackend creates a new SimpleBucketBackend.
|
||||||
|
func newBackend() gofakes3.Backend {
|
||||||
|
return &s3Backend{
|
||||||
|
meta: new(sync.Map),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListBuckets always returns the default bucket.
|
||||||
|
func (b *s3Backend) ListBuckets() ([]gofakes3.BucketInfo, error) {
|
||||||
|
buckets, err := getAndParseBuckets()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var response []gofakes3.BucketInfo
|
||||||
|
ctx := context.Background()
|
||||||
|
for _, b := range buckets {
|
||||||
|
node, _ := fs.Get(ctx, b.Path, &fs.GetArgs{})
|
||||||
|
response = append(response, gofakes3.BucketInfo{
|
||||||
|
// Name: gofakes3.URLEncode(b.Name),
|
||||||
|
Name: b.Name,
|
||||||
|
CreationDate: gofakes3.NewContentTime(node.ModTime()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListBucket lists the objects in the given bucket.
|
||||||
|
func (b *s3Backend) ListBucket(bucketName string, prefix *gofakes3.Prefix, page gofakes3.ListBucketPage) (*gofakes3.ObjectList, error) {
|
||||||
|
bucket, err := getBucketByName(bucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bucketPath := bucket.Path
|
||||||
|
|
||||||
|
if prefix == nil {
|
||||||
|
prefix = emptyPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// workaround
|
||||||
|
if strings.TrimSpace(prefix.Prefix) == "" {
|
||||||
|
prefix.HasPrefix = false
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(prefix.Delimiter) == "" {
|
||||||
|
prefix.HasDelimiter = false
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gofakes3.NewObjectList()
|
||||||
|
path, remaining := prefixParser(prefix)
|
||||||
|
|
||||||
|
err = b.entryListR(bucketPath, path, remaining, prefix.HasDelimiter, response)
|
||||||
|
if err == gofakes3.ErrNoSuchKey {
|
||||||
|
// AWS just returns an empty list
|
||||||
|
response = gofakes3.NewObjectList()
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.pager(response, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadObject returns the fileinfo for the given object name.
|
||||||
|
//
|
||||||
|
// Note that the metadata is not supported yet.
|
||||||
|
func (b *s3Backend) HeadObject(bucketName, objectName string) (*gofakes3.Object, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
bucket, err := getBucketByName(bucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bucketPath := bucket.Path
|
||||||
|
|
||||||
|
fp := path.Join(bucketPath, objectName)
|
||||||
|
fmeta, _ := op.GetNearestMeta(fp)
|
||||||
|
node, err := fs.Get(context.WithValue(ctx, "meta", fmeta), fp, &fs.GetArgs{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, gofakes3.KeyNotFound(objectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.IsDir() {
|
||||||
|
return nil, gofakes3.KeyNotFound(objectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
size := node.GetSize()
|
||||||
|
// hash := getFileHashByte(fobj)
|
||||||
|
|
||||||
|
meta := map[string]string{
|
||||||
|
"Last-Modified": node.ModTime().Format(timeFormat),
|
||||||
|
"Content-Type": utils.GetMimeType(fp),
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := b.meta.Load(fp); ok {
|
||||||
|
metaMap := val.(map[string]string)
|
||||||
|
for k, v := range metaMap {
|
||||||
|
meta[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gofakes3.Object{
|
||||||
|
Name: objectName,
|
||||||
|
// Hash: hash,
|
||||||
|
Metadata: meta,
|
||||||
|
Size: size,
|
||||||
|
Contents: noOpReadCloser{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetObject fetchs the object from the filesystem.
|
||||||
|
func (b *s3Backend) GetObject(bucketName, objectName string, rangeRequest *gofakes3.ObjectRangeRequest) (obj *gofakes3.Object, err error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
bucket, err := getBucketByName(bucketName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bucketPath := bucket.Path
|
||||||
|
|
||||||
|
fp := path.Join(bucketPath, objectName)
|
||||||
|
fmeta, _ := op.GetNearestMeta(fp)
|
||||||
|
node, err := fs.Get(context.WithValue(ctx, "meta", fmeta), fp, &fs.GetArgs{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, gofakes3.KeyNotFound(objectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.IsDir() {
|
||||||
|
return nil, gofakes3.KeyNotFound(objectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
link, file, err := fs.Link(ctx, fp, model.LinkArgs{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
size := file.GetSize()
|
||||||
|
rnge, err := rangeRequest.Range(size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if link.RangeReadCloser == nil && link.MFile == nil && len(link.URL) == 0 {
|
||||||
|
return nil, fmt.Errorf("the remote storage driver need to be enhanced to support s3")
|
||||||
|
}
|
||||||
|
remoteFileSize := file.GetSize()
|
||||||
|
remoteClosers := utils.EmptyClosers()
|
||||||
|
rangeReaderFunc := func(ctx context.Context, start, length int64) (io.ReadCloser, error) {
|
||||||
|
if length >= 0 && start+length >= remoteFileSize {
|
||||||
|
length = -1
|
||||||
|
}
|
||||||
|
rrc := link.RangeReadCloser
|
||||||
|
if len(link.URL) > 0 {
|
||||||
|
|
||||||
|
rangedRemoteLink := &model.Link{
|
||||||
|
URL: link.URL,
|
||||||
|
Header: link.Header,
|
||||||
|
}
|
||||||
|
var converted, err = stream.GetRangeReadCloserFromLink(remoteFileSize, rangedRemoteLink)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rrc = converted
|
||||||
|
}
|
||||||
|
if rrc != nil {
|
||||||
|
remoteReader, err := rrc.RangeRead(ctx, http_range.Range{Start: start, Length: length})
|
||||||
|
remoteClosers.AddClosers(rrc.GetClosers())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return remoteReader, nil
|
||||||
|
}
|
||||||
|
if link.MFile != nil {
|
||||||
|
_, err := link.MFile.Seek(start, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//remoteClosers.Add(remoteLink.MFile)
|
||||||
|
//keep reuse same MFile and close at last.
|
||||||
|
remoteClosers.Add(link.MFile)
|
||||||
|
return io.NopCloser(link.MFile), nil
|
||||||
|
}
|
||||||
|
return nil, errs.NotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
var rdr io.ReadCloser
|
||||||
|
if rnge != nil {
|
||||||
|
rdr, err = rangeReaderFunc(ctx, rnge.Start, rnge.Length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rdr, err = rangeReaderFunc(ctx, 0, -1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := map[string]string{
|
||||||
|
"Last-Modified": node.ModTime().Format(timeFormat),
|
||||||
|
"Content-Type": utils.GetMimeType(fp),
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := b.meta.Load(fp); ok {
|
||||||
|
metaMap := val.(map[string]string)
|
||||||
|
for k, v := range metaMap {
|
||||||
|
meta[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gofakes3.Object{
|
||||||
|
// Name: gofakes3.URLEncode(objectName),
|
||||||
|
Name: objectName,
|
||||||
|
// Hash: "",
|
||||||
|
Metadata: meta,
|
||||||
|
Size: size,
|
||||||
|
Range: rnge,
|
||||||
|
Contents: rdr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TouchObject creates or updates meta on specified object.
|
||||||
|
func (b *s3Backend) TouchObject(fp string, meta map[string]string) (result gofakes3.PutObjectResult, err error) {
|
||||||
|
//TODO: implement
|
||||||
|
return result, gofakes3.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutObject creates or overwrites the object with the given name.
|
||||||
|
func (b *s3Backend) PutObject(
|
||||||
|
bucketName, objectName string,
|
||||||
|
meta map[string]string,
|
||||||
|
input io.Reader, size int64,
|
||||||
|
) (result gofakes3.PutObjectResult, err error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
bucket, err := getBucketByName(bucketName)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
bucketPath := bucket.Path
|
||||||
|
|
||||||
|
fp := path.Join(bucketPath, objectName)
|
||||||
|
reqPath := path.Dir(fp)
|
||||||
|
fmeta, _ := op.GetNearestMeta(fp)
|
||||||
|
_, err = fs.Get(context.WithValue(ctx, "meta", fmeta), reqPath, &fs.GetArgs{})
|
||||||
|
if err != nil {
|
||||||
|
return result, gofakes3.KeyNotFound(objectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ti time.Time
|
||||||
|
|
||||||
|
if val, ok := meta["X-Amz-Meta-Mtime"]; ok {
|
||||||
|
ti, _ = swift.FloatStringToTime(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := meta["mtime"]; ok {
|
||||||
|
ti, _ = swift.FloatStringToTime(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := model.Object{
|
||||||
|
Name: path.Base(fp),
|
||||||
|
Size: size,
|
||||||
|
Modified: ti,
|
||||||
|
Ctime: time.Now(),
|
||||||
|
}
|
||||||
|
stream := &stream.FileStream{
|
||||||
|
Obj: &obj,
|
||||||
|
Reader: input,
|
||||||
|
Mimetype: meta["Content-Type"],
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fs.PutDirectly(ctx, path.Dir(reqPath), stream)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stream.Close(); err != nil {
|
||||||
|
// remove file when close error occurred (FsPutErr)
|
||||||
|
_ = fs.Remove(ctx, fp)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.meta.Store(fp, meta)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMulti deletes multiple objects in a single request.
|
||||||
|
func (b *s3Backend) DeleteMulti(bucketName string, objects ...string) (result gofakes3.MultiDeleteResult, rerr error) {
|
||||||
|
for _, object := range objects {
|
||||||
|
if err := b.deleteObject(bucketName, object); err != nil {
|
||||||
|
utils.Log.Errorf("serve s3", "delete object failed: %v", err)
|
||||||
|
result.Error = append(result.Error, gofakes3.ErrorResult{
|
||||||
|
Code: gofakes3.ErrInternal,
|
||||||
|
Message: gofakes3.ErrInternal.Message(),
|
||||||
|
Key: object,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
result.Deleted = append(result.Deleted, gofakes3.ObjectID{
|
||||||
|
Key: object,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteObject deletes the object with the given name.
|
||||||
|
func (b *s3Backend) DeleteObject(bucketName, objectName string) (result gofakes3.ObjectDeleteResult, rerr error) {
|
||||||
|
return result, b.deleteObject(bucketName, objectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteObject deletes the object from the filesystem.
|
||||||
|
func (b *s3Backend) deleteObject(bucketName, objectName string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
bucket, err := getBucketByName(bucketName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bucketPath := bucket.Path
|
||||||
|
|
||||||
|
fp := path.Join(bucketPath, objectName)
|
||||||
|
fmeta, _ := op.GetNearestMeta(fp)
|
||||||
|
// S3 does not report an error when attemping to delete a key that does not exist, so
|
||||||
|
// we need to skip IsNotExist errors.
|
||||||
|
if _, err := fs.Get(context.WithValue(ctx, "meta", fmeta), fp, &fs.GetArgs{}); err != nil && !errs.IsObjectNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.Remove(ctx, fp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucket creates a new bucket.
|
||||||
|
func (b *s3Backend) CreateBucket(name string) error {
|
||||||
|
return gofakes3.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBucket deletes the bucket with the given name.
|
||||||
|
func (b *s3Backend) DeleteBucket(name string) error {
|
||||||
|
return gofakes3.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// BucketExists checks if the bucket exists.
|
||||||
|
func (b *s3Backend) BucketExists(name string) (exists bool, err error) {
|
||||||
|
buckets, err := getAndParseBuckets()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, b := range buckets {
|
||||||
|
if b.Name == name {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyObject copy specified object from srcKey to dstKey.
|
||||||
|
func (b *s3Backend) CopyObject(srcBucket, srcKey, dstBucket, dstKey string, meta map[string]string) (result gofakes3.CopyObjectResult, err error) {
|
||||||
|
if srcBucket == dstBucket && srcKey == dstKey {
|
||||||
|
//TODO: update meta
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
srcB, err := getBucketByName(srcBucket)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
srcBucketPath := srcB.Path
|
||||||
|
|
||||||
|
srcFp := path.Join(srcBucketPath, srcKey)
|
||||||
|
fmeta, _ := op.GetNearestMeta(srcFp)
|
||||||
|
srcNode, err := fs.Get(context.WithValue(ctx, "meta", fmeta), srcFp, &fs.GetArgs{})
|
||||||
|
|
||||||
|
c, err := b.GetObject(srcBucket, srcKey, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = c.Contents.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for k, v := range c.Metadata {
|
||||||
|
if _, found := meta[k]; !found && k != "X-Amz-Acl" {
|
||||||
|
meta[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := meta["mtime"]; !ok {
|
||||||
|
meta["mtime"] = swift.TimeToFloatString(srcNode.ModTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = b.PutObject(dstBucket, dstKey, meta, c.Contents, c.Size)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return gofakes3.CopyObjectResult{
|
||||||
|
ETag: `"` + hex.EncodeToString(c.Hash) + `"`,
|
||||||
|
LastModified: gofakes3.NewContentTime(srcNode.ModTime()),
|
||||||
|
}, nil
|
||||||
|
}
|
36
server/s3/ioutils.go
Normal file
36
server/s3/ioutils.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||||
|
// Package s3 implements a fake s3 server for alist
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type noOpReadCloser struct{}
|
||||||
|
|
||||||
|
type readerWithCloser struct {
|
||||||
|
io.Reader
|
||||||
|
closer func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ io.ReadCloser = &readerWithCloser{}
|
||||||
|
|
||||||
|
func (d noOpReadCloser) Read(b []byte) (n int, err error) {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d noOpReadCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func limitReadCloser(rdr io.Reader, closer func() error, sz int64) io.ReadCloser {
|
||||||
|
return &readerWithCloser{
|
||||||
|
Reader: io.LimitReader(rdr, sz),
|
||||||
|
closer: closer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwc *readerWithCloser) Close() error {
|
||||||
|
if rwc.closer != nil {
|
||||||
|
return rwc.closer()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
53
server/s3/list.go
Normal file
53
server/s3/list.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||||
|
// Package s3 implements a fake s3 server for alist
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Mikubill/gofakes3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *s3Backend) entryListR(bucket, fdPath, name string, addPrefix bool, response *gofakes3.ObjectList) error {
|
||||||
|
fp := path.Join(bucket, fdPath)
|
||||||
|
|
||||||
|
dirEntries, err := getDirEntries(fp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range dirEntries {
|
||||||
|
object := entry.GetName()
|
||||||
|
|
||||||
|
// workround for control-chars detect
|
||||||
|
objectPath := path.Join(fdPath, object)
|
||||||
|
|
||||||
|
if !strings.HasPrefix(object, name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.IsDir() {
|
||||||
|
if addPrefix {
|
||||||
|
// response.AddPrefix(gofakes3.URLEncode(objectPath))
|
||||||
|
response.AddPrefix(objectPath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := b.entryListR(bucket, path.Join(fdPath, object), "", false, response)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item := &gofakes3.Content{
|
||||||
|
// Key: gofakes3.URLEncode(objectPath),
|
||||||
|
Key: objectPath,
|
||||||
|
LastModified: gofakes3.NewContentTime(entry.ModTime()),
|
||||||
|
ETag: getFileHash(entry),
|
||||||
|
Size: entry.GetSize(),
|
||||||
|
StorageClass: gofakes3.StorageStandard,
|
||||||
|
}
|
||||||
|
response.Add(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
27
server/s3/logger.go
Normal file
27
server/s3/logger.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||||
|
// Package s3 implements a fake s3 server for alist
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Mikubill/gofakes3"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// logger output formatted message
|
||||||
|
type logger struct{}
|
||||||
|
|
||||||
|
// print log message
|
||||||
|
func (l logger) Print(level gofakes3.LogLevel, v ...interface{}) {
|
||||||
|
switch level {
|
||||||
|
default:
|
||||||
|
fallthrough
|
||||||
|
case gofakes3.LogErr:
|
||||||
|
utils.Log.Errorf("serve s3: %s", fmt.Sprintln(v...))
|
||||||
|
case gofakes3.LogWarn:
|
||||||
|
utils.Log.Infof("serve s3: %s", fmt.Sprintln(v...))
|
||||||
|
case gofakes3.LogInfo:
|
||||||
|
utils.Log.Debugf("serve s3: %s", fmt.Sprintln(v...))
|
||||||
|
}
|
||||||
|
}
|
67
server/s3/pager.go
Normal file
67
server/s3/pager.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||||
|
// Package s3 implements a fake s3 server for alist
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/Mikubill/gofakes3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pager splits the object list into smulitply pages.
|
||||||
|
func (db *s3Backend) pager(list *gofakes3.ObjectList, page gofakes3.ListBucketPage) (*gofakes3.ObjectList, error) {
|
||||||
|
// sort by alphabet
|
||||||
|
sort.Slice(list.CommonPrefixes, func(i, j int) bool {
|
||||||
|
return list.CommonPrefixes[i].Prefix < list.CommonPrefixes[j].Prefix
|
||||||
|
})
|
||||||
|
// sort by modtime
|
||||||
|
sort.Slice(list.Contents, func(i, j int) bool {
|
||||||
|
return list.Contents[i].LastModified.Before(list.Contents[j].LastModified.Time)
|
||||||
|
})
|
||||||
|
tokens := page.MaxKeys
|
||||||
|
if tokens == 0 {
|
||||||
|
tokens = 1000
|
||||||
|
}
|
||||||
|
if page.HasMarker {
|
||||||
|
for i, obj := range list.Contents {
|
||||||
|
if obj.Key == page.Marker {
|
||||||
|
list.Contents = list.Contents[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, obj := range list.CommonPrefixes {
|
||||||
|
if obj.Prefix == page.Marker {
|
||||||
|
list.CommonPrefixes = list.CommonPrefixes[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gofakes3.NewObjectList()
|
||||||
|
for _, obj := range list.CommonPrefixes {
|
||||||
|
if tokens <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
response.AddPrefix(obj.Prefix)
|
||||||
|
tokens--
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, obj := range list.Contents {
|
||||||
|
if tokens <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
response.Add(obj)
|
||||||
|
tokens--
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(list.CommonPrefixes)+len(list.Contents) > int(page.MaxKeys) {
|
||||||
|
response.IsTruncated = true
|
||||||
|
if len(response.Contents) > 0 {
|
||||||
|
response.NextMarker = response.Contents[len(response.Contents)-1].Key
|
||||||
|
} else {
|
||||||
|
response.NextMarker = response.CommonPrefixes[len(response.CommonPrefixes)-1].Prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
27
server/s3/server.go
Normal file
27
server/s3/server.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||||
|
// Package s3 implements a fake s3 server for alist
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/Mikubill/gofakes3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Make a new S3 Server to serve the remote
|
||||||
|
func NewServer(ctx context.Context, authpair []string) (h http.Handler, err error) {
|
||||||
|
var newLogger logger
|
||||||
|
faker := gofakes3.New(
|
||||||
|
newBackend(),
|
||||||
|
// gofakes3.WithHostBucket(!opt.pathBucketMode),
|
||||||
|
gofakes3.WithLogger(newLogger),
|
||||||
|
gofakes3.WithRequestID(rand.Uint64()),
|
||||||
|
gofakes3.WithoutVersioning(),
|
||||||
|
gofakes3.WithV4Auth(authlistResolver(authpair)),
|
||||||
|
gofakes3.WithIntegrityCheck(true), // Check Content-MD5 if supplied
|
||||||
|
)
|
||||||
|
|
||||||
|
return faker.Server(), nil
|
||||||
|
}
|
164
server/s3/utils.go
Normal file
164
server/s3/utils.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||||
|
// Package s3 implements a fake s3 server for alist
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Mikubill/gofakes3"
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bucket struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAndParseBuckets() ([]Bucket, error) {
|
||||||
|
var res []Bucket
|
||||||
|
err := json.Unmarshal([]byte(setting.GetStr(conf.S3Buckets)), &res)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBucketByName(name string) (Bucket, error) {
|
||||||
|
buckets, err := getAndParseBuckets()
|
||||||
|
if err != nil {
|
||||||
|
return Bucket{}, err
|
||||||
|
}
|
||||||
|
for _, b := range buckets {
|
||||||
|
if b.Name == name {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Bucket{}, gofakes3.BucketNotFound(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDirEntries(path string) ([]model.Obj, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
meta, _ := op.GetNearestMeta(path)
|
||||||
|
fi, err := fs.Get(context.WithValue(ctx, "meta", meta), path, &fs.GetArgs{})
|
||||||
|
if errs.IsNotFoundError(err) {
|
||||||
|
return nil, gofakes3.ErrNoSuchKey
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, gofakes3.ErrNoSuchKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return nil, gofakes3.ErrNoSuchKey
|
||||||
|
}
|
||||||
|
|
||||||
|
dirEntries, err := fs.List(context.WithValue(ctx, "meta", meta), path, &fs.ListArgs{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirEntries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func getFileHashByte(node interface{}) []byte {
|
||||||
|
// b, err := hex.DecodeString(getFileHash(node))
|
||||||
|
// if err != nil {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
// return b
|
||||||
|
// }
|
||||||
|
|
||||||
|
func getFileHash(node interface{}) string {
|
||||||
|
// var o fs.Object
|
||||||
|
|
||||||
|
// switch b := node.(type) {
|
||||||
|
// case vfs.Node:
|
||||||
|
// fsObj, ok := b.DirEntry().(fs.Object)
|
||||||
|
// if !ok {
|
||||||
|
// fs.Debugf("serve s3", "File uploading - reading hash from VFS cache")
|
||||||
|
// in, err := b.Open(os.O_RDONLY)
|
||||||
|
// if err != nil {
|
||||||
|
// return ""
|
||||||
|
// }
|
||||||
|
// defer func() {
|
||||||
|
// _ = in.Close()
|
||||||
|
// }()
|
||||||
|
// h, err := hash.NewMultiHasherTypes(hash.NewHashSet(hash.MD5))
|
||||||
|
// if err != nil {
|
||||||
|
// return ""
|
||||||
|
// }
|
||||||
|
// _, err = io.Copy(h, in)
|
||||||
|
// if err != nil {
|
||||||
|
// return ""
|
||||||
|
// }
|
||||||
|
// return h.Sums()[hash.MD5]
|
||||||
|
// }
|
||||||
|
// o = fsObj
|
||||||
|
// case fs.Object:
|
||||||
|
// o = b
|
||||||
|
// }
|
||||||
|
|
||||||
|
// hash, err := o.Hash(context.Background(), hash.MD5)
|
||||||
|
// if err != nil {
|
||||||
|
// return ""
|
||||||
|
// }
|
||||||
|
// return hash
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func prefixParser(p *gofakes3.Prefix) (path, remaining string) {
|
||||||
|
idx := strings.LastIndexByte(p.Prefix, '/')
|
||||||
|
if idx < 0 {
|
||||||
|
return "", p.Prefix
|
||||||
|
}
|
||||||
|
return p.Prefix[:idx], p.Prefix[idx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// // FIXME this could be implemented by VFS.MkdirAll()
|
||||||
|
// func mkdirRecursive(path string, VFS *vfs.VFS) error {
|
||||||
|
// path = strings.Trim(path, "/")
|
||||||
|
// dirs := strings.Split(path, "/")
|
||||||
|
// dir := ""
|
||||||
|
// for _, d := range dirs {
|
||||||
|
// dir += "/" + d
|
||||||
|
// if _, err := VFS.Stat(dir); err != nil {
|
||||||
|
// err := VFS.Mkdir(dir, 0777)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func rmdirRecursive(p string, VFS *vfs.VFS) {
|
||||||
|
// dir := path.Dir(p)
|
||||||
|
// if !strings.ContainsAny(dir, "/\\") {
|
||||||
|
// // might be bucket(root)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if _, err := VFS.Stat(dir); err == nil {
|
||||||
|
// err := VFS.Remove(dir)
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// rmdirRecursive(dir, VFS)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func authlistResolver(list []string) map[string]string {
|
||||||
|
authList := make(map[string]string)
|
||||||
|
for _, v := range list {
|
||||||
|
parts := strings.Split(v, ",")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
utils.Log.Infof(fmt.Sprintf("Ignored: invalid auth pair %s", v))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
authList[parts[0]] = parts[1]
|
||||||
|
}
|
||||||
|
return authList
|
||||||
|
}
|
@ -3,7 +3,6 @@ package static
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/alist-org/alist/v3/public"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -13,14 +12,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/setting"
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/alist-org/alist/v3/public"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var static fs.FS = public.Public
|
var static fs.FS
|
||||||
|
|
||||||
func initStatic() {
|
func initStatic() {
|
||||||
if conf.Conf.DistDir == "" {
|
if conf.Conf.DistDir == "" {
|
||||||
dist, err := fs.Sub(static, "dist")
|
dist, err := fs.Sub(public.Public, "dist")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log.Fatalf("failed to read dist dir")
|
utils.Log.Fatalf("failed to read dist dir")
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
)
|
)
|
||||||
@ -384,7 +386,11 @@ func findLastModified(ctx context.Context, ls LockSystem, name string, fi model.
|
|||||||
return fi.ModTime().UTC().Format(http.TimeFormat), nil
|
return fi.ModTime().UTC().Format(http.TimeFormat), nil
|
||||||
}
|
}
|
||||||
func findCreationDate(ctx context.Context, ls LockSystem, name string, fi model.Obj) (string, error) {
|
func findCreationDate(ctx context.Context, ls LockSystem, name string, fi model.Obj) (string, error) {
|
||||||
return fi.CreateTime().UTC().Format(http.TimeFormat), nil
|
userAgent := ctx.Value("userAgent").(string)
|
||||||
|
if strings.Contains(strings.ToLower(userAgent), "microsoft-webdav") {
|
||||||
|
return fi.CreateTime().UTC().Format(http.TimeFormat), nil
|
||||||
|
}
|
||||||
|
return fi.CreateTime().UTC().Format(time.RFC3339), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrNotImplemented should be returned by optional interfaces if they
|
// ErrNotImplemented should be returned by optional interfaces if they
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
package webdav // import "golang.org/x/net/webdav"
|
package webdav // import "golang.org/x/net/webdav"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -619,6 +620,8 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
userAgent := r.Header.Get("User-Agent")
|
||||||
|
ctx = context.WithValue(ctx, "userAgent", userAgent)
|
||||||
user := ctx.Value("user").(*model.User)
|
user := ctx.Value("user").(*model.User)
|
||||||
reqPath, err = user.JoinPath(reqPath)
|
reqPath, err = user.JoinPath(reqPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Reference in New Issue
Block a user