Compare commits
76 Commits
Author | SHA1 | Date | |
---|---|---|---|
79df63d319 | |||
ec54831162 | |||
c8f3e8ab4d | |||
4be8524d80 | |||
0d3146b51d | |||
f95d843969 | |||
28aee8c493 | |||
de3ea82eb9 | |||
268ba3d069 | |||
309d6558fb | |||
c08fdfc868 | |||
1b28e6af3e | |||
8655e33e60 | |||
50579fef84 | |||
e39299bfe2 | |||
d1ab2443f1 | |||
658cf368bb | |||
fd36ce59f6 | |||
95b3b87672 | |||
0d07d81802 | |||
923937b530 | |||
09492193c4 | |||
40b26a81a0 | |||
4293a0ba8c | |||
6c2f3486fc | |||
3c7512f64a | |||
84219d3d70 | |||
05d3727335 | |||
ee77c3b113 | |||
fcaf485e0b | |||
bd83469bb1 | |||
90f111b24f | |||
7d1034c569 | |||
236c17176c | |||
6ee4c10e8f | |||
3798634028 | |||
567ba5ccd4 | |||
ae2ee1821a | |||
805b1e4fa3 | |||
d92c10da56 | |||
6659f6d367 | |||
fe416ba15c | |||
de66708b24 | |||
2ca3e0b8bc | |||
ae04a0a760 | |||
c28168c970 | |||
46b2ed2507 | |||
22843ffc70 | |||
e1b6368343 | |||
62dae50d70 | |||
43a8ed472b | |||
d87878c232 | |||
ab7dee49b0 | |||
dca115506d | |||
be17fba0c6 | |||
cd58aa5efe | |||
946833d2cc | |||
eb42d09849 | |||
9d00492750 | |||
b6711d6ab9 | |||
7bc46de8aa | |||
a4f4fb2d73 | |||
a181b56ea7 | |||
d0b743d955 | |||
a985b748e9 | |||
44cb8aaafe | |||
51f5d1b3c4 | |||
36e0d6f787 | |||
3d0065bdcf | |||
7bf8071095 | |||
30d39f8e10 | |||
20d3ef7de6 | |||
86e5dae4d1 | |||
d89b1d4871 | |||
080e6fb22a | |||
e1cd71616d |
19
.github/stale.yml
vendored
Normal file
19
.github/stale.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 44
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 8
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- accepted
|
||||||
|
- security
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: stale
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: >
|
||||||
|
This issue was closed due to inactive more than 52 days. You can reopen or
|
||||||
|
recreate it if you think it should continue. Thank you for your contributions again.
|
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -25,6 +25,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
sudo snap install zig --classic --beta
|
||||||
docker pull crazymax/xgo:latest
|
docker pull crazymax/xgo:latest
|
||||||
go install github.com/crazy-max/xgo@latest
|
go install github.com/crazy-max/xgo@latest
|
||||||
sudo apt install upx
|
sudo apt install upx
|
||||||
|
2
.github/workflows/build_docker.yml
vendored
2
.github/workflows/build_docker.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
|
17
.github/workflows/issue_check_inactive.yml
vendored
17
.github/workflows/issue_check_inactive.yml
vendored
@ -1,17 +0,0 @@
|
|||||||
name: Check inactive
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 1 * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check-inactive:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: check-inactive
|
|
||||||
uses: actions-cool/issues-helper@v3
|
|
||||||
with:
|
|
||||||
actions: 'check-inactive'
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
inactive-day: 30
|
|
||||||
body: Hello, this issue has been inactive for more than 30 days and will be closed if inactive for another 30 days.
|
|
21
.github/workflows/issue_close_inactive.yml
vendored
21
.github/workflows/issue_close_inactive.yml
vendored
@ -1,21 +0,0 @@
|
|||||||
name: Close inactive
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 */7 * *"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
close-inactive:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: close-issues
|
|
||||||
uses: actions-cool/issues-helper@v3
|
|
||||||
with:
|
|
||||||
actions: 'close-issues'
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
labels: 'inactive'
|
|
||||||
inactive-day: 30
|
|
||||||
close-reason: 'not_planned'
|
|
||||||
body: |
|
|
||||||
Hello @${{ github.event.issue.user.login }}, this issue was closed due to inactive more than 60 days. You can reopen or recreate it if you think it should continue.
|
|
2
.github/workflows/issue_question.yml
vendored
2
.github/workflows/issue_question.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
if: github.event.label.name == 'question'
|
if: github.event.label.name == 'question'
|
||||||
steps:
|
steps:
|
||||||
- name: Create comment
|
- name: Create comment
|
||||||
uses: actions-cool/issues-helper@v3.3.3
|
uses: actions-cool/issues-helper@v3.4.0
|
||||||
with:
|
with:
|
||||||
actions: 'create-comment'
|
actions: 'create-comment'
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@ -38,6 +38,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
sudo snap install zig --classic --beta
|
||||||
docker pull crazymax/xgo:latest
|
docker pull crazymax/xgo:latest
|
||||||
go install github.com/crazy-max/xgo@latest
|
go install github.com/crazy-max/xgo@latest
|
||||||
sudo apt install upx
|
sudo apt install upx
|
||||||
|
2
.github/workflows/release_docker.yml
vendored
2
.github/workflows/release_docker.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
|
14
README_cn.md
14
README_cn.md
@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
[English](./README.md) | 中文 | [Contributing](./CONTRIBUTING.md) | [CODE_OF_CONDUCT](./CODE_OF_CONDUCT.md)
|
[English](./README.md) | 中文 | [Contributing](./CONTRIBUTING.md) | [CODE_OF_CONDUCT](./CODE_OF_CONDUCT.md)
|
||||||
|
|
||||||
## Features
|
## 功能
|
||||||
|
|
||||||
- [x] 多种存储
|
- [x] 多种存储
|
||||||
- [x] 本地存储
|
- [x] 本地存储
|
||||||
@ -89,7 +89,7 @@
|
|||||||
- [x] 离线下载
|
- [x] 离线下载
|
||||||
- [x] 跨存储复制文件
|
- [x] 跨存储复制文件
|
||||||
|
|
||||||
## Document
|
## 文档
|
||||||
|
|
||||||
<https://alist.nn.ci/zh/>
|
<https://alist.nn.ci/zh/>
|
||||||
|
|
||||||
@ -97,21 +97,21 @@
|
|||||||
|
|
||||||
<https://al.nn.ci>
|
<https://al.nn.ci>
|
||||||
|
|
||||||
## Discussion
|
## 讨论
|
||||||
|
|
||||||
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) ,**issue仅针对错误报告和功能请求。**
|
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) ,**issue仅针对错误报告和功能请求。**
|
||||||
|
|
||||||
## Sponsor
|
## 赞助
|
||||||
|
|
||||||
AList 是一个开源软件,如果你碰巧喜欢这个项目,并希望我继续下去,请考虑赞助我或提供一个单一的捐款!感谢所有的爱和支持:https://alist.nn.ci/zh/guide/sponsor.html
|
AList 是一个开源软件,如果你碰巧喜欢这个项目,并希望我继续下去,请考虑赞助我或提供一个单一的捐款!感谢所有的爱和支持:https://alist.nn.ci/zh/guide/sponsor.html
|
||||||
|
|
||||||
### Special sponsors
|
### 特别赞助
|
||||||
|
|
||||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/)
|
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/)
|
||||||
- [KinhDown 百度云盘不限速下载!永久免费!已稳定运行3年!非常可靠!Q群 -> 786799372](https://kinhdown.com)
|
- [KinhDown 百度云盘不限速下载!永久免费!已稳定运行3年!非常可靠!Q群 -> 786799372](https://kinhdown.com)
|
||||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
||||||
|
|
||||||
## Contributors
|
## 贡献者
|
||||||
|
|
||||||
Thanks goes to these wonderful people:
|
Thanks goes to these wonderful people:
|
||||||
|
|
||||||
@ -130,4 +130,4 @@ Thanks goes to these wonderful people:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> [@博客](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@Telegram群](https://t.me/alist_chat) · [@Discord](https://discord.gg/F4ymsH4xv2)
|
> [@博客](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@Telegram群](https://t.me/alist_chat) · [@Discord](https://discord.gg/F4ymsH4xv2)
|
||||||
|
18
build.sh
18
build.sh
@ -1,7 +1,7 @@
|
|||||||
appName="alist"
|
appName="alist"
|
||||||
builtAt="$(date +'%F %T %z')"
|
builtAt="$(date +'%F %T %z')"
|
||||||
goVersion=$(go version | sed 's/go version //')
|
goVersion=$(go version | sed 's/go version //')
|
||||||
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
|
gitAuthor="Xhofe <i@nn.ci>"
|
||||||
gitCommit=$(git log --pretty=format:"%h" -1)
|
gitCommit=$(git log --pretty=format:"%h" -1)
|
||||||
|
|
||||||
if [ "$1" = "dev" ]; then
|
if [ "$1" = "dev" ]; then
|
||||||
@ -41,6 +41,17 @@ FetchWebRelease() {
|
|||||||
rm -rf dist.tar.gz
|
rm -rf dist.tar.gz
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BuildWinArm64() {
|
||||||
|
echo building for windows-arm64
|
||||||
|
chmod +x ./wrapper/zcc-arm64
|
||||||
|
chmod +x ./wrapper/zcxx-arm64
|
||||||
|
export GOOS=windows
|
||||||
|
export GOARCH=arm64
|
||||||
|
export CC=$(pwd)/wrapper/zcc-arm64
|
||||||
|
export CXX=$(pwd)/wrapper/zcxx-arm64
|
||||||
|
go build -o "$1" -ldflags="$ldflags" -tags=jsoniter .
|
||||||
|
}
|
||||||
|
|
||||||
BuildDev() {
|
BuildDev() {
|
||||||
rm -rf .git/
|
rm -rf .git/
|
||||||
xgo -targets=linux/amd64,windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
xgo -targets=linux/amd64,windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||||
@ -48,7 +59,7 @@ BuildDev() {
|
|||||||
mv alist-* dist
|
mv alist-* dist
|
||||||
cd dist
|
cd dist
|
||||||
upx -9 ./alist-linux*
|
upx -9 ./alist-linux*
|
||||||
upx -9 ./alist-windows*
|
upx -9 ./alist-windows-amd64.exe
|
||||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||||
cat md5.txt
|
cat md5.txt
|
||||||
}
|
}
|
||||||
@ -80,10 +91,11 @@ BuildRelease() {
|
|||||||
export CGO_ENABLED=1
|
export CGO_ENABLED=1
|
||||||
go build -o ./build/$appName-$os_arch -ldflags="$muslflags" -tags=jsoniter .
|
go build -o ./build/$appName-$os_arch -ldflags="$muslflags" -tags=jsoniter .
|
||||||
done
|
done
|
||||||
|
BuildWinArm64 ./build/alist-windows-arm64.exe
|
||||||
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||||
# why? Because some target platforms seem to have issues with upx compression
|
# why? Because some target platforms seem to have issues with upx compression
|
||||||
upx -9 ./alist-linux-amd64
|
upx -9 ./alist-linux-amd64
|
||||||
upx -9 ./alist-windows*
|
upx -9 ./alist-windows-amd64.exe
|
||||||
mv alist-* build
|
mv alist-* build
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,10 +78,19 @@ func writeFile(name string, data interface{}) {
|
|||||||
func generateDriversJson() {
|
func generateDriversJson() {
|
||||||
drivers := make(Drivers)
|
drivers := make(Drivers)
|
||||||
drivers["drivers"] = make(KV[interface{}])
|
drivers["drivers"] = make(KV[interface{}])
|
||||||
|
drivers["config"] = make(KV[interface{}])
|
||||||
driverInfoMap := op.GetDriverInfoMap()
|
driverInfoMap := op.GetDriverInfoMap()
|
||||||
for k, v := range driverInfoMap {
|
for k, v := range driverInfoMap {
|
||||||
drivers["drivers"][k] = convert(k)
|
drivers["drivers"][k] = convert(k)
|
||||||
items := make(KV[interface{}])
|
items := make(KV[interface{}])
|
||||||
|
config := map[string]string{}
|
||||||
|
if v.Config.Alert != "" {
|
||||||
|
alert := strings.SplitN(v.Config.Alert, "|", 2)
|
||||||
|
if len(alert) > 1 {
|
||||||
|
config["alert"] = alert[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drivers["config"][k] = config
|
||||||
for i := range v.Additional {
|
for i := range v.Additional {
|
||||||
item := v.Additional[i]
|
item := v.Additional[i]
|
||||||
items[item.Name] = convert(item.Name)
|
items[item.Name] = convert(item.Name)
|
||||||
|
@ -29,6 +29,7 @@ the address is defined in config file`,
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Init()
|
Init()
|
||||||
bootstrap.InitAria2()
|
bootstrap.InitAria2()
|
||||||
|
bootstrap.InitQbittorrent()
|
||||||
bootstrap.LoadStorages()
|
bootstrap.LoadStorages()
|
||||||
if !flags.Debug && !flags.Dev {
|
if !flags.Debug && !flags.Dev {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
@ -96,14 +96,14 @@ func (d *Pan123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
u_ := fmt.Sprintf("https://%s%s", u.Host, u.Path)
|
u_ := u.String()
|
||||||
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Head(u_)
|
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Head(u_)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debug(res.String())
|
log.Debug(res.String())
|
||||||
link := model.Link{
|
link := model.Link{
|
||||||
URL: downloadUrl,
|
URL: u_,
|
||||||
}
|
}
|
||||||
log.Debugln("res code: ", res.StatusCode())
|
log.Debugln("res code: ", res.StatusCode())
|
||||||
if res.StatusCode() == 302 {
|
if res.StatusCode() == 302 {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
@ -77,18 +78,19 @@ 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) {
|
||||||
next := "0"
|
page := 1
|
||||||
res := make([]File, 0)
|
res := make([]File, 0)
|
||||||
for next != "-1" {
|
for {
|
||||||
var resp Files
|
var resp Files
|
||||||
query := map[string]string{
|
query := map[string]string{
|
||||||
"driveId": "0",
|
"driveId": "0",
|
||||||
"limit": "100",
|
"limit": "100",
|
||||||
"next": next,
|
"next": "0",
|
||||||
"orderBy": d.OrderBy,
|
"orderBy": d.OrderBy,
|
||||||
"orderDirection": d.OrderDirection,
|
"orderDirection": d.OrderDirection,
|
||||||
"parentFileId": parentId,
|
"parentFileId": parentId,
|
||||||
"trashed": "false",
|
"trashed": "false",
|
||||||
|
"Page": strconv.Itoa(page),
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/api/file/list/new", http.MethodGet, func(req *resty.Request) {
|
_, err := d.request("https://www.123pan.com/api/file/list/new", http.MethodGet, func(req *resty.Request) {
|
||||||
req.SetQueryParams(query)
|
req.SetQueryParams(query)
|
||||||
@ -96,8 +98,11 @@ func (d *Pan123) getFiles(parentId string) ([]File, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
next = resp.Data.Next
|
page++
|
||||||
res = append(res, resp.Data.InfoList...)
|
res = append(res, resp.Data.InfoList...)
|
||||||
|
if len(resp.Data.InfoList) == 0 || resp.Data.Next == "-1" {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@ -83,8 +83,7 @@ func (d *Yun139) MakeDir(ctx context.Context, parentDir model.Obj, dirName strin
|
|||||||
}
|
}
|
||||||
pathname = "/orchestration/familyCloud/cloudCatalog/v1.0/createCloudDoc"
|
pathname = "/orchestration/familyCloud/cloudCatalog/v1.0/createCloudDoc"
|
||||||
}
|
}
|
||||||
_, err := d.post(pathname,
|
_, err := d.post(pathname, data, nil)
|
||||||
data, nil)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +221,22 @@ func (d *Yun139) Remove(ctx context.Context, obj model.Obj) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ = iota //ignore first value by assigning to blank identifier
|
||||||
|
KB = 1 << (10 * iota)
|
||||||
|
MB
|
||||||
|
GB
|
||||||
|
TB
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPartSize(size int64) int64 {
|
||||||
|
// 网盘对于分片数量存在上限
|
||||||
|
if size/GB > 30 {
|
||||||
|
return 512 * MB
|
||||||
|
}
|
||||||
|
return 100 * MB
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
data := base.Json{
|
data := base.Json{
|
||||||
"manualRename": 2,
|
"manualRename": 2,
|
||||||
@ -267,17 +282,17 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
// Progress
|
// Progress
|
||||||
p := driver.NewProgress(stream.GetSize(), up)
|
p := driver.NewProgress(stream.GetSize(), up)
|
||||||
|
|
||||||
var Default int64 = 104857600
|
var partSize = getPartSize(stream.GetSize())
|
||||||
part := (stream.GetSize() + Default - 1) / Default
|
part := (stream.GetSize() + partSize - 1) / partSize
|
||||||
for i := int64(0); i < part; i++ {
|
for i := int64(0); i < part; i++ {
|
||||||
if utils.IsCanceled(ctx) {
|
if utils.IsCanceled(ctx) {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
start := i * Default
|
start := i * partSize
|
||||||
byteSize := stream.GetSize() - start
|
byteSize := stream.GetSize() - start
|
||||||
if byteSize > Default {
|
if byteSize > partSize {
|
||||||
byteSize = Default
|
byteSize = partSize
|
||||||
}
|
}
|
||||||
|
|
||||||
limitReader := io.LimitReader(stream, byteSize)
|
limitReader := io.LimitReader(stream, byteSize)
|
||||||
@ -301,6 +316,11 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Debugf("%+v", res)
|
log.Debugf("%+v", res)
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package alist_v3
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -55,6 +56,9 @@ func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if resp.Code != 200 {
|
||||||
|
return nil, errors.New(resp.Message)
|
||||||
|
}
|
||||||
var files []model.Obj
|
var files []model.Obj
|
||||||
for _, f := range resp.Data.Content {
|
for _, f := range resp.Data.Content {
|
||||||
file := model.ObjThumb{
|
file := model.ObjThumb{
|
||||||
@ -84,6 +88,9 @@ func (d *AListV3) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if resp.Code != 200 {
|
||||||
|
return nil, errors.New(resp.Message)
|
||||||
|
}
|
||||||
return &model.Link{
|
return &model.Link{
|
||||||
URL: resp.Data.RawURL,
|
URL: resp.Data.RawURL,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -10,7 +10,7 @@ func checkResp(resp common.Resp[interface{}], err error) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.Message == "success" {
|
if resp.Code == 200 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New(resp.Message)
|
return errors.New(resp.Message)
|
||||||
|
@ -31,6 +31,7 @@ type AliDrive struct {
|
|||||||
AccessToken string
|
AccessToken string
|
||||||
cron *cron.Cron
|
cron *cron.Cron
|
||||||
DriveId string
|
DriveId string
|
||||||
|
UserID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliDrive) Config() driver.Config {
|
func (d *AliDrive) Config() driver.Config {
|
||||||
@ -54,6 +55,7 @@ func (d *AliDrive) Init(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.DriveId = utils.Json.Get(res, "default_drive_id").ToString()
|
d.DriveId = utils.Json.Get(res, "default_drive_id").ToString()
|
||||||
|
d.UserID = utils.Json.Get(res, "user_id").ToString()
|
||||||
d.cron = cron.NewCron(time.Hour * 2)
|
d.cron = cron.NewCron(time.Hour * 2)
|
||||||
d.cron.Do(func() {
|
d.cron.Do(func() {
|
||||||
err := d.refreshToken()
|
err := d.refreshToken()
|
||||||
@ -61,7 +63,22 @@ func (d *AliDrive) Init(ctx context.Context) error {
|
|||||||
log.Errorf("%+v", err)
|
log.Errorf("%+v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return err
|
if global.Has(d.UserID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// init deviceID
|
||||||
|
deviceID := utils.GetSHA256Encode(d.UserID)
|
||||||
|
// init privateKey
|
||||||
|
privateKey, _ := NewPrivateKeyFromHex(deviceID)
|
||||||
|
state := State{
|
||||||
|
privateKey: privateKey,
|
||||||
|
deviceID: deviceID,
|
||||||
|
}
|
||||||
|
// store state
|
||||||
|
global.Store(d.UserID, &state)
|
||||||
|
// init signature
|
||||||
|
d.sign()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliDrive) Drop(ctx context.Context) error {
|
func (d *AliDrive) Drop(ctx context.Context) error {
|
||||||
@ -169,17 +186,27 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
"type": "file",
|
"type": "file",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localFile *os.File
|
||||||
|
if fileStream, ok := file.ReadCloser.(*model.FileStream); ok {
|
||||||
|
localFile, _ = fileStream.ReadCloser.(*os.File)
|
||||||
|
}
|
||||||
if d.RapidUpload {
|
if d.RapidUpload {
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||||
io.CopyN(buf, file, 1024)
|
io.CopyN(buf, file, 1024)
|
||||||
reqBody["pre_hash"] = utils.GetSHA1Encode(buf.String())
|
reqBody["pre_hash"] = utils.GetSHA1Encode(buf.String())
|
||||||
// 把头部拼接回去
|
if localFile != nil {
|
||||||
file.ReadCloser = struct {
|
if _, err := localFile.Seek(0, io.SeekStart); err != nil {
|
||||||
io.Reader
|
return err
|
||||||
io.Closer
|
}
|
||||||
}{
|
} else {
|
||||||
Reader: io.MultiReader(buf, file),
|
// 把头部拼接回去
|
||||||
Closer: file,
|
file.ReadCloser = struct {
|
||||||
|
io.Reader
|
||||||
|
io.Closer
|
||||||
|
}{
|
||||||
|
Reader: io.MultiReader(buf, file),
|
||||||
|
Closer: file,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reqBody["content_hash_name"] = "none"
|
reqBody["content_hash_name"] = "none"
|
||||||
@ -196,18 +223,28 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.RapidUpload && e.Code == "PreHashMatched" {
|
if d.RapidUpload && e.Code == "PreHashMatched" {
|
||||||
tempFile, err := os.CreateTemp(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = tempFile.Close()
|
|
||||||
_ = os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
delete(reqBody, "pre_hash")
|
delete(reqBody, "pre_hash")
|
||||||
h := sha1.New()
|
h := sha1.New()
|
||||||
if _, err = io.Copy(io.MultiWriter(tempFile, h), file); err != nil {
|
if localFile != nil {
|
||||||
return err
|
if _, err = io.Copy(h, localFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = localFile.Seek(0, io.SeekStart); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tempFile, err := os.CreateTemp(conf.Conf.TempDir, "file-*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = tempFile.Close()
|
||||||
|
_ = os.Remove(tempFile.Name())
|
||||||
|
}()
|
||||||
|
if _, err = io.Copy(io.MultiWriter(tempFile, h), file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localFile = tempFile
|
||||||
}
|
}
|
||||||
reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil))
|
reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil))
|
||||||
reqBody["content_hash_name"] = "sha1"
|
reqBody["content_hash_name"] = "sha1"
|
||||||
@ -228,7 +265,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
if file.GetSize() > 0 {
|
if file.GetSize() > 0 {
|
||||||
o = r.Mod(r, i)
|
o = r.Mod(r, i)
|
||||||
}
|
}
|
||||||
n, _ := io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
|
n, _ := io.NewSectionReader(localFile, o.Int64(), 8).Read(buf[:8])
|
||||||
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
||||||
|
|
||||||
_, err, e := d.request("https://api.aliyundrive.com/adrive/v2/file/createWithFolders", http.MethodPost, func(req *resty.Request) {
|
_, err, e := d.request("https://api.aliyundrive.com/adrive/v2/file/createWithFolders", http.MethodPost, func(req *resty.Request) {
|
||||||
@ -241,17 +278,21 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// 秒传失败
|
// 秒传失败
|
||||||
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
if _, err = localFile.Seek(0, io.SeekStart); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
file.ReadCloser = tempFile
|
file.ReadCloser = localFile
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, partInfo := range resp.PartInfoList {
|
for i, partInfo := range resp.PartInfoList {
|
||||||
if utils.IsCanceled(ctx) {
|
if utils.IsCanceled(ctx) {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("PUT", partInfo.UploadUrl, io.LimitReader(file, DEFAULT))
|
url := partInfo.UploadUrl
|
||||||
|
if d.InternalUpload {
|
||||||
|
url = partInfo.InternalUploadUrl
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("PUT", url, io.LimitReader(file, DEFAULT))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -296,6 +337,7 @@ func (d *AliDrive) Other(ctx context.Context, args model.OtherArgs) (interface{}
|
|||||||
case "video_preview":
|
case "video_preview":
|
||||||
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
|
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
|
||||||
data["category"] = "live_transcoding"
|
data["category"] = "live_transcoding"
|
||||||
|
data["url_expire_sec"] = 14400
|
||||||
default:
|
default:
|
||||||
return nil, errs.NotSupport
|
return nil, errs.NotSupport
|
||||||
}
|
}
|
||||||
|
16
drivers/aliyundrive/global.go
Normal file
16
drivers/aliyundrive/global.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package aliyundrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/pkg/generic_sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
deviceID string
|
||||||
|
signature string
|
||||||
|
retry int
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
var global = generic_sync.MapOf[string, *State]{}
|
66
drivers/aliyundrive/help.go
Normal file
66
drivers/aliyundrive/help.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package aliyundrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/dustinxie/ecc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPrivateKey() (*ecdsa.PrivateKey, error) {
|
||||||
|
p256k1 := ecc.P256k1()
|
||||||
|
return ecdsa.GenerateKey(p256k1, rand.Reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivateKeyFromHex(hex_ string) (*ecdsa.PrivateKey, error) {
|
||||||
|
data, err := hex.DecodeString(hex_)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewPrivateKeyFromBytes(data), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivateKeyFromBytes(priv []byte) *ecdsa.PrivateKey {
|
||||||
|
p256k1 := ecc.P256k1()
|
||||||
|
x, y := p256k1.ScalarBaseMult(priv)
|
||||||
|
return &ecdsa.PrivateKey{
|
||||||
|
PublicKey: ecdsa.PublicKey{
|
||||||
|
Curve: p256k1,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
},
|
||||||
|
D: new(big.Int).SetBytes(priv),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivateKeyToHex(private *ecdsa.PrivateKey) string {
|
||||||
|
return hex.EncodeToString(PrivateKeyToBytes(private))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivateKeyToBytes(private *ecdsa.PrivateKey) []byte {
|
||||||
|
return private.D.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicKeyToHex(public *ecdsa.PublicKey) string {
|
||||||
|
return hex.EncodeToString(PublicKeyToBytes(public))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicKeyToBytes(public *ecdsa.PublicKey) []byte {
|
||||||
|
x := public.X.Bytes()
|
||||||
|
if len(x) < 32 {
|
||||||
|
for i := 0; i < 32-len(x); i++ {
|
||||||
|
x = append([]byte{0}, x...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
y := public.Y.Bytes()
|
||||||
|
if len(y) < 32 {
|
||||||
|
for i := 0; i < 32-len(y); i++ {
|
||||||
|
y = append([]byte{0}, y...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(x, y...)
|
||||||
|
}
|
@ -7,15 +7,20 @@ import (
|
|||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
driver.RootID
|
driver.RootID
|
||||||
RefreshToken string `json:"refresh_token" required:"true"`
|
RefreshToken string `json:"refresh_token" required:"true"`
|
||||||
|
//DeviceID string `json:"device_id" required:"true"`
|
||||||
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"`
|
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"`
|
||||||
OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"`
|
OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"`
|
||||||
RapidUpload bool `json:"rapid_upload"`
|
RapidUpload bool `json:"rapid_upload"`
|
||||||
|
InternalUpload bool `json:"internal_upload"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
Name: "Aliyundrive",
|
Name: "Aliyundrive",
|
||||||
DefaultRoot: "root",
|
DefaultRoot: "root",
|
||||||
|
Alert: `warning|There may be an infinite loop bug in this driver.
|
||||||
|
Deprecated, no longer maintained and will be removed in a future version.
|
||||||
|
We recommend using the official driver AliyundriveOpen.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -48,7 +48,8 @@ type UploadResp struct {
|
|||||||
FileId string `json:"file_id"`
|
FileId string `json:"file_id"`
|
||||||
UploadId string `json:"upload_id"`
|
UploadId string `json:"upload_id"`
|
||||||
PartInfoList []struct {
|
PartInfoList []struct {
|
||||||
UploadUrl string `json:"upload_url"`
|
UploadUrl string `json:"upload_url"`
|
||||||
|
InternalUploadUrl string `json:"internal_upload_url"`
|
||||||
} `json:"part_info_list"`
|
} `json:"part_info_list"`
|
||||||
|
|
||||||
RapidUpload bool `json:"rapid_upload"`
|
RapidUpload bool `json:"rapid_upload"`
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package aliyundrive
|
package aliyundrive
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -8,9 +10,51 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/dustinxie/ecc"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (d *AliDrive) createSession() error {
|
||||||
|
state, ok := global.Load(d.UserID)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't load user state, user_id: %s", d.UserID)
|
||||||
|
}
|
||||||
|
d.sign()
|
||||||
|
state.retry++
|
||||||
|
if state.retry > 3 {
|
||||||
|
state.retry = 0
|
||||||
|
return fmt.Errorf("createSession failed after three retries")
|
||||||
|
}
|
||||||
|
_, err, _ := d.request("https://api.aliyundrive.com/users/v1/users/device/create_session", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"deviceName": "samsung",
|
||||||
|
"modelName": "SM-G9810",
|
||||||
|
"nonce": 0,
|
||||||
|
"pubKey": PublicKeyToHex(&state.privateKey.PublicKey),
|
||||||
|
"refreshToken": d.RefreshToken,
|
||||||
|
})
|
||||||
|
}, nil)
|
||||||
|
if err == nil{
|
||||||
|
state.retry = 0
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (d *AliDrive) renewSession() error {
|
||||||
|
// _, err, _ := d.request("https://api.aliyundrive.com/users/v1/users/device/renew_session", http.MethodPost, nil, nil)
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (d *AliDrive) sign() {
|
||||||
|
state, _ := global.Load(d.UserID)
|
||||||
|
secpAppID := "5dde4e1bdf9e4966b387ba58f4b3fdc3"
|
||||||
|
singdata := fmt.Sprintf("%s:%s:%s:%d", secpAppID, state.deviceID, d.UserID, 0)
|
||||||
|
hash := sha256.Sum256([]byte(singdata))
|
||||||
|
data, _ := ecc.SignBytes(state.privateKey, hash[:], ecc.RecID|ecc.LowerS)
|
||||||
|
state.signature = hex.EncodeToString(data) //strconv.Itoa(state.nonce)
|
||||||
|
}
|
||||||
|
|
||||||
// do others that not defined in Driver interface
|
// do others that not defined in Driver interface
|
||||||
|
|
||||||
func (d *AliDrive) refreshToken() error {
|
func (d *AliDrive) refreshToken() error {
|
||||||
@ -39,9 +83,24 @@ func (d *AliDrive) refreshToken() error {
|
|||||||
|
|
||||||
func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) {
|
func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) {
|
||||||
req := base.RestyClient.R()
|
req := base.RestyClient.R()
|
||||||
req.SetHeader("Authorization", "Bearer\t"+d.AccessToken)
|
state, ok := global.Load(d.UserID)
|
||||||
req.SetHeader("content-type", "application/json")
|
if !ok {
|
||||||
req.SetHeader("origin", "https://www.aliyundrive.com")
|
if url == "https://api.aliyundrive.com/v2/user/get" {
|
||||||
|
state = &State{}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("can't load user state, user_id: %s", d.UserID), RespErr{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.SetHeaders(map[string]string{
|
||||||
|
"Authorization": "Bearer\t" + d.AccessToken,
|
||||||
|
"content-type": "application/json",
|
||||||
|
"origin": "https://www.aliyundrive.com",
|
||||||
|
"Referer": "https://aliyundrive.com/",
|
||||||
|
"X-Signature": state.signature,
|
||||||
|
"x-request-id": uuid.NewString(),
|
||||||
|
"X-Canary": "client=Android,app=adrive,version=v4.1.0",
|
||||||
|
"X-Device-Id": state.deviceID,
|
||||||
|
})
|
||||||
if callback != nil {
|
if callback != nil {
|
||||||
callback(req)
|
callback(req)
|
||||||
} else {
|
} else {
|
||||||
@ -57,14 +116,21 @@ func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp i
|
|||||||
return nil, err, e
|
return nil, err, e
|
||||||
}
|
}
|
||||||
if e.Code != "" {
|
if e.Code != "" {
|
||||||
if e.Code == "AccessTokenInvalid" {
|
switch e.Code {
|
||||||
|
case "AccessTokenInvalid":
|
||||||
err = d.refreshToken()
|
err = d.refreshToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err, e
|
return nil, err, e
|
||||||
}
|
}
|
||||||
return d.request(url, method, callback, resp)
|
case "DeviceSessionSignatureInvalid":
|
||||||
|
err = d.createSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err, e
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New(e.Message), e
|
||||||
}
|
}
|
||||||
return nil, errors.New(e.Message), e
|
return d.request(url, method, callback, resp)
|
||||||
} else if res.IsError() {
|
} else if res.IsError() {
|
||||||
return nil, errors.New("bad status code " + res.Status()), e
|
return nil, errors.New("bad status code " + res.Status()), e
|
||||||
}
|
}
|
||||||
|
217
drivers/aliyundrive_open/driver.go
Normal file
217
drivers/aliyundrive_open/driver.go
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
package aliyundrive_open
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyundriveOpen struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
base string
|
||||||
|
|
||||||
|
AccessToken string
|
||||||
|
DriveId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) Init(ctx context.Context) error {
|
||||||
|
err := d.refreshToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res, err := d.request("/adrive/v1.0/user/getDriveInfo", http.MethodPost, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.DriveId = utils.Json.Get(res, "default_drive_id").ToString()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
files, err := d.getFiles(dir.GetID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
||||||
|
return fileToObj(src), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
res, err := d.request("/adrive/v1.0/openFile/getDownloadUrl", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"file_id": file.GetID(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
url := utils.Json.Get(res, "url").ToString()
|
||||||
|
return &model.Link{
|
||||||
|
URL: url,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
|
_, err := d.request("/adrive/v1.0/openFile/create", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"parent_file_id": parentDir.GetID(),
|
||||||
|
"name": dirName,
|
||||||
|
"type": "folder",
|
||||||
|
"check_name_mode": "refuse",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
_, err := d.request("/adrive/v1.0/openFile/move", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"file_id": srcObj.GetID(),
|
||||||
|
"to_parent_file_id": dstDir.GetID(),
|
||||||
|
"check_name_mode": "refuse", // optional:ignore,auto_rename,refuse
|
||||||
|
//"new_name": "newName", // The new name to use when a file of the same name exists
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
|
_, err := d.request("/adrive/v1.0/openFile/update", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"file_id": srcObj.GetID(),
|
||||||
|
"name": newName,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
_, err := d.request("/adrive/v1.0/openFile/copy", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"file_id": srcObj.GetID(),
|
||||||
|
"to_parent_file_id": dstDir.GetID(),
|
||||||
|
"auto_rename": true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
|
_, err := d.request("/adrive/v1.0/openFile/recyclebin/trash", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"file_id": obj.GetID(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
// rapid_upload is not currently supported
|
||||||
|
// 1. create
|
||||||
|
const DEFAULT int64 = 20971520
|
||||||
|
createData := base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"parent_file_id": dstDir.GetID(),
|
||||||
|
"name": stream.GetName(),
|
||||||
|
"type": "file",
|
||||||
|
"check_name_mode": "ignore",
|
||||||
|
}
|
||||||
|
count := 1
|
||||||
|
if stream.GetSize() > DEFAULT {
|
||||||
|
count = int(math.Ceil(float64(stream.GetSize()) / float64(DEFAULT)))
|
||||||
|
partInfoList := make([]base.Json, 0, count)
|
||||||
|
for i := 1; i <= count; i++ {
|
||||||
|
partInfoList = append(partInfoList, base.Json{"part_number": i})
|
||||||
|
}
|
||||||
|
createData["part_info_list"] = partInfoList
|
||||||
|
}
|
||||||
|
var createResp CreateResp
|
||||||
|
_, err := d.request("/adrive/v1.0/openFile/create", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(createData).SetResult(&createResp)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 2. upload
|
||||||
|
for i, partInfo := range createResp.PartInfoList {
|
||||||
|
if utils.IsCanceled(ctx) {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("PUT", partInfo.UploadUrl, io.LimitReader(stream, DEFAULT))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
res, err := base.HttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
if count > 0 {
|
||||||
|
up(i * 100 / count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3. complete
|
||||||
|
_, err = d.request("/adrive/v1.0/openFile/complete", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"file_id": createResp.FileId,
|
||||||
|
"upload_id": createResp.UploadId,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
|
var resp base.Json
|
||||||
|
var uri string
|
||||||
|
data := base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"file_id": args.Obj.GetID(),
|
||||||
|
}
|
||||||
|
switch args.Method {
|
||||||
|
case "video_preview":
|
||||||
|
uri = "/adrive/v1.0/openFile/getVideoPreviewPlayInfo"
|
||||||
|
data["category"] = "live_transcoding"
|
||||||
|
data["url_expire_sec"] = 14400
|
||||||
|
default:
|
||||||
|
return nil, errs.NotSupport
|
||||||
|
}
|
||||||
|
_, err := d.request(uri, http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(data).SetResult(&resp)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*AliyundriveOpen)(nil)
|
35
drivers/aliyundrive_open/meta.go
Normal file
35
drivers/aliyundrive_open/meta.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package aliyundrive_open
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
driver.RootID
|
||||||
|
RefreshToken string `json:"refresh_token" required:"true"`
|
||||||
|
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"`
|
||||||
|
OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"`
|
||||||
|
OauthTokenURL string `json:"oauth_token_url" default:"https://api.nn.ci/alist/ali_open/token"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "AliyundriveOpen",
|
||||||
|
LocalSort: false,
|
||||||
|
OnlyLocal: false,
|
||||||
|
OnlyProxy: false,
|
||||||
|
NoCache: false,
|
||||||
|
NoUpload: false,
|
||||||
|
NeedMs: false,
|
||||||
|
DefaultRoot: "root",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &AliyundriveOpen{
|
||||||
|
base: "https://open.aliyundrive.com",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
67
drivers/aliyundrive_open/types.go
Normal file
67
drivers/aliyundrive_open/types.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package aliyundrive_open
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrResp struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Files struct {
|
||||||
|
Items []File `json:"items"`
|
||||||
|
NextMarker string `json:"next_marker"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
DriveId string `json:"drive_id"`
|
||||||
|
FileId string `json:"file_id"`
|
||||||
|
ParentFileId string `json:"parent_file_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
FileExtension string `json:"file_extension"`
|
||||||
|
ContentHash string `json:"content_hash"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
CreatedAt *time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileToObj(f File) *model.ObjThumb {
|
||||||
|
return &model.ObjThumb{
|
||||||
|
Object: model.Object{
|
||||||
|
ID: f.FileId,
|
||||||
|
Name: f.Name,
|
||||||
|
Size: f.Size,
|
||||||
|
Modified: f.UpdatedAt,
|
||||||
|
IsFolder: f.Type == "folder",
|
||||||
|
},
|
||||||
|
Thumbnail: model.Thumbnail{Thumbnail: f.Thumbnail},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateResp struct {
|
||||||
|
//Type string `json:"type"`
|
||||||
|
//ParentFileId string `json:"parent_file_id"`
|
||||||
|
//DriveId string `json:"drive_id"`
|
||||||
|
FileId string `json:"file_id"`
|
||||||
|
//RevisionId string `json:"revision_id"`
|
||||||
|
//EncryptMode string `json:"encrypt_mode"`
|
||||||
|
//DomainId string `json:"domain_id"`
|
||||||
|
//FileName string `json:"file_name"`
|
||||||
|
UploadId string `json:"upload_id"`
|
||||||
|
//Location string `json:"location"`
|
||||||
|
RapidUpload bool `json:"rapid_upload"`
|
||||||
|
PartInfoList []struct {
|
||||||
|
Etag interface{} `json:"etag"`
|
||||||
|
PartNumber int `json:"part_number"`
|
||||||
|
PartSize interface{} `json:"part_size"`
|
||||||
|
UploadUrl string `json:"upload_url"`
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
} `json:"part_info_list"`
|
||||||
|
}
|
108
drivers/aliyundrive_open/util.go
Normal file
108
drivers/aliyundrive_open/util.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package aliyundrive_open
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// do others that not defined in Driver interface
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) refreshToken() error {
|
||||||
|
url := d.base + "/oauth/access_token"
|
||||||
|
if d.OauthTokenURL != "" && d.ClientID == "" {
|
||||||
|
url = d.OauthTokenURL
|
||||||
|
}
|
||||||
|
var resp base.TokenResp
|
||||||
|
var e ErrResp
|
||||||
|
_, err := base.RestyClient.R().
|
||||||
|
ForceContentType("application/json").
|
||||||
|
SetBody(base.Json{
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
"client_secret": d.ClientSecret,
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
"refresh_token": d.RefreshToken,
|
||||||
|
}).
|
||||||
|
SetResult(&resp).
|
||||||
|
SetError(&e).
|
||||||
|
Post(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Code != "" {
|
||||||
|
return fmt.Errorf("failed to refresh token: %s", e.Message)
|
||||||
|
}
|
||||||
|
if resp.RefreshToken == "" {
|
||||||
|
return errors.New("failed to refresh token: refresh token is empty")
|
||||||
|
}
|
||||||
|
d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
|
||||||
|
op.MustSaveDriverStorage(d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) request(uri, method string, callback base.ReqCallback, retry ...bool) ([]byte, error) {
|
||||||
|
req := base.RestyClient.R()
|
||||||
|
// TODO check whether access_token is expired
|
||||||
|
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
||||||
|
if method == http.MethodPost {
|
||||||
|
req.SetHeader("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
if callback != nil {
|
||||||
|
callback(req)
|
||||||
|
}
|
||||||
|
var e ErrResp
|
||||||
|
req.SetError(&e)
|
||||||
|
res, err := req.Execute(method, d.base+uri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
isRetry := len(retry) > 0 && retry[0]
|
||||||
|
if e.Code != "" {
|
||||||
|
if !isRetry && e.Code == "AccessTokenInvalid" {
|
||||||
|
err = d.refreshToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.request(uri, method, callback, true)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%s:%s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
return res.Body(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) getFiles(fileId string) ([]File, error) {
|
||||||
|
marker := "first"
|
||||||
|
res := make([]File, 0)
|
||||||
|
for marker != "" {
|
||||||
|
if marker == "first" {
|
||||||
|
marker = ""
|
||||||
|
}
|
||||||
|
var resp Files
|
||||||
|
data := base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"limit": 200,
|
||||||
|
"marker": marker,
|
||||||
|
"order_by": d.OrderBy,
|
||||||
|
"order_direction": d.OrderDirection,
|
||||||
|
"parent_file_id": fileId,
|
||||||
|
//"category": "",
|
||||||
|
//"type": "",
|
||||||
|
//"video_thumbnail_time": 120000,
|
||||||
|
//"video_thumbnail_width": 480,
|
||||||
|
//"image_thumbnail_width": 480,
|
||||||
|
}
|
||||||
|
_, err := d.request("/adrive/v1.0/openFile/list", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(data).SetResult(&resp)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
marker = resp.NextMarker
|
||||||
|
res = append(res, resp.Items...)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
@ -2,15 +2,16 @@ package aliyundrive_share
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/pkg/cron"
|
"github.com/alist-org/alist/v3/pkg/cron"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ func (d *AliyundriveShare) Drop(ctx context.Context) error {
|
|||||||
if d.cron != nil {
|
if d.cron != nil {
|
||||||
d.cron.Stop()
|
d.cron.Stop()
|
||||||
}
|
}
|
||||||
|
d.DriveId = ""
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,40 +78,43 @@ func (d *AliyundriveShare) Link(ctx context.Context, file model.Obj, args model.
|
|||||||
"share_id": d.ShareId,
|
"share_id": d.ShareId,
|
||||||
}
|
}
|
||||||
var resp ShareLinkResp
|
var resp ShareLinkResp
|
||||||
var e ErrorResp
|
_, err := d.request("https://api.aliyundrive.com/v2/file/get_share_link_download_url", http.MethodPost, func(req *resty.Request) {
|
||||||
_, err := base.RestyClient.R().
|
req.SetBody(data).SetResult(&resp)
|
||||||
SetError(&e).SetBody(data).SetResult(&resp).
|
})
|
||||||
SetHeader("content-type", "application/json").
|
|
||||||
SetHeader("Authorization", "Bearer\t"+d.AccessToken).
|
|
||||||
SetHeader("x-share-token", d.ShareToken).
|
|
||||||
Post("https://api.aliyundrive.com/v2/file/get_share_link_download_url")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var u string
|
|
||||||
if e.Code != "" {
|
|
||||||
if e.Code == "AccessTokenInvalid" || e.Code == "ShareLinkTokenInvalid" {
|
|
||||||
if e.Code == "AccessTokenInvalid" {
|
|
||||||
err = d.refreshToken()
|
|
||||||
} else {
|
|
||||||
err = d.getShareToken()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return d.Link(ctx, file, args)
|
|
||||||
} else {
|
|
||||||
return nil, errors.New(e.Code + ": " + e.Message)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
u = resp.DownloadUrl
|
|
||||||
}
|
|
||||||
return &model.Link{
|
return &model.Link{
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
"Referer": []string{"https://www.aliyundrive.com/"},
|
"Referer": []string{"https://www.aliyundrive.com/"},
|
||||||
},
|
},
|
||||||
URL: u,
|
URL: resp.DownloadUrl,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveShare) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
|
var resp base.Json
|
||||||
|
var url string
|
||||||
|
data := base.Json{
|
||||||
|
"share_id": d.ShareId,
|
||||||
|
"file_id": args.Obj.GetID(),
|
||||||
|
}
|
||||||
|
switch args.Method {
|
||||||
|
case "doc_preview":
|
||||||
|
url = "https://api.aliyundrive.com/v2/file/get_office_preview_url"
|
||||||
|
case "video_preview":
|
||||||
|
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
|
||||||
|
data["category"] = "live_transcoding"
|
||||||
|
default:
|
||||||
|
return nil, errs.NotSupport
|
||||||
|
}
|
||||||
|
_, err := d.request(url, http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(data).SetResult(&resp)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
var _ driver.Driver = (*AliyundriveShare)(nil)
|
var _ driver.Driver = (*AliyundriveShare)(nil)
|
||||||
|
@ -52,6 +52,40 @@ func (d *AliyundriveShare) getShareToken() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveShare) request(url, method string, callback base.ReqCallback) ([]byte, error) {
|
||||||
|
var e ErrorResp
|
||||||
|
req := base.RestyClient.R().
|
||||||
|
SetError(&e).
|
||||||
|
SetHeader("content-type", "application/json").
|
||||||
|
SetHeader("Authorization", "Bearer\t"+d.AccessToken).
|
||||||
|
SetHeader("x-share-token", d.ShareToken)
|
||||||
|
if callback != nil {
|
||||||
|
callback(req)
|
||||||
|
} else {
|
||||||
|
req.SetBody("{}")
|
||||||
|
}
|
||||||
|
resp, err := req.Execute(method, url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if e.Code != "" {
|
||||||
|
if e.Code == "AccessTokenInvalid" || e.Code == "ShareLinkTokenInvalid" {
|
||||||
|
if e.Code == "AccessTokenInvalid" {
|
||||||
|
err = d.refreshToken()
|
||||||
|
} else {
|
||||||
|
err = d.getShareToken()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.request(url, method, callback)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New(e.Code + ": " + e.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp.Body(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *AliyundriveShare) getFiles(fileId string) ([]File, error) {
|
func (d *AliyundriveShare) getFiles(fileId string) ([]File, error) {
|
||||||
files := make([]File, 0)
|
files := make([]File, 0)
|
||||||
data := base.Json{
|
data := base.Json{
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
_ "github.com/alist-org/alist/v3/drivers/alist_v2"
|
_ "github.com/alist-org/alist/v3/drivers/alist_v2"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/alist_v3"
|
_ "github.com/alist-org/alist/v3/drivers/alist_v3"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/aliyundrive"
|
_ "github.com/alist-org/alist/v3/drivers/aliyundrive"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/aliyundrive_open"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/aliyundrive_share"
|
_ "github.com/alist-org/alist/v3/drivers/aliyundrive_share"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/baidu_netdisk"
|
_ "github.com/alist-org/alist/v3/drivers/baidu_netdisk"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/baidu_photo"
|
_ "github.com/alist-org/alist/v3/drivers/baidu_photo"
|
||||||
|
@ -187,7 +187,7 @@ func (d *BaiduNetdisk) create(path string, size int64, isdir int, uploadid, bloc
|
|||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"method": "create",
|
"method": "create",
|
||||||
}
|
}
|
||||||
data := fmt.Sprintf("path=%s&size=%d&isdir=%d", encodeURIComponent(path), size, isdir)
|
data := fmt.Sprintf("path=%s&size=%d&isdir=%d&rtype=3", encodeURIComponent(path), size, isdir)
|
||||||
if uploadid != "" {
|
if uploadid != "" {
|
||||||
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
|
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewRestyClient() *resty.Client {
|
func NewRestyClient() *resty.Client {
|
||||||
return resty.New().
|
client := resty.New().
|
||||||
SetHeader("user-agent", UserAgent).
|
SetHeader("user-agent", UserAgent).
|
||||||
SetRetryCount(3).
|
SetRetryCount(3).
|
||||||
SetTimeout(DefaultTimeout)
|
SetTimeout(DefaultTimeout)
|
||||||
|
return client
|
||||||
}
|
}
|
||||||
|
30
drivers/base/util.go
Normal file
30
drivers/base/util.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleRange(link *model.Link, file io.ReadSeekCloser, header http.Header, size int64) {
|
||||||
|
if header.Get("Range") != "" {
|
||||||
|
r, err := http_range.ParseRange(header.Get("Range"), size)
|
||||||
|
if err == nil && len(r) > 0 {
|
||||||
|
_, err := file.Seek(r[0].Start, io.SeekStart)
|
||||||
|
if err == nil {
|
||||||
|
link.Data = utils.NewLimitReadCloser(file, func() error {
|
||||||
|
return file.Close()
|
||||||
|
}, r[0].Length)
|
||||||
|
link.Status = http.StatusPartialContent
|
||||||
|
link.Header = http.Header{
|
||||||
|
"Content-Range": []string{r[0].ContentRange(size)},
|
||||||
|
"Content-Length": []string{strconv.FormatInt(r[0].Length, 10)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -56,7 +56,7 @@ func (d *GoogleDrive) Link(ctx context.Context, file model.Obj, args model.LinkA
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
link := model.Link{
|
link := model.Link{
|
||||||
URL: url + "&alt=media",
|
URL: url + "&alt=media&acknowledgeAbuse=true",
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
"Authorization": []string{"Bearer " + d.AccessToken},
|
"Authorization": []string{"Bearer " + d.AccessToken},
|
||||||
},
|
},
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -20,7 +21,6 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/sign"
|
"github.com/alist-org/alist/v3/internal/sign"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
|
||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
_ "golang.org/x/image/webp"
|
_ "golang.org/x/image/webp"
|
||||||
)
|
)
|
||||||
@ -35,6 +35,9 @@ func (d *Local) Config() driver.Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Local) Init(ctx context.Context) error {
|
func (d *Local) Init(ctx context.Context) error {
|
||||||
|
if d.MkdirPerm == 0 {
|
||||||
|
d.MkdirPerm = 777
|
||||||
|
}
|
||||||
if !utils.Exists(d.GetRootPath()) {
|
if !utils.Exists(d.GetRootPath()) {
|
||||||
return fmt.Errorf("root folder %s not exists", d.GetRootPath())
|
return fmt.Errorf("root folder %s not exists", d.GetRootPath())
|
||||||
}
|
}
|
||||||
@ -68,10 +71,13 @@ func (d *Local) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
thumb := ""
|
thumb := ""
|
||||||
if d.Thumbnail && utils.GetFileType(f.Name()) == conf.IMAGE {
|
if d.Thumbnail {
|
||||||
thumb = common.GetApiUrl(nil) + stdpath.Join("/d", args.ReqPath, f.Name())
|
typeName := utils.GetFileType(f.Name())
|
||||||
thumb = utils.EncodePath(thumb, true)
|
if typeName == conf.IMAGE || typeName == conf.VIDEO {
|
||||||
thumb += "?type=thumb&sign=" + sign.Sign(stdpath.Join(args.ReqPath, f.Name()))
|
thumb = common.GetApiUrl(nil) + stdpath.Join("/d", args.ReqPath, f.Name())
|
||||||
|
thumb = utils.EncodePath(thumb, true)
|
||||||
|
thumb += "?type=thumb&sign=" + sign.Sign(stdpath.Join(args.ReqPath, f.Name()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
isFolder := f.IsDir() || isSymlinkDir(f, fullPath)
|
isFolder := f.IsDir() || isSymlinkDir(f, fullPath)
|
||||||
var size int64
|
var size int64
|
||||||
@ -123,11 +129,22 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
|||||||
fullPath := file.GetPath()
|
fullPath := file.GetPath()
|
||||||
var link model.Link
|
var link model.Link
|
||||||
if args.Type == "thumb" && utils.Ext(file.GetName()) != "svg" {
|
if args.Type == "thumb" && utils.Ext(file.GetName()) != "svg" {
|
||||||
imgData, err := ioutil.ReadFile(fullPath)
|
var srcBuf *bytes.Buffer
|
||||||
if err != nil {
|
if utils.GetFileType(file.GetName()) == conf.VIDEO {
|
||||||
return nil, err
|
videoBuf, err := GetSnapshot(fullPath, 10)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
srcBuf = videoBuf
|
||||||
|
} else {
|
||||||
|
imgData, err := ioutil.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
imgBuf := bytes.NewBuffer(imgData)
|
||||||
|
srcBuf = imgBuf
|
||||||
}
|
}
|
||||||
srcBuf := bytes.NewBuffer(imgData)
|
|
||||||
image, err := imaging.Decode(srcBuf)
|
image, err := imaging.Decode(srcBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -151,7 +168,7 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
|||||||
|
|
||||||
func (d *Local) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *Local) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
fullPath := filepath.Join(parentDir.GetPath(), dirName)
|
fullPath := filepath.Join(parentDir.GetPath(), dirName)
|
||||||
err := os.MkdirAll(fullPath, 0700)
|
err := os.MkdirAll(fullPath, os.FileMode(d.MkdirPerm))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,9 @@ import (
|
|||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
driver.RootPath
|
driver.RootPath
|
||||||
Thumbnail bool `json:"thumbnail" required:"true" help:"enable thumbnail"`
|
Thumbnail bool `json:"thumbnail" required:"true" help:"enable thumbnail"`
|
||||||
ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"`
|
ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"`
|
||||||
|
MkdirPerm uint32 `json:"mkdir_perm" type:"number" default:"777"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -23,3 +26,16 @@ func isSymlinkDir(f fs.FileInfo, path string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSnapshot(videoPath string, frameNum int) (imgData *bytes.Buffer, err error) {
|
||||||
|
srcBuf := bytes.NewBuffer(nil)
|
||||||
|
err = ffmpeg.Input(videoPath).Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}).
|
||||||
|
Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}).
|
||||||
|
WithOutput(srcBuf, os.Stdout).
|
||||||
|
Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return srcBuf, nil
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
"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"
|
||||||
@ -75,9 +76,19 @@ func (d *Onedrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName str
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Onedrive) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
func (d *Onedrive) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
parentPath := ""
|
||||||
|
if dstDir.GetID() == "" {
|
||||||
|
parentPath = dstDir.GetPath()
|
||||||
|
if utils.PathEqual(parentPath, "/") {
|
||||||
|
parentPath = path.Join("/drive/root", parentPath)
|
||||||
|
} else {
|
||||||
|
parentPath = path.Join("/drive/root:/", parentPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
data := base.Json{
|
data := base.Json{
|
||||||
"parentReference": base.Json{
|
"parentReference": base.Json{
|
||||||
"id": dstDir.GetID(),
|
"id": dstDir.GetID(),
|
||||||
|
"path": parentPath,
|
||||||
},
|
},
|
||||||
"name": srcObj.GetName(),
|
"name": srcObj.GetName(),
|
||||||
}
|
}
|
||||||
@ -89,13 +100,15 @@ func (d *Onedrive) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Onedrive) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
func (d *Onedrive) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
//dstDir, err := op.GetUnwrap(ctx, d, stdpath.Dir(srcObj.GetPath()))
|
|
||||||
var parentID string
|
var parentID string
|
||||||
if o, ok := srcObj.(*Object); ok {
|
if o, ok := srcObj.(*Object); ok {
|
||||||
parentID = o.ParentID
|
parentID = o.ParentID
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("srcObj is not Object")
|
return fmt.Errorf("srcObj is not Object")
|
||||||
}
|
}
|
||||||
|
if parentID == "" {
|
||||||
|
parentID = "root"
|
||||||
|
}
|
||||||
data := base.Json{
|
data := base.Json{
|
||||||
"parentReference": base.Json{
|
"parentReference": base.Json{
|
||||||
"id": parentID,
|
"id": parentID,
|
||||||
|
@ -11,7 +11,7 @@ type Addition struct {
|
|||||||
IsSharepoint bool `json:"is_sharepoint"`
|
IsSharepoint bool `json:"is_sharepoint"`
|
||||||
ClientID string `json:"client_id" required:"true"`
|
ClientID string `json:"client_id" required:"true"`
|
||||||
ClientSecret string `json:"client_secret" required:"true"`
|
ClientSecret string `json:"client_secret" required:"true"`
|
||||||
RedirectUri string `json:"redirect_uri" required:"true" default:"https://tool.nn.ci/onedrive/callback"`
|
RedirectUri string `json:"redirect_uri" required:"true" default:"https://alist.nn.ci/tool/onedrive/callback"`
|
||||||
RefreshToken string `json:"refresh_token" required:"true"`
|
RefreshToken string `json:"refresh_token" required:"true"`
|
||||||
SiteId string `json:"site_id"`
|
SiteId string `json:"site_id"`
|
||||||
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
|
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
|
||||||
|
@ -43,7 +43,7 @@ type File struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Object struct {
|
type Object struct {
|
||||||
model.ObjThumbURL
|
model.ObjThumb
|
||||||
ParentID string
|
ParentID string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ func fileToObj(f File, parentID string) *Object {
|
|||||||
thumb = f.Thumbnails[0].Medium.Url
|
thumb = f.Thumbnails[0].Medium.Url
|
||||||
}
|
}
|
||||||
return &Object{
|
return &Object{
|
||||||
ObjThumbURL: model.ObjThumbURL{
|
ObjThumb: model.ObjThumb{
|
||||||
Object: model.Object{
|
Object: model.Object{
|
||||||
ID: f.Id,
|
ID: f.Id,
|
||||||
Name: f.Name,
|
Name: f.Name,
|
||||||
@ -62,7 +62,7 @@ func fileToObj(f File, parentID string) *Object {
|
|||||||
IsFolder: f.File == nil,
|
IsFolder: f.File == nil,
|
||||||
},
|
},
|
||||||
Thumbnail: model.Thumbnail{Thumbnail: thumb},
|
Thumbnail: model.Thumbnail{Thumbnail: thumb},
|
||||||
Url: model.Url{Url: f.Url},
|
//Url: model.Url{Url: f.Url},
|
||||||
},
|
},
|
||||||
ParentID: parentID,
|
ParentID: parentID,
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ func (d *Onedrive) Request(url string, method string, callback base.ReqCallback,
|
|||||||
|
|
||||||
func (d *Onedrive) getFiles(path string) ([]File, error) {
|
func (d *Onedrive) getFiles(path string) ([]File, error) {
|
||||||
var res []File
|
var res []File
|
||||||
nextLink := d.GetMetaUrl(false, path) + "/children?$expand=thumbnails"
|
nextLink := d.GetMetaUrl(false, path) + "/children?$top=5000&$expand=thumbnails($select=medium)&$select=id,name,size,lastModifiedDateTime,content.downloadUrl,file,parentReference"
|
||||||
for nextLink != "" {
|
for nextLink != "" {
|
||||||
var files Files
|
var files Files
|
||||||
_, err := d.Request(nextLink, http.MethodGet, nil, &files)
|
_, err := d.Request(nextLink, http.MethodGet, nil, &files)
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PikPakShare struct {
|
type PikPakShare struct {
|
||||||
@ -71,10 +70,6 @@ func (d *PikPakShare) Link(ctx context.Context, file model.Obj, args model.LinkA
|
|||||||
link := model.Link{
|
link := model.Link{
|
||||||
URL: resp.FileInfo.WebContentLink,
|
URL: resp.FileInfo.WebContentLink,
|
||||||
}
|
}
|
||||||
if len(resp.FileInfo.Medias) > 0 && resp.FileInfo.Medias[0].Link.Url != "" {
|
|
||||||
log.Debugln("use media link")
|
|
||||||
link.URL = resp.FileInfo.Medias[0].Link.Url
|
|
||||||
}
|
|
||||||
return &link, nil
|
return &link, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
@ -52,9 +53,11 @@ func (d *SFTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &model.Link{
|
link := &model.Link{
|
||||||
Data: remoteFile,
|
Data: remoteFile,
|
||||||
}, nil
|
}
|
||||||
|
base.HandleRange(link, remoteFile, args.Header, file.GetSize())
|
||||||
|
return link, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SFTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *SFTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
@ -79,10 +80,12 @@ func (d *SMB) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*m
|
|||||||
d.cleanLastConnTime()
|
d.cleanLastConnTime()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
d.updateLastConnTime()
|
link := &model.Link{
|
||||||
return &model.Link{
|
|
||||||
Data: remoteFile,
|
Data: remoteFile,
|
||||||
}, nil
|
}
|
||||||
|
base.HandleRange(link, remoteFile, args.Header, file.GetSize())
|
||||||
|
d.updateLastConnTime()
|
||||||
|
return link, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SMB) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *SMB) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
|
48
go.mod
48
go.mod
@ -5,16 +5,16 @@ go 1.19
|
|||||||
require (
|
require (
|
||||||
github.com/SheltonZhu/115driver v1.0.13
|
github.com/SheltonZhu/115driver v1.0.13
|
||||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||||
github.com/aws/aws-sdk-go v1.44.174
|
github.com/aws/aws-sdk-go v1.44.194
|
||||||
github.com/blevesearch/bleve/v2 v2.3.6
|
github.com/blevesearch/bleve/v2 v2.3.6
|
||||||
github.com/caarlos0/env/v6 v6.10.1
|
|
||||||
github.com/caarlos0/env/v7 v7.0.0
|
github.com/caarlos0/env/v7 v7.0.0
|
||||||
github.com/deckarep/golang-set/v2 v2.1.0
|
github.com/deckarep/golang-set/v2 v2.1.0
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564
|
||||||
github.com/gin-contrib/cors v1.4.0
|
github.com/gin-contrib/cors v1.4.0
|
||||||
github.com/gin-gonic/gin v1.8.2
|
github.com/gin-gonic/gin v1.9.0
|
||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/go-resty/resty/v2 v2.7.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/hirochachacha/go-smb2 v1.1.0
|
github.com/hirochachacha/go-smb2 v1.1.0
|
||||||
@ -27,16 +27,17 @@ require (
|
|||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf
|
github.com/t3rm1n4l/go-mega v0.0.0-20230220145126-b87ebf5801d8
|
||||||
|
github.com/u2takey/ffmpeg-go v0.4.1
|
||||||
github.com/upyun/go-sdk/v3 v3.0.3
|
github.com/upyun/go-sdk/v3 v3.0.3
|
||||||
github.com/winfsp/cgofuse v1.5.0
|
github.com/winfsp/cgofuse v1.5.0
|
||||||
golang.org/x/crypto v0.5.0
|
golang.org/x/crypto v0.6.0
|
||||||
golang.org/x/image v0.3.0
|
golang.org/x/image v0.5.0
|
||||||
golang.org/x/net v0.5.0
|
golang.org/x/net v0.7.0
|
||||||
gorm.io/driver/mysql v1.4.5
|
gorm.io/driver/mysql v1.4.7
|
||||||
gorm.io/driver/postgres v1.4.6
|
gorm.io/driver/postgres v1.4.8
|
||||||
gorm.io/driver/sqlite v1.4.4
|
gorm.io/driver/sqlite v1.4.4
|
||||||
gorm.io/gorm v1.24.3
|
gorm.io/gorm v1.24.5
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -63,14 +64,16 @@ require (
|
|||||||
github.com/blevesearch/zapx/v15 v15.3.8 // indirect
|
github.com/blevesearch/zapx/v15 v15.3.8 // indirect
|
||||||
github.com/bluele/gcache v0.0.2 // indirect
|
github.com/bluele/gcache v0.0.2 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
|
github.com/bytedance/sonic v1.8.0 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
github.com/gaoyb7/115drive-webdav v0.1.8 // indirect
|
github.com/gaoyb7/115drive-webdav v0.1.8 // indirect
|
||||||
github.com/geoffgarside/ber v1.1.0 // indirect
|
github.com/geoffgarside/ber v1.1.0 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
github.com/goccy/go-json v0.9.11 // indirect
|
github.com/goccy/go-json v0.10.0 // indirect
|
||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||||
github.com/golang/protobuf v1.5.0 // indirect
|
github.com/golang/protobuf v1.5.0 // indirect
|
||||||
github.com/golang/snappy v0.0.1 // indirect
|
github.com/golang/snappy v0.0.1 // indirect
|
||||||
@ -79,13 +82,14 @@ require (
|
|||||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgx/v5 v5.2.0 // indirect
|
github.com/jackc/pgx/v5 v5.3.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
@ -95,13 +99,17 @@ require (
|
|||||||
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/u2takey/go-utils v0.3.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||||
go.etcd.io/bbolt v1.3.5 // indirect
|
go.etcd.io/bbolt v1.3.5 // indirect
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||||
golang.org/x/text v0.6.0 // indirect
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
|
golang.org/x/text v0.7.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
125
go.sum
125
go.sum
@ -12,10 +12,9 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1e
|
|||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
github.com/andreburgaud/crypt2go v1.1.0 h1:eitZxTPY1krUsxinsng3Qvt/Ud7q/aQmmYRh8p4hyPw=
|
github.com/andreburgaud/crypt2go v1.1.0 h1:eitZxTPY1krUsxinsng3Qvt/Ud7q/aQmmYRh8p4hyPw=
|
||||||
github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc=
|
github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc=
|
||||||
github.com/aws/aws-sdk-go v1.44.173 h1:8kXIxvQnBpGhmR3Eof6SnCKgR0q5/L/3Qbv9vAC5wic=
|
github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.44.173/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
github.com/aws/aws-sdk-go v1.44.194 h1:1ZDK+QDcc5oRbZGgRZSz561eR8XVizXCeGpoZKo33NU=
|
||||||
github.com/aws/aws-sdk-go v1.44.174 h1:9lR4a6MKQW/t6YCG0ZKAt1GAkjdEPP8sWch/pfcuR0c=
|
github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||||
github.com/aws/aws-sdk-go v1.44.174/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
|
||||||
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||||
github.com/blevesearch/bleve/v2 v2.3.6 h1:NlntUHcV5CSWIhpugx4d/BRMGCiaoI8ZZXrXlahzNq4=
|
github.com/blevesearch/bleve/v2 v2.3.6 h1:NlntUHcV5CSWIhpugx4d/BRMGCiaoI8ZZXrXlahzNq4=
|
||||||
@ -54,9 +53,14 @@ github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
|||||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
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/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
|
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
|
||||||
|
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
|
github.com/caarlos0/env/v7 v7.0.0 h1:cyczlTd/zREwSr9ch/mwaDl7Hse7kJuUY8hvHfXu5WI=
|
||||||
github.com/caarlos0/env/v7 v7.0.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
|
github.com/caarlos0/env/v7 v7.0.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -66,6 +70,9 @@ github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6
|
|||||||
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
|
||||||
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564/go.mod h1:yekO+3ZShy19S+bsmnERmznGy9Rfg6dWWWpiGJjNAz8=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/gaoyb7/115drive-webdav v0.1.8 h1:EJt4PSmcbvBY4KUh2zSo5p6fN9LZFNkIzuKejipubVw=
|
github.com/gaoyb7/115drive-webdav v0.1.8 h1:EJt4PSmcbvBY4KUh2zSo5p6fN9LZFNkIzuKejipubVw=
|
||||||
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=
|
||||||
@ -77,16 +84,25 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
|
|||||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||||
github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY=
|
github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY=
|
||||||
github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
|
github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
|
||||||
|
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||||
|
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||||
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
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.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||||
|
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
|
||||||
|
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
|
||||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||||
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-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
@ -94,8 +110,11 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
|
|||||||
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=
|
||||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||||
@ -106,6 +125,7 @@ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
|||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
@ -115,18 +135,18 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
|||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
|
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
|
||||||
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
|
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8=
|
github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA=
|
||||||
github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
|
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||||
github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels=
|
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
@ -139,8 +159,13 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
|
|||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@ -158,11 +183,15 @@ github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/
|
|||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||||
@ -171,12 +200,14 @@ github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4
|
|||||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||||
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 h1:dg/EaaJLPIg4xn2kaZil7Ax3wfoxcFXaBwyOTlcz5AI=
|
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 h1:dg/EaaJLPIg4xn2kaZil7Ax3wfoxcFXaBwyOTlcz5AI=
|
||||||
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77/go.mod h1:FD9a09Vw07CSMTdT0Y7ttStOa1WZsnPBslliMw2DkeM=
|
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77/go.mod h1:FD9a09Vw07CSMTdT0Y7ttStOa1WZsnPBslliMw2DkeM=
|
||||||
|
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.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||||
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
|
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
|
||||||
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go=
|
github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go=
|
||||||
@ -194,6 +225,7 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
|
|||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||||
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=
|
||||||
@ -214,9 +246,19 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
|
|||||||
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/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf h1:Y43S3e9P1NPs/QF4R5/SdlXj2d31540hP4Gk8VKNvDg=
|
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf h1:Y43S3e9P1NPs/QF4R5/SdlXj2d31540hP4Gk8VKNvDg=
|
||||||
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf/go.mod h1:c+cGNU1qi9bO7ZF4IRMYk+KaZTNiQ/gQrSbyMmGFq1Q=
|
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf/go.mod h1:c+cGNU1qi9bO7ZF4IRMYk+KaZTNiQ/gQrSbyMmGFq1Q=
|
||||||
|
github.com/t3rm1n4l/go-mega v0.0.0-20230220145126-b87ebf5801d8 h1:Jg3qJLX/qhwrh4MdB75+Z9l/JkCODVHG8nXY187qa1E=
|
||||||
|
github.com/t3rm1n4l/go-mega v0.0.0-20230220145126-b87ebf5801d8/go.mod h1:c+cGNU1qi9bO7ZF4IRMYk+KaZTNiQ/gQrSbyMmGFq1Q=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/u2takey/ffmpeg-go v0.4.1 h1:l5ClIwL3N2LaH1zF3xivb3kP2HW95eyG5xhHE1JdZ9Y=
|
||||||
|
github.com/u2takey/ffmpeg-go v0.4.1/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc=
|
||||||
|
github.com/u2takey/go-utils v0.3.1 h1:TaQTgmEZZeDHQFYfd+AdUT1cT4QJgJn/XVPELhHw4ys=
|
||||||
|
github.com/u2takey/go-utils v0.3.1/go.mod h1:6e+v5vEZ/6gu12w/DC2ixZdZtCrNokVxD0JUklcqdCs=
|
||||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
|
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
||||||
|
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/upyun/go-sdk/v3 v3.0.3 h1:2wUkNk2fyJReMYHMvJyav050D83rYwSjN7mEPR0Pp8Q=
|
github.com/upyun/go-sdk/v3 v3.0.3 h1:2wUkNk2fyJReMYHMvJyav050D83rYwSjN7mEPR0Pp8Q=
|
||||||
github.com/upyun/go-sdk/v3 v3.0.3/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
|
github.com/upyun/go-sdk/v3 v3.0.3/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
|
||||||
github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
|
github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
|
||||||
@ -225,40 +267,44 @@ github.com/winfsp/cgofuse v1.5.0/go.mod h1:h3awhoUOcn2VYVKCwDaYxSLlZwnyK+A8KaDoL
|
|||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
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-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-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.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
|
||||||
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.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
|
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||||
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
|
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||||
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/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-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-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-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.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
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.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
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=
|
||||||
@ -271,27 +317,27 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.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.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
|
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
|
||||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
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-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/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=
|
||||||
@ -310,6 +356,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
@ -317,14 +364,16 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.4.5 h1:u1lytId4+o9dDaNcPCFzNv7h6wvmc92UjNk3z8enSBU=
|
gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y=
|
||||||
gorm.io/driver/mysql v1.4.5/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
|
gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
|
||||||
gorm.io/driver/postgres v1.4.6 h1:1FPESNXqIKG5JmraaH2bfCVlMQ7paLoCreFxDtqzwdc=
|
gorm.io/driver/postgres v1.4.8 h1:NDWizaclb7Q2aupT0jkwK8jx1HVCNzt+PQ8v/VnxviA=
|
||||||
gorm.io/driver/postgres v1.4.6/go.mod h1:UJChCNLFKeBqQRE+HrkFUbKbq9idPXmTOk2u4Wok8S4=
|
gorm.io/driver/postgres v1.4.8/go.mod h1:O9MruWGNLUBUWVYfWuBClpf3HeGjOoybY0SNmCs3wsw=
|
||||||
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
|
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
|
||||||
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||||
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||||
gorm.io/gorm v1.24.3 h1:WL2ifUmzR/SLp85CSURAfybcHnGZ+yLSGSxgYXlFBHg=
|
gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
|
||||||
gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/cmd/flags"
|
"github.com/alist-org/alist/v3/cmd/flags"
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/caarlos0/env/v6"
|
"github.com/caarlos0/env/v7"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,7 +53,7 @@ func InitConfig() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("marshal config error: %+v", err)
|
log.Fatalf("marshal config error: %+v", err)
|
||||||
}
|
}
|
||||||
err = os.WriteFile(configPath, confBody, 0777)
|
err = os.WriteFile(configPath, confBody, 0o777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("update config struct error: %+v", err)
|
log.Fatalf("update config struct error: %+v", err)
|
||||||
}
|
}
|
||||||
@ -69,11 +73,15 @@ func InitConfig() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("failed delete temp file:", err)
|
log.Errorln("failed delete temp file:", err)
|
||||||
}
|
}
|
||||||
err = os.MkdirAll(conf.Conf.TempDir, 0777)
|
err = os.MkdirAll(conf.Conf.TempDir, 0o777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("create temp dir error: %+v", err)
|
log.Fatalf("create temp dir error: %+v", err)
|
||||||
}
|
}
|
||||||
log.Debugf("config: %+v", conf.Conf)
|
log.Debugf("config: %+v", conf.Conf)
|
||||||
|
if conf.Conf.TlsInsecureSkipVerify {
|
||||||
|
base.RestyClient = base.RestyClient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||||
|
}
|
||||||
|
initURL()
|
||||||
}
|
}
|
||||||
|
|
||||||
func confFromEnv() {
|
func confFromEnv() {
|
||||||
@ -88,3 +96,14 @@ func confFromEnv() {
|
|||||||
log.Fatalf("load config from env error: %+v", err)
|
log.Fatalf("load config from env error: %+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initURL() {
|
||||||
|
if !strings.Contains(conf.Conf.SiteURL, "://") {
|
||||||
|
conf.Conf.SiteURL = utils.FixAndCleanPath(conf.Conf.SiteURL)
|
||||||
|
}
|
||||||
|
u, err := url.Parse(conf.Conf.SiteURL)
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.Fatalf("can't parse site_url: %+v", err)
|
||||||
|
}
|
||||||
|
conf.URL = u
|
||||||
|
}
|
||||||
|
@ -110,7 +110,7 @@ func InitialSettings() []model.SettingItem {
|
|||||||
"PDF.js":"https://alist-org.github.io/pdf.js/web/viewer.html?file=$e_url"
|
"PDF.js":"https://alist-org.github.io/pdf.js/web/viewer.html?file=$e_url"
|
||||||
},
|
},
|
||||||
"epub": {
|
"epub": {
|
||||||
"EPUB.js":"/static/epub.js/viewer.html?url=$e_url"
|
"EPUB.js":"https://alist-org.github.io/static/epub.js/viewer.html?url=$e_url"
|
||||||
}
|
}
|
||||||
}`, Type: conf.TypeText, Group: model.PREVIEW},
|
}`, Type: conf.TypeText, Group: model.PREVIEW},
|
||||||
// {Key: conf.OfficeViewers, Value: `{
|
// {Key: conf.OfficeViewers, Value: `{
|
||||||
@ -129,13 +129,14 @@ func InitialSettings() []model.SettingItem {
|
|||||||
{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, 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.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: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: conf.PrivacyRegs, Value: `(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])
|
{Key: conf.PrivacyRegs, Value: `(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])
|
||||||
([[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}|::|:(?::[[:xdigit:]]{1,4}){1,6}|[[:xdigit:]]{1,4}:(?::[[:xdigit:]]{1,4}){1,5}|(?:[[:xdigit:]]{1,4}:){2}(?::[[:xdigit:]]{1,4}){1,4}|(?:[[:xdigit:]]{1,4}:){3}(?::[[:xdigit:]]{1,4}){1,3}|(?:[[:xdigit:]]{1,4}:){4}(?::[[:xdigit:]]{1,4}){1,2}|(?:[[:xdigit:]]{1,4}:){5}:[[:xdigit:]]{1,4}|(?:[[:xdigit:]]{1,4}:){1,6}:)
|
([[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}|::|:(?::[[:xdigit:]]{1,4}){1,6}|[[:xdigit:]]{1,4}:(?::[[:xdigit:]]{1,4}){1,5}|(?:[[:xdigit:]]{1,4}:){2}(?::[[:xdigit:]]{1,4}){1,4}|(?:[[:xdigit:]]{1,4}:){3}(?::[[:xdigit:]]{1,4}){1,3}|(?:[[:xdigit:]]{1,4}:){4}(?::[[:xdigit:]]{1,4}){1,2}|(?:[[:xdigit:]]{1,4}:){5}:[[:xdigit:]]{1,4}|(?:[[:xdigit:]]{1,4}:){1,6}:)
|
||||||
(?U)access_token=(.*)&`,
|
(?U)access_token=(.*)&`,
|
||||||
Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: conf.OcrApi, Value: "https://api.nn.ci/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL},
|
{Key: conf.OcrApi, Value: "https://api.nn.ci/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL},
|
||||||
{Key: conf.FilenameCharMapping, Value: `{"/": "|"}`, Type: conf.TypeText, Group: model.GLOBAL},
|
{Key: conf.FilenameCharMapping, Value: `{"/": "|"}`, Type: conf.TypeText, Group: model.GLOBAL},
|
||||||
|
{Key: conf.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
||||||
|
|
||||||
// aria2 settings
|
// aria2 settings
|
||||||
{Key: conf.Aria2Uri, Value: "http://localhost:6800/jsonrpc", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
{Key: conf.Aria2Uri, Value: "http://localhost:6800/jsonrpc", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
||||||
@ -153,6 +154,9 @@ func InitialSettings() []model.SettingItem {
|
|||||||
{Key: conf.GithubClientId, Value: "", Type: conf.TypeString, Group: model.GITHUB, Flag: model.PRIVATE},
|
{Key: conf.GithubClientId, Value: "", Type: conf.TypeString, Group: model.GITHUB, Flag: model.PRIVATE},
|
||||||
{Key: conf.GithubClientSecrets, Value: "", Type: conf.TypeString, Group: model.GITHUB, Flag: model.PRIVATE},
|
{Key: conf.GithubClientSecrets, Value: "", Type: conf.TypeString, Group: model.GITHUB, Flag: model.PRIVATE},
|
||||||
{Key: conf.GithubLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GITHUB, Flag: model.PUBLIC},
|
{Key: conf.GithubLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GITHUB, Flag: model.PUBLIC},
|
||||||
|
|
||||||
|
// qbittorrent settings
|
||||||
|
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
}
|
}
|
||||||
if flags.Dev {
|
if flags.Dev {
|
||||||
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
||||||
|
@ -48,6 +48,7 @@ func initUser() {
|
|||||||
Role: model.GUEST,
|
Role: model.GUEST,
|
||||||
BasePath: "/",
|
BasePath: "/",
|
||||||
Permission: 0,
|
Permission: 0,
|
||||||
|
Disabled: true,
|
||||||
}
|
}
|
||||||
if err := db.CreateUser(guest); err != nil {
|
if err := db.CreateUser(guest); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -35,19 +35,22 @@ func setLog(l *logrus.Logger) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Log() {
|
func Log() {
|
||||||
log.SetOutput(logrus.StandardLogger().Out)
|
|
||||||
setLog(logrus.StandardLogger())
|
setLog(logrus.StandardLogger())
|
||||||
setLog(utils.Log)
|
setLog(utils.Log)
|
||||||
logConfig := conf.Conf.Log
|
logConfig := conf.Conf.Log
|
||||||
if logConfig.Enable {
|
if logConfig.Enable {
|
||||||
mw := io.MultiWriter(os.Stdout, &lumberjack.Logger{
|
var w io.Writer = &lumberjack.Logger{
|
||||||
Filename: logConfig.Name,
|
Filename: logConfig.Name,
|
||||||
MaxSize: logConfig.MaxSize, // megabytes
|
MaxSize: logConfig.MaxSize, // megabytes
|
||||||
MaxBackups: logConfig.MaxBackups,
|
MaxBackups: logConfig.MaxBackups,
|
||||||
MaxAge: logConfig.MaxAge, //days
|
MaxAge: logConfig.MaxAge, //days
|
||||||
Compress: logConfig.Compress, // disabled by default
|
Compress: logConfig.Compress, // disabled by default
|
||||||
})
|
}
|
||||||
logrus.SetOutput(mw)
|
if flags.Debug || flags.Dev {
|
||||||
|
w = io.MultiWriter(os.Stdout, w)
|
||||||
|
}
|
||||||
|
logrus.SetOutput(w)
|
||||||
}
|
}
|
||||||
|
log.SetOutput(logrus.StandardLogger().Out)
|
||||||
utils.Log.Infof("init logrus...")
|
utils.Log.Infof("init logrus...")
|
||||||
}
|
}
|
||||||
|
15
internal/bootstrap/qbittorrent.go
Normal file
15
internal/bootstrap/qbittorrent.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/qbittorrent"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitQbittorrent() {
|
||||||
|
go func() {
|
||||||
|
err := qbittorrent.InitClient()
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.Infof("qbittorrent not ready.")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
@ -35,19 +35,20 @@ type LogConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Force bool `json:"force" env:"FORCE"`
|
Force bool `json:"force" env:"FORCE"`
|
||||||
Address string `json:"address" env:"ADDR"`
|
Address string `json:"address" env:"ADDR"`
|
||||||
Port int `json:"port" env:"PORT"`
|
Port int `json:"port" env:"PORT"`
|
||||||
SiteURL string `json:"site_url" env:"SITE_URL"`
|
SiteURL string `json:"site_url" env:"SITE_URL"`
|
||||||
Cdn string `json:"cdn" env:"CDN"`
|
Cdn string `json:"cdn" env:"CDN"`
|
||||||
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"`
|
Database Database `json:"database"`
|
||||||
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"`
|
||||||
Log LogConfig `json:"log"`
|
Log LogConfig `json:"log"`
|
||||||
MaxConnections int `json:"max_connections" env:"MAX_CONNECTIONS"`
|
MaxConnections int `json:"max_connections" env:"MAX_CONNECTIONS"`
|
||||||
|
TlsInsecureSkipVerify bool `json:"tls_insecure_skip_verify" env:"TLS_INSECURE_SKIP_VERIFY"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
@ -75,6 +76,7 @@ func DefaultConfig() *Config {
|
|||||||
MaxBackups: 5,
|
MaxBackups: 5,
|
||||||
MaxAge: 28,
|
MaxAge: 28,
|
||||||
},
|
},
|
||||||
MaxConnections: 0,
|
MaxConnections: 0,
|
||||||
|
TlsInsecureSkipVerify: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,70 +1,74 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeString = "string"
|
TypeString = "string"
|
||||||
TypeSelect = "select"
|
TypeSelect = "select"
|
||||||
TypeBool = "bool"
|
TypeBool = "bool"
|
||||||
TypeText = "text"
|
TypeText = "text"
|
||||||
TypeNumber = "number"
|
TypeNumber = "number"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// site
|
// site
|
||||||
VERSION = "version"
|
VERSION = "version"
|
||||||
SiteTitle = "site_title"
|
SiteTitle = "site_title"
|
||||||
Announcement = "announcement"
|
Announcement = "announcement"
|
||||||
AllowIndexed = "allow_indexed"
|
AllowIndexed = "allow_indexed"
|
||||||
|
|
||||||
Logo = "logo"
|
Logo = "logo"
|
||||||
Favicon = "favicon"
|
Favicon = "favicon"
|
||||||
MainColor = "main_color"
|
MainColor = "main_color"
|
||||||
|
|
||||||
// preview
|
// preview
|
||||||
TextTypes = "text_types"
|
TextTypes = "text_types"
|
||||||
AudioTypes = "audio_types"
|
AudioTypes = "audio_types"
|
||||||
VideoTypes = "video_types"
|
VideoTypes = "video_types"
|
||||||
ImageTypes = "image_types"
|
ImageTypes = "image_types"
|
||||||
ProxyTypes = "proxy_types"
|
ProxyTypes = "proxy_types"
|
||||||
ProxyIgnoreHeaders = "proxy_ignore_headers"
|
ProxyIgnoreHeaders = "proxy_ignore_headers"
|
||||||
AudioAutoplay = "audio_autoplay"
|
AudioAutoplay = "audio_autoplay"
|
||||||
VideoAutoplay = "video_autoplay"
|
VideoAutoplay = "video_autoplay"
|
||||||
|
|
||||||
// global
|
// global
|
||||||
HideFiles = "hide_files"
|
HideFiles = "hide_files"
|
||||||
CustomizeHead = "customize_head"
|
CustomizeHead = "customize_head"
|
||||||
CustomizeBody = "customize_body"
|
CustomizeBody = "customize_body"
|
||||||
LinkExpiration = "link_expiration"
|
LinkExpiration = "link_expiration"
|
||||||
SignAll = "sign_all"
|
SignAll = "sign_all"
|
||||||
PrivacyRegs = "privacy_regs"
|
PrivacyRegs = "privacy_regs"
|
||||||
OcrApi = "ocr_api"
|
OcrApi = "ocr_api"
|
||||||
FilenameCharMapping = "filename_char_mapping"
|
FilenameCharMapping = "filename_char_mapping"
|
||||||
|
ForwardDirectLinkParams = "forward_direct_link_params"
|
||||||
|
|
||||||
// index
|
// index
|
||||||
SearchIndex = "search_index"
|
SearchIndex = "search_index"
|
||||||
AutoUpdateIndex = "auto_update_index"
|
AutoUpdateIndex = "auto_update_index"
|
||||||
IgnorePaths = "ignore_paths"
|
IgnorePaths = "ignore_paths"
|
||||||
MaxIndexDepth = "max_index_depth"
|
MaxIndexDepth = "max_index_depth"
|
||||||
|
|
||||||
// aria2
|
// aria2
|
||||||
Aria2Uri = "aria2_uri"
|
Aria2Uri = "aria2_uri"
|
||||||
Aria2Secret = "aria2_secret"
|
Aria2Secret = "aria2_secret"
|
||||||
|
|
||||||
// single
|
// single
|
||||||
Token = "token"
|
Token = "token"
|
||||||
IndexProgress = "index_progress"
|
IndexProgress = "index_progress"
|
||||||
|
|
||||||
//Github
|
//Github
|
||||||
GithubClientId = "github_client_id"
|
GithubClientId = "github_client_id"
|
||||||
GithubClientSecrets = "github_client_secrets"
|
GithubClientSecrets = "github_client_secrets"
|
||||||
GithubLoginEnabled = "github_login_enabled"
|
GithubLoginEnabled = "github_login_enabled"
|
||||||
|
|
||||||
|
// qbittorrent
|
||||||
|
QbittorrentUrl = "qbittorrent_url"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UNKNOWN = iota
|
UNKNOWN = iota
|
||||||
FOLDER
|
FOLDER
|
||||||
//OFFICE
|
//OFFICE
|
||||||
VIDEO
|
VIDEO
|
||||||
AUDIO
|
AUDIO
|
||||||
TEXT
|
TEXT
|
||||||
IMAGE
|
IMAGE
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import "regexp"
|
import (
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
BuiltAt string
|
BuiltAt string
|
||||||
@ -13,6 +16,7 @@ var (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Conf *Config
|
Conf *Config
|
||||||
|
URL *url.URL
|
||||||
)
|
)
|
||||||
|
|
||||||
var SlicesMap = make(map[string][]string)
|
var SlicesMap = make(map[string][]string)
|
||||||
|
@ -9,7 +9,8 @@ type Config struct {
|
|||||||
NoUpload bool `json:"no_upload"`
|
NoUpload bool `json:"no_upload"`
|
||||||
NeedMs bool `json:"need_ms"` // if need get message from user, such as validate code
|
NeedMs bool `json:"need_ms"` // if need get message from user, such as validate code
|
||||||
DefaultRoot string `json:"default_root"`
|
DefaultRoot string `json:"default_root"`
|
||||||
CheckStatus bool
|
CheckStatus bool `json:"-"`
|
||||||
|
Alert string `json:"alert"` //info,success,warning,danger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) MustProxy() bool {
|
func (c Config) MustProxy() bool {
|
||||||
|
@ -41,9 +41,9 @@ func getFileStreamFromLink(file model.Obj, link *model.Link) (*model.FileStream,
|
|||||||
if link.Data != nil {
|
if link.Data != nil {
|
||||||
rc = link.Data
|
rc = link.Data
|
||||||
} else if link.FilePath != nil {
|
} else if link.FilePath != nil {
|
||||||
// copy a new temp, because will be deleted after upload
|
// create a new temp symbolic link, because it will be deleted after upload
|
||||||
newFilePath := stdpath.Join(conf.Conf.TempDir, fmt.Sprintf("%s-%s", uuid.NewString(), file.GetName()))
|
newFilePath := stdpath.Join(conf.Conf.TempDir, fmt.Sprintf("%s-%s", uuid.NewString(), file.GetName()))
|
||||||
err := utils.CopyFile(*link.FilePath, newFilePath)
|
err := utils.SymlinkOrCopyFile(*link.FilePath, newFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -18,17 +18,19 @@ type User struct {
|
|||||||
Password string `json:"password"` // password
|
Password string `json:"password"` // password
|
||||||
BasePath string `json:"base_path"` // base path
|
BasePath string `json:"base_path"` // base path
|
||||||
Role int `json:"role"` // user's role
|
Role int `json:"role"` // user's role
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
// Determine permissions by bit
|
// Determine permissions by bit
|
||||||
// 0: can see hidden files
|
// 0: can see hidden files
|
||||||
// 1: can access without password
|
// 1: can access without password
|
||||||
// 2: can add aria2 tasks
|
// 2: can add aria2 tasks
|
||||||
// 3: can mkdir and upload
|
// 3: can mkdir and upload
|
||||||
// 4: can rename
|
// 4: can rename
|
||||||
// 5: can move
|
// 5: can move
|
||||||
// 6: can copy
|
// 6: can copy
|
||||||
// 7: can remove
|
// 7: can remove
|
||||||
// 8: webdav read
|
// 8: webdav read
|
||||||
// 9: webdav write
|
// 9: webdav write
|
||||||
|
// 10: can add qbittorrent tasks
|
||||||
Permission int32 `json:"permission"`
|
Permission int32 `json:"permission"`
|
||||||
OtpSecret string `json:"-"`
|
OtpSecret string `json:"-"`
|
||||||
GithubID int `json:"github_id"`
|
GithubID int `json:"github_id"`
|
||||||
@ -92,6 +94,10 @@ func (u User) CanWebdavManage() bool {
|
|||||||
return u.IsAdmin() || (u.Permission>>9)&1 == 1
|
return u.IsAdmin() || (u.Permission>>9)&1 == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u User) CanAddQbittorrentTasks() bool {
|
||||||
|
return u.IsAdmin() || (u.Permission>>10)&1 == 1
|
||||||
|
}
|
||||||
|
|
||||||
func (u User) JoinPath(reqPath string) (string, error) {
|
func (u User) JoinPath(reqPath string) (string, error) {
|
||||||
return utils.JoinBasePath(u.BasePath, reqPath)
|
return utils.JoinBasePath(u.BasePath, reqPath)
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,10 @@ func GetStorageAndActualPath(rawPath string) (storage driver.Driver, actualPath
|
|||||||
rawPath = utils.FixAndCleanPath(rawPath)
|
rawPath = utils.FixAndCleanPath(rawPath)
|
||||||
storage = GetBalancedStorage(rawPath)
|
storage = GetBalancedStorage(rawPath)
|
||||||
if storage == nil {
|
if storage == nil {
|
||||||
|
if rawPath == "/" {
|
||||||
|
err = errors.New("please add a storage first.")
|
||||||
|
return
|
||||||
|
}
|
||||||
err = errors.Errorf("can't find storage with rawPath: %s", rawPath)
|
err = errors.Errorf("can't find storage with rawPath: %s", rawPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
58
internal/qbittorrent/add.go
Normal file
58
internal/qbittorrent/add.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package qbittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/task"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddURL(ctx context.Context, url string, dstDirPath string) error {
|
||||||
|
// check storage
|
||||||
|
storage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed get storage")
|
||||||
|
}
|
||||||
|
// check is it could upload
|
||||||
|
if storage.Config().NoUpload {
|
||||||
|
return errors.WithStack(errs.UploadNotSupported)
|
||||||
|
}
|
||||||
|
// check path is valid
|
||||||
|
obj, err := op.Get(ctx, storage, dstDirActualPath)
|
||||||
|
if err != nil {
|
||||||
|
if !errs.IsObjectNotFound(err) {
|
||||||
|
return errors.WithMessage(err, "failed get object")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !obj.IsDir() {
|
||||||
|
// can't add to a file
|
||||||
|
return errors.WithStack(errs.NotFolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// call qbittorrent
|
||||||
|
id := uuid.NewString()
|
||||||
|
tempDir := filepath.Join(conf.Conf.TempDir, "qbittorrent", id)
|
||||||
|
err = qbclient.AddFromLink(url, tempDir, id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to add url %s", url)
|
||||||
|
}
|
||||||
|
DownTaskManager.Submit(task.WithCancelCtx(&task.Task[string]{
|
||||||
|
ID: id,
|
||||||
|
Name: fmt.Sprintf("download %s to [%s](%s)", url, storage.GetStorage().MountPath, dstDirActualPath),
|
||||||
|
Func: func(tsk *task.Task[string]) error {
|
||||||
|
m := &Monitor{
|
||||||
|
tsk: tsk,
|
||||||
|
tempDir: tempDir,
|
||||||
|
dstDirPath: dstDirPath,
|
||||||
|
}
|
||||||
|
return m.Loop()
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
return nil
|
||||||
|
}
|
364
internal/qbittorrent/client.go
Normal file
364
internal/qbittorrent/client.go
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
package qbittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
AddFromLink(link string, savePath string, id string) error
|
||||||
|
GetInfo(id string) (TorrentInfo, error)
|
||||||
|
GetFiles(id string) ([]FileInfo, error)
|
||||||
|
Delete(id string, deleteFiles bool) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
url *url.URL
|
||||||
|
client http.Client
|
||||||
|
Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(webuiUrl string) (Client, error) {
|
||||||
|
u, err := url.Parse(webuiUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jar, err := cookiejar.New(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var c = &client{
|
||||||
|
url: u,
|
||||||
|
client: http.Client{Jar: jar},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.checkAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) checkAuthorization() error {
|
||||||
|
// check authorization
|
||||||
|
if c.authorized() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check authorization after logging in
|
||||||
|
err := c.login()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.authorized() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("unauthorized qbittorrent url")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) authorized() bool {
|
||||||
|
resp, err := c.post("/api/v2/app/version", nil)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return resp.StatusCode == 200 // the status code will be 403 if not authorized
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) login() error {
|
||||||
|
// prepare HTTP request
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("username", c.url.User.Username())
|
||||||
|
passwd, _ := c.url.User.Password()
|
||||||
|
v.Set("password", passwd)
|
||||||
|
resp, err := c.post("/api/v2/auth/login", v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check result
|
||||||
|
body := make([]byte, 2)
|
||||||
|
_, err = resp.Body.Read(body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if string(body) != "Ok" {
|
||||||
|
return errors.New("failed to login into qBittorrent webui with url: " + c.url.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) post(path string, data url.Values) (*http.Response, error) {
|
||||||
|
u := c.url.JoinPath(path)
|
||||||
|
u.User = nil // remove userinfo for requests
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", u.String(), bytes.NewReader([]byte(data.Encode())))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if data != nil {
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Cookies() != nil {
|
||||||
|
c.client.Jar.SetCookies(u, resp.Cookies())
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) AddFromLink(link string, savePath string, id string) error {
|
||||||
|
err := c.checkAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
writer := multipart.NewWriter(buf)
|
||||||
|
|
||||||
|
addField := func(name string, value string) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = writer.WriteField(name, value)
|
||||||
|
}
|
||||||
|
addField("urls", link)
|
||||||
|
addField("savepath", savePath)
|
||||||
|
addField("tags", "alist-"+id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
u := c.url.JoinPath("/api/v2/torrents/add")
|
||||||
|
u.User = nil // remove userinfo for requests
|
||||||
|
req, err := http.NewRequest("POST", u.String(), buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check result
|
||||||
|
body := make([]byte, 2)
|
||||||
|
_, err = resp.Body.Read(body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 || string(body) != "Ok" {
|
||||||
|
return errors.New("failed to add qBittorrent task: " + link)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TorrentStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ERROR TorrentStatus = "error"
|
||||||
|
MISSINGFILES TorrentStatus = "missingFiles"
|
||||||
|
UPLOADING TorrentStatus = "uploading"
|
||||||
|
PAUSEDUP TorrentStatus = "pausedUP"
|
||||||
|
QUEUEDUP TorrentStatus = "queuedUP"
|
||||||
|
STALLEDUP TorrentStatus = "stalledUP"
|
||||||
|
CHECKINGUP TorrentStatus = "checkingUP"
|
||||||
|
FORCEDUP TorrentStatus = "forcedUP"
|
||||||
|
ALLOCATING TorrentStatus = "allocating"
|
||||||
|
DOWNLOADING TorrentStatus = "downloading"
|
||||||
|
METADL TorrentStatus = "metaDL"
|
||||||
|
PAUSEDDL TorrentStatus = "pausedDL"
|
||||||
|
QUEUEDDL TorrentStatus = "queuedDL"
|
||||||
|
STALLEDDL TorrentStatus = "stalledDL"
|
||||||
|
CHECKINGDL TorrentStatus = "checkingDL"
|
||||||
|
FORCEDDL TorrentStatus = "forcedDL"
|
||||||
|
CHECKINGRESUMEDATA TorrentStatus = "checkingResumeData"
|
||||||
|
MOVING TorrentStatus = "moving"
|
||||||
|
UNKNOWN TorrentStatus = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://github.com/DGuang21/PTGo/blob/main/app/client/client_distributer.go
|
||||||
|
type TorrentInfo struct {
|
||||||
|
AddedOn int `json:"added_on"` // 将 torrent 添加到客户端的时间(Unix Epoch)
|
||||||
|
AmountLeft int64 `json:"amount_left"` // 剩余大小(字节)
|
||||||
|
AutoTmm bool `json:"auto_tmm"` // 此 torrent 是否由 Automatic Torrent Management 管理
|
||||||
|
Availability float64 `json:"availability"` // 当前百分比
|
||||||
|
Category string `json:"category"` //
|
||||||
|
Completed int64 `json:"completed"` // 完成的传输数据量(字节)
|
||||||
|
CompletionOn int `json:"completion_on"` // Torrent 完成的时间(Unix Epoch)
|
||||||
|
ContentPath string `json:"content_path"` // torrent 内容的绝对路径(多文件 torrent 的根路径,单文件 torrent 的绝对文件路径)
|
||||||
|
DlLimit int `json:"dl_limit"` // Torrent 下载速度限制(字节/秒)
|
||||||
|
Dlspeed int `json:"dlspeed"` // Torrent 下载速度(字节/秒)
|
||||||
|
Downloaded int64 `json:"downloaded"` // 已经下载大小
|
||||||
|
DownloadedSession int64 `json:"downloaded_session"` // 此会话下载的数据量
|
||||||
|
Eta int `json:"eta"` //
|
||||||
|
FLPiecePrio bool `json:"f_l_piece_prio"` // 如果第一个最后一块被优先考虑,则为true
|
||||||
|
ForceStart bool `json:"force_start"` // 如果为此 torrent 启用了强制启动,则为true
|
||||||
|
Hash string `json:"hash"` //
|
||||||
|
LastActivity int `json:"last_activity"` // 上次活跃的时间(Unix Epoch)
|
||||||
|
MagnetURI string `json:"magnet_uri"` // 与此 torrent 对应的 Magnet URI
|
||||||
|
MaxRatio int `json:"max_ratio"` // 种子/上传停止种子前的最大共享比率
|
||||||
|
MaxSeedingTime int `json:"max_seeding_time"` // 停止种子种子前的最长种子时间(秒)
|
||||||
|
Name string `json:"name"` //
|
||||||
|
NumComplete int `json:"num_complete"` //
|
||||||
|
NumIncomplete int `json:"num_incomplete"` //
|
||||||
|
NumLeechs int `json:"num_leechs"` // 连接到的 leechers 的数量
|
||||||
|
NumSeeds int `json:"num_seeds"` // 连接到的种子数
|
||||||
|
Priority int `json:"priority"` // 速度优先。如果队列被禁用或 torrent 处于种子模式,则返回 -1
|
||||||
|
Progress float64 `json:"progress"` // 进度
|
||||||
|
Ratio float64 `json:"ratio"` // Torrent 共享比率
|
||||||
|
RatioLimit int `json:"ratio_limit"` //
|
||||||
|
SavePath string `json:"save_path"`
|
||||||
|
SeedingTime int `json:"seeding_time"` // Torrent 完成用时(秒)
|
||||||
|
SeedingTimeLimit int `json:"seeding_time_limit"` // max_seeding_time
|
||||||
|
SeenComplete int `json:"seen_complete"` // 上次 torrent 完成的时间
|
||||||
|
SeqDl bool `json:"seq_dl"` // 如果启用顺序下载,则为true
|
||||||
|
Size int64 `json:"size"` //
|
||||||
|
State TorrentStatus `json:"state"` // 参见https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-torrent-list
|
||||||
|
SuperSeeding bool `json:"super_seeding"` // 如果启用超级播种,则为true
|
||||||
|
Tags string `json:"tags"` // Torrent 的逗号连接标签列表
|
||||||
|
TimeActive int `json:"time_active"` // 总活动时间(秒)
|
||||||
|
TotalSize int64 `json:"total_size"` // 此 torrent 中所有文件的总大小(字节)(包括未选择的文件)
|
||||||
|
Tracker string `json:"tracker"` // 第一个具有工作状态的tracker。如果没有tracker在工作,则返回空字符串。
|
||||||
|
TrackersCount int `json:"trackers_count"` //
|
||||||
|
UpLimit int `json:"up_limit"` // 上传限制
|
||||||
|
Uploaded int64 `json:"uploaded"` // 累计上传
|
||||||
|
UploadedSession int64 `json:"uploaded_session"` // 当前session累计上传
|
||||||
|
Upspeed int `json:"upspeed"` // 上传速度(字节/秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
type InfoNotFoundError struct {
|
||||||
|
Id string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i InfoNotFoundError) Error() string {
|
||||||
|
return "there should be exactly one task with tag \"alist-" + i.Id + "\""
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInfoNotFoundError(id string) InfoNotFoundError {
|
||||||
|
return InfoNotFoundError{Id: id}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) GetInfo(id string) (TorrentInfo, error) {
|
||||||
|
var infos []TorrentInfo
|
||||||
|
|
||||||
|
err := c.checkAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return TorrentInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("tag", "alist-"+id)
|
||||||
|
response, err := c.post("/api/v2/torrents/info", v)
|
||||||
|
if err != nil {
|
||||||
|
return TorrentInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return TorrentInfo{}, err
|
||||||
|
}
|
||||||
|
err = utils.Json.Unmarshal(body, &infos)
|
||||||
|
if err != nil {
|
||||||
|
return TorrentInfo{}, err
|
||||||
|
}
|
||||||
|
if len(infos) != 1 {
|
||||||
|
return TorrentInfo{}, NewInfoNotFoundError(id)
|
||||||
|
}
|
||||||
|
return infos[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileInfo struct {
|
||||||
|
Index int `json:"index"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Progress float32 `json:"progress"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
IsSeed bool `json:"is_seed"`
|
||||||
|
PieceRange []int `json:"piece_range"`
|
||||||
|
Availability float32 `json:"availability"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) GetFiles(id string) ([]FileInfo, error) {
|
||||||
|
var infos []FileInfo
|
||||||
|
|
||||||
|
err := c.checkAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return []FileInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tInfo, err := c.GetInfo(id)
|
||||||
|
if err != nil {
|
||||||
|
return []FileInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("hash", tInfo.Hash)
|
||||||
|
response, err := c.post("/api/v2/torrents/files", v)
|
||||||
|
if err != nil {
|
||||||
|
return []FileInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []FileInfo{}, err
|
||||||
|
}
|
||||||
|
err = utils.Json.Unmarshal(body, &infos)
|
||||||
|
if err != nil {
|
||||||
|
return []FileInfo{}, err
|
||||||
|
}
|
||||||
|
return infos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Delete(id string, deleteFiles bool) error {
|
||||||
|
err := c.checkAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := c.GetInfo(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("hashes", info.Hash)
|
||||||
|
if deleteFiles {
|
||||||
|
v.Set("deleteFiles", "true")
|
||||||
|
} else {
|
||||||
|
v.Set("deleteFiles", "false")
|
||||||
|
}
|
||||||
|
response, err := c.post("/api/v2/torrents/delete", v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.StatusCode != 200 {
|
||||||
|
return errors.New("failed to delete qbittorrent task")
|
||||||
|
}
|
||||||
|
|
||||||
|
v = url.Values{}
|
||||||
|
v.Set("tags", "alist-"+id)
|
||||||
|
response, err = c.post("/api/v2/torrents/deleteTags", v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.StatusCode != 200 {
|
||||||
|
return errors.New("failed to delete qbittorrent tag")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
154
internal/qbittorrent/client_test.go
Normal file
154
internal/qbittorrent/client_test.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package qbittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLogin(t *testing.T) {
|
||||||
|
// test logging in with wrong password
|
||||||
|
u, err := url.Parse("http://admin:admin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
jar, err := cookiejar.New(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
var c = &client{
|
||||||
|
url: u,
|
||||||
|
client: http.Client{Jar: jar},
|
||||||
|
}
|
||||||
|
err = c.login()
|
||||||
|
if err == nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test logging in with correct password
|
||||||
|
u, err = url.Parse("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
c.url = u
|
||||||
|
err = c.login()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in this test, the `Bypass authentication for clients on localhost` option in qBittorrent webui should be disabled
|
||||||
|
func TestAuthorized(t *testing.T) {
|
||||||
|
// init client
|
||||||
|
u, err := url.Parse("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
jar, err := cookiejar.New(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
var c = &client{
|
||||||
|
url: u,
|
||||||
|
client: http.Client{Jar: jar},
|
||||||
|
}
|
||||||
|
|
||||||
|
// test without logging in, which should be unauthorized
|
||||||
|
authorized := c.authorized()
|
||||||
|
if authorized {
|
||||||
|
t.Error("Should not be authorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test after logging in
|
||||||
|
err = c.login()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
authorized = c.authorized()
|
||||||
|
if !authorized {
|
||||||
|
t.Error("Should be authorized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
_, err := New("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, err = New("http://admin:wrong_password@127.0.0.1:8080/")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Should get an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdd(t *testing.T) {
|
||||||
|
// init client
|
||||||
|
c, err := New("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = c.AddFromLink(
|
||||||
|
"https://releases.ubuntu.com/22.04/ubuntu-22.04.1-desktop-amd64.iso.torrent",
|
||||||
|
"D:\\qBittorrentDownload\\alist",
|
||||||
|
"uuid-1",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = c.AddFromLink(
|
||||||
|
"magnet:?xt=urn:btih:375ae3280cd80a8e9d7212e11dfaf7c45069dd35&dn=archlinux-2023.02.01-x86_64.iso",
|
||||||
|
"D:\\qBittorrentDownload\\alist",
|
||||||
|
"uuid-2",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInfo(t *testing.T) {
|
||||||
|
// init client
|
||||||
|
c, err := New("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, err = c.GetInfo("uuid-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFiles(t *testing.T) {
|
||||||
|
// init client
|
||||||
|
c, err := New("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
files, err := c.GetFiles("uuid-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(files) != 1 {
|
||||||
|
t.Error("should have exactly one file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
// init client
|
||||||
|
c, err := New("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = c.AddFromLink(
|
||||||
|
"https://releases.ubuntu.com/22.04/ubuntu-22.04.1-desktop-amd64.iso.torrent",
|
||||||
|
"D:\\qBittorrentDownload\\alist",
|
||||||
|
"uuid-1",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = c.Delete("uuid-1", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
162
internal/qbittorrent/monitor.go
Normal file
162
internal/qbittorrent/monitor.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package qbittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/task"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Monitor struct {
|
||||||
|
tsk *task.Task[string]
|
||||||
|
tempDir string
|
||||||
|
dstDirPath string
|
||||||
|
finish chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) Loop() error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
completed bool
|
||||||
|
)
|
||||||
|
m.finish = make(chan struct{})
|
||||||
|
|
||||||
|
// wait for qbittorrent to parse torrent and create task
|
||||||
|
m.tsk.SetStatus("waiting for qbittorrent to parse torrent and create task")
|
||||||
|
waitCount := 0
|
||||||
|
for {
|
||||||
|
_, err := qbclient.GetInfo(m.tsk.ID)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch err.(type) {
|
||||||
|
case InfoNotFoundError:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
waitCount += 1
|
||||||
|
if waitCount >= 60 {
|
||||||
|
return errors.New("torrent parse timeout")
|
||||||
|
}
|
||||||
|
timer := time.NewTimer(time.Second)
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
|
||||||
|
outer:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.tsk.Ctx.Done():
|
||||||
|
// delete qbittorrent task and downloaded files when the task exits with error
|
||||||
|
return qbclient.Delete(m.tsk.ID, true)
|
||||||
|
case <-time.After(time.Second * 2):
|
||||||
|
completed, err = m.update()
|
||||||
|
if completed {
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.tsk.SetStatus("qbittorrent download completed, transferring")
|
||||||
|
<-m.finish
|
||||||
|
m.tsk.SetStatus("completed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) update() (bool, error) {
|
||||||
|
info, err := qbclient.GetInfo(m.tsk.ID)
|
||||||
|
if err != nil {
|
||||||
|
m.tsk.SetStatus("qbittorrent " + string(info.State))
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
progress := float64(info.Completed) / float64(info.Size) * 100
|
||||||
|
m.tsk.SetProgress(int(progress))
|
||||||
|
switch info.State {
|
||||||
|
case UPLOADING, PAUSEDUP, QUEUEDUP, STALLEDUP, FORCEDUP, CHECKINGUP:
|
||||||
|
err = m.complete()
|
||||||
|
return true, errors.WithMessage(err, "failed to transfer file")
|
||||||
|
case ALLOCATING, DOWNLOADING, METADL, PAUSEDDL, QUEUEDDL, STALLEDDL, CHECKINGDL, FORCEDDL, CHECKINGRESUMEDATA, MOVING:
|
||||||
|
m.tsk.SetStatus("qbittorrent downloading")
|
||||||
|
return false, nil
|
||||||
|
case ERROR, MISSINGFILES, UNKNOWN:
|
||||||
|
return true, errors.Errorf("failed to download %s, error: %s", m.tsk.ID, info.State)
|
||||||
|
}
|
||||||
|
return true, errors.New("unknown error occurred downloading qbittorrent") // should never happen
|
||||||
|
}
|
||||||
|
|
||||||
|
var TransferTaskManager = task.NewTaskManager(3, func(k *uint64) {
|
||||||
|
atomic.AddUint64(k, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
func (m *Monitor) complete() error {
|
||||||
|
// check dstDir again
|
||||||
|
storage, dstBaseDir, err := op.GetStorageAndActualPath(m.dstDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed get storage")
|
||||||
|
}
|
||||||
|
// get files
|
||||||
|
files, err := qbclient.GetFiles(m.tsk.ID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to get files of %s", m.tsk.ID)
|
||||||
|
}
|
||||||
|
log.Debugf("files len: %d", len(files))
|
||||||
|
// delete qbittorrent task but do not delete the files before transferring to avoid qbittorrent
|
||||||
|
// accessing downloaded files and throw `cannot access the file because it is being used by another process` error
|
||||||
|
err = qbclient.Delete(m.tsk.ID, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// upload files
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(files))
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
err := os.RemoveAll(m.tempDir)
|
||||||
|
m.finish <- struct{}{}
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to remove qbittorrent temp dir: %+v", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for _, file := range files {
|
||||||
|
tempPath := filepath.Join(m.tempDir, file.Name)
|
||||||
|
dstPath := filepath.Join(dstBaseDir, file.Name)
|
||||||
|
dstDir := filepath.Dir(dstPath)
|
||||||
|
fileName := filepath.Base(dstPath)
|
||||||
|
TransferTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
||||||
|
Name: fmt.Sprintf("transfer %s to [%s](%s)", tempPath, storage.GetStorage().MountPath, dstPath),
|
||||||
|
Func: func(tsk *task.Task[uint64]) error {
|
||||||
|
defer wg.Done()
|
||||||
|
size := file.Size
|
||||||
|
mimetype := utils.GetMimeType(tempPath)
|
||||||
|
f, err := os.Open(tempPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open file %s", tempPath)
|
||||||
|
}
|
||||||
|
stream := &model.FileStream{
|
||||||
|
Obj: &model.Object{
|
||||||
|
Name: fileName,
|
||||||
|
Size: size,
|
||||||
|
Modified: time.Now(),
|
||||||
|
IsFolder: false,
|
||||||
|
},
|
||||||
|
ReadCloser: f,
|
||||||
|
Mimetype: mimetype,
|
||||||
|
}
|
||||||
|
return op.Put(tsk.Ctx, storage, dstDir, stream, tsk.SetProgress)
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
23
internal/qbittorrent/qbittorrent.go
Normal file
23
internal/qbittorrent/qbittorrent.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package qbittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DownTaskManager = task.NewTaskManager[string](3)
|
||||||
|
var qbclient Client
|
||||||
|
|
||||||
|
func InitClient() error {
|
||||||
|
var err error
|
||||||
|
qbclient = nil
|
||||||
|
|
||||||
|
url := setting.GetStr(conf.QbittorrentUrl)
|
||||||
|
qbclient, err = New(url)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsQbittorrentReady() bool {
|
||||||
|
return qbclient != nil
|
||||||
|
}
|
@ -52,10 +52,12 @@ func updateIgnorePaths() {
|
|||||||
url := addition.Address + "/api/public/settings"
|
url := addition.Address + "/api/public/settings"
|
||||||
res, err := base.RestyClient.R().Get(url)
|
res, err := base.RestyClient.R().Get(url)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
allowIndexed = utils.Json.Get(res.Body(), "data", conf.AllowIndexed).ToBool()
|
log.Debugf("allow_indexed body: %+v", res.String())
|
||||||
|
allowIndexed = utils.Json.Get(res.Body(), "data", conf.AllowIndexed).ToString() == "true"
|
||||||
v3Visited[addition.Address] = allowIndexed
|
v3Visited[addition.Address] = allowIndexed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Debugf("%s allow_indexed: %v", addition.Address, allowIndexed)
|
||||||
if !allowIndexed {
|
if !allowIndexed {
|
||||||
ignorePaths = append(ignorePaths, storage.GetStorage().MountPath)
|
ignorePaths = append(ignorePaths, storage.GetStorage().MountPath)
|
||||||
}
|
}
|
||||||
|
107
pkg/http_range/range.go
Normal file
107
pkg/http_range/range.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// Package http_range implements http range parsing.
|
||||||
|
package http_range
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/textproto"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Range specifies the byte range to be sent to the client.
|
||||||
|
type Range struct {
|
||||||
|
Start int64
|
||||||
|
Length int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentRange returns Content-Range header value.
|
||||||
|
func (r Range) ContentRange(size int64) string {
|
||||||
|
return fmt.Sprintf("bytes %d-%d/%d", r.Start, r.Start+r.Length-1, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNoOverlap is returned by ParseRange if first-byte-pos of
|
||||||
|
// all of the byte-range-spec values is greater than the content size.
|
||||||
|
ErrNoOverlap = errors.New("invalid range: failed to overlap")
|
||||||
|
|
||||||
|
// ErrInvalid is returned by ParseRange on invalid input.
|
||||||
|
ErrInvalid = errors.New("invalid range")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseRange parses a Range header string as per RFC 7233.
|
||||||
|
// ErrNoOverlap is returned if none of the ranges overlap.
|
||||||
|
// ErrInvalid is returned if s is invalid range.
|
||||||
|
func ParseRange(s string, size int64) ([]Range, error) { // nolint:gocognit
|
||||||
|
if s == "" {
|
||||||
|
return nil, nil // header not present
|
||||||
|
}
|
||||||
|
const b = "bytes="
|
||||||
|
if !strings.HasPrefix(s, b) {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
var ranges []Range
|
||||||
|
noOverlap := false
|
||||||
|
for _, ra := range strings.Split(s[len(b):], ",") {
|
||||||
|
ra = textproto.TrimString(ra)
|
||||||
|
if ra == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i := strings.Index(ra, "-")
|
||||||
|
if i < 0 {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
start, end := textproto.TrimString(ra[:i]), textproto.TrimString(ra[i+1:])
|
||||||
|
var r Range
|
||||||
|
if start == "" {
|
||||||
|
// If no start is specified, end specifies the
|
||||||
|
// range start relative to the end of the file,
|
||||||
|
// and we are dealing with <suffix-length>
|
||||||
|
// which has to be a non-negative integer as per
|
||||||
|
// RFC 7233 Section 2.1 "Byte-Ranges".
|
||||||
|
if end == "" || end[0] == '-' {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
i, err := strconv.ParseInt(end, 10, 64)
|
||||||
|
if i < 0 || err != nil {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
if i > size {
|
||||||
|
i = size
|
||||||
|
}
|
||||||
|
r.Start = size - i
|
||||||
|
r.Length = size - r.Start
|
||||||
|
} else {
|
||||||
|
i, err := strconv.ParseInt(start, 10, 64)
|
||||||
|
if err != nil || i < 0 {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
if i >= size {
|
||||||
|
// If the range begins after the size of the content,
|
||||||
|
// then it does not overlap.
|
||||||
|
noOverlap = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.Start = i
|
||||||
|
if end == "" {
|
||||||
|
// If no end is specified, range extends to end of the file.
|
||||||
|
r.Length = size - r.Start
|
||||||
|
} else {
|
||||||
|
i, err := strconv.ParseInt(end, 10, 64)
|
||||||
|
if err != nil || r.Start > i {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
if i >= size {
|
||||||
|
i = size - 1
|
||||||
|
}
|
||||||
|
r.Length = i - r.Start + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ranges = append(ranges, r)
|
||||||
|
}
|
||||||
|
if noOverlap && len(ranges) == 0 {
|
||||||
|
// The specified ranges did not overlap with the content.
|
||||||
|
return nil, ErrNoOverlap
|
||||||
|
}
|
||||||
|
return ranges, nil
|
||||||
|
}
|
@ -40,7 +40,7 @@ func CopyFile(src, dst string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CopyDir Dir copies a whole directory recursively
|
// CopyDir Dir copies a whole directory recursively
|
||||||
func CopyDir(src string, dst string) error {
|
func CopyDir(src, dst string) error {
|
||||||
var err error
|
var err error
|
||||||
var fds []os.DirEntry
|
var fds []os.DirEntry
|
||||||
var srcinfo os.FileInfo
|
var srcinfo os.FileInfo
|
||||||
@ -71,6 +71,17 @@ func CopyDir(src string, dst string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SymlinkOrCopyFile symlinks a file or copy if symlink failed
|
||||||
|
func SymlinkOrCopyFile(src, dst string) error {
|
||||||
|
if err := CreateNestedDirectory(filepath.Dir(dst)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Symlink(src, dst); err != nil {
|
||||||
|
return CopyFile(src, dst)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Exists determine whether the file exists
|
// Exists determine whether the file exists
|
||||||
func Exists(name string) bool {
|
func Exists(name string) bool {
|
||||||
if _, err := os.Stat(name); err != nil {
|
if _, err := os.Stat(name); err != nil {
|
||||||
@ -81,15 +92,20 @@ func Exists(name string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateNestedDirectory create nested directory
|
||||||
|
func CreateNestedDirectory(path string) error {
|
||||||
|
err := os.MkdirAll(path, 0700)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("can't create folder, %s", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// CreateNestedFile create nested file
|
// CreateNestedFile create nested file
|
||||||
func CreateNestedFile(path string) (*os.File, error) {
|
func CreateNestedFile(path string) (*os.File, error) {
|
||||||
basePath := filepath.Dir(path)
|
basePath := filepath.Dir(path)
|
||||||
if !Exists(basePath) {
|
if err := CreateNestedDirectory(basePath); err != nil {
|
||||||
err := os.MkdirAll(basePath, 0700)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
log.Errorf("can't create folder, %s", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return os.Create(path)
|
return os.Create(path)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"strings"
|
"strings"
|
||||||
@ -14,6 +15,12 @@ func GetSHA1Encode(data string) string {
|
|||||||
return hex.EncodeToString(h.Sum(nil))
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSHA256Encode(data string) string {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(data))
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
func GetMD5Encode(data string) string {
|
func GetMD5Encode(data string) string {
|
||||||
h := md5.New()
|
h := md5.New()
|
||||||
h.Write([]byte(data))
|
h.Write([]byte(data))
|
||||||
|
@ -69,3 +69,25 @@ func (l limitWriter) Write(p []byte) (n int, err error) {
|
|||||||
func LimitWriter(w io.Writer, size int64) io.Writer {
|
func LimitWriter(w io.Writer, size int64) io.Writer {
|
||||||
return &limitWriter{w: w, limit: size}
|
return &limitWriter{w: w, limit: size}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReadCloser struct {
|
||||||
|
io.Reader
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloseFunc func() error
|
||||||
|
|
||||||
|
func (c CloseFunc) Close() error {
|
||||||
|
return c()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReadCloser(reader io.Reader, close CloseFunc) io.ReadCloser {
|
||||||
|
return ReadCloser{
|
||||||
|
Reader: reader,
|
||||||
|
Closer: close,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLimitReadCloser(reader io.Reader, close CloseFunc, limit int64) io.ReadCloser {
|
||||||
|
return NewReadCloser(io.LimitReader(reader, limit), close)
|
||||||
|
}
|
||||||
|
21
pkg/utils/url.go
Normal file
21
pkg/utils/url.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InjectQuery(raw string, query url.Values) (string, error) {
|
||||||
|
param := query.Encode()
|
||||||
|
if param == "" {
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
u, err := url.Parse(raw)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
joiner := "?"
|
||||||
|
if u.RawQuery != "" {
|
||||||
|
joiner = "&"
|
||||||
|
}
|
||||||
|
return raw + joiner + param, nil
|
||||||
|
}
|
@ -14,7 +14,7 @@ func GetApiUrl(r *http.Request) string {
|
|||||||
if strings.HasPrefix(api, "http") {
|
if strings.HasPrefix(api, "http") {
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
if r != nil && api == "" {
|
if r != nil {
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" {
|
if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" {
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
@ -25,6 +25,6 @@ func GetApiUrl(r *http.Request) string {
|
|||||||
}
|
}
|
||||||
api = fmt.Sprintf("%s://%s", protocol, stdpath.Join(host, api))
|
api = fmt.Sprintf("%s://%s", protocol, stdpath.Join(host, api))
|
||||||
}
|
}
|
||||||
strings.TrimSuffix(api, "/")
|
api = strings.TrimSuffix(api, "/")
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/fs"
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/alist-org/alist/v3/internal/sign"
|
"github.com/alist-org/alist/v3/internal/sign"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
@ -27,6 +28,7 @@ func Down(c *gin.Context) {
|
|||||||
Proxy(c)
|
Proxy(c)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
link, _, err := fs.Link(c, rawPath, model.LinkArgs{
|
link, _, err := fs.Link(c, rawPath, model.LinkArgs{
|
||||||
IP: c.ClientIP(),
|
IP: c.ClientIP(),
|
||||||
Header: c.Request.Header,
|
Header: c.Request.Header,
|
||||||
@ -38,6 +40,15 @@ func Down(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
c.Header("Referrer-Policy", "no-referrer")
|
c.Header("Referrer-Policy", "no-referrer")
|
||||||
c.Header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
|
c.Header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
|
||||||
|
if setting.GetBool(conf.ForwardDirectLinkParams) {
|
||||||
|
query := c.Request.URL.Query()
|
||||||
|
query.Del("sign")
|
||||||
|
link.URL, err = utils.InjectQuery(link.URL, query)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
c.Redirect(302, link.URL)
|
c.Redirect(302, link.URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +82,15 @@ func Proxy(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if link.URL != "" && setting.GetBool(conf.ForwardDirectLinkParams) {
|
||||||
|
query := c.Request.URL.Query()
|
||||||
|
query.Del("sign")
|
||||||
|
link.URL, err = utils.InjectQuery(link.URL, query)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
err = common.Proxy(c.Writer, c.Request, link, file)
|
err = common.Proxy(c.Writer, c.Request, link, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500, true)
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
73
server/handles/qbittorrent.go
Normal file
73
server/handles/qbittorrent.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package handles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/internal/qbittorrent"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetQbittorrentReq struct {
|
||||||
|
Url string `json:"url" form:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetQbittorrent(c *gin.Context) {
|
||||||
|
var req SetQbittorrentReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item := &model.SettingItem{
|
||||||
|
Key: conf.QbittorrentUrl,
|
||||||
|
Value: req.Url,
|
||||||
|
Type: conf.TypeString,
|
||||||
|
Group: model.SINGLE,
|
||||||
|
Flag: model.PRIVATE,
|
||||||
|
}
|
||||||
|
if err := op.SaveSettingItem(item); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := qbittorrent.InitClient(); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddQbittorrentReq struct {
|
||||||
|
Urls []string `json:"urls"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddQbittorrent(c *gin.Context) {
|
||||||
|
user := c.MustGet("user").(*model.User)
|
||||||
|
if !user.CanAddQbittorrentTasks() {
|
||||||
|
common.ErrorStrResp(c, "permission denied", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !qbittorrent.IsQbittorrentReady() {
|
||||||
|
common.ErrorStrResp(c, "qbittorrent not ready", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req AddQbittorrentReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reqPath, err := user.JoinPath(req.Path)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, url := range req.Urls {
|
||||||
|
err := qbittorrent.AddURL(c, url, reqPath)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/aria2"
|
"github.com/alist-org/alist/v3/internal/aria2"
|
||||||
"github.com/alist-org/alist/v3/internal/fs"
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/qbittorrent"
|
||||||
"github.com/alist-org/alist/v3/pkg/task"
|
"github.com/alist-org/alist/v3/pkg/task"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -19,9 +20,19 @@ type TaskInfo struct {
|
|||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTaskInfoUint(task *task.Task[uint64]) TaskInfo {
|
type K2Str[K comparable] func(k K) string
|
||||||
|
|
||||||
|
func uint64K2Str(k uint64) string {
|
||||||
|
return strconv.FormatUint(k, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func strK2Str(str string) string {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTaskInfo[K comparable](task *task.Task[K], k2Str K2Str[K]) TaskInfo {
|
||||||
return TaskInfo{
|
return TaskInfo{
|
||||||
ID: strconv.FormatUint(task.ID, 10),
|
ID: k2Str(task.ID),
|
||||||
Name: task.Name,
|
Name: task.Name,
|
||||||
State: task.GetState(),
|
State: task.GetState(),
|
||||||
Status: task.GetStatus(),
|
Status: task.GetStatus(),
|
||||||
@ -30,183 +41,68 @@ func getTaskInfoUint(task *task.Task[uint64]) TaskInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTaskInfoStr(task *task.Task[string]) TaskInfo {
|
func getTaskInfos[K comparable](tasks []*task.Task[K], k2Str K2Str[K]) []TaskInfo {
|
||||||
return TaskInfo{
|
|
||||||
ID: task.ID,
|
|
||||||
Name: task.Name,
|
|
||||||
State: task.GetState(),
|
|
||||||
Status: task.GetStatus(),
|
|
||||||
Progress: task.GetProgress(),
|
|
||||||
Error: task.GetErrMsg(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTaskInfosUint(tasks []*task.Task[uint64]) []TaskInfo {
|
|
||||||
var infos []TaskInfo
|
var infos []TaskInfo
|
||||||
for _, t := range tasks {
|
for _, t := range tasks {
|
||||||
infos = append(infos, getTaskInfoUint(t))
|
infos = append(infos, getTaskInfo(t, k2Str))
|
||||||
}
|
}
|
||||||
return infos
|
return infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTaskInfosStr(tasks []*task.Task[string]) []TaskInfo {
|
type Str2K[K comparable] func(str string) (K, error)
|
||||||
var infos []TaskInfo
|
|
||||||
for _, t := range tasks {
|
func str2Uint64K(str string) (uint64, error) {
|
||||||
infos = append(infos, getTaskInfoStr(t))
|
return strconv.ParseUint(str, 10, 64)
|
||||||
}
|
|
||||||
return infos
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UndoneDownTask(c *gin.Context) {
|
func str2StrK(str string) (string, error) {
|
||||||
common.SuccessResp(c, getTaskInfosStr(aria2.DownTaskManager.ListUndone()))
|
return str, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoneDownTask(c *gin.Context) {
|
func taskRoute[K comparable](g *gin.RouterGroup, manager *task.Manager[K], k2Str K2Str[K], str2K Str2K[K]) {
|
||||||
common.SuccessResp(c, getTaskInfosStr(aria2.DownTaskManager.ListDone()))
|
g.GET("/undone", func(c *gin.Context) {
|
||||||
}
|
common.SuccessResp(c, getTaskInfos(manager.ListUndone(), k2Str))
|
||||||
|
})
|
||||||
func CancelDownTask(c *gin.Context) {
|
g.GET("/done", func(c *gin.Context) {
|
||||||
tid := c.Query("tid")
|
common.SuccessResp(c, getTaskInfos(manager.ListDone(), k2Str))
|
||||||
if err := aria2.DownTaskManager.Cancel(tid); err != nil {
|
})
|
||||||
common.ErrorResp(c, err, 500)
|
g.POST("/cancel", func(c *gin.Context) {
|
||||||
} else {
|
tid := c.Query("tid")
|
||||||
|
id, err := str2K(tid)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := manager.Cancel(id); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
g.POST("/delete", func(c *gin.Context) {
|
||||||
|
tid := c.Query("tid")
|
||||||
|
id, err := str2K(tid)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := manager.Remove(id); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
g.POST("/clear_done", func(c *gin.Context) {
|
||||||
|
manager.ClearDone()
|
||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteDownTask(c *gin.Context) {
|
func SetupTaskRoute(g *gin.RouterGroup) {
|
||||||
tid := c.Query("tid")
|
taskRoute(g.Group("/aria2_down"), aria2.DownTaskManager, strK2Str, str2StrK)
|
||||||
if err := aria2.DownTaskManager.Remove(tid); err != nil {
|
taskRoute(g.Group("/aria2_transfer"), aria2.TransferTaskManager, uint64K2Str, str2Uint64K)
|
||||||
common.ErrorResp(c, err, 500)
|
taskRoute(g.Group("/upload"), fs.UploadTaskManager, uint64K2Str, str2Uint64K)
|
||||||
} else {
|
taskRoute(g.Group("/copy"), fs.CopyTaskManager, uint64K2Str, str2Uint64K)
|
||||||
common.SuccessResp(c)
|
taskRoute(g.Group("/qbit_down"), qbittorrent.DownTaskManager, strK2Str, str2StrK)
|
||||||
}
|
taskRoute(g.Group("/qbit_transfer"), qbittorrent.TransferTaskManager, uint64K2Str, str2Uint64K)
|
||||||
}
|
|
||||||
|
|
||||||
func ClearDoneDownTasks(c *gin.Context) {
|
|
||||||
aria2.DownTaskManager.ClearDone()
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UndoneTransferTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(aria2.TransferTaskManager.ListUndone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoneTransferTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(aria2.TransferTaskManager.ListDone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CancelTransferTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := aria2.TransferTaskManager.Cancel(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTransferTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := aria2.TransferTaskManager.Remove(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClearDoneTransferTasks(c *gin.Context) {
|
|
||||||
aria2.TransferTaskManager.ClearDone()
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UndoneUploadTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(fs.UploadTaskManager.ListUndone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoneUploadTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(fs.UploadTaskManager.ListDone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CancelUploadTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := fs.UploadTaskManager.Cancel(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteUploadTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := fs.UploadTaskManager.Remove(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClearDoneUploadTasks(c *gin.Context) {
|
|
||||||
fs.UploadTaskManager.ClearDone()
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UndoneCopyTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(fs.CopyTaskManager.ListUndone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoneCopyTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(fs.CopyTaskManager.ListDone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CancelCopyTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := fs.CopyTaskManager.Cancel(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteCopyTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := fs.CopyTaskManager.Remove(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClearDoneCopyTasks(c *gin.Context) {
|
|
||||||
fs.CopyTaskManager.ClearDone()
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,10 @@ func UpdateUser(c *gin.Context) {
|
|||||||
if req.OtpSecret == "" {
|
if req.OtpSecret == "" {
|
||||||
req.OtpSecret = user.OtpSecret
|
req.OtpSecret = user.OtpSecret
|
||||||
}
|
}
|
||||||
|
if req.Disabled && req.IsAdmin() {
|
||||||
|
common.ErrorStrResp(c, "admin user can not be disabled", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
if err := op.UpdateUser(&req); err != nil {
|
if err := op.UpdateUser(&req); err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
} else {
|
} else {
|
||||||
|
@ -33,6 +33,11 @@ func Auth(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if guest.Disabled {
|
||||||
|
common.ErrorStrResp(c, "Guest user is disabled, login please", 401)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
c.Set("user", guest)
|
c.Set("user", guest)
|
||||||
log.Debugf("use empty token: %+v", guest)
|
log.Debugf("use empty token: %+v", guest)
|
||||||
c.Next()
|
c.Next()
|
||||||
@ -50,6 +55,11 @@ func Auth(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if user.Disabled {
|
||||||
|
common.ErrorStrResp(c, "Current user is disabled, replace please", 401)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
c.Set("user", user)
|
c.Set("user", user)
|
||||||
log.Debugf("use login token: %+v", user)
|
log.Debugf("use login token: %+v", user)
|
||||||
c.Next()
|
c.Next()
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/cmd/flags"
|
"github.com/alist-org/alist/v3/cmd/flags"
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/message"
|
"github.com/alist-org/alist/v3/internal/message"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/alist-org/alist/v3/server/handles"
|
"github.com/alist-org/alist/v3/server/handles"
|
||||||
"github.com/alist-org/alist/v3/server/middlewares"
|
"github.com/alist-org/alist/v3/server/middlewares"
|
||||||
@ -12,21 +13,27 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(r *gin.Engine) {
|
func Init(e *gin.Engine) {
|
||||||
common.SecretKey = []byte(conf.Conf.JwtSecret)
|
if !utils.SliceContains([]string{"", "/"}, conf.URL.Path) {
|
||||||
Cors(r)
|
e.GET("/", func(c *gin.Context) {
|
||||||
r.Use(middlewares.StoragesLoaded)
|
c.Redirect(302, conf.URL.Path)
|
||||||
if conf.Conf.MaxConnections > 0 {
|
})
|
||||||
r.Use(middlewares.MaxAllowed(conf.Conf.MaxConnections))
|
|
||||||
}
|
}
|
||||||
WebDav(r.Group("/dav"))
|
Cors(e)
|
||||||
|
g := e.Group(conf.URL.Path)
|
||||||
|
common.SecretKey = []byte(conf.Conf.JwtSecret)
|
||||||
|
g.Use(middlewares.StoragesLoaded)
|
||||||
|
if conf.Conf.MaxConnections > 0 {
|
||||||
|
g.Use(middlewares.MaxAllowed(conf.Conf.MaxConnections))
|
||||||
|
}
|
||||||
|
WebDav(g.Group("/dav"))
|
||||||
|
|
||||||
r.GET("/favicon.ico", handles.Favicon)
|
g.GET("/favicon.ico", handles.Favicon)
|
||||||
r.GET("/i/:link_name", handles.Plist)
|
g.GET("/i/:link_name", handles.Plist)
|
||||||
r.GET("/d/*path", middlewares.Down, handles.Down)
|
g.GET("/d/*path", middlewares.Down, handles.Down)
|
||||||
r.GET("/p/*path", middlewares.Down, handles.Proxy)
|
g.GET("/p/*path", middlewares.Down, handles.Proxy)
|
||||||
|
|
||||||
api := r.Group("/api")
|
api := g.Group("/api")
|
||||||
auth := api.Group("", middlewares.Auth)
|
auth := api.Group("", middlewares.Auth)
|
||||||
|
|
||||||
api.POST("/auth/login", handles.Login)
|
api.POST("/auth/login", handles.Login)
|
||||||
@ -34,8 +41,10 @@ func Init(r *gin.Engine) {
|
|||||||
auth.POST("/me/update", handles.UpdateCurrent)
|
auth.POST("/me/update", handles.UpdateCurrent)
|
||||||
auth.POST("/auth/2fa/generate", handles.Generate2FA)
|
auth.POST("/auth/2fa/generate", handles.Generate2FA)
|
||||||
auth.POST("/auth/2fa/verify", handles.Verify2FA)
|
auth.POST("/auth/2fa/verify", handles.Verify2FA)
|
||||||
auth.GET("/auth/github", handles.GithubLoginRedirect)
|
|
||||||
auth.GET("/auth/github_callback", handles.GithubLoginCallback)
|
// github auth
|
||||||
|
api.GET("/auth/github", handles.GithubLoginRedirect)
|
||||||
|
api.GET("/auth/github_callback", handles.GithubLoginCallback)
|
||||||
|
|
||||||
// no need auth
|
// no need auth
|
||||||
public := api.Group("/public")
|
public := api.Group("/public")
|
||||||
@ -44,9 +53,11 @@ func Init(r *gin.Engine) {
|
|||||||
_fs(auth.Group("/fs"))
|
_fs(auth.Group("/fs"))
|
||||||
admin(auth.Group("/admin", middlewares.AuthAdmin))
|
admin(auth.Group("/admin", middlewares.AuthAdmin))
|
||||||
if flags.Dev {
|
if flags.Dev {
|
||||||
dev(r.Group("/dev"))
|
dev(g.Group("/dev"))
|
||||||
}
|
}
|
||||||
static.Static(r)
|
static.Static(g, func(handlers ...gin.HandlerFunc) {
|
||||||
|
e.NoRoute(handlers...)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func admin(g *gin.RouterGroup) {
|
func admin(g *gin.RouterGroup) {
|
||||||
@ -87,28 +98,10 @@ func admin(g *gin.RouterGroup) {
|
|||||||
setting.POST("/delete", handles.DeleteSetting)
|
setting.POST("/delete", handles.DeleteSetting)
|
||||||
setting.POST("/reset_token", handles.ResetToken)
|
setting.POST("/reset_token", handles.ResetToken)
|
||||||
setting.POST("/set_aria2", handles.SetAria2)
|
setting.POST("/set_aria2", handles.SetAria2)
|
||||||
|
setting.POST("/set_qbit", handles.SetQbittorrent)
|
||||||
|
|
||||||
task := g.Group("/task")
|
task := g.Group("/task")
|
||||||
task.GET("/down/undone", handles.UndoneDownTask)
|
handles.SetupTaskRoute(task)
|
||||||
task.GET("/down/done", handles.DoneDownTask)
|
|
||||||
task.POST("/down/cancel", handles.CancelDownTask)
|
|
||||||
task.POST("/down/delete", handles.DeleteDownTask)
|
|
||||||
task.POST("/down/clear_done", handles.ClearDoneDownTasks)
|
|
||||||
task.GET("/transfer/undone", handles.UndoneTransferTask)
|
|
||||||
task.GET("/transfer/done", handles.DoneTransferTask)
|
|
||||||
task.POST("/transfer/cancel", handles.CancelTransferTask)
|
|
||||||
task.POST("/transfer/delete", handles.DeleteTransferTask)
|
|
||||||
task.POST("/transfer/clear_done", handles.ClearDoneTransferTasks)
|
|
||||||
task.GET("/upload/undone", handles.UndoneUploadTask)
|
|
||||||
task.GET("/upload/done", handles.DoneUploadTask)
|
|
||||||
task.POST("/upload/cancel", handles.CancelUploadTask)
|
|
||||||
task.POST("/upload/delete", handles.DeleteUploadTask)
|
|
||||||
task.POST("/upload/clear_done", handles.ClearDoneUploadTasks)
|
|
||||||
task.GET("/copy/undone", handles.UndoneCopyTask)
|
|
||||||
task.GET("/copy/done", handles.DoneCopyTask)
|
|
||||||
task.POST("/copy/cancel", handles.CancelCopyTask)
|
|
||||||
task.POST("/copy/delete", handles.DeleteCopyTask)
|
|
||||||
task.POST("/copy/clear_done", handles.ClearDoneCopyTasks)
|
|
||||||
|
|
||||||
ms := g.Group("/message")
|
ms := g.Group("/message")
|
||||||
ms.POST("/get", message.HttpInstance.GetHandle)
|
ms.POST("/get", message.HttpInstance.GetHandle)
|
||||||
@ -137,12 +130,12 @@ func _fs(g *gin.RouterGroup) {
|
|||||||
g.PUT("/form", middlewares.FsUp, handles.FsForm)
|
g.PUT("/form", middlewares.FsUp, handles.FsForm)
|
||||||
g.POST("/link", middlewares.AuthAdmin, handles.Link)
|
g.POST("/link", middlewares.AuthAdmin, handles.Link)
|
||||||
g.POST("/add_aria2", handles.AddAria2)
|
g.POST("/add_aria2", handles.AddAria2)
|
||||||
|
g.POST("/add_qbit", handles.AddQbittorrent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Cors(r *gin.Engine) {
|
func Cors(r *gin.Engine) {
|
||||||
config := cors.DefaultConfig()
|
config := cors.DefaultConfig()
|
||||||
config.AllowAllOrigins = true
|
config.AllowAllOrigins = true
|
||||||
//config.AllowHeaders = append(config.AllowHeaders, "Authorization", "range", "File-Path", "As-Task", "Password")
|
|
||||||
config.AllowHeaders = []string{"*"}
|
config.AllowHeaders = []string{"*"}
|
||||||
config.AllowMethods = []string{"*"}
|
config.AllowMethods = []string{"*"}
|
||||||
r.Use(cors.New(config))
|
r.Use(cors.New(config))
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package static
|
package static
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
@ -9,26 +8,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SiteConfig struct {
|
type SiteConfig struct {
|
||||||
ApiURL string
|
|
||||||
BasePath string
|
BasePath string
|
||||||
Cdn string
|
Cdn string
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSiteConfig() SiteConfig {
|
func getSiteConfig() SiteConfig {
|
||||||
u, err := url.Parse(conf.Conf.SiteURL)
|
|
||||||
if err != nil {
|
|
||||||
utils.Log.Fatalf("can't parse site_url: %+v", err)
|
|
||||||
}
|
|
||||||
siteConfig := SiteConfig{
|
siteConfig := SiteConfig{
|
||||||
ApiURL: conf.Conf.SiteURL,
|
BasePath: conf.URL.Path,
|
||||||
BasePath: u.Path,
|
|
||||||
Cdn: strings.ReplaceAll(strings.TrimSuffix(conf.Conf.Cdn, "/"), "$version", conf.WebVersion),
|
Cdn: strings.ReplaceAll(strings.TrimSuffix(conf.Conf.Cdn, "/"), "$version", conf.WebVersion),
|
||||||
}
|
}
|
||||||
if siteConfig.BasePath != "" {
|
if siteConfig.BasePath != "" {
|
||||||
siteConfig.BasePath = utils.FixAndCleanPath(siteConfig.BasePath)
|
siteConfig.BasePath = utils.FixAndCleanPath(siteConfig.BasePath)
|
||||||
}
|
}
|
||||||
if siteConfig.Cdn == "" {
|
if siteConfig.Cdn == "" {
|
||||||
siteConfig.Cdn = siteConfig.BasePath
|
siteConfig.Cdn = strings.TrimSuffix(siteConfig.BasePath, "/")
|
||||||
}
|
}
|
||||||
return siteConfig
|
return siteConfig
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ func InitIndex() {
|
|||||||
replaceMap := map[string]string{
|
replaceMap := map[string]string{
|
||||||
"cdn: undefined": fmt.Sprintf("cdn: '%s'", siteConfig.Cdn),
|
"cdn: undefined": fmt.Sprintf("cdn: '%s'", siteConfig.Cdn),
|
||||||
"base_path: undefined": fmt.Sprintf("base_path: '%s'", siteConfig.BasePath),
|
"base_path: undefined": fmt.Sprintf("base_path: '%s'", siteConfig.BasePath),
|
||||||
"api: undefined": fmt.Sprintf("api: '%s'", siteConfig.ApiURL),
|
|
||||||
}
|
}
|
||||||
for k, v := range replaceMap {
|
for k, v := range replaceMap {
|
||||||
conf.RawIndexHtml = strings.Replace(conf.RawIndexHtml, k, v, 1)
|
conf.RawIndexHtml = strings.Replace(conf.RawIndexHtml, k, v, 1)
|
||||||
@ -62,7 +61,7 @@ func UpdateIndex() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Static(r *gin.Engine) {
|
func Static(r *gin.RouterGroup, noRoute func(handlers ...gin.HandlerFunc)) {
|
||||||
InitIndex()
|
InitIndex()
|
||||||
folders := []string{"assets", "images", "streamer", "static"}
|
folders := []string{"assets", "images", "streamer", "static"}
|
||||||
r.Use(func(c *gin.Context) {
|
r.Use(func(c *gin.Context) {
|
||||||
@ -81,7 +80,7 @@ func Static(r *gin.Engine) {
|
|||||||
r.StaticFS(fmt.Sprintf("/%s/", folders[i]), http.FS(sub))
|
r.StaticFS(fmt.Sprintf("/%s/", folders[i]), http.FS(sub))
|
||||||
}
|
}
|
||||||
|
|
||||||
r.NoRoute(func(c *gin.Context) {
|
noRoute(func(c *gin.Context) {
|
||||||
c.Header("Content-Type", "text/html")
|
c.Header("Content-Type", "text/html")
|
||||||
c.Status(200)
|
c.Status(200)
|
||||||
if strings.HasPrefix(c.Request.URL.Path, "/@manage") {
|
if strings.HasPrefix(c.Request.URL.Path, "/@manage") {
|
||||||
|
@ -3,7 +3,9 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
@ -14,17 +16,14 @@ import (
|
|||||||
|
|
||||||
var handler *webdav.Handler
|
var handler *webdav.Handler
|
||||||
|
|
||||||
func init() {
|
func WebDav(dav *gin.RouterGroup) {
|
||||||
handler = &webdav.Handler{
|
handler = &webdav.Handler{
|
||||||
Prefix: "/dav",
|
Prefix: path.Join(conf.URL.Path, "/dav"),
|
||||||
LockSystem: webdav.NewMemLS(),
|
LockSystem: webdav.NewMemLS(),
|
||||||
Logger: func(request *http.Request, err error) {
|
Logger: func(request *http.Request, err error) {
|
||||||
log.Errorf("%s %s %+v", request.Method, request.URL.Path, err)
|
log.Errorf("%s %s %+v", request.Method, request.URL.Path, err)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func WebDav(dav *gin.RouterGroup) {
|
|
||||||
dav.Use(WebDAVAuth)
|
dav.Use(WebDAVAuth)
|
||||||
dav.Any("/*path", ServeWebDAV)
|
dav.Any("/*path", ServeWebDAV)
|
||||||
dav.Any("", ServeWebDAV)
|
dav.Any("", ServeWebDAV)
|
||||||
|
@ -296,6 +296,9 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
|
if reqPath == "" {
|
||||||
|
return http.StatusMethodNotAllowed, nil
|
||||||
|
}
|
||||||
release, status, err := h.confirmLocks(r, reqPath, "")
|
release, status, err := h.confirmLocks(r, reqPath, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status, err
|
return status, err
|
||||||
|
2
wrapper/zcc-arm64
Normal file
2
wrapper/zcc-arm64
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
zig cc -target aarch64-windows-gnu $@
|
2
wrapper/zcxx-arm64
Normal file
2
wrapper/zcxx-arm64
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
zig c++ -target aarch64-windows-gnu $@
|
Reference in New Issue
Block a user