Compare commits

...

70 Commits

Author SHA1 Message Date
5596661ce8 feat(aliyundrive_open): optional delete file directly (close #3769) 2023-03-08 19:19:13 +08:00
2379cb8d67 style: go mod tidy 2023-03-08 19:08:11 +08:00
8c0ebe0841 revert: "fix(deps): update module gorm.io/gorm to v1.24.6 (#3684)" (close #3746)
This reverts commit c595fd7f94.
2023-03-08 19:07:04 +08:00
fd868bac84 fix(deps): update module github.com/caarlos0/env/v7 to v7.1.0 (#3763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-08 16:21:43 +08:00
ebcbb29a0f feat: ping api (close #3752) 2023-03-07 19:05:52 +08:00
00ff0a43a7 feat(cmd): disable a storage with specific mountPath (close #3564) 2023-03-07 19:01:40 +08:00
3d3f23ec9e fix: upload check if disable sub folder (close #3741) 2023-03-07 14:13:39 +08:00
d484219c48 fix(security): compare auth token in constant time (#3740 close #3739) 2023-03-06 23:41:06 +08:00
dd4c97393e feat: show sso settings at a more reasonable sort (#3735) 2023-03-06 20:59:45 +08:00
07b8ff25a7 ci: auto release desktop 2023-03-06 18:05:57 +08:00
0d5c3c5080 fix(deps): update module github.com/deckarep/golang-set/v2 to v2.2.0 [skip ci] (#3727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-06 17:54:17 +08:00
75b4429f73 feat(quark): enable NoOverwriteUpload (#3720) 2023-03-05 18:00:00 +08:00
34ef6bd18d feat(115): enable NoOverwriteUpload [skip ci] (close #3669) 2023-03-05 17:59:19 +08:00
c915313ec9 feat: rename then delete if storage doesn't support overwrite upload (close #3643) 2023-03-05 15:36:12 +08:00
12a095a1d6 fix: slice bounds out of range on CanAccess check 2023-03-05 15:29:53 +08:00
dc000f640a feat: optional log to std 2023-03-05 15:07:06 +08:00
aa1c5b2be3 fix(deps): update module golang.org/x/crypto to v0.7.0 [skip ci] (#3717)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-05 14:32:41 +08:00
1d4ec3c50d fix(deps): update module golang.org/x/net to v0.8.0 [skip ci] (#3715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-05 14:32:26 +08:00
ebfeef52f4 fix(deps): update module golang.org/x/image to v0.6.0 [skip ci] (#3714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-05 13:52:53 +08:00
c595fd7f94 fix(deps): update module gorm.io/gorm to v1.24.6 (#3684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-02 19:15:50 +08:00
421052f88a fix(deps): update github.com/t3rm1n4l/go-mega digest to a01a2cd (#3665)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-02 19:03:38 +08:00
603681fbe6 feat: rebuild Single sign-on system (#3649 close #3571)
* rebuild single sign on system

* perf: use cache

* fix: codefactor check

---------

Co-authored-by: Andy Hsu <i@nn.ci>
2023-03-02 17:55:33 +08:00
f442185aa5 perf(123): optimize login error 2023-02-28 21:17:15 +08:00
ca9e739465 fix: hide apply to sub path without enable (close #3661) 2023-02-28 18:43:52 +08:00
53a1c4283b fix(baidu_netdisk): maybe optimize crack api (#3652)
User-Agent to netdisk and remove origin=dlna(is baned)
2023-02-28 18:27:07 +08:00
93dd768234 fix(webdav): disabled is not working in webdav (#3659)
A disabled user with webdav permission can use webdav normally, which is not allowed.
2023-02-28 18:26:13 +08:00
c9c4d6bc7e fix!(local): perm on mkdir (close #3626) 2023-02-26 21:25:32 +08:00
81e10f8939 ci: set prerelease before the build completes 2023-02-25 18:06:35 +08:00
4dd753de52 fix(aliyundrive_open): missed expire_sec while get link (close #3610) 2023-02-25 17:54:36 +08:00
79df63d319 chore(aliyundrive): change alert info 2023-02-25 14:28:27 +08:00
ec54831162 fix: only refresh token while do request (close #3591) 2023-02-24 20:31:12 +08:00
c8f3e8ab4d feat!: skip tls insecure verify by default 2023-02-23 22:33:54 +08:00
4be8524d80 feat: add alert for driver 2023-02-23 22:03:11 +08:00
0d3146b51d fix(webdav): disable put with empty path (close #3569) 2023-02-23 21:19:50 +08:00
f95d843969 feat(aliyundrive): add url_expire_sec for video preview (close #3522) 2023-02-23 20:50:31 +08:00
28aee8c493 feat: add aliyundrive open driver (#3437)
close #3533 
close #3521 
close #3459 
close #3375 

* feat: add aliyundrive open driver

* feat: adapt alist api

* fix: trailing spaces

* feat(aliyundrive_open): video preview api
2023-02-23 20:45:57 +08:00
de3ea82eb9 ci: add closeComment for stale 2023-02-22 22:17:33 +08:00
268ba3d069 fix(deps): update module github.com/gin-gonic/gin to v1.9.0 [skip ci] (#3551)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-22 21:24:35 +08:00
309d6558fb feat(local): add thumbnail for video with ffmpeg (#3556)
* feat(local): add ffmpeg

* fix: missed `+`

---------

Co-authored-by: Andy Hsu <i@nn.ci>
2023-02-22 21:19:42 +08:00
c08fdfc868 fix: missed assignment [skip ci] 2023-02-22 20:20:28 +08:00
1b28e6af3e ci: replace issues-helper with stale for inactive check 2023-02-22 20:07:18 +08:00
8655e33e60 fix: incorrect api if not set site_url (6c2f348) 2023-02-21 19:57:50 +08:00
50579fef84 fix: cancel api replace to avoid missing host 2023-02-21 19:45:09 +08:00
e39299bfe2 fix(local): missed type of MkdirPerm (923937b) 2023-02-21 17:45:15 +08:00
d1ab2443f1 feat(qbittorrent): delete tags when deleting qbittorrent tasks (#3546)
* feat & refactor(qbittorrent/client): support `deleteFiles` arg for `Client.Delete()` method

* feat(qbittorrent/client): also delete tags in `Client.Delete()`
2023-02-21 16:45:41 +08:00
658cf368bb fix(deps): update github.com/t3rm1n4l/go-mega digest to b87ebf5 (#3539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-21 16:43:37 +08:00
fd36ce59f6 fix(onedrive): either id or path in parentReference must be specified (close #3028) 2023-02-21 16:19:46 +08:00
95b3b87672 feat(sftp): support range header 2023-02-20 16:57:52 +08:00
0d07d81802 feat(smb): support range header (close #3192) 2023-02-20 16:46:38 +08:00
923937b530 feat(local): custom mkdir perm (close #3196) 2023-02-20 16:20:36 +08:00
09492193c4 fix(alist_v3): api error pass (close #3326) 2023-02-20 16:15:52 +08:00
40b26a81a0 fix!: change default epub viewer (close #3519) 2023-02-20 16:08:10 +08:00
4293a0ba8c fix(deps): update module github.com/golang-jwt/jwt/v4 to v4.5.0 [skip ci] (#3525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-20 16:06:35 +08:00
6c2f3486fc fix!: reverse proxy to sub-directory (#3483)
from this commit, if you want reverse proxy to sub-directory like `alist` with `nginx`, you need config:

```nginx
location /alist/ {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Range $http_range;
    proxy_set_header If-Range $http_if_range;
    proxy_redirect off;
    proxy_pass http://127.0.0.1:5244/alist/;
    # the max size of file to upload
    client_max_body_size 20000m;
}
```
2023-02-18 19:03:07 +08:00
3c7512f64a fix(qbittorrent): fix two file transferring related bugs [skip ci] (#3501)
* fix(qbittorrent): delete qbittorrent task before transferring

* fix(qbittorrent): parse the path correctly when the torrent contains folders
2023-02-18 18:54:51 +08:00
84219d3d70 fix(deps): update module gorm.io/driver/mysql to v1.4.7 [skip ci] (#3495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-18 18:13:41 +08:00
05d3727335 fix(deps): update module golang.org/x/image to v0.5.0 [security skip ci] (#3489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-18 18:13:22 +08:00
ee77c3b113 fix: friendly tip for initial logging in [skip ci] (#3406)
* refactor: friendly tip for initial logging in

* fix CodeFactor issue

more info pls refer to: https://segmentfault.com/a/1190000043031147
2023-02-18 17:53:11 +08:00
fcaf485e0b fix(deps): update module gorm.io/driver/postgres to v1.4.8 [skip ci] (#3496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-18 17:52:03 +08:00
bd83469bb1 fix(deps): update module golang.org/x/net to v0.7.0 [security skip ci] (#3502)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-18 17:51:33 +08:00
90f111b24f docs: translate title [skip ci] (#3498)
* Update README_cn.md

* Update README_cn.md

---------

Co-authored-by: Andy Hsu <i@nn.ci>
2023-02-18 17:50:42 +08:00
7d1034c569 fix(aliyundrive): error occurred when running multiple instances at the same time (#3448)
* fix(aliyundrive):an error occurred when running multiple instances at the same time

* Update util.go

fix(aliyunpan):clear retry count
2023-02-16 22:12:19 +08:00
236c17176c fix(123): adapt new file list api (close #3464) 2023-02-16 22:09:45 +08:00
6ee4c10e8f chore(onedrive)!: change default redirect_uri [skip ci] 2023-02-16 21:37:20 +08:00
3798634028 fix(pikpak_share): change media url to content url (close #3273) (#3441) 2023-02-16 15:42:11 +08:00
567ba5ccd4 feat(aliyundrive_share): aliyun office preview (close #3408) 2023-02-15 16:52:24 +08:00
ae2ee1821a chore: change qBittorrent setting [skip ci] 2023-02-15 16:51:29 +08:00
805b1e4fa3 fix: different url encoding (close #3423) 2023-02-15 16:20:30 +08:00
d92c10da56 fix(qbittorrent): fix multiple bugs for qbittorrent download (close #3413 in #3427)
* fix(qbittorrent): wait for qbittorrent to parse torrent and create task

#3413

* fix(qbittorrent): check task state correctly

* fix(qbittorrent): fix path sent to `op.Put()`
2023-02-15 15:58:31 +08:00
6659f6d367 fix: windows arm64 build [skip ci] 2023-02-14 20:28:05 +08:00
75 changed files with 1546 additions and 530 deletions

19
.github/stale.yml vendored Normal file
View 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.

View File

@ -54,7 +54,7 @@ jobs:
cd alist-web
git add .
git config --local user.email "i@nn.ci"
git config --local user.name "Noah Hsu"
git config --local user.name "Andy Hsu"
git commit -m "chore: auto update i18n file" -a 2>/dev/null || :
cd ..

19
.github/workflows/changelog.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: auto changelog
on:
push:
tags:
- '*'
jobs:
changelog:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- run: npx changelogithub # or changelogithub@0.12 if ensure the stable result
env:
GITHUB_TOKEN: ${{secrets.MY_TOKEN}}

View File

@ -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.

View File

@ -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.

View File

@ -1,31 +1,25 @@
name: release
on:
push:
tags:
- '*'
release:
types: [ published ]
jobs:
changelog:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- run: npx changelogithub # or changelogithub@0.12 if ensure the stable result
env:
GITHUB_TOKEN: ${{secrets.MY_TOKEN}}
release:
needs: changelog
strategy:
matrix:
platform: [ubuntu-latest]
go-version: [1.19]
platform: [ ubuntu-latest ]
go-version: [ 1.19 ]
name: Release
runs-on: ${{ matrix.platform }}
steps:
- name: prerelease
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.MY_TOKEN }}
id: ${{ github.event.release.id }}
prerelease: true
- name: Setup Go
uses: actions/setup-go@v3
with:
@ -47,7 +41,41 @@ jobs:
run: |
bash build.sh release
- name: prerelease
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.MY_TOKEN }}
id: ${{ github.event.release.id }}
prerelease: false
- name: Release
uses: softprops/action-gh-release@v1
with:
files: build/compress/*
files: build/compress/*
release_desktop:
needs: release
name: Release desktop
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
repository: alist-org/desktop-release
ref: main
persist-credentials: false
fetch-depth: 0
- name: Add tag
run: |
git config --local user.email "i@nn.ci"
git config --local user.name "Andy Hsu"
version=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
git tag -a $version -m "release $version"
- name: Push tags
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.MY_TOKEN }}
branch: main
repository: alist-org/desktop-release

View File

@ -57,7 +57,7 @@ jobs:
- name: Add tag
run: |
git config --local user.email "i@nn.ci"
git config --local user.name "Noah Hsu"
git config --local user.name "Andy Hsu"
git tag -a ${{ github.ref_name }} -m "release ${{ github.ref_name }}"
- name: Push tags

View File

@ -41,7 +41,7 @@
[English](./README.md) | 中文 | [Contributing](./CONTRIBUTING.md) | [CODE_OF_CONDUCT](./CODE_OF_CONDUCT.md)
## Features
## 功能
- [x] 多种存储
- [x] 本地存储
@ -89,7 +89,7 @@
- [x] 离线下载
- [x] 跨存储复制文件
## Document
## 文档
<https://alist.nn.ci/zh/>
@ -97,21 +97,21 @@
<https://al.nn.ci>
## Discussion
## 讨论
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) **issue仅针对错误报告和功能请求。**
## Sponsor
## 赞助
AList 是一个开源软件如果你碰巧喜欢这个项目并希望我继续下去请考虑赞助我或提供一个单一的捐款感谢所有的爱和支持https://alist.nn.ci/zh/guide/sponsor.html
### Special sponsors
### 特别赞助
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/)
- [KinhDown 百度云盘不限速下载永久免费已稳定运行3年非常可靠Q群 -> 786799372](https://kinhdown.com)
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
## Contributors
## 贡献者
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)

View File

@ -43,6 +43,8 @@ FetchWebRelease() {
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

View File

@ -6,4 +6,5 @@ var (
NoPrefix bool
Dev bool
ForceBinDir bool
LogStd bool
)

View File

@ -78,10 +78,19 @@ func writeFile(name string, data interface{}) {
func generateDriversJson() {
drivers := make(Drivers)
drivers["drivers"] = make(KV[interface{}])
drivers["config"] = make(KV[interface{}])
driverInfoMap := op.GetDriverInfoMap()
for k, v := range driverInfoMap {
drivers["drivers"][k] = convert(k)
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 {
item := v.Additional[i]
items[item.Name] = convert(item.Name)

View File

@ -29,4 +29,5 @@ func init() {
RootCmd.PersistentFlags().BoolVar(&flags.NoPrefix, "no-prefix", false, "disable env prefix")
RootCmd.PersistentFlags().BoolVar(&flags.Dev, "dev", false, "start with dev mode")
RootCmd.PersistentFlags().BoolVar(&flags.ForceBinDir, "force-bin-dir", false, "Force to use the directory where the binary file is located as data directory")
RootCmd.PersistentFlags().BoolVar(&flags.LogStd, "log-std", false, "Force to log to std")
}

52
cmd/storage.go Normal file
View File

@ -0,0 +1,52 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/spf13/cobra"
)
// storageCmd represents the storage command
var storageCmd = &cobra.Command{
Use: "storage",
Short: "Manage storage",
}
func init() {
var mountPath string
var disable = &cobra.Command{
Use: "disable",
Short: "Disable a storage",
Run: func(cmd *cobra.Command, args []string) {
Init()
storage, err := db.GetStorageByMountPath(mountPath)
if err != nil {
utils.Log.Errorf("failed to query storage: %+v", err)
} else {
storage.Disabled = true
err = db.UpdateStorage(storage)
if err != nil {
utils.Log.Errorf("failed to update storage: %+v", err)
} else {
utils.Log.Infof("Storage with mount path [%s] have been disabled", mountPath)
}
}
},
}
disable.Flags().StringVarP(&mountPath, "mount-path", "m", "", "The mountPath of storage")
RootCmd.AddCommand(storageCmd)
storageCmd.AddCommand(disable)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// storageCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// storageCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View File

@ -12,10 +12,11 @@ type Addition struct {
}
var config = driver.Config{
Name: "115 Cloud",
DefaultRoot: "0",
OnlyProxy: true,
OnlyLocal: true,
Name: "115 Cloud",
DefaultRoot: "0",
OnlyProxy: true,
OnlyLocal: true,
NoOverwriteUpload: true,
}
func init() {

View File

@ -7,18 +7,6 @@ import (
"github.com/alist-org/alist/v3/internal/model"
)
//type BaseResp struct {
// Code interface{} `json:"code"`
// Message string `json:"message"`
//}
type TokenResp struct {
//BaseResp
Data struct {
Token string `json:"token"`
} `json:"data"`
}
type File struct {
FileName string `json:"FileName"`
Size int64 `json:"Size"`

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"net/http"
"strconv"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/pkg/utils"
@ -28,9 +29,7 @@ func (d *Pan123) login() error {
"password": d.Password,
}
}
var resp TokenResp
res, err := base.RestyClient.R().
SetResult(&resp).
SetBody(body).Post(url)
if err != nil {
return err
@ -38,7 +37,7 @@ func (d *Pan123) login() error {
if utils.Json.Get(res.Body(), "code").ToInt() != 200 {
err = fmt.Errorf(utils.Json.Get(res.Body(), "message").ToString())
} else {
d.AccessToken = resp.Data.Token
d.AccessToken = utils.Json.Get(res.Body(), "data", "token").ToString()
}
return err
}
@ -77,18 +76,19 @@ func (d *Pan123) request(url string, method string, callback base.ReqCallback, r
}
func (d *Pan123) getFiles(parentId string) ([]File, error) {
next := "0"
page := 1
res := make([]File, 0)
for next != "-1" {
for {
var resp Files
query := map[string]string{
"driveId": "0",
"limit": "100",
"next": next,
"next": "0",
"orderBy": d.OrderBy,
"orderDirection": d.OrderDirection,
"parentFileId": parentId,
"trashed": "false",
"Page": strconv.Itoa(page),
}
_, err := d.request("https://www.123pan.com/api/file/list/new", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
@ -96,8 +96,11 @@ func (d *Pan123) getFiles(parentId string) ([]File, error) {
if err != nil {
return nil, err
}
next = resp.Data.Next
page++
res = append(res, resp.Data.InfoList...)
if len(resp.Data.InfoList) == 0 || resp.Data.Next == "-1" {
break
}
}
return res, nil
}

View File

@ -2,6 +2,7 @@ package alist_v3
import (
"context"
"errors"
"io"
"path"
"strconv"
@ -55,6 +56,9 @@ func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs)
if err != nil {
return nil, err
}
if resp.Code != 200 {
return nil, errors.New(resp.Message)
}
var files []model.Obj
for _, f := range resp.Data.Content {
file := model.ObjThumb{
@ -84,6 +88,9 @@ func (d *AListV3) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
if err != nil {
return nil, err
}
if resp.Code != 200 {
return nil, errors.New(resp.Message)
}
return &model.Link{
URL: resp.Data.RawURL,
}, nil

View File

@ -10,7 +10,7 @@ func checkResp(resp common.Resp[interface{}], err error) error {
if err != nil {
return err
}
if resp.Message == "success" {
if resp.Code == 200 {
return nil
}
return errors.New(resp.Message)

View File

@ -71,14 +71,14 @@ func (d *AliDrive) Init(ctx context.Context) error {
// init privateKey
privateKey, _ := NewPrivateKeyFromHex(deviceID)
state := State{
nonce: -1,
privateKey: privateKey,
deviceID: deviceID,
}
// store state
global.Store(d.UserID, &state)
// init signature
return d.reSign()
d.sign()
return nil
}
func (d *AliDrive) Drop(ctx context.Context) error {
@ -337,6 +337,7 @@ func (d *AliDrive) Other(ctx context.Context, args model.OtherArgs) (interface{}
case "video_preview":
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
data["category"] = "live_transcoding"
data["url_expire_sec"] = 14400
default:
return nil, errs.NotSupport
}

View File

@ -9,7 +9,7 @@ import (
type State struct {
deviceID string
signature string
nonce int
retry int
privateKey *ecdsa.PrivateKey
}

View File

@ -18,6 +18,9 @@ type Addition struct {
var config = driver.Config{
Name: "Aliyundrive",
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() {

View File

@ -13,7 +13,6 @@ import (
"github.com/dustinxie/ecc"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
)
func (d *AliDrive) createSession() error {
@ -21,56 +20,39 @@ func (d *AliDrive) createSession() error {
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": state.nonce,
"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) 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, ok := global.Load(d.UserID)
if !ok {
log.Errorf("can't load user state, user_id: %s", d.UserID)
return
}
state, _ := global.Load(d.UserID)
secpAppID := "5dde4e1bdf9e4966b387ba58f4b3fdc3"
singdata := fmt.Sprintf("%s:%s:%s:%d", secpAppID, state.deviceID, d.UserID, state.nonce)
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)
}
func (d *AliDrive) reSign() error {
state, ok := global.Load(d.UserID)
if !ok {
return fmt.Errorf("can't load user state, user_id: %s", d.UserID)
}
state.nonce++
if state.nonce >= 1073741823 {
state.nonce = 0
}
d.sign()
if state.nonce == 0 {
return d.createSession()
}
err := d.renewSession()
if err != nil && err.Error() == "device session signature error" {
state.nonce = 0
d.sign()
return d.createSession()
}
return nil
state.signature = hex.EncodeToString(data) //strconv.Itoa(state.nonce)
}
// do others that not defined in Driver interface
@ -141,7 +123,7 @@ func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp i
return nil, err, e
}
case "DeviceSessionSignatureInvalid":
err = d.reSign()
err = d.createSession()
if err != nil {
return nil, err, e
}

View File

@ -0,0 +1,222 @@
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(),
"expire_sec": 14400,
})
})
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 {
uri := "/adrive/v1.0/openFile/recyclebin/trash"
if d.RemoveWay == "delete" {
uri = "/adrive/v1.0/openFile/delete"
}
_, err := d.request(uri, 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)

View File

@ -0,0 +1,37 @@
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"`
RemoveWay string `json:"remove_way" required:"true" type:"select" options:"trash,delete"`
}
var config = driver.Config{
Name: "AliyundriveOpen",
LocalSort: false,
OnlyLocal: false,
OnlyProxy: false,
NoCache: false,
NoUpload: false,
NeedMs: false,
DefaultRoot: "root",
NoOverwriteUpload: true,
}
func init() {
op.RegisterDriver(func() driver.Driver {
return &AliyundriveOpen{
base: "https://open.aliyundrive.com",
}
})
}

View 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"`
}

View 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
}

View File

@ -2,15 +2,16 @@ package aliyundrive_share
import (
"context"
"errors"
"net/http"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/cron"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
@ -77,40 +78,43 @@ func (d *AliyundriveShare) Link(ctx context.Context, file model.Obj, args model.
"share_id": d.ShareId,
}
var resp ShareLinkResp
var e ErrorResp
_, err := base.RestyClient.R().
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")
_, err := d.request("https://api.aliyundrive.com/v2/file/get_share_link_download_url", http.MethodPost, func(req *resty.Request) {
req.SetBody(data).SetResult(&resp)
})
if err != nil {
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{
Header: http.Header{
"Referer": []string{"https://www.aliyundrive.com/"},
},
URL: u,
URL: resp.DownloadUrl,
}, 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)

View File

@ -52,6 +52,40 @@ func (d *AliyundriveShare) getShareToken() error {
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) {
files := make([]File, 0)
data := base.Json{

View File

@ -9,6 +9,7 @@ import (
_ "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/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/baidu_netdisk"
_ "github.com/alist-org/alist/v3/drivers/baidu_photo"

View File

@ -154,7 +154,7 @@ func (d *BaiduNetdisk) linkCrack(file model.Obj, args model.LinkArgs) (*model.Li
"target": fmt.Sprintf("[\"%s\"]", file.GetPath()),
"dlink": "1",
"web": "5",
"origin": "dlna",
//"origin": "dlna",
}
_, err := d.request("https://pan.baidu.com/api/filemetas", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(param)
@ -165,7 +165,7 @@ func (d *BaiduNetdisk) linkCrack(file model.Obj, args model.LinkArgs) (*model.Li
return &model.Link{
URL: resp.Info[0].Dlink,
Header: http.Header{
"User-Agent": []string{"pan.baidu.com"},
"User-Agent": []string{"netdisk"},
},
}, nil
}

30
drivers/base/util.go Normal file
View 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)},
}
}
}
}
}

View File

@ -28,6 +28,7 @@ import (
type Local struct {
model.Storage
Addition
mkdirPerm int32
}
func (d *Local) Config() driver.Config {
@ -35,6 +36,15 @@ func (d *Local) Config() driver.Config {
}
func (d *Local) Init(ctx context.Context) error {
if d.MkdirPerm == "" {
d.mkdirPerm = 0777
} else {
v, err := strconv.ParseUint(d.MkdirPerm, 8, 32)
if err != nil {
return err
}
d.mkdirPerm = int32(v)
}
if !utils.Exists(d.GetRootPath()) {
return fmt.Errorf("root folder %s not exists", d.GetRootPath())
}
@ -68,10 +78,13 @@ func (d *Local) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([
continue
}
thumb := ""
if d.Thumbnail && utils.GetFileType(f.Name()) == conf.IMAGE {
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()))
if d.Thumbnail {
typeName := utils.GetFileType(f.Name())
if typeName == conf.IMAGE || typeName == conf.VIDEO {
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)
var size int64
@ -123,11 +136,22 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
fullPath := file.GetPath()
var link model.Link
if args.Type == "thumb" && utils.Ext(file.GetName()) != "svg" {
imgData, err := ioutil.ReadFile(fullPath)
if err != nil {
return nil, err
var srcBuf *bytes.Buffer
if utils.GetFileType(file.GetName()) == conf.VIDEO {
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)
if err != nil {
return nil, err
@ -151,7 +175,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 {
fullPath := filepath.Join(parentDir.GetPath(), dirName)
err := os.MkdirAll(fullPath, 0777)
err := os.MkdirAll(fullPath, os.FileMode(d.mkdirPerm))
if err != nil {
return err
}

View File

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

View File

@ -1,6 +1,9 @@
package local
import (
"bytes"
"fmt"
ffmpeg "github.com/u2takey/ffmpeg-go"
"io/fs"
"os"
"path/filepath"
@ -23,3 +26,16 @@ func isSymlinkDir(f fs.FileInfo, path string) bool {
}
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
}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"path"
"github.com/alist-org/alist/v3/drivers/base"
"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 {
parentPath := ""
if dstDir.GetID() == "" {
parentPath = dstDir.GetPath()
if utils.PathEqual(parentPath, "/") {
parentPath = path.Join("/drive/root", parentPath)
} else {
parentPath = path.Join("/drive/root:/", parentPath)
}
}
data := base.Json{
"parentReference": base.Json{
"id": dstDir.GetID(),
"id": dstDir.GetID(),
"path": parentPath,
},
"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 {
//dstDir, err := op.GetUnwrap(ctx, d, stdpath.Dir(srcObj.GetPath()))
var parentID string
if o, ok := srcObj.(*Object); ok {
parentID = o.ParentID
} else {
return fmt.Errorf("srcObj is not Object")
}
if parentID == "" {
parentID = "root"
}
data := base.Json{
"parentReference": base.Json{
"id": parentID,

View File

@ -11,7 +11,7 @@ type Addition struct {
IsSharepoint bool `json:"is_sharepoint"`
ClientID string `json:"client_id" 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"`
SiteId string `json:"site_id"`
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`

View File

@ -8,7 +8,6 @@ import (
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
type PikPakShare struct {
@ -71,10 +70,6 @@ func (d *PikPakShare) Link(ctx context.Context, file model.Obj, args model.LinkA
link := model.Link{
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
}

View File

@ -16,6 +16,7 @@ var config = driver.Config{
Name: "Quark",
OnlyProxy: true,
DefaultRoot: "0",
NoOverwriteUpload: true,
}
func init() {

View File

@ -5,6 +5,7 @@ import (
"os"
"path"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
@ -52,9 +53,11 @@ func (d *SFTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*
if err != nil {
return nil, err
}
return &model.Link{
link := &model.Link{
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 {

View File

@ -7,6 +7,7 @@ import (
"strings"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
@ -79,10 +80,12 @@ func (d *SMB) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*m
d.cleanLastConnTime()
return nil, err
}
d.updateLastConnTime()
return &model.Link{
link := &model.Link{
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 {

47
go.mod
View File

@ -7,14 +7,14 @@ require (
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
github.com/aws/aws-sdk-go v1.44.194
github.com/blevesearch/bleve/v2 v2.3.6
github.com/caarlos0/env/v7 v7.0.0
github.com/deckarep/golang-set/v2 v2.1.0
github.com/caarlos0/env/v7 v7.1.0
github.com/deckarep/golang-set/v2 v2.2.0
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-gonic/gin v1.8.2
github.com/gin-gonic/gin v1.9.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/gorilla/websocket v1.5.0
github.com/hirochachacha/go-smb2 v1.1.0
@ -27,14 +27,15 @@ require (
github.com/pquerna/otp v1.4.0
github.com/sirupsen/logrus v1.9.0
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-20230228171823-a01a2cda13ca
github.com/u2takey/ffmpeg-go v0.4.1
github.com/upyun/go-sdk/v3 v3.0.3
github.com/winfsp/cgofuse v1.5.0
golang.org/x/crypto v0.6.0
golang.org/x/image v0.4.0
golang.org/x/net v0.6.0
gorm.io/driver/mysql v1.4.6
gorm.io/driver/postgres v1.4.7
golang.org/x/crypto v0.7.0
golang.org/x/image v0.6.0
golang.org/x/net v0.8.0
gorm.io/driver/mysql v1.4.7
gorm.io/driver/postgres v1.4.8
gorm.io/driver/sqlite v1.4.4
gorm.io/gorm v1.24.5
)
@ -63,14 +64,16 @@ require (
github.com/blevesearch/zapx/v15 v15.3.8 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gaoyb7/115drive-webdav v0.1.8 // indirect
github.com/geoffgarside/ber v1.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/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // 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/protobuf v1.5.0 // indirect
github.com/golang/snappy v0.0.1 // indirect
@ -79,13 +82,14 @@ require (
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.2.0 // indirect
github.com/jackc/pgx/v5 v5.3.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/kr/fs v0.1.0 // 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@ -95,13 +99,16 @@ require (
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // 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
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

132
go.sum
View File

@ -12,6 +12,7 @@ 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/andreburgaud/crypt2go v1.1.0 h1:eitZxTPY1krUsxinsng3Qvt/Ud7q/aQmmYRh8p4hyPw=
github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc=
github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.44.194 h1:1ZDK+QDcc5oRbZGgRZSz561eR8XVizXCeGpoZKo33NU=
github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
@ -52,19 +53,26 @@ github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
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/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
github.com/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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/deckarep/golang-set/v2 v2.2.0 h1:2pMQd3Soi6qfw7E5MMKaEh5W5ES18bW3AbFFnGl6LgQ=
github.com/deckarep/golang-set/v2 v2.2.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
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/go.mod h1:BKbeY6j8SKs3+rzBFFALznGxbPmefEm3vA+dGhqgOGU=
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
@ -74,27 +82,31 @@ github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURU
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
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/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
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/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
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/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.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/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/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/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
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/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
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/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
@ -105,6 +117,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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
@ -114,18 +127,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/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/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/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
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/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/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8=
github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels=
github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA=
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
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/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
@ -138,8 +151,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/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
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/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -155,13 +173,15 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
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.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/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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
@ -170,12 +190,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/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/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.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
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/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go=
@ -193,6 +215,7 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/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/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@ -211,11 +234,18 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
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/go.mod h1:c+cGNU1qi9bO7ZF4IRMYk+KaZTNiQ/gQrSbyMmGFq1Q=
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca h1:I9rVnNXdIkij4UvMT7OmKhH9sOIvS8iXkxfPdnn9wQA=
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
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/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.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/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
@ -224,40 +254,46 @@ 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=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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-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-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-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-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-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.4.0 h1:x1RWAiZIvERqkltrFjtQP1ycmiR5pmhjtCfVOtdURuQ=
golang.org/x/image v0.4.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-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-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.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/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-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-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-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -270,29 +306,32 @@ 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-20220811171246-fbc7d0a398ab/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/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.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.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
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-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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
@ -309,6 +348,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/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.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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@ -316,10 +356,10 @@ 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.4.6 h1:5zS3vIKcyb46byXZNcYxaT9EWNIhXzu0gPuvvVrwZ8s=
gorm.io/driver/mysql v1.4.6/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
gorm.io/driver/postgres v1.4.7 h1:J06jXZCNq7Pdf7LIPn8tZn9LsWjd81BRSKveKNr0ZfA=
gorm.io/driver/postgres v1.4.7/go.mod h1:UJChCNLFKeBqQRE+HrkFUbKbq9idPXmTOk2u4Wok8S4=
gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y=
gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
gorm.io/driver/postgres v1.4.8 h1:NDWizaclb7Q2aupT0jkwK8jx1HVCNzt+PQ8v/VnxviA=
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/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
@ -327,3 +367,5 @@ gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@ -2,8 +2,10 @@ package bootstrap
import (
"crypto/tls"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/alist-org/alist/v3/cmd/flags"
"github.com/alist-org/alist/v3/drivers/base"
@ -79,6 +81,7 @@ func InitConfig() {
if conf.Conf.TlsInsecureSkipVerify {
base.RestyClient = base.RestyClient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}
initURL()
}
func confFromEnv() {
@ -93,3 +96,14 @@ func confFromEnv() {
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
}

View File

@ -110,7 +110,7 @@ func InitialSettings() []model.SettingItem {
"PDF.js":"https://alist-org.github.io/pdf.js/web/viewer.html?file=$e_url"
},
"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},
// {Key: conf.OfficeViewers, Value: `{
@ -150,13 +150,14 @@ func InitialSettings() []model.SettingItem {
{Key: conf.MaxIndexDepth, Value: "20", Type: conf.TypeNumber, Group: model.INDEX, Flag: model.PRIVATE, Help: `max depth of index`},
{Key: conf.IndexProgress, Value: "{}", Type: conf.TypeText, Group: model.SINGLE, Flag: model.PRIVATE},
// GitHub settings
{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.GithubLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GITHUB, Flag: model.PUBLIC},
// SSO settings
{Key: conf.SSOLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.SSO, Flag: model.PUBLIC},
{Key: conf.SSOLoginplatform, Type: conf.TypeSelect, Options: "Github,Microsoft,Google,Dingtalk", Group: model.SSO, Flag: model.PUBLIC},
{Key: conf.SSOClientId, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
{Key: conf.SSOClientSecret, Value: "", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
// qbittorrent settings
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.QBITTORRENT, Flag: model.PRIVATE},
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
}
if flags.Dev {
initialSettingItems = append(initialSettingItems, []model.SettingItem{

View File

@ -46,7 +46,7 @@ func Log() {
MaxAge: logConfig.MaxAge, //days
Compress: logConfig.Compress, // disabled by default
}
if flags.Debug || flags.Dev {
if flags.Debug || flags.Dev || flags.LogStd {
w = io.MultiWriter(os.Stdout, w)
}
logrus.SetOutput(w)

View File

@ -77,6 +77,6 @@ func DefaultConfig() *Config {
MaxAge: 28,
},
MaxConnections: 0,
TlsInsecureSkipVerify: false,
TlsInsecureSkipVerify: true,
}
}

View File

@ -1,33 +1,33 @@
package conf
const (
TypeString = "string"
TypeSelect = "select"
TypeBool = "bool"
TypeText = "text"
TypeNumber = "number"
TypeString = "string"
TypeSelect = "select"
TypeBool = "bool"
TypeText = "text"
TypeNumber = "number"
)
const (
// site
VERSION = "version"
SiteTitle = "site_title"
Announcement = "announcement"
AllowIndexed = "allow_indexed"
// site
VERSION = "version"
SiteTitle = "site_title"
Announcement = "announcement"
AllowIndexed = "allow_indexed"
Logo = "logo"
Favicon = "favicon"
MainColor = "main_color"
Logo = "logo"
Favicon = "favicon"
MainColor = "main_color"
// preview
TextTypes = "text_types"
AudioTypes = "audio_types"
VideoTypes = "video_types"
ImageTypes = "image_types"
ProxyTypes = "proxy_types"
ProxyIgnoreHeaders = "proxy_ignore_headers"
AudioAutoplay = "audio_autoplay"
VideoAutoplay = "video_autoplay"
// preview
TextTypes = "text_types"
AudioTypes = "audio_types"
VideoTypes = "video_types"
ImageTypes = "image_types"
ProxyTypes = "proxy_types"
ProxyIgnoreHeaders = "proxy_ignore_headers"
AudioAutoplay = "audio_autoplay"
VideoAutoplay = "video_autoplay"
// global
HideFiles = "hide_files"
@ -39,36 +39,37 @@ const (
OcrApi = "ocr_api"
FilenameCharMapping = "filename_char_mapping"
ForwardDirectLinkParams = "forward_direct_link_params"
// index
SearchIndex = "search_index"
AutoUpdateIndex = "auto_update_index"
IgnorePaths = "ignore_paths"
MaxIndexDepth = "max_index_depth"
// aria2
Aria2Uri = "aria2_uri"
Aria2Secret = "aria2_secret"
// aria2
Aria2Uri = "aria2_uri"
Aria2Secret = "aria2_secret"
// single
Token = "token"
IndexProgress = "index_progress"
// single
Token = "token"
IndexProgress = "index_progress"
//Github
GithubClientId = "github_client_id"
GithubClientSecrets = "github_client_secrets"
GithubLoginEnabled = "github_login_enabled"
//SSO
SSOClientId = "sso_client_id"
SSOClientSecret = "sso_client_secret"
SSOLoginEnabled = "sso_login_enabled"
SSOLoginplatform = "sso_login_platform"
// qbittorrent
QbittorrentUrl = "qbittorrent_url"
// qbittorrent
QbittorrentUrl = "qbittorrent_url"
)
const (
UNKNOWN = iota
FOLDER
//OFFICE
VIDEO
AUDIO
TEXT
IMAGE
UNKNOWN = iota
FOLDER
//OFFICE
VIDEO
AUDIO
TEXT
IMAGE
)

View File

@ -1,6 +1,9 @@
package conf
import "regexp"
import (
"net/url"
"regexp"
)
var (
BuiltAt string
@ -13,6 +16,7 @@ var (
var (
Conf *Config
URL *url.URL
)
var SlicesMap = make(map[string][]string)

View File

@ -51,6 +51,15 @@ func GetStorageById(id uint) (*model.Storage, error) {
return &storage, nil
}
// GetStorageByMountPath Get Storage by mountPath, used to update storage usually
func GetStorageByMountPath(mountPath string) (*model.Storage, error) {
var storage model.Storage
if err := db.Where("mount_path = ?", mountPath).First(&storage).Error; err != nil {
return nil, errors.WithStack(err)
}
return &storage, nil
}
func GetEnabledStorages() ([]model.Storage, error) {
var storages []model.Storage
if err := db.Where(fmt.Sprintf("%s = ?", columnName("disabled")), false).Find(&storages).Error; err != nil {

View File

@ -21,10 +21,10 @@ func GetUserByName(username string) (*model.User, error) {
return &user, nil
}
func GetUserByGithubID(githubID int) (*model.User, error) {
user := model.User{GithubID: githubID}
func GetUserBySSOID(ssoID string) (*model.User, error) {
user := model.User{SsoID: ssoID}
if err := db.Where(user).First(&user).Error; err != nil {
return nil, errors.Wrapf(err, "The Github ID is not associated with a user")
return nil, errors.Wrapf(err, "The single sign on platform is not bound to any users")
}
return &user, nil
}

View File

@ -1,15 +1,17 @@
package driver
type Config struct {
Name string `json:"name"`
LocalSort bool `json:"local_sort"`
OnlyLocal bool `json:"only_local"`
OnlyProxy bool `json:"only_proxy"`
NoCache bool `json:"no_cache"`
NoUpload bool `json:"no_upload"`
NeedMs bool `json:"need_ms"` // if need get message from user, such as validate code
DefaultRoot string `json:"default_root"`
CheckStatus bool
Name string `json:"name"`
LocalSort bool `json:"local_sort"`
OnlyLocal bool `json:"only_local"`
OnlyProxy bool `json:"only_proxy"`
NoCache bool `json:"no_cache"`
NoUpload bool `json:"no_upload"`
NeedMs bool `json:"need_ms"` // if need get message from user, such as validate code
DefaultRoot string `json:"default_root"`
CheckStatus bool `json:"-"`
Alert string `json:"alert"` //info,success,warning,danger
NoOverwriteUpload bool `json:"-"`
}
func (c Config) MustProxy() bool {

View File

@ -8,8 +8,7 @@ const (
GLOBAL
ARIA2
INDEX
GITHUB
QBITTORRENT
SSO
)
const (

View File

@ -33,7 +33,7 @@ type User struct {
// 10: can add qbittorrent tasks
Permission int32 `json:"permission"`
OtpSecret string `json:"-"`
GithubID int `json:"github_id"`
SsoID string `json:"sso_id"`
}
func (u User) IsGuest() bool {

View File

@ -459,6 +459,7 @@ func Remove(ctx context.Context, storage driver.Driver, path string) error {
if err != nil {
// if object not found, it's ok
if errs.IsObjectNotFound(err) {
log.Debugf("%s have been removed", path)
return nil
}
return errors.WithMessage(err, "failed to get object")
@ -497,6 +498,8 @@ func Put(ctx context.Context, storage driver.Driver, dstDirPath string, file *mo
// if file exist and size = 0, delete it
dstDirPath = utils.FixAndCleanPath(dstDirPath)
dstPath := stdpath.Join(dstDirPath, file.GetName())
tempName := file.GetName() + ".alist_to_delete"
tempPath := stdpath.Join(dstDirPath, tempName)
fi, err := GetUnwrap(ctx, storage, dstPath)
if err == nil {
if fi.GetSize() == 0 {
@ -504,6 +507,12 @@ func Put(ctx context.Context, storage driver.Driver, dstDirPath string, file *mo
if err != nil {
return errors.WithMessagef(err, "failed remove file that exist and have size 0")
}
} else if storage.Config().NoOverwriteUpload {
// try to rename old obj
err = Rename(ctx, storage, dstPath, tempName)
if err != nil {
return err
}
} else {
file.Old = fi
}
@ -542,10 +551,20 @@ func Put(ctx context.Context, storage driver.Driver, dstDirPath string, file *mo
return errs.NotImplement
}
log.Debugf("put file [%s] done", file.GetName())
//if err == nil {
// //clear cache
// key := stdpath.Join(storage.GetStorage().MountPath, dstDirPath)
// listCache.Del(key)
//}
if storage.Config().NoOverwriteUpload && fi != nil && fi.GetSize() > 0 {
if err != nil {
// upload failed, recover old obj
err := Rename(ctx, storage, tempPath, file.GetName())
if err != nil {
log.Errorf("failed recover old obj: %+v", err)
}
} else {
// upload success, remove old obj
err := Remove(ctx, storage, tempPath)
if err != nil {
return err
}
}
}
return errors.WithStack(err)
}

View File

@ -15,6 +15,10 @@ func GetStorageAndActualPath(rawPath string) (storage driver.Driver, actualPath
rawPath = utils.FixAndCleanPath(rawPath)
storage = GetBalancedStorage(rawPath)
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)
return
}

View File

@ -15,7 +15,7 @@ type Client interface {
AddFromLink(link string, savePath string, id string) error
GetInfo(id string) (TorrentInfo, error)
GetFiles(id string) ([]FileInfo, error)
Delete(id string) error
Delete(id string, deleteFiles bool) error
}
type client struct {
@ -242,6 +242,19 @@ type TorrentInfo struct {
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
@ -266,7 +279,7 @@ func (c *client) GetInfo(id string) (TorrentInfo, error) {
return TorrentInfo{}, err
}
if len(infos) != 1 {
return TorrentInfo{}, errors.New("there should be exactly one task with tag \"alist-" + id + "\"")
return TorrentInfo{}, NewInfoNotFoundError(id)
}
return infos[0], nil
}
@ -313,7 +326,7 @@ func (c *client) GetFiles(id string) ([]FileInfo, error) {
return infos, nil
}
func (c *client) Delete(id string) error {
func (c *client) Delete(id string, deleteFiles bool) error {
err := c.checkAuthorization()
if err != nil {
return err
@ -325,13 +338,27 @@ func (c *client) Delete(id string) error {
}
v := url.Values{}
v.Set("hashes", info.Hash)
v.Set("deleteFiles", "false")
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")
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
}

View File

@ -88,12 +88,6 @@ func TestAdd(t *testing.T) {
if err != nil {
t.Error(err)
}
// test add
err = c.login()
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",
@ -145,7 +139,15 @@ func TestDelete(t *testing.T) {
if err != nil {
t.Error(err)
}
err = c.Delete("uuid-2")
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)
}

View File

@ -9,7 +9,6 @@ import (
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"os"
"path"
"path/filepath"
"sync"
"sync/atomic"
@ -29,12 +28,36 @@ func (m *Monitor) Loop() 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():
err = qbclient.Delete(m.tsk.ID)
return err
// 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 {
@ -61,29 +84,13 @@ func (m *Monitor) update() (bool, error) {
progress := float64(info.Completed) / float64(info.Size) * 100
m.tsk.SetProgress(int(progress))
switch info.State {
case UPLOADING:
case PAUSEDUP:
case QUEUEDUP:
case STALLEDUP:
case FORCEDUP:
case CHECKINGUP:
case UPLOADING, PAUSEDUP, QUEUEDUP, STALLEDUP, FORCEDUP, CHECKINGUP:
err = m.complete()
return true, errors.WithMessage(err, "failed to transfer file")
case ALLOCATING:
case DOWNLOADING:
case METADL:
case PAUSEDDL:
case QUEUEDDL:
case STALLEDDL:
case CHECKINGDL:
case FORCEDDL:
case CHECKINGRESUMEDATA:
case MOVING:
case UNKNOWN: // or maybe should return an error for UNKNOWN?
case ALLOCATING, DOWNLOADING, METADL, PAUSEDDL, QUEUEDDL, STALLEDDL, CHECKINGDL, FORCEDDL, CHECKINGRESUMEDATA, MOVING:
m.tsk.SetStatus("qbittorrent downloading")
return false, nil
case ERROR:
case MISSINGFILES:
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
@ -95,7 +102,7 @@ var TransferTaskManager = task.NewTaskManager(3, func(k *uint64) {
func (m *Monitor) complete() error {
// check dstDir again
storage, dstDirActualPath, err := op.GetStorageAndActualPath(m.dstDirPath)
storage, dstBaseDir, err := op.GetStorageAndActualPath(m.dstDirPath)
if err != nil {
return errors.WithMessage(err, "failed get storage")
}
@ -105,6 +112,12 @@ func (m *Monitor) complete() error {
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))
@ -117,20 +130,23 @@ func (m *Monitor) complete() error {
}
}()
for _, file := range files {
filePath := filepath.Join(m.tempDir, file.Name)
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)", filePath, storage.GetStorage().MountPath, dstDirActualPath),
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(filePath)
f, err := os.Open(filePath)
mimetype := utils.GetMimeType(tempPath)
f, err := os.Open(tempPath)
if err != nil {
return errors.Wrapf(err, "failed to open file %s", filePath)
return errors.Wrapf(err, "failed to open file %s", tempPath)
}
stream := &model.FileStream{
Obj: &model.Object{
Name: path.Base(filePath),
Name: fileName,
Size: size,
Modified: time.Now(),
IsFolder: false,
@ -138,8 +154,7 @@ func (m *Monitor) complete() error {
ReadCloser: f,
Mimetype: mimetype,
}
newDistDir := filepath.Join(dstDirActualPath, file.Name)
return op.Put(tsk.Ctx, storage, newDistDir, stream, tsk.SetProgress)
return op.Put(tsk.Ctx, storage, dstDir, stream, tsk.SetProgress)
},
}))
}

107
pkg/http_range/range.go Normal file
View 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
}

View File

@ -69,3 +69,25 @@ func (l limitWriter) Write(p []byte) (n int, err error) {
func LimitWriter(w io.Writer, size int64) io.Writer {
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
View 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
}

View File

@ -14,7 +14,7 @@ func GetApiUrl(r *http.Request) string {
if strings.HasPrefix(api, "http") {
return api
}
if r != nil && api == "" {
if r != nil {
protocol := "http"
if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" {
protocol = "https"
@ -25,6 +25,6 @@ func GetApiUrl(r *http.Request) string {
}
api = fmt.Sprintf("%s://%s", protocol, stdpath.Join(host, api))
}
strings.TrimSuffix(api, "/")
api = strings.TrimSuffix(api, "/")
return api
}

View File

@ -1,6 +1,7 @@
package common
import (
"path"
"regexp"
"strings"
@ -15,12 +16,20 @@ func CanWrite(meta *model.Meta, path string) bool {
return meta.WSub || meta.Path == path
}
func IsApply(metaPath, reqPath string, applySub bool) bool {
if utils.PathEqual(metaPath, reqPath) {
return true
}
return utils.IsSubPath(reqPath, metaPath) && applySub
}
func CanAccess(user *model.User, meta *model.Meta, reqPath string, password string) bool {
// if the reqPath is in hide (only can check the nearest meta) and user can't see hides, can't access
if meta != nil && !user.CanSeeHides() && meta.Hide != "" && !utils.PathEqual(meta.Path, reqPath) {
if meta != nil && !user.CanSeeHides() && meta.Hide != "" &&
IsApply(meta.Path, path.Dir(reqPath), meta.HSub) { // the meta should apply to the parent of current path
for _, hide := range strings.Split(meta.Hide, "\n") {
re := regexp.MustCompile(hide)
if re.MatchString(reqPath[len(meta.Path)+1:]) {
if re.MatchString(path.Base(reqPath)) {
return false
}
}

View File

@ -101,7 +101,7 @@ func UpdateCurrent(c *gin.Context) {
if req.Password != "" {
user.Password = req.Password
}
user.GithubID = req.GithubID
user.SsoID = req.SsoID
if err := op.UpdateUser(user); err != nil {
common.ErrorResp(c, err, 500)
} else {

View File

@ -2,7 +2,6 @@ package handles
import (
"fmt"
"net/url"
stdpath "path"
"strings"
@ -42,21 +41,13 @@ func Down(c *gin.Context) {
c.Header("Referrer-Policy", "no-referrer")
c.Header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
if setting.GetBool(conf.ForwardDirectLinkParams) {
params := c.Request.URL.Query()
params.Del("sign")
u, err := url.Parse(link.URL)
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
}
values := u.Query()
for k := range params {
for i := range params[k] {
values.Set(k, params[k][i])
}
}
u.RawQuery = values.Encode()
link.URL = u.String()
}
c.Redirect(302, link.URL)
}
@ -92,21 +83,13 @@ func Proxy(c *gin.Context) {
return
}
if link.URL != "" && setting.GetBool(conf.ForwardDirectLinkParams) {
params := c.Request.URL.Query()
params.Del("sign")
u, err := url.Parse(link.URL)
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
}
values := u.Query()
for k := range params {
for i := range params[k] {
values.Set(k, params[k][i])
}
}
u.RawQuery = values.Encode()
link.URL = u.String()
}
err = common.Proxy(c.Writer, c.Request, link, file)
if err != nil {

View File

@ -1,101 +0,0 @@
package handles
import (
"errors"
"net/url"
"strconv"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
)
func GithubLoginRedirect(c *gin.Context) {
method := c.Query("method")
callbackURL := c.Query("callback_url")
withParams := c.Query("with_params")
enabled, err := db.GetSettingItemByKey("github_login_enabled")
clientId, err := db.GetSettingItemByKey("github_client_id")
if err != nil {
common.ErrorResp(c, err, 400)
return
} else if enabled.Value == "true" {
urlValues := url.Values{}
urlValues.Add("client_id", clientId.Value)
if method == "get_github_id" {
urlValues.Add("allow_signup", "true")
} else if method == "github_callback_login" {
urlValues.Add("allow_signup", "false")
}
if method == "" {
common.ErrorStrResp(c, "no method provided", 400)
return
}
if withParams != "" {
urlValues.Add("redirect_uri", common.GetApiUrl(c.Request)+"/api/auth/github_callback"+"?method="+method+"&callback_url="+callbackURL+"&with_params="+withParams)
} else {
urlValues.Add("redirect_uri", common.GetApiUrl(c.Request)+"/api/auth/github_callback"+"?method="+method+"&callback_url="+callbackURL)
}
c.Redirect(302, "https://github.com/login/oauth/authorize?"+urlValues.Encode())
} else {
common.ErrorStrResp(c, "github Login not enabled", 403)
}
}
var githubClient = resty.New().SetRetryCount(3)
func GithubLoginCallback(c *gin.Context) {
argument := c.Query("method")
callbackUrl := c.Query("callback_url")
if argument == "get_github_id" || argument == "github_login" {
enabled, err := db.GetSettingItemByKey("github_login_enabled")
clientId, err := db.GetSettingItemByKey("github_client_id")
clientSecret, err := db.GetSettingItemByKey("github_client_secrets")
if err != nil {
common.ErrorResp(c, err, 400)
return
} else if enabled.Value == "true" {
callbackCode := c.Query("code")
if callbackCode == "" {
common.ErrorStrResp(c, "No code provided", 400)
return
}
resp, err := githubClient.R().SetHeader("content-type", "application/json").
SetBody(map[string]string{
"client_id": clientId.Value,
"client_secret": clientSecret.Value,
"code": callbackCode,
"redirect_uri": common.GetApiUrl(c.Request) + "/api/auth/github_callback",
}).Post("https://github.com/login/oauth/access_token")
if err != nil {
common.ErrorResp(c, err, 400)
return
}
accessToken := utils.Json.Get(resp.Body(), "access_token").ToString()
resp, err = githubClient.R().SetHeader("Authorization", "Bearer "+accessToken).
Get("https://api.github.com/user")
ghUserID := utils.Json.Get(resp.Body(), "id").ToInt()
if argument == "get_github_id" {
c.Redirect(302, callbackUrl+"?githubID="+strconv.Itoa(ghUserID))
}
if argument == "github_login" {
user, err := db.GetUserByGithubID(ghUserID)
if err != nil {
common.ErrorResp(c, err, 400)
}
token, err := common.GenerateToken(user.Username)
withParams := c.Query("with_params")
if withParams == "true" {
c.Redirect(302, callbackUrl+"&token="+token)
} else if withParams == "false" {
c.Redirect(302, callbackUrl+"?token="+token)
}
return
}
} else {
common.ErrorResp(c, errors.New("invalid request"), 500)
}
}
}

View File

@ -19,10 +19,14 @@ func SetQbittorrent(c *gin.Context) {
common.ErrorResp(c, err, 400)
return
}
items := []model.SettingItem{
{Key: conf.QbittorrentUrl, Value: req.Url, Type: conf.TypeString, Group: model.QBITTORRENT, Flag: model.PRIVATE},
item := &model.SettingItem{
Key: conf.QbittorrentUrl,
Value: req.Url,
Type: conf.TypeString,
Group: model.SINGLE,
Flag: model.PRIVATE,
}
if err := op.SaveSettingItems(items); err != nil {
if err := op.SaveSettingItem(item); err != nil {
common.ErrorResp(c, err, 500)
return
}

181
server/handles/ssologin.go Normal file
View File

@ -0,0 +1,181 @@
package handles
import (
"errors"
"fmt"
"net/url"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/setting"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
)
func SSOLoginRedirect(c *gin.Context) {
method := c.Query("method")
enabled := setting.GetBool(conf.SSOLoginEnabled)
clientId := setting.GetStr(conf.SSOClientId)
platform := setting.GetStr(conf.SSOLoginplatform)
var r_url string
var redirect_uri string
if enabled {
urlValues := url.Values{}
if method == "" {
common.ErrorStrResp(c, "no method provided", 400)
return
}
redirect_uri = common.GetApiUrl(c.Request) + "/api/auth/sso_callback" + "?method=" + method
urlValues.Add("response_type", "code")
urlValues.Add("redirect_uri", redirect_uri)
urlValues.Add("client_id", clientId)
switch platform {
case "Github":
r_url = "https://github.com/login/oauth/authorize?"
urlValues.Add("scope", "read:user")
case "Microsoft":
r_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?"
urlValues.Add("scope", "user.read")
urlValues.Add("response_mode", "query")
case "Google":
r_url = "https://accounts.google.com/o/oauth2/v2/auth?"
urlValues.Add("scope", "https://www.googleapis.com/auth/userinfo.profile")
case "Dingtalk":
r_url = "https://login.dingtalk.com/oauth2/auth?"
urlValues.Add("scope", "openid")
urlValues.Add("prompt", "consent")
urlValues.Add("response_type", "code")
default:
common.ErrorStrResp(c, "invalid platform", 400)
return
}
c.Redirect(302, r_url+urlValues.Encode())
} else {
common.ErrorStrResp(c, "Single sign-on is not enabled", 403)
}
}
var ssoClient = resty.New().SetRetryCount(3)
func SSOLoginCallback(c *gin.Context) {
argument := c.Query("method")
if argument == "get_sso_id" || argument == "sso_get_token" {
enabled := setting.GetBool(conf.SSOLoginEnabled)
clientId := setting.GetStr(conf.SSOClientId)
platform := setting.GetStr(conf.SSOLoginplatform)
clientSecret := setting.GetStr(conf.SSOClientSecret)
var url1, url2, additionalbody, scope, authstring, idstring string
switch platform {
case "Github":
url1 = "https://github.com/login/oauth/access_token"
url2 = "https://api.github.com/user"
additionalbody = ""
authstring = "code"
scope = "read:user"
idstring = "id"
case "Microsoft":
url1 = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
url2 = "https://graph.microsoft.com/v1.0/me"
additionalbody = "&grant_type=authorization_code"
scope = "user.read"
authstring = "code"
idstring = "id"
case "Google":
url1 = "https://oauth2.googleapis.com/token"
url2 = "https://www.googleapis.com/oauth2/v1/userinfo"
additionalbody = "&grant_type=authorization_code"
scope = "https://www.googleapis.com/auth/userinfo.profile"
authstring = "code"
idstring = "id"
case "Dingtalk":
url1 = "https://api.dingtalk.com/v1.0/oauth2/userAccessToken"
url2 = "https://api.dingtalk.com/v1.0/contact/users/me"
authstring = "authCode"
idstring = "unionId"
default:
common.ErrorStrResp(c, "invalid platform", 400)
return
}
if enabled {
callbackCode := c.Query(authstring)
if callbackCode == "" {
common.ErrorStrResp(c, "No code provided", 400)
return
}
var resp *resty.Response
var err error
if platform == "Dingtalk" {
resp, err = ssoClient.R().SetHeader("content-type", "application/json").SetHeader("Accept", "application/json").
SetBody(map[string]string{
"clientId": clientId,
"clientSecret": clientSecret,
"code": callbackCode,
"grantType": "authorization_code",
}).
Post(url1)
} else {
resp, err = ssoClient.R().SetHeader("content-type", "application/x-www-form-urlencoded").SetHeader("Accept", "application/json").
SetBody("client_id=" + clientId + "&client_secret=" + clientSecret + "&code=" + callbackCode + "&redirect_uri=" + common.GetApiUrl(c.Request) + "/api/auth/sso_callback?method=" + argument + "&scope=" + scope + additionalbody).
Post(url1)
}
if err != nil {
common.ErrorResp(c, err, 400)
return
}
if platform == "Dingtalk" {
accessToken := utils.Json.Get(resp.Body(), "accessToken").ToString()
resp, err = ssoClient.R().SetHeader("x-acs-dingtalk-access-token", accessToken).
Get(url2)
} else {
accessToken := utils.Json.Get(resp.Body(), "access_token").ToString()
resp, err = ssoClient.R().SetHeader("Authorization", "Bearer "+accessToken).
Get(url2)
}
if err != nil {
common.ErrorResp(c, err, 400)
return
}
UserID := utils.Json.Get(resp.Body(), idstring).ToString()
if UserID == "0" {
common.ErrorResp(c, errors.New("error occured"), 400)
return
}
if argument == "get_sso_id" {
html := fmt.Sprintf(`<!DOCTYPE html>
<head></head>
<body>
<script>
window.opener.postMessage({"sso_id": "%s"}, "*")
window.close()
</script>
</body>`, UserID)
c.Data(200, "text/html; charset=utf-8", []byte(html))
return
}
if argument == "sso_get_token" {
user, err := db.GetUserBySSOID(UserID)
if err != nil {
common.ErrorResp(c, err, 400)
}
token, err := common.GenerateToken(user.Username)
if err != nil {
common.ErrorResp(c, err, 400)
}
html := fmt.Sprintf(`<!DOCTYPE html>
<head></head>
<body>
<script>
window.opener.postMessage({"token":"%s"}, "*")
window.close()
</script>
</body>`, token)
c.Data(200, "text/html; charset=utf-8", []byte(html))
return
}
} else {
common.ErrorResp(c, errors.New("invalid request"), 500)
}
}
}

View File

@ -1,6 +1,8 @@
package middlewares
import (
"crypto/subtle"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
@ -14,7 +16,7 @@ import (
// if token is empty, set user to guest
func Auth(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == setting.GetStr(conf.Token) {
if subtle.ConstantTimeCompare([]byte(token), []byte(setting.GetStr(conf.Token))) == 1 {
admin, err := op.GetAdmin()
if err != nil {
common.ErrorResp(c, err, 500)

View File

@ -35,7 +35,7 @@ func FsUp(c *gin.Context) {
return
}
}
if !(common.CanAccess(user, meta, path, password) && (user.CanWrite() || common.CanWrite(meta, path))) {
if !(common.CanAccess(user, meta, path, password) && (user.CanWrite() || common.CanWrite(meta, stdpath.Dir(path)))) {
common.ErrorResp(c, errs.PermissionDenied, 403)
c.Abort()
return

View File

@ -4,6 +4,7 @@ import (
"github.com/alist-org/alist/v3/cmd/flags"
"github.com/alist-org/alist/v3/internal/conf"
"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/handles"
"github.com/alist-org/alist/v3/server/middlewares"
@ -12,21 +13,30 @@ import (
"github.com/gin-gonic/gin"
)
func Init(r *gin.Engine) {
common.SecretKey = []byte(conf.Conf.JwtSecret)
Cors(r)
r.Use(middlewares.StoragesLoaded)
if conf.Conf.MaxConnections > 0 {
r.Use(middlewares.MaxAllowed(conf.Conf.MaxConnections))
func Init(e *gin.Engine) {
if !utils.SliceContains([]string{"", "/"}, conf.URL.Path) {
e.GET("/", func(c *gin.Context) {
c.Redirect(302, conf.URL.Path)
})
}
WebDav(r.Group("/dav"))
Cors(e)
g := e.Group(conf.URL.Path)
g.Any("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
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)
r.GET("/i/:link_name", handles.Plist)
r.GET("/d/*path", middlewares.Down, handles.Down)
r.GET("/p/*path", middlewares.Down, handles.Proxy)
g.GET("/favicon.ico", handles.Favicon)
g.GET("/i/:link_name", handles.Plist)
g.GET("/d/*path", middlewares.Down, handles.Down)
g.GET("/p/*path", middlewares.Down, handles.Proxy)
api := r.Group("/api")
api := g.Group("/api")
auth := api.Group("", middlewares.Auth)
api.POST("/auth/login", handles.Login)
@ -36,8 +46,8 @@ func Init(r *gin.Engine) {
auth.POST("/auth/2fa/verify", handles.Verify2FA)
// github auth
api.GET("/auth/github", handles.GithubLoginRedirect)
api.GET("/auth/github_callback", handles.GithubLoginCallback)
api.GET("/auth/sso", handles.SSOLoginRedirect)
api.GET("/auth/sso_callback", handles.SSOLoginCallback)
// no need auth
public := api.Group("/public")
@ -46,9 +56,11 @@ func Init(r *gin.Engine) {
_fs(auth.Group("/fs"))
admin(auth.Group("/admin", middlewares.AuthAdmin))
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) {
@ -89,7 +101,7 @@ func admin(g *gin.RouterGroup) {
setting.POST("/delete", handles.DeleteSetting)
setting.POST("/reset_token", handles.ResetToken)
setting.POST("/set_aria2", handles.SetAria2)
setting.POST("/set_qbittorrent", handles.SetQbittorrent)
setting.POST("/set_qbit", handles.SetQbittorrent)
task := g.Group("/task")
handles.SetupTaskRoute(task)
@ -127,7 +139,6 @@ func _fs(g *gin.RouterGroup) {
func Cors(r *gin.Engine) {
config := cors.DefaultConfig()
config.AllowAllOrigins = true
//config.AllowHeaders = append(config.AllowHeaders, "Authorization", "range", "File-Path", "As-Task", "Password")
config.AllowHeaders = []string{"*"}
config.AllowMethods = []string{"*"}
r.Use(cors.New(config))

View File

@ -1,7 +1,6 @@
package static
import (
"net/url"
"strings"
"github.com/alist-org/alist/v3/internal/conf"
@ -9,26 +8,20 @@ import (
)
type SiteConfig struct {
ApiURL string
BasePath string
Cdn string
}
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{
ApiURL: conf.Conf.SiteURL,
BasePath: u.Path,
BasePath: conf.URL.Path,
Cdn: strings.ReplaceAll(strings.TrimSuffix(conf.Conf.Cdn, "/"), "$version", conf.WebVersion),
}
if siteConfig.BasePath != "" {
siteConfig.BasePath = utils.FixAndCleanPath(siteConfig.BasePath)
}
if siteConfig.Cdn == "" {
siteConfig.Cdn = siteConfig.BasePath
siteConfig.Cdn = strings.TrimSuffix(siteConfig.BasePath, "/")
}
return siteConfig
}

View File

@ -29,7 +29,6 @@ func InitIndex() {
replaceMap := map[string]string{
"cdn: undefined": fmt.Sprintf("cdn: '%s'", siteConfig.Cdn),
"base_path: undefined": fmt.Sprintf("base_path: '%s'", siteConfig.BasePath),
"api: undefined": fmt.Sprintf("api: '%s'", siteConfig.ApiURL),
}
for k, v := range replaceMap {
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()
folders := []string{"assets", "images", "streamer", "static"}
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.NoRoute(func(c *gin.Context) {
noRoute(func(c *gin.Context) {
c.Header("Content-Type", "text/html")
c.Status(200)
if strings.HasPrefix(c.Request.URL.Path, "/@manage") {

View File

@ -3,7 +3,9 @@ package server
import (
"context"
"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/op"
"github.com/alist-org/alist/v3/pkg/utils"
@ -14,17 +16,14 @@ import (
var handler *webdav.Handler
func init() {
func WebDav(dav *gin.RouterGroup) {
handler = &webdav.Handler{
Prefix: "/dav",
Prefix: path.Join(conf.URL.Path, "/dav"),
LockSystem: webdav.NewMemLS(),
Logger: func(request *http.Request, err error) {
log.Errorf("%s %s %+v", request.Method, request.URL.Path, err)
},
}
}
func WebDav(dav *gin.RouterGroup) {
dav.Use(WebDAVAuth)
dav.Any("/*path", ServeWebDAV)
dav.Any("", ServeWebDAV)
@ -69,7 +68,7 @@ func WebDAVAuth(c *gin.Context) {
c.Abort()
return
}
if !user.CanWebdavRead() {
if user.Disabled || !user.CanWebdavRead() {
if c.Request.Method == "OPTIONS" {
c.Set("user", guest)
c.Next()

View File

@ -296,6 +296,9 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
if err != nil {
return status, err
}
if reqPath == "" {
return http.StatusMethodNotAllowed, nil
}
release, status, err := h.confirmLocks(r, reqPath, "")
if err != nil {
return status, err