Compare commits

..

88 Commits

Author SHA1 Message Date
bd52df1926 ci: use own proxy musl url 2022-08-14 15:38:22 +08:00
ba9d568539 fix: add -password flag back 2022-08-13 20:56:19 +08:00
a66b2d92b7 ci: use personal token 2022-08-13 11:52:14 +08:00
0b3c5f3e2b fix: random password of webdav guest user 2022-08-13 11:28:40 +08:00
cc74effd30 feat: add chaoxing support (#1457)
Co-authored-by: Admin <kbtxwer@qq.com>
2022-08-13 11:25:50 +08:00
51b8b4380d feat: integrate alist with casdoor (#1453)
* feat: integrate alist with casdoor

* fix: casdoor as an option for login

Co-authored-by: wenxuan70 <t736660416@gmail.com>
2022-08-06 16:47:39 +08:00
5e59b0a697 docs: fix typo [skip ci] 2022-07-28 18:53:05 +08:00
1d3b86985f fix: config json and env tag 2022-07-22 13:40:12 +08:00
c711ba53a3 fix!: change default assets to local 2022-07-22 13:39:25 +08:00
532a326ad6 ci: prettify auto generate changelog 2022-07-21 22:26:24 +08:00
917ad07ab7 fix(123): download out of memory 2022-07-21 22:08:11 +08:00
50fd7de045 fix(139): rename file (close: #1331) 2022-07-21 21:08:49 +08:00
f6527f1c4c fix(native): download while root_folder is not root (fix: #1340) 2022-07-21 20:50:43 +08:00
a3ef3d1416 docs: add sponsor 2022-07-17 23:59:27 +08:00
89b05021f8 chore: moved alist-web 2022-07-14 22:25:51 +08:00
7e40acad3f feat: Added SubFolder and localAssets configuration(#1324)
Co-authored-by: eaglexmw <eagle_xmw@163.com>
2022-07-14 22:22:47 +08:00
eb15bce24b ci: auto generate changelog 2022-06-06 16:22:12 +08:00
52814266b8 chore: Merge pull request #1200 from alist-org/all-contributors/add-XZB-1248
docs: add XZB-1248 as a contributor for code
2022-06-06 16:16:11 +08:00
f845ec05e0 docs: update .all-contributorsrc [skip ci] 2022-06-06 08:15:50 +00:00
29fb02c886 docs: update CONTRIBUTORS.md [skip ci] 2022-06-06 08:15:49 +00:00
072e854a71 chore: Merge pull request #1199 from alist-org/dev
Merge dev branch
2022-06-06 16:10:35 +08:00
cae0a5f603 chore: Merge pull request #1191 from XZB-1248/dev
fix: filename is urlencoded when using Safari
2022-06-03 21:57:42 +08:00
XZB
7c6d8ca222 fix(proxy): filename is urlencoded when using Safari 2022-06-02 18:13:17 +08:00
f6be50f15a fix(189): login and get link (close #1182) 2022-05-31 16:03:54 +08:00
c35d54d092 chore: Merge pull request #1167 from Xhofe/dev 2022-05-28 21:01:47 +08:00
323dad2a1c fix(sftp): infinite loop while remove file (close #1094) 2022-05-28 21:01:04 +08:00
62aefc4f68 fix(189): new resty client 2022-05-28 20:43:13 +08:00
6a7eb8b3eb fix: don't save search files of balance account (close #1125) 2022-05-21 22:12:18 +08:00
eb549f2631 feat: add pdf viewer url to settings (close #1109) 2022-05-19 15:31:47 +08:00
9207eb69ee feat: add m4v to default video types (close #1114) 2022-05-19 15:31:40 +08:00
866df0540b chore: Merge pull request #1110 from foxxorcat/dev
增加123流式上传选择
2022-05-17 12:49:22 +08:00
04e04a1aa6 fix(189pc): delete user-agent for upload 2022-05-16 23:33:12 +08:00
6a66e39d5b feat(123):add io stream upload 2022-05-16 21:03:00 +08:00
f2b2728be7 fix(123,189pc,alidriver,xunlei):tempfile remove 2022-05-16 09:48:33 +08:00
39b8f28fc4 fix: disable pprof while not debug 2022-05-15 16:17:52 +08:00
e1ccc0b215 chore: Merge pull request #1093 from Xhofe/dev
2.5.2
2022-05-13 17:39:26 +08:00
87e339850d fix(ftp): remove dir (#1082) 2022-05-13 17:38:22 +08:00
79c9b6ac77 chore: Merge pull request #1090 from foxxorcat/dev (#1090) 2022-05-13 13:54:04 +08:00
aeb2297f1f perf(123):file thumbnail 2022-05-12 22:27:32 +08:00
3b59bb5c09 perf(123):upload 2022-05-12 21:39:55 +08:00
bc4bac921f chore: Merge pull request #1089 from foxxorcat/dev
修复迅雷一些已知问题
2022-05-12 20:42:49 +08:00
f917882a84 perf(xunlei):upload 2022-05-12 19:18:28 +08:00
6a67d1cf69 fix(xunlei):check captchaToken 2022-05-12 19:15:39 +08:00
041b3587bf fix(xunlei):turn page 2022-05-12 13:27:49 +08:00
0eef7a129c fix(xunlei):the verification code cannot be obtained from the mobile phone number or email 2022-05-12 13:26:12 +08:00
4b635f06e3 fix(189pc): delete user-agent for upload 2022-05-11 20:01:15 +08:00
279111a8e2 chore: Merge pull request #1079 from foxxorcat/dev 2022-05-10 22:35:59 +08:00
67674835da fix(alidriver):fast upload file is not close 2022-05-10 21:54:44 +08:00
732e9eb1c3 feat:add pprof 2022-05-10 21:40:43 +08:00
b6af9aa587 fix(139,189,189pc,alidrive,onedrive,yandex):http response body is not close #1072 2022-05-10 21:37:48 +08:00
a9027c0f06 fix(baidu.photo):update download api 2022-05-10 20:35:19 +08:00
d780fa18a5 fix(sftp): error while has no files(close #1078) 2022-05-10 18:18:06 +08:00
d5626d6e2f fix: cancel QueryEscape Disposition (close #1074) 2022-05-10 18:16:32 +08:00
52dcbfe1a4 fix(xunlei):missing x-client-id error in some user requests 2022-05-09 18:14:52 +08:00
bf0ee3d315 refactor(baidu.photo): add a file api of download 2022-05-09 12:43:51 +08:00
0237e78c1e chore: Merge branch 'dev' into v2 2022-05-08 14:30:01 +08:00
44b8c6abf7 fix(189): typo 2022-05-08 14:28:26 +08:00
33e1acd344 chore: Merge pull request #1060 from Xhofe/dev
Dev 2.5.1
2022-05-08 14:26:27 +08:00
c54cb61f14 chore: add debug info 2022-05-08 14:25:37 +08:00
734b204709 chore: change ocr api 2022-05-08 14:22:07 +08:00
b7d9c5e4ff chore: Merge pull request #1059 from foxxorcat/dev (close #1053)
fix baidu.photo and xunlei
2022-05-08 13:59:58 +08:00
e698b457b9 fix(baidu.photo):windows path error 2022-05-08 13:37:56 +08:00
5258c21656 fix(xunlei):login error 2022-05-08 13:36:36 +08:00
1ca9a3d14e chore: Merge pull request #1052 from Xhofe/dev
docs: add baidu.photo
2022-05-07 16:47:22 +08:00
f23bec9a35 docs: add baidu.photo [skip ci] 2022-05-07 16:43:02 +08:00
62a1acd1f4 chore: Merge pull request #1051 from Xhofe/all-contributors/add-WntFlm
docs: add WntFlm as a contributor for code
2022-05-07 16:38:33 +08:00
fa6e3fe567 docs: update .all-contributorsrc [skip ci] 2022-05-07 08:38:10 +00:00
b71b62ee35 docs: update CONTRIBUTORS.md [skip ci] 2022-05-07 08:38:09 +00:00
410b4939a4 chore: Merge pull request #1050 from Xhofe/all-contributors/add-ericarena
docs: add ericarena as a contributor for code
2022-05-07 16:37:24 +08:00
62c0071f29 docs: update .all-contributorsrc [skip ci] 2022-05-07 08:36:58 +00:00
f043a41005 docs: update CONTRIBUTORS.md [skip ci] 2022-05-07 08:36:57 +00:00
2e9da57036 chore: Merge pull request #1048 from Xhofe/all-contributors/add-Windman1320
docs: add Windman1320 as a contributor for code
2022-05-07 16:36:17 +08:00
d83cd37984 docs: update .all-contributorsrc [skip ci] 2022-05-07 08:35:50 +00:00
bad8b0ebbb docs: update CONTRIBUTORS.md [skip ci] 2022-05-07 08:35:49 +00:00
4535e65948 chore: Merge pull request #1047 from Xhofe/dev
Dev 2.5.0
2022-05-07 16:29:10 +08:00
3b413c2ee2 chore: Merge pull request #1021 from WntFlm/mimefix
fix(webdav): empty mimeType
2022-05-01 19:13:29 +08:00
427ae56333 chore: Merge pull request #1020 from foxxorcat/dev
fix(xunlei):download link speed limit
2022-05-01 13:25:54 +08:00
658fd5ad6e fix(webdav): empty mimeType
Now mimeType will always be a non-empty string, by defaulting it to "application/octet-stream".
2022-05-01 09:42:25 +08:00
11830bb51c fix(xunlei):download link speed limit 2022-04-30 21:41:15 +08:00
75c98429bf fix(webdav): wrong MIMEType (close #1007) 2022-04-29 14:09:51 +08:00
f77ea1b3a5 chore: Merge pull request #1011 from foxxorcat/dev
增加一刻相册支持,优化迅雷代码
2022-04-29 14:08:04 +08:00
0a8bd96d33 feat: support baidu.photo 2022-04-28 23:44:22 +08:00
68f37fc11f refactor(xunlei): optimized code 2022-04-28 23:15:37 +08:00
d6775cda69 fix(123): can't delete folder (close #1009) 2022-04-28 21:17:11 +08:00
43c6e07bac feat: add aria2 download settings(#1000)
* feat: add aria2 support

在右键菜单中增加了使用aria2下载的item,可以直接发送选中的文件链接到aria2,省略复制再粘贴到aria2的步骤

* feat: set default value for `Aria2 RPC url`

Co-authored-by: Xhofe <i@nn.ci>
2022-04-28 18:05:07 +08:00
4901e9080c fix(quark): file size over i32 (close #997) 2022-04-26 15:22:39 +08:00
48049a5ea3 docs: upgrade golang version [skip ci] 2022-04-25 16:05:49 +08:00
bd7260f0ff chore: base for template 2022-04-24 21:22:24 +08:00
56 changed files with 2849 additions and 596 deletions

View File

@ -51,11 +51,47 @@
"contributions": [
"doc"
]
},
{
"login": "Windman1320",
"name": "Windman",
"avatar_url": "https://avatars.githubusercontent.com/u/9999486?v=4",
"profile": "https://github.com/Windman1320",
"contributions": [
"code"
]
},
{
"login": "ericarena",
"name": "ericarena",
"avatar_url": "https://avatars.githubusercontent.com/u/4518927?v=4",
"profile": "https://github.com/ericarena",
"contributions": [
"code"
]
},
{
"login": "WntFlm",
"name": "WntFlm",
"avatar_url": "https://avatars.githubusercontent.com/u/34620278?v=4",
"profile": "https://github.com/WntFlm",
"contributions": [
"code"
]
},
{
"login": "XZB-1248",
"name": "XZB-1248",
"avatar_url": "https://avatars.githubusercontent.com/u/28593573?v=4",
"profile": "https://github.com/XZB-1248",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
"projectName": "alist",
"projectOwner": "Xhofe",
"projectOwner": "alist-org",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true

View File

@ -6,7 +6,19 @@ on:
- '*'
jobs:
changelog:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
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]

View File

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

View File

@ -1,5 +1,5 @@
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
## Contributors ✨
@ -11,11 +11,17 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=foxxorcat" title="Code">💻</a></td>
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=vg-land" title="Code">💻</a></td>
<td align="center"><a href="https://c5y.moe"><img src="https://avatars.githubusercontent.com/u/18461360?v=4?s=100" width="100px;" alt=""/><br /><sub><b>凌莞~(=^▽^=)</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Clansty" title="Documentation">📖</a></td>
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/alist-org/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=foxxorcat" title="Code">💻</a></td>
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=vg-land" title="Code">💻</a></td>
<td align="center"><a href="https://c5y.moe"><img src="https://avatars.githubusercontent.com/u/18461360?v=4?s=100" width="100px;" alt=""/><br /><sub><b>凌莞~(=^▽^=)</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=Clansty" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/Windman1320"><img src="https://avatars.githubusercontent.com/u/9999486?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Windman</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=Windman1320" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/ericarena"><img src="https://avatars.githubusercontent.com/u/4518927?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ericarena</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=ericarena" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/WntFlm"><img src="https://avatars.githubusercontent.com/u/34620278?v=4?s=100" width="100px;" alt=""/><br /><sub><b>WntFlm</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=WntFlm" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/XZB-1248"><img src="https://avatars.githubusercontent.com/u/28593573?v=4?s=100" width="100px;" alt=""/><br /><sub><b>XZB-1248</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=XZB-1248" title="Code">💻</a></td>
</tr>
</table>

View File

@ -39,6 +39,7 @@ English | [中文](./README_cn.md) | [Contributors](./CONTRIBUTORS.md) | [Contri
- [x] [Quark](https://pan.quark.cn)
- [x] [XunleiCloud](https://pan.xunlei.com/)
- [x] SFTP
- [x] [Baidu.Photo](https://photo.baidu.com/)
- [x] Easy to deploy and out-of-the-box
- [x] File preview (PDF, markdown, code, plain text, ...)
- [x] Image preview in gallery mode
@ -71,7 +72,8 @@ Available at: <https://alist.nn.ci>.
<https://alist-doc.nn.ci/en/>
## Special sponsors
- [Find Resources - Aliyundrive Resource Search Engine](https://zhaoziyuan.la/)
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/)
- [KinhDown 百度云盘不限速下载永久免费已稳定运行3年非常可靠!](https://kinhdown.com/?Type=Tutorials)
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
## License

View File

@ -39,6 +39,7 @@
- [x] [夸克网盘](https://pan.quark.cn)
- [x] [迅雷云盘](https://pan.xunlei.com/)
- [x] SFTP
- [x] [一刻相册](https://photo.baidu.com/)
- [x] 部署方便,开箱即用
- [x] 文件预览PDF、markdown、代码、纯文本……
- [x] 画廊模式下的图像预览
@ -72,6 +73,7 @@
## 特别赞助
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/)
- [KinhDown 百度云盘不限速下载永久免费已稳定运行3年非常可靠!](https://kinhdown.com/?Type=Tutorials)
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
## 许可

View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"github.com/Xhofe/alist/bootstrap"
"github.com/Xhofe/alist/conf"
_ "github.com/Xhofe/alist/drivers"
@ -26,6 +27,7 @@ func Init() bool {
}
server.InitIndex()
bootstrap.InitSettings()
bootstrap.InitAuth()
bootstrap.InitAccounts()
bootstrap.InitCache()
return true

51
bootstrap/auth.go Normal file
View File

@ -0,0 +1,51 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
log "github.com/sirupsen/logrus"
"strings"
)
type AuthConfig struct {
OrganizationName string `json:"organization_name"`
ApplicationName string `json:"application_name"`
Endpoint string `json:"endpoint"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
JwtPublicKey string `json:"jwt_public_key"`
}
// InitAuth init auth
func InitAuth() {
log.Infof("init auth...")
enableCasdoor := conf.GetBool("Enable Casdoor")
if !enableCasdoor {
return
}
auth := readAuthConfig()
if !CheckAuthConfig(auth) {
panic("invalid auth config")
}
casdoorsdk.InitConfig(strings.TrimRight(auth.Endpoint, "/"), auth.ClientId, auth.ClientSecret, auth.JwtPublicKey, auth.OrganizationName, auth.ApplicationName)
}
func readAuthConfig() AuthConfig {
return AuthConfig{
OrganizationName: conf.GetStr("Casdoor Organization name"),
ApplicationName: conf.GetStr("Casdoor Application name"),
Endpoint: conf.GetStr("Casdoor Endpoint"),
ClientId: conf.GetStr("Casdoor Client id"),
ClientSecret: conf.GetStr("Casdoor Client secret"),
JwtPublicKey: conf.GetStr("Casdoor Jwt Public Key"),
}
}
func CheckAuthConfig(authConfig AuthConfig) bool {
return authConfig.Endpoint != "" &&
authConfig.ClientId != "" &&
authConfig.ClientSecret != "" &&
authConfig.OrganizationName != "" &&
authConfig.ApplicationName != "" &&
authConfig.JwtPublicKey != ""
}

View File

@ -2,6 +2,7 @@ package bootstrap
import (
"flag"
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
)

View File

@ -1,12 +1,13 @@
package bootstrap
import (
"strings"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
"strings"
)
func InitSettings() {
@ -124,6 +125,13 @@ func InitSettings() {
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "pdf viewer url",
Type: "string",
Value: "https://alist-org.github.io/pdf.js/web/viewer.html?file=$url",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "autoplay video",
Value: "false",
@ -228,7 +236,7 @@ func InitSettings() {
},
{
Key: "Visitor WebDAV password",
Value: "guest",
Value: utils.RandomStr(8),
Description: "Visitor WebDAV password",
Type: "string",
Access: model.PRIVATE,
@ -252,7 +260,7 @@ func InitSettings() {
},
{
Key: "ocr api",
Value: "https://api.xhofe.top/ocr/file/json",
Value: "https://api.nn.ci/ocr/file/json",
Description: "Used to identify verification codes",
Type: "string",
Access: model.PRIVATE,
@ -266,6 +274,78 @@ func InitSettings() {
Group: model.BACK,
Description: "Experimental function, not recommended as it's still under development",
},
{
Key: "Aria2 RPC url",
Value: "http://localhost:6800/jsonrpc",
Description: "Aria2 RPC url, e.g. 'http://aria2.example.com:6800/jsonrpc'",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "Aria2 RPC secret",
Value: "",
Description: "Aria2 RPC secret, e.g. '123456'",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "Enable Casdoor",
Value: "false",
Description: "Enable Casdoor login",
Type: "bool",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "Casdoor Organization name",
Value: "",
Description: "Casdoor Organization name",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "Casdoor Application name",
Value: "",
Description: "Casdoor Application name",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "Casdoor Endpoint",
Value: "",
Description: "Casdoor Endpoint, e.g. 'http://localhost:8000'",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "Casdoor Client id",
Value: "",
Description: "Casdoor Client id",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "Casdoor Client secret",
Value: "",
Description: "Casdoor Client secret",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "Casdoor Jwt Public Key",
Value: "",
Description: "Casdoor Jwt Public Key",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
}
for i, _ := range settings {
v := settings[i]

View File

@ -2,8 +2,8 @@
# 构建前端,在当前目录产生一个dist文件夹
BUILD_WEB() {
git clone https://github.com/alist-org/alist-web.git
cd alist-web
git clone https://github.com/alist-org/web-v2.git
cd web-v2
yarn
yarn build
sed -i -e "s/\/CDN_URL\//\//g" dist/index.html
@ -11,11 +11,11 @@ BUILD_WEB() {
rm -f dist/index.html-e
mv dist ..
cd .. || exit
rm -rf alist-web
rm -rf web-v2
}
CDN_WEB() {
curl -L https://github.com/alist-org/alist-web/releases/latest/download/dist.tar.gz -o dist.tar.gz
curl -L https://github.com/alist-org/web-v2/releases/latest/download/dist.tar.gz -o dist.tar.gz
tar -zxvf dist.tar.gz
rm -f dist.tar.gz
}
@ -28,7 +28,7 @@ BUILD_DOCKER() {
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always)
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/web-v2/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
ldflags="\
-w -s \
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
@ -49,7 +49,7 @@ BUILD() {
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always)
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/web-v2/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
echo "build version: $gitTag"
ldflags="\
@ -81,7 +81,7 @@ BUILD() {
}
BUILD_MUSL() {
BASE="https://musl.cc/"
BASE="https://musl.nn.ci/"
FILES=(x86_64-linux-musl-cross aarch64-linux-musl-cross arm-linux-musleabihf-cross mips-linux-musl-cross mips64-linux-musl-cross mips64el-linux-musl-cross mipsel-linux-musl-cross powerpc64le-linux-musl-cross s390x-linux-musl-cross)
for i in "${FILES[@]}"; do
url="${BASE}${i}.tgz"
@ -95,7 +95,7 @@ BUILD_MUSL() {
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always)
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/web-v2/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
ldflags="\
-w -s --extldflags '-static -fpic' \
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \

View File

@ -24,22 +24,26 @@ type CacheConfig struct {
}
type Config struct {
Force bool `json:"force"`
Address string `json:"address" env:"ADDR"`
Port int `json:"port" env:"PORT"`
Assets string `json:"assets" env:"ASSETS"`
Database Database `json:"database"`
Scheme Scheme `json:"scheme"`
Cache CacheConfig `json:"cache"`
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
Force bool `json:"force"`
Address string `json:"address" env:"ADDR"`
Port int `json:"port" env:"PORT"`
Assets string `json:"assets" env:"ASSETS"`
LocalAssets string `json:"local_assets" env:"LOCAL_ASSETS"`
SubFolder string `json:"sub_folder" env:"SUB_FOLDER"`
Database Database `json:"database"`
Scheme Scheme `json:"scheme"`
Cache CacheConfig `json:"cache"`
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
}
func DefaultConfig() *Config {
return &Config{
Address: "0.0.0.0",
Port: 5244,
Assets: "https://npm.elemecdn.com/alist-web@$version/dist",
TempDir: "data/temp",
Address: "0.0.0.0",
Port: 5244,
Assets: "/",
SubFolder: "",
LocalAssets: "",
TempDir: "data/temp",
Database: Database{
Type: "sqlite3",
Port: 0,

View File

@ -40,7 +40,7 @@ var (
"go", "sh", "c", "cpp", "h", "hpp", "tsx", "vtt", "srt", "ass"}
DProxyTypes = []string{"m3u8"}
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv", "m4v"}
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav", "opus"}
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
)
@ -86,6 +86,9 @@ var (
"default page size", "load type",
"ocr api", "favicon",
"enable search",
"Enable Casdoor", "Casdoor Organization name", "Casdoor Application name",
"Casdoor Endpoint", "Casdoor Client id", "Casdoor Client secret",
"Casdoor Jwt Public Key",
}
)

View File

@ -3,15 +3,15 @@ package _23
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"path/filepath"
"strconv"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"path/filepath"
"strconv"
)
func (driver Pan123) Login(account *model.Account) error {
@ -40,21 +40,22 @@ func (driver Pan123) Login(account *model.Account) error {
return err
}
func (driver Pan123) FormatFile(file *Pan123File) *model.File {
func (driver Pan123) FormatFile(file *File) *model.File {
f := &model.File{
Id: strconv.FormatInt(file.FileId, 10),
Name: file.FileName,
Size: file.Size,
Driver: driver.Config().Name,
UpdatedAt: file.UpdateAt,
Thumbnail: file.DownloadUrl,
}
f.Type = file.GetType()
return f
}
func (driver Pan123) GetFiles(parentId string, account *model.Account) ([]Pan123File, error) {
func (driver Pan123) GetFiles(parentId string, account *model.Account) ([]File, error) {
next := "0"
res := make([]Pan123File, 0)
res := make([]File, 0)
for next != "-1" {
var resp Pan123Files
query := map[string]string{
@ -66,7 +67,7 @@ func (driver Pan123) GetFiles(parentId string, account *model.Account) ([]Pan123
"parentFileId": parentId,
"trashed": "false",
}
_, err := driver.Request("https://www.123pan.com/api/file/list",
_, err := driver.Request("https://www.123pan.com/api/file/list/new",
base.Get, nil, query, nil, &resp, false, account)
if err != nil {
return nil, err
@ -139,7 +140,7 @@ func (driver Pan123) Request(url string, method int, headers, query map[string]s
// return body, nil
//}
func (driver Pan123) GetFile(path string, account *model.Account) (*Pan123File, error) {
func (driver Pan123) GetFile(path string, account *model.Account) (*File, error) {
dir, name := filepath.Split(path)
dir = utils.ParsePath(dir)
_, err := driver.Files(dir, account)
@ -147,14 +148,15 @@ func (driver Pan123) GetFile(path string, account *model.Account) (*Pan123File,
return nil, err
}
parentFiles_, _ := base.GetCache(dir, account)
parentFiles, _ := parentFiles_.([]Pan123File)
parentFiles, _ := parentFiles_.([]File)
for _, file := range parentFiles {
if file.FileName == name {
if file.Type != conf.FOLDER {
return &file, err
} else {
return nil, base.ErrNotFile
}
//if file.Type != conf.FOLDER {
// return &file, err
//} else {
// return nil, base.ErrNotFile
//}
return &file, nil
}
}
return nil, base.ErrPathNotFound

View File

@ -1,9 +1,18 @@
package _23
import (
"bytes"
"crypto/md5"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strconv"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
@ -13,12 +22,6 @@ import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strconv"
)
type Pan123 struct{}
@ -67,6 +70,12 @@ func (driver Pan123) Items() []base.Item {
Required: true,
Default: "asc",
},
{
Name: "bool_1",
Label: "stream upload",
Type: base.TypeBool,
Description: "io stream upload (test)",
},
}
}
@ -108,10 +117,10 @@ func (driver Pan123) File(path string, account *model.Account) (*model.File, err
func (driver Pan123) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []Pan123File
var rawFiles []File
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]Pan123File)
rawFiles, _ = cache.([]File)
} else {
file, err := driver.File(path, account)
if err != nil {
@ -125,7 +134,7 @@ func (driver Pan123) Files(path string, account *model.Account) ([]model.File, e
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
files := make([]model.File, 0, len(rawFiles))
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
@ -167,7 +176,7 @@ func (driver Pan123) Link(args base.Args, account *model.Account) (*base.Link, e
return nil, err
}
u_ := fmt.Sprintf("https://%s%s", u.Host, u.Path)
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Get(u_)
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Head(u_)
if err != nil {
return nil, err
}
@ -175,6 +184,7 @@ func (driver Pan123) Link(args base.Args, account *model.Account) (*base.Link, e
link := base.Link{
Url: resp.Data.DownloadUrl,
}
log.Debugln("res code: ", res.StatusCode())
if res.StatusCode() == 302 {
link.Url = res.Header().Get("location")
}
@ -278,12 +288,13 @@ func (driver Pan123) Delete(path string, account *model.Account) error {
if err != nil {
return err
}
log.Debugln("delete 123 file: ", file)
data := base.Json{
"driveId": 0,
"operation": true,
"fileTrashInfoList": file,
"fileTrashInfoList": []File{*file},
}
_, err = driver.Request("https://www.123pan.com/api/file/trash",
_, err = driver.Request("https://www.123pan.com/b/api/file/trash",
base.Post, nil, nil, &data, nil, false, account)
return err
}
@ -299,46 +310,57 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
if !parentFile.IsDir() {
return base.ErrNotFolder
}
parentFileId, _ := strconv.Atoi(parentFile.Id)
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
const DEFAULT int64 = 10485760
var uploadFile io.Reader
h := md5.New()
_, err = io.Copy(h, tempFile)
if err != nil {
return err
if account.Bool1 && file.GetSize() > uint64(DEFAULT) {
// 只计算前10MIB
buf := bytes.NewBuffer(make([]byte, 0, DEFAULT))
if n, err := io.CopyN(io.MultiWriter(buf, h), file, DEFAULT); err != io.EOF && n == 0 {
return err
}
// 增加额外参数防止MD5碰撞
h.Write([]byte(file.Name))
num := make([]byte, 8)
binary.BigEndian.PutUint64(num, file.Size)
h.Write(num)
// 拼装
uploadFile = io.MultiReader(buf, file)
} else {
// 计算完整文件MD5
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
if _, err = io.Copy(io.MultiWriter(tempFile, h), file); err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
uploadFile = tempFile
}
etag := hex.EncodeToString(h.Sum(nil))
log.Debugln("md5:", etag)
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
data := base.Json{
"driveId": 0,
"duplicate": true,
"duplicate": 2, // 2->覆盖 1->重命名 0->默认
"etag": etag,
"fileName": file.GetFileName(),
"parentFileId": parentFileId,
"parentFileId": parentFile.Id,
"size": file.GetSize(),
"type": 0,
}
var resp UploadResp
_, err = driver.Request("https://www.123pan.com/api/file/upload_request",
base.Post, nil, nil, &data, &resp, false, account)
base.Post, map[string]string{"app-version": "1.1"}, nil, &data, &resp, false, account)
//res, err := driver.Post("https://www.123pan.com/api/file/upload_request", data, account)
if err != nil {
return err
@ -360,7 +382,7 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket,
Key: &resp.Data.Key,
Body: tempFile,
Body: uploadFile,
}
_, err = uploader.Upload(input)
if err != nil {

View File

@ -1,31 +1,33 @@
package _23
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
"path"
"time"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
)
type Pan123File struct {
FileName string `json:"FileName"`
Size int64 `json:"Size"`
UpdateAt *time.Time `json:"UpdateAt"`
FileId int64 `json:"FileId"`
Type int `json:"Type"`
Etag string `json:"Etag"`
S3KeyFlag string `json:"S3KeyFlag"`
type File struct {
FileName string `json:"FileName"`
Size int64 `json:"Size"`
UpdateAt *time.Time `json:"UpdateAt"`
FileId int64 `json:"FileId"`
Type int `json:"Type"`
Etag string `json:"Etag"`
S3KeyFlag string `json:"S3KeyFlag"`
DownloadUrl string `json:"DownloadUrl"`
}
func (f Pan123File) GetSize() uint64 {
func (f File) GetSize() uint64 {
return uint64(f.Size)
}
func (f Pan123File) GetName() string {
func (f File) GetName() string {
return f.FileName
}
func (f Pan123File) GetType() int {
func (f File) GetType() int {
if f.Type == 1 {
return conf.FOLDER
}
@ -47,8 +49,8 @@ type Pan123TokenResp struct {
type Pan123Files struct {
BaseResp
Data struct {
InfoList []Pan123File `json:"InfoList"`
Next string `json:"Next"`
InfoList []File `json:"InfoList"`
Next string `json:"Next"`
} `json:"data"`
}

View File

@ -270,7 +270,7 @@ func (driver Cloud139) Rename(src string, dst string, account *model.Account) er
"accountType": 1,
},
}
pathname = "/orchestration/personalCloud/catalog/v1.0/updateContentInfo"
pathname = "/orchestration/personalCloud/content/v1.0/updateContentInfo"
}
_, err = driver.Post(pathname, data, nil, account)
return err
@ -448,6 +448,7 @@ func (driver Cloud139) Upload(file *model.FileStream, account *model.Account) er
return err
}
log.Debugf("%+v", res)
res.Body.Close()
start += byteSize
}
return nil

View File

@ -7,6 +7,14 @@ import (
"encoding/hex"
"errors"
"fmt"
"io"
"math"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
@ -14,14 +22,6 @@ import (
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"io"
"math"
"net/http"
"net/http/cookiejar"
"regexp"
"strconv"
"strings"
"time"
)
var client189Map map[string]*resty.Client
@ -94,24 +94,20 @@ type LoginResp struct {
// Login refer to PanIndex
func (driver Cloud189) Login(account *model.Account) error {
client, ok := client189Map[account.Name]
if !ok {
//cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
client = resty.New()
//client.SetCookieJar(cookieJar)
client.SetTimeout(base.DefaultTimeout)
client.SetRetryCount(3)
client.SetHeader("Referer", "https://cloud.189.cn/")
}
// clear cookie
jar, _ := cookiejar.New(nil)
client.SetCookieJar(jar)
client := resty.New()
//client.SetCookieJar(cookieJar)
client.SetTimeout(base.DefaultTimeout)
client.SetRetryCount(3)
client.SetHeader("Referer", "https://cloud.189.cn/")
client.SetHeader("User-Agent", base.UserAgent)
url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
b := ""
lt := ""
ltText := regexp.MustCompile(`lt = "(.+?)"`)
var res *resty.Response
var err error
for i := 0; i < 3; i++ {
res, err := client.R().Get(url)
res, err = client.R().Get(url)
if err != nil {
return err
}
@ -129,7 +125,8 @@ func (driver Cloud189) Login(account *model.Account) error {
}
}
if lt == "" {
return errors.New("get page: " + b)
return fmt.Errorf("get page: %s \nstatus: %d \nrequest url: %s\nredirect url: %s",
b, res.StatusCode(), res.RawResponse.Request.URL.String(), res.Header().Get("location"))
}
captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
@ -169,7 +166,7 @@ func (driver Cloud189) Login(account *model.Account) error {
passwordRsa := RsaEncode([]byte(account.Password), jRsakey, true)
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
var loginResp LoginResp
res, err := client.R().
res, err = client.R().
SetHeaders(map[string]string{
"lt": lt,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
@ -575,6 +572,7 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
r, err := base.HttpClient.Do(req)
log.Debugf("%+v %+v", r, r.Request.Header)
r.Body.Close()
if err != nil {
return err
}

View File

@ -2,15 +2,16 @@ package _89
import (
"fmt"
"net/http"
"path/filepath"
"strings"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
"path/filepath"
"strings"
)
type Cloud189 struct{}
@ -179,23 +180,27 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}))
res, err := client.R().Get("https:" + resp.FileDownloadUrl)
res, err := client.R().SetHeader("User-Agent", base.UserAgent).Get("https:" + resp.FileDownloadUrl)
if err != nil {
return nil, err
}
log.Debugln(res.Status())
log.Debugln(res.String())
link := base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
//{Name: "Authorization", Value: ""},
},
}
log.Debugln("first url:", resp.FileDownloadUrl)
if res.StatusCode() == 302 {
link.Url = res.Header().Get("location")
res, err = client.R().Get(link.Url)
log.Debugln("second url:", link.Url)
_, _ = client.R().Get(link.Url)
if res.StatusCode() == 302 {
link.Url = res.Header().Get("location")
}
log.Debugln("third url:", link.Url)
} else {
link.Url = resp.FileDownloadUrl
}

View File

@ -607,6 +607,7 @@ func (driver Cloud189) CommonUpload(file *model.FileStream, parentFile *model.Fi
uploadData := uploadUrl.UploadUrls[fmt.Sprint("partNumber_", i)]
req, _ := http.NewRequest(http.MethodPut, uploadData.RequestURL, byteData)
req.Header.Set("User-Agent", "")
for k, v := range ParseHttpHeader(uploadData.RequestHeader) {
req.Header.Set(k, v)
}
@ -619,8 +620,10 @@ func (driver Cloud189) CommonUpload(file *model.FileStream, parentFile *model.Fi
}
if r.StatusCode != http.StatusOK {
data, _ := io.ReadAll(r.Body)
r.Body.Close()
return fmt.Errorf(string(data))
}
r.Body.Close()
}
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
@ -647,9 +650,10 @@ func (driver Cloud189) FastUpload(file *model.FileStream, parentFile *model.File
if err != nil {
return err
}
defer tempFile.Close()
defer os.Remove(tempFile.Name())
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
// 初始化上传
state := GetState(account)
@ -715,6 +719,7 @@ func (driver Cloud189) FastUpload(file *model.FileStream, parentFile *model.File
for i := 1; i <= count; i++ {
uploadData := uploadUrls.UploadUrls[fmt.Sprint("partNumber_", i)]
req, _ := http.NewRequest(http.MethodPut, uploadData.RequestURL, io.NewSectionReader(tempFile, int64(i-1)*DEFAULT, DEFAULT))
req.Header.Set("User-Agent", "")
for k, v := range ParseHttpHeader(uploadData.RequestHeader) {
req.Header.Set(k, v)
}
@ -727,8 +732,10 @@ func (driver Cloud189) FastUpload(file *model.FileStream, parentFile *model.File
}
if r.StatusCode != http.StatusOK {
data, _ := io.ReadAll(r.Body)
r.Body.Close()
return fmt.Errorf(string(data))
}
r.Body.Close()
}
}
@ -750,8 +757,10 @@ func (driver Cloud189) uploadFamily(file *model.FileStream, parentFile *model.Fi
return err
}
defer tempFile.Close()
defer os.Remove(tempFile.Name())
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
fileMd5 := md5.New()
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
@ -799,8 +808,10 @@ func (driver Cloud189) uploadPerson(file *model.FileStream, parentFile *model.Fi
return err
}
defer tempFile.Close()
defer os.Remove(tempFile.Name())
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
fileMd5 := md5.New()
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {

View File

@ -424,10 +424,17 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
}
if account.Bool1 {
buf := make([]byte, 1024)
n, _ := file.Read(buf[:])
reqBody["pre_hash"] = utils.GetSHA1Encode(string(buf[:n]))
file.File = io.NopCloser(io.MultiReader(bytes.NewReader(buf[:n]), file.File))
buf := bytes.NewBuffer(make([]byte, 0, 1024))
io.CopyN(buf, file, 1024)
reqBody["pre_hash"] = utils.GetSHA1Encode(buf.String())
// 把头部拼接回去
file.File = struct {
io.Reader
io.Closer
}{
Reader: io.MultiReader(buf, file.File),
Closer: file.File,
}
} else {
reqBody["content_hash_name"] = "none"
reqBody["proof_version"] = "v1"
@ -454,14 +461,16 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
return fmt.Errorf("%s", e.Message)
}
if e.Code == "PreHashMatched" && account.Bool1 {
if account.Bool1 && e.Code == "PreHashMatched" {
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer tempFile.Close()
defer os.Remove(tempFile.Name())
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
delete(reqBody, "pre_hash")
h := sha1.New()
@ -499,6 +508,7 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
return nil
}
// 秒传失败
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
return err
}
@ -515,6 +525,7 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
return err
}
log.Debugf("%+v", res)
res.Body.Close()
//res, err := base.BaseClient.R().
// SetHeader("Content-Type","").
// SetBody(byteData).Put(resp.PartInfoList[i].UploadUrl)

View File

@ -29,7 +29,7 @@ func (driver *Alist) Login(account *model.Account) error {
var resp BaseResp
_, err := base.RestyClient.R().SetResult(&resp).
SetHeader("Authorization", account.AccessToken).
Get(account.SiteUrl + "/api/admin/login")
Get(account.SiteUrl + "/api/admin/verify")
if err != nil {
return err
}

View File

@ -113,7 +113,7 @@ func (driver Alist) Link(args base.Args, account *model.Account) (*base.Link, er
flag = "p"
}
link := base.Link{}
link.Url = fmt.Sprintf("%s/%s%s?sign=%s", account.SiteUrl, flag, path, utils.SignWithToken(name, conf.Token))
link.Url = fmt.Sprintf("%s/%s%s?sign=%s", account.SiteUrl, flag, utils.Join(utils.ParsePath(account.RootFolder), path), utils.SignWithToken(name, conf.Token))
return &link, nil
}

View File

@ -8,7 +8,9 @@ import (
_ "github.com/Xhofe/alist/drivers/alidrive"
_ "github.com/Xhofe/alist/drivers/alist"
_ "github.com/Xhofe/alist/drivers/baidu"
_ "github.com/Xhofe/alist/drivers/baiduphoto"
"github.com/Xhofe/alist/drivers/base"
_ "github.com/Xhofe/alist/drivers/chaoxing"
_ "github.com/Xhofe/alist/drivers/ftp"
_ "github.com/Xhofe/alist/drivers/google"
_ "github.com/Xhofe/alist/drivers/lanzou"

259
drivers/baiduphoto/baidu.go Normal file
View File

@ -0,0 +1,259 @@
package baiduphoto
import (
"fmt"
"net/http"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
func (driver Baidu) RefreshToken(account *model.Account) error {
err := driver.refreshToken(account)
if err != nil && err == base.ErrEmptyToken {
err = driver.refreshToken(account)
}
if err != nil {
account.Status = err.Error()
}
_ = model.SaveAccount(account)
return err
}
func (driver Baidu) refreshToken(account *model.Account) error {
u := "https://openapi.baidu.com/oauth/2.0/token"
var resp base.TokenResp
var e TokenErrResp
_, err := base.RestyClient.R().
SetResult(&resp).
SetError(&e).
SetQueryParams(map[string]string{
"grant_type": "refresh_token",
"refresh_token": account.RefreshToken,
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
}).Get(u)
if err != nil {
return err
}
if e.ErrorMsg != "" {
return &e
}
if resp.RefreshToken == "" {
return base.ErrEmptyToken
}
account.Status = "work"
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
return nil
}
func (driver Baidu) Request(method string, url string, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
req := base.RestyClient.R()
req.SetQueryParam("access_token", account.AccessToken)
if callback != nil {
callback(req)
}
res, err := req.Execute(method, url)
if err != nil {
return nil, err
}
log.Debug(res.String())
var erron Erron
if err = utils.Json.Unmarshal(res.Body(), &erron); err != nil {
return nil, err
}
switch erron.Errno {
case 0:
return res, nil
case -6:
if err = driver.RefreshToken(account); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("errno: %d, refer to https://photo.baidu.com/union/doc", erron.Errno)
}
return driver.Request(method, url, callback, account)
}
// 获取所有根文件
func (driver Baidu) GetAllFile(account *model.Account) (files []File, err error) {
var cursor string
for {
var resp FileListResp
_, err = driver.Request(http.MethodGet, FILE_API_URL_V1+"/list", func(r *resty.Request) {
r.SetQueryParams(map[string]string{
"need_thumbnail": "1",
"need_filter_hidden": "0",
"cursor": cursor,
})
r.SetResult(&resp)
}, account)
if err != nil {
return
}
cursor = resp.Cursor
files = append(files, resp.List...)
if !resp.HasNextPage() {
return
}
}
}
// 获取所有相册
func (driver Baidu) GetAllAlbum(account *model.Account) (albums []Album, err error) {
var cursor string
for {
var resp AlbumListResp
_, err = driver.Request(http.MethodGet, ALBUM_API_URL+"/list", func(r *resty.Request) {
r.SetQueryParams(map[string]string{
"need_amount": "1",
"limit": "100",
"cursor": cursor,
})
r.SetResult(&resp)
}, account)
if err != nil {
return
}
if albums == nil {
albums = make([]Album, 0, resp.TotalCount)
}
cursor = resp.Cursor
albums = append(albums, resp.List...)
if !resp.HasNextPage() {
return
}
}
}
// 获取相册中所有文件
func (driver Baidu) GetAllAlbumFile(albumID string, account *model.Account) (files []AlbumFile, err error) {
var cursor string
for {
var resp AlbumFileListResp
_, err = driver.Request(http.MethodGet, ALBUM_API_URL+"/listfile", func(r *resty.Request) {
r.SetQueryParams(map[string]string{
"album_id": splitID(albumID)[0],
"need_amount": "1",
"limit": "1000",
"cursor": cursor,
})
r.SetResult(&resp)
}, account)
if err != nil {
return
}
if files == nil {
files = make([]AlbumFile, 0, resp.TotalCount)
}
cursor = resp.Cursor
files = append(files, resp.List...)
if !resp.HasNextPage() {
return
}
}
}
// 创建相册
func (driver Baidu) CreateAlbum(name string, account *model.Account) error {
if !checkName(name) {
return ErrNotSupportName
}
_, err := driver.Request(http.MethodPost, ALBUM_API_URL+"/create", func(r *resty.Request) {
r.SetQueryParams(map[string]string{
"title": name,
"tid": getTid(),
"source": "0",
})
}, account)
return err
}
// 相册改名
func (driver Baidu) SetAlbumName(albumID string, name string, account *model.Account) error {
if !checkName(name) {
return ErrNotSupportName
}
e := splitID(albumID)
_, err := driver.Request(http.MethodPost, ALBUM_API_URL+"/settitle", func(r *resty.Request) {
r.SetFormData(map[string]string{
"title": name,
"album_id": e[0],
"tid": e[1],
})
}, account)
return err
}
// 删除相册
func (driver Baidu) DeleteAlbum(albumID string, account *model.Account) error {
e := splitID(albumID)
_, err := driver.Request(http.MethodPost, ALBUM_API_URL+"/delete", func(r *resty.Request) {
r.SetFormData(map[string]string{
"album_id": e[0],
"tid": e[1],
"delete_origin_image": "0", // 是否删除原图 0 不删除
})
}, account)
return err
}
// 删除相册文件
func (driver Baidu) DeleteAlbumFile(albumID string, account *model.Account, fileIDs ...string) error {
e := splitID(albumID)
_, err := driver.Request(http.MethodPost, ALBUM_API_URL+"/delfile", func(r *resty.Request) {
r.SetFormData(map[string]string{
"album_id": e[0],
"tid": e[1],
"list": fsidsFormat(fileIDs...),
"del_origin": "0", // 是否删除原图 0 不删除 1 删除
})
}, account)
return err
}
// 增加相册文件
func (driver Baidu) AddAlbumFile(albumID string, account *model.Account, fileIDs ...string) error {
e := splitID(albumID)
_, err := driver.Request(http.MethodGet, ALBUM_API_URL+"/addfile", func(r *resty.Request) {
r.SetQueryParams(map[string]string{
"album_id": e[0],
"tid": e[1],
"list": fsidsFormatNotUk(fileIDs...),
})
}, account)
return err
}
// 保存相册文件为根文件
func (driver Baidu) CopyAlbumFile(albumID string, account *model.Account, fileID string) (*CopyFile, error) {
var resp CopyFileResp
e := splitID(fileID)
_, err := driver.Request(http.MethodPost, ALBUM_API_URL+"/copyfile", func(r *resty.Request) {
r.SetFormData(map[string]string{
"album_id": splitID(albumID)[0],
"tid": e[2],
"uk": e[1],
"list": fsidsFormatNotUk(fileID),
})
r.SetResult(&resp)
}, account)
if err != nil {
return nil, err
}
return &resp.List[0], err
}

View File

@ -0,0 +1,502 @@
package baiduphoto
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"math"
"net/http"
"os"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
)
type Baidu struct{}
func init() {
base.RegisterDriver(new(Baidu))
}
func (driver Baidu) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Baidu.Photo",
LocalSort: true,
}
}
func (driver Baidu) Items() []base.Item {
return []base.Item{
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "album_id",
Type: base.TypeString,
},
{
Name: "internal_type",
Label: "download api",
Type: base.TypeSelect,
Required: true,
Values: "file,album",
Default: "album",
},
{
Name: "client_id",
Label: "client id",
Default: "iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Default: "jXiFMOPVPCWlO2M5CwWQzffpNPaGTRBG",
Type: base.TypeString,
Required: true,
},
}
}
func (driver Baidu) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
return driver.RefreshToken(account)
}
func (driver Baidu) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := utils.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Baidu) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
return files, nil
}
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
if IsAlbum(file) {
albumFiles, err := driver.GetAllAlbumFile(file.Id, account)
if err != nil {
return nil, err
}
files = make([]model.File, 0, len(albumFiles))
for _, file := range albumFiles {
var thumbnail string
if len(file.Thumburl) > 0 {
thumbnail = file.Thumburl[0]
}
files = append(files, model.File{
Id: joinID(file.Fsid, file.Uk, file.Tid),
Name: file.Name(),
Size: file.Size,
Type: utils.GetFileType(utils.Ext(file.Path)),
Driver: driver.Config().Name,
UpdatedAt: getTime(file.Mtime),
Thumbnail: thumbnail,
})
}
} else if IsRoot(file) {
albums, err := driver.GetAllAlbum(account)
if err != nil {
return nil, err
}
files = make([]model.File, 0, len(albums))
for _, album := range albums {
files = append(files, model.File{
Id: joinID(album.AlbumID, album.Tid),
Name: album.Title,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: getTime(album.Mtime),
})
}
} else {
return nil, base.ErrNotSupport
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Baidu) Link(args base.Args, account *model.Account) (*base.Link, error) {
if account.InternalType == "file" {
return driver.LinkFile(args, account)
}
return driver.LinkAlbum(args, account)
}
func (driver Baidu) LinkAlbum(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
if !IsAlbumFile(file) {
return nil, base.ErrNotSupport
}
album, err := driver.File(utils.Dir(utils.ParsePath(args.Path)), account)
if err != nil {
return nil, err
}
e := splitID(file.Id)
res, err := base.NoRedirectClient.R().
SetQueryParams(map[string]string{
"access_token": account.AccessToken,
"album_id": splitID(album.Id)[0],
"tid": e[2],
"fsid": e[0],
"uk": e[1],
}).
Head(ALBUM_API_URL + "/download")
if err != nil {
return nil, err
}
return &base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
},
Url: res.Header().Get("location"),
}, nil
}
func (driver Baidu) LinkFile(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
if !IsAlbumFile(file) {
return nil, base.ErrNotSupport
}
album, err := driver.File(utils.Dir(utils.ParsePath(args.Path)), account)
if err != nil {
return nil, err
}
// 拷贝到根目录
cfile, err := driver.CopyAlbumFile(album.Id, account, file.Id)
if err != nil {
return nil, err
}
res, err := driver.Request(http.MethodGet, FILE_API_URL_V2+"/download", func(r *resty.Request) {
r.SetQueryParams(map[string]string{
"fsid": fmt.Sprint(cfile.Fsid),
})
}, account)
if err != nil {
return nil, err
}
return &base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
},
Url: utils.Json.Get(res.Body(), "dlink").ToString(),
}, nil
}
func (driver Baidu) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Baidu) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Baidu) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
if IsAlbum(srcFile) {
return driver.SetAlbumName(srcFile.Id, utils.Base(dst), account)
}
return base.ErrNotSupport
}
func (driver Baidu) MakeDir(path string, account *model.Account) error {
dir, name := utils.Split(path)
parentFile, err := driver.File(dir, account)
if err != nil {
return err
}
if !IsRoot(parentFile) {
return base.ErrNotSupport
}
return driver.CreateAlbum(name, account)
}
func (driver Baidu) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
if IsAlbumFile(srcFile) {
// 移动相册文件
dstAlbum, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
if !IsAlbum(dstAlbum) {
return base.ErrNotSupport
}
srcAlbum, err := driver.File(utils.Dir(src), account)
if err != nil {
return err
}
newFile, err := driver.CopyAlbumFile(srcAlbum.Id, account, srcFile.Id)
if err != nil {
return err
}
err = driver.DeleteAlbumFile(srcAlbum.Id, account, srcFile.Id)
if err != nil {
return err
}
err = driver.AddAlbumFile(dstAlbum.Id, account, joinID(newFile.Fsid))
if err != nil {
return err
}
return nil
}
return base.ErrNotSupport
}
func (driver Baidu) Copy(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
if IsAlbumFile(srcFile) {
// 复制相册文件
dstAlbum, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
if !IsAlbum(dstAlbum) {
return base.ErrNotSupport
}
srcAlbum, err := driver.File(utils.Dir(src), account)
if err != nil {
return err
}
newFile, err := driver.CopyAlbumFile(srcAlbum.Id, account, srcFile.Id)
if err != nil {
return err
}
err = driver.AddAlbumFile(dstAlbum.Id, account, joinID(newFile.Fsid))
if err != nil {
return err
}
return nil
}
return base.ErrNotSupport
}
func (driver Baidu) Delete(path string, account *model.Account) error {
file, err := driver.File(path, account)
if err != nil {
return err
}
// 删除相册
if IsAlbum(file) {
return driver.DeleteAlbum(file.Id, account)
}
// 生成相册文件
if IsAlbumFile(file) {
// 删除相册文件
album, err := driver.File(utils.Dir(path), account)
if err != nil {
return err
}
return driver.DeleteAlbumFile(album.Id, account, file.Id)
}
return base.ErrNotSupport
}
func (driver Baidu) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
if !IsAlbum(parentFile) {
return base.ErrNotSupport
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
tempFile.Close()
os.Remove(tempFile.Name())
}()
// 计算需要的数据
const DEFAULT = 1 << 22
const SliceSize = 1 << 18
count := int(math.Ceil(float64(file.Size) / float64(DEFAULT)))
sliceMD5List := make([]string, 0, count)
fileMd5 := md5.New()
sliceMd5 := md5.New()
for i := 1; i <= count; i++ {
if n, err := io.CopyN(io.MultiWriter(fileMd5, sliceMd5, tempFile), file, DEFAULT); err != io.EOF && n == 0 {
return err
}
sliceMD5List = append(sliceMD5List, hex.EncodeToString(sliceMd5.Sum(nil)))
sliceMd5.Reset()
}
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
return err
}
content_md5 := hex.EncodeToString(fileMd5.Sum(nil))
slice_md5 := content_md5
if file.GetSize() > SliceSize {
sliceData := make([]byte, SliceSize)
if _, err = io.ReadFull(tempFile, sliceData); err != nil {
return err
}
sliceMd5.Write(sliceData)
slice_md5 = hex.EncodeToString(sliceMd5.Sum(nil))
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
return err
}
}
// 开始执行上传
params := map[string]string{
"autoinit": "1",
"isdir": "0",
"rtype": "1",
"ctype": "11",
"path": utils.ParsePath(file.Name),
"size": fmt.Sprint(file.Size),
"slice-md5": slice_md5,
"content-md5": content_md5,
"block_list": MustString(utils.Json.MarshalToString(sliceMD5List)),
}
// 预上传
var precreateResp PrecreateResp
_, err = driver.Request(http.MethodPost, FILE_API_URL_V1+"/precreate", func(r *resty.Request) {
r.SetFormData(params)
r.SetResult(&precreateResp)
}, account)
if err != nil {
return err
}
switch precreateResp.ReturnType {
case 1: // 上传文件
uploadParams := map[string]string{
"method": "upload",
"path": params["path"],
"uploadid": precreateResp.UploadID,
}
for i := 0; i < count; i++ {
uploadParams["partseq"] = fmt.Sprint(i)
_, err = driver.Request(http.MethodPost, "https://c3.pcs.baidu.com/rest/2.0/pcs/superfile2", func(r *resty.Request) {
r.SetQueryParams(uploadParams)
r.SetFileReader("file", file.Name, io.LimitReader(tempFile, DEFAULT))
}, account)
if err != nil {
return err
}
}
fallthrough
case 2: // 创建文件
params["uploadid"] = precreateResp.UploadID
_, err = driver.Request(http.MethodPost, FILE_API_URL_V1+"/create", func(r *resty.Request) {
r.SetFormData(params)
r.SetResult(&precreateResp)
}, account)
if err != nil {
return err
}
fallthrough
case 3: // 增加到相册
err = driver.AddAlbumFile(parentFile.Id, account, joinID(precreateResp.Data.FsID))
if err != nil {
return err
}
}
return nil
}
var _ base.Driver = (*Baidu)(nil)

126
drivers/baiduphoto/types.go Normal file
View File

@ -0,0 +1,126 @@
package baiduphoto
import (
"fmt"
"github.com/Xhofe/alist/utils"
)
type TokenErrResp struct {
ErrorDescription string `json:"error_description"`
ErrorMsg string `json:"error"`
}
func (e *TokenErrResp) Error() string {
return fmt.Sprint(e.ErrorMsg, " : ", e.ErrorDescription)
}
type Erron struct {
Errno int `json:"errno"`
RequestID int `json:"request_id"`
}
type Page struct {
HasMore int `json:"has_more"`
Cursor string `json:"cursor"`
}
func (p Page) HasNextPage() bool {
return p.HasMore == 1
}
type (
FileListResp struct {
Page
List []File `json:"list"`
}
File struct {
Fsid int64 `json:"fsid"` // 文件ID
Path string `json:"path"` // 文件路径
Size int64 `json:"size"`
Ctime int64 `json:"ctime"` // 创建时间 s
Mtime int64 `json:"mtime"` // 修改时间 s
Thumburl []string `json:"thumburl"`
}
)
func (f File) Name() string {
return utils.Base(f.Path)
}
/*相册部分*/
type (
AlbumListResp struct {
Page
List []Album `json:"list"`
Reset int64 `json:"reset"`
TotalCount int64 `json:"total_count"`
}
Album struct {
AlbumID string `json:"album_id"`
Tid int64 `json:"tid"`
Title string `json:"title"`
JoinTime int64 `json:"join_time"`
CreateTime int64 `json:"create_time"`
Mtime int64 `json:"mtime"`
}
AlbumFileListResp struct {
Page
List []AlbumFile `json:"list"`
Reset int64 `json:"reset"`
TotalCount int64 `json:"total_count"`
}
AlbumFile struct {
File
Tid int64 `json:"tid"`
Uk int64 `json:"uk"`
}
)
type (
CopyFileResp struct {
List []CopyFile `json:"list"`
}
CopyFile struct {
FromFsid int64 `json:"from_fsid"` // 源ID
Fsid int64 `json:"fsid"` // 目标ID
Path string `json:"path"`
ShootTime int `json:"shoot_time"`
}
)
/*上传部分*/
type (
UploadFile struct {
FsID int64 `json:"fs_id"`
Size int64 `json:"size"`
Md5 string `json:"md5"`
ServerFilename string `json:"server_filename"`
Path string `json:"path"`
Ctime int `json:"ctime"`
Mtime int `json:"mtime"`
Isdir int `json:"isdir"`
Category int `json:"category"`
ServerMd5 string `json:"server_md5"`
ShootTime int `json:"shoot_time"`
}
CreateFileResp struct {
Data UploadFile `json:"data"`
}
PrecreateResp struct {
ReturnType int `json:"return_type"` //存在返回2 不存在返回1 已经保存3
//存在返回
CreateFileResp
//不存在返回
Path string `json:"path"`
UploadID string `json:"uploadid"`
Blocklist []int64 `json:"block_list"`
}
)

View File

@ -0,0 +1,84 @@
package baiduphoto
import (
"errors"
"fmt"
"math"
"math/rand"
"regexp"
"strings"
"time"
"github.com/Xhofe/alist/model"
)
const (
API_URL = "https://photo.baidu.com/youai"
ALBUM_API_URL = API_URL + "/album/v1"
FILE_API_URL_V1 = API_URL + "/file/v1"
FILE_API_URL_V2 = API_URL + "/file/v2"
)
var (
ErrNotSupportName = errors.New("only chinese and english, numbers and underscores are supported, and the length is no more than 20")
)
//Tid生成
func getTid() string {
return fmt.Sprintf("3%d%.0f", time.Now().Unix(), math.Floor(9000000*rand.Float64()+1000000))
}
// 检查名称
func checkName(name string) bool {
return len(name) <= 20 && regexp.MustCompile("[\u4e00-\u9fa5A-Za-z0-9_]").MatchString(name)
}
func getTime(t int64) *time.Time {
tm := time.Unix(t, 0)
return &tm
}
func fsidsFormat(ids ...string) string {
var buf []string
for _, id := range ids {
e := strings.Split(id, "|")
buf = append(buf, fmt.Sprintf("{\"fsid\":%s,\"uk\":%s}", e[0], e[1]))
}
return fmt.Sprintf("[%s]", strings.Join(buf, ","))
}
func fsidsFormatNotUk(ids ...string) string {
var buf []string
for _, id := range ids {
buf = append(buf, fmt.Sprintf("{\"fsid\":%s}", strings.Split(id, "|")[0]))
}
return fmt.Sprintf("[%s]", strings.Join(buf, ","))
}
func splitID(id string) []string {
return strings.SplitN(id, "|", 3)[:3]
}
func joinID(ids ...interface{}) string {
idsStr := make([]string, 0, len(ids))
for _, id := range ids {
idsStr = append(idsStr, fmt.Sprint(id))
}
return strings.Join(idsStr, "|")
}
func IsAlbum(file *model.File) bool {
return file.Id != "" && file.IsDir()
}
func IsAlbumFile(file *model.File) bool {
return file.Id != "" && !file.IsDir()
}
func IsRoot(file *model.File) bool {
return file.Id == "" && file.IsDir()
}
func MustString(str string, err error) string {
return str
}

61
drivers/base/base.go Normal file
View File

@ -0,0 +1,61 @@
package base
import "github.com/Xhofe/alist/model"
type Base struct{}
func (b Base) Config() DriverConfig {
return DriverConfig{}
}
func (b Base) Items() []Item {
return nil
}
func (b Base) Save(account *model.Account, old *model.Account) error {
return ErrNotImplement
}
func (b Base) File(path string, account *model.Account) (*model.File, error) {
return nil, ErrNotImplement
}
func (b Base) Files(path string, account *model.Account) ([]model.File, error) {
return nil, ErrNotImplement
}
func (b Base) Link(args Args, account *model.Account) (*Link, error) {
return nil, ErrNotImplement
}
func (b Base) Path(path string, account *model.Account) (*model.File, []model.File, error) {
return nil, nil, ErrNotImplement
}
func (b Base) Preview(path string, account *model.Account) (interface{}, error) {
return nil, ErrNotImplement
}
func (b Base) MakeDir(path string, account *model.Account) error {
return ErrNotImplement
}
func (b Base) Move(src string, dst string, account *model.Account) error {
return ErrNotImplement
}
func (b Base) Rename(src string, dst string, account *model.Account) error {
return ErrNotImplement
}
func (b Base) Copy(src string, dst string, account *model.Account) error {
return ErrNotImplement
}
func (b Base) Delete(path string, account *model.Account) error {
return ErrNotImplement
}
func (b Base) Upload(file *model.FileStream, account *model.Account) error {
return ErrNotImplement
}

View File

@ -5,6 +5,7 @@ import (
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"strings"
)
func KeyCache(path string, account *model.Account) string {
@ -15,6 +16,9 @@ func KeyCache(path string, account *model.Account) string {
}
func SaveSearchFiles[T model.ISearchFile](key string, obj []T) {
if strings.Contains(key, ".balance") {
return
}
err := model.DeleteSearchFilesByPath(key)
if err != nil {
log.Errorln("failed create search files", err)

View File

@ -0,0 +1,90 @@
package template
import (
"errors"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
"path"
)
func LoginOrRefreshToken(account *model.Account) error {
return nil
}
func Request(u string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeaders(map[string]string{
"Authorization": "Bearer" + account.AccessToken,
"Accept": "application/json, text/plain, */*",
})
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var e Resp
var err error
var res *resty.Response
req.SetError(&e)
switch method {
case base.Get:
res, err = req.Get(u)
case base.Post:
res, err = req.Post(u)
case base.Delete:
res, err = req.Delete(u)
case base.Patch:
res, err = req.Patch(u)
case base.Put:
res, err = req.Put(u)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
if e.Code >= 400 {
if e.Code == 401 {
err = LoginOrRefreshToken(account)
if err != nil {
return nil, err
}
return Request(u, method, headers, query, form, data, resp, account)
}
return nil, errors.New(e.Message)
}
return res.Body(), nil
}
func (driver ChaoxingDrive) formatFile(f *File) *model.File {
file := model.File{
Id: f.Id,
Name: f.FileName,
Size: f.Size,
Driver: driver.Config().Name,
UpdatedAt: f.UpdatedAt,
}
if f.File {
file.Type = utils.GetFileType(path.Ext(f.FileName))
} else {
file.Type = conf.FOLDER
}
return &file
}
func init() {
base.RegisterDriver(&ChaoxingDrive{})
}

261
drivers/chaoxing/driver.go Normal file
View File

@ -0,0 +1,261 @@
package template
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
"path/filepath"
"strings"
)
type ChaoxingDrive struct {
base.Base
}
func (driver ChaoxingDrive) Config() base.DriverConfig {
return base.DriverConfig{
Name: "ChaoXing",
OnlyProxy: true,
OnlyLocal: false,
ApiProxy: false,
NoNeedSetLink: false,
NoCors: false,
LocalSort: false,
}
}
func (driver ChaoxingDrive) Items() []base.Item {
// TODO fill need info
return []base.Item{
{
Name: "username",
Label: "手机号/超星号",
Type: base.TypeString,
Required: true,
},
{
Name: "password",
Label: "enc (加密后的密码)",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "根目录id",
Type: base.TypeString,
Default: "",
Required: false,
},
{
Name: "limit",
Label: "目录文件上限",
Type: base.TypeNumber,
Default: "50",
Required: false,
},
}
}
// Save 用户更新账号信息
func (driver ChaoxingDrive) Save(account *model.Account, old *model.Account) error {
// TODO test available or init
if old != nil {
conf.Cron.Remove(cron.EntryID(old.CronId))
}
if account == nil {
return nil
}
if account.Limit <= 0 {
account.Limit = 50
}
// 登录网盘并取得 Enc 字符串 enc保存在 account.AccessSecret 中; cookie 以键值对的形式保存在 account.AccessToken 中)
err := driver.GetEnc(account)
if err != nil {
return err
}
// 每隔一天重新获取一次 Enc(和
cronId, err := conf.Cron.AddFunc("@every 24h", func() {
id := account.ID
log.Debugf("ali account id: %d", id)
newAccount, err := model.GetAccountById(id)
log.Debugf("ali account: %+v", newAccount)
if err != nil {
return
}
err = driver.GetEnc(newAccount)
_ = model.SaveAccount(newAccount)
})
// 记录当前计划任务的id
account.CronId = int(cronId)
err = model.SaveAccount(account)
if err != nil {
return err
}
return nil
}
// File 通过用户路径获取到文件对象(主要是id号)
func (driver ChaoxingDrive) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: "",
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
// 将路径分割成 父文件夹 和 文件名
dir, name := filepath.Split(path)
// 等同于访问上级目录
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
// Files 列出所有文件
func (driver ChaoxingDrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
// 先从缓存中获取结果
cache, err := base.GetCache(path, account)
var fileList []model.File
if err == nil {
// 缓存命中,将目录信息保存到变量中
fileList = cache.([]model.File)
} else {
// 缓存未命中
//尝试获取上级目录的id递归地尝试从上级目录的缓存中读取信息直到获取到根目录为止
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
//列出上级目录的文件
fileList, err = driver.ListFile(file.Id, account)
if err != nil {
return nil, err
}
// 缓存数据
if len(fileList) > 0 {
_ = base.SetCache(path, fileList, account)
}
}
files := make([]model.File, 0)
for _, file := range fileList {
files = append(files, file)
}
return files, nil
}
var api_file_link = "https://pan-yz.chaoxing.com/download/downloadfile?fleid=%s&puid=1"
// Link 返回传入路径对应的文件的直链(本地除外),并包含需要携带的请求头
func (driver ChaoxingDrive) Link(args base.Args, account *model.Account) (*base.Link, error) {
// TODO get file link
file, e := driver.File(args.Path, account)
if e != nil {
return nil, e
}
url := fmt.Sprintf(api_file_link, file.Id[:strings.Index(file.Id, "_")])
//var resp base.Json
//var err Resp
// https://pan-yz.chaoxing.com/download/downloadfile?fleid=582519780768600064&puid=1
//_, e = chaoxingClient.R().SetResult(&resp).SetError(&err).
// SetHeader("Cookie", account.AccessToken).
// SetHeader("Referer", "https://pan-yz.chaoxing.com/").
// Get(url)
return &base.Link{
Headers: []base.Header{
{
Name: "Referer",
Value: "https://pan-yz.chaoxing.com/",
},
//{
// Name: "Cookie",
// Value: account.AccessToken,
//},
},
Url: url,
}, nil
}
// Path 通过调用上述的File与Files函数判断是文件还是文件夹并进行返回当是文件时附带文件的直链。
func (driver ChaoxingDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
// Optional function
//func (driver ChaoxingDrive) Preview(path string, account *model.Account) (interface{}, error) {
// //TODO preview interface if driver support
// return nil, base.ErrNotImplement
//}
func (driver ChaoxingDrive) MakeDir(path string, account *model.Account) error {
// 三个参数缺一不可,均不能写死
// https://pan-yz.chaoxing.com/opt/newfolder?parentId=205255741446029312&name=test&puid=54351295
return base.ErrNotImplement
}
func (driver ChaoxingDrive) Move(src string, dst string, account *model.Account) error {
// 注意 folderid 是由 {id}_{puid} 组成的(截取前两段即可)
// https://pan-yz.chaoxing.com/opt/moveres?folderid=762268051373813760_54351295&resids=762263362701209600,
return base.ErrNotImplement
}
func (driver ChaoxingDrive) Rename(src string, dst string, account *model.Account) error {
// resid 就是 fileid
// https://pan-yz.chaoxing.com/opt/rename?resid=762263362701209600&name=test.pdf&puid=54351295
return base.ErrNotImplement
}
// 超星网盘不支持复制
//func (driver ChaoxingDrive) Copy(src string, dst string, account *model.Account) error {
// //TODO copy file/dir
// return base.ErrNotImplement
//}
// Delete 这个函数太危险了,不想实现
func (driver ChaoxingDrive) Delete(path string, account *model.Account) error {
// 删除单个文件
// https://pan-yz.chaoxing.com/opt/delres?resids=762268051373813760&resourcetype=0&puids=54351295
// 删除多个文件
// https://pan-yz.chaoxing.com/opt/delres?resids=762269933587513344,762269920078848000,&resourcetype=0,0,&puids=54351295,54351295,
return base.ErrNotImplement
}
//func (driver ChaoxingDrive) Upload(file *model.FileStream, account *model.Account) error {
// //TODO upload file
// return base.ErrNotImplement
//}
var _ base.Driver = (*ChaoxingDrive)(nil)

27
drivers/chaoxing/types.go Normal file
View File

@ -0,0 +1,27 @@
package template
import "time"
// write all struct here
// TYPE_CX_FILE type=1 文件
var TYPE_CX_FILE = int64(1)
// TYPE_CX_FOLDER type=2 文件夹
var TYPE_CX_FOLDER = int64(2)
// TYPE_CX_SHARED_ROOT type=4 共享文件的根目录
var TYPE_CX_SHARED_ROOT = int64(4)
type Resp struct {
Code int `json:"code"`
Message string `json:"message"`
}
type File struct {
Id string `json:"id"`
FileName string `json:"file_name"`
Size int64 `json:"size"`
File bool `json:"file"`
UpdatedAt *time.Time `json:"updated_at"`
}

151
drivers/chaoxing/util.go Normal file
View File

@ -0,0 +1,151 @@
package template
import (
"encoding/json"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/bitly/go-simplejson"
"github.com/go-resty/resty/v2"
"regexp"
"strconv"
"strings"
"time"
)
// write util func here, such as cal sign
var chaoxingClient = resty.New()
var form_login_fmt = "fid=-1&uname=%s&password=%s&t=true&forbidotherlogin=0&validate=&doubleFactorLogin=0"
var api_list_root = "https://pan-yz.chaoxing.com/opt/listres?page=1&size=%d&enc=%s"
var api_list_file = "https://pan-yz.chaoxing.com/opt/listres?puid=%s&shareid=%s&parentId=%s&page=1&size=%d&enc=%s"
var api_list_shared_root = "https://pan-yz.chaoxing.com/opt/listres?puid=0&shareid=-1&parentId=0&page=1&size=%d&enc=%s"
var reg_enc_fmt = regexp.MustCompile("enc[ ]*=\"(.*)\"")
func (driver ChaoxingDrive) Login(account *model.Account) error {
url := "https://passport2.chaoxing.com/fanyalogin"
var resp base.Json
var err Resp
req_body := fmt.Sprintf(form_login_fmt, account.Username, account.Password)
loginReq, e := chaoxingClient.R().SetBody(req_body).
SetResult(&resp).SetError(&err).
SetHeader("Content-Length", strconv.FormatInt(int64(len(req_body)), 10)).
SetHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").
SetHeader("Host", "passport2.chaoxing.com").
SetHeader("User-Agent", " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36").
SetHeader("X-Requested-With", "XMLHttpRequest").
Post(url)
if e != nil {
return e
}
account.AccessToken = ""
for _, cookie := range loginReq.Cookies() {
//route=9d169c0aea4b7c89fa0d073417b5645f;
account.AccessToken += fmt.Sprintf("%s=%s; ", cookie.Name, cookie.Value)
}
return nil
}
func (driver ChaoxingDrive) GetEnc(account *model.Account) error {
url := "https://pan-yz.chaoxing.com/"
encReq, e := chaoxingClient.R().SetHeader("Cookie", account.AccessToken).Get(url)
if e != nil {
return e
}
//直接读取响应
sresp := string(encReq.Body())
submatch := reg_enc_fmt.FindAllStringSubmatch(sresp, 1)
//第一次获取失败,可能是未登录
if len(submatch) == 0 {
e = driver.Login(account)
if e != nil {
return e
}
encReq, e = chaoxingClient.R().SetHeader("Cookie", account.AccessToken).Get(url)
if e != nil {
return e
}
sresp = string(encReq.Body())
submatch = reg_enc_fmt.FindAllStringSubmatch(sresp, 1)
if len(submatch) == 0 {
account.Status = "failed"
return fmt.Errorf("登录失败,服务器返回信息:%s", sresp)
}
}
enc := submatch[0][1]
account.AccessSecret = enc
account.Status = "work"
return nil
}
func (driver ChaoxingDrive) ListFile(folder_id string, account *model.Account) ([]model.File, error) {
var url string
//按规则解析 id 号
folder_id_info := strings.Split(folder_id, "_")
if len(folder_id_info) == 3 {
folder_id = folder_id_info[0]
folder_puid := folder_id_info[1]
folder_shareid := folder_id_info[2]
if folder_id == "0" {
//访问“共享给我的文件夹”
url = fmt.Sprintf(api_list_shared_root, account.Limit, account.AccessSecret)
} else {
//访问其他目录
url = fmt.Sprintf(api_list_file, folder_puid, folder_shareid, folder_id, account.Limit, account.AccessSecret)
}
} else {
//id无法解析为三段应当是访问根目录此时为 ""
url = fmt.Sprintf(api_list_root, account.Limit, account.AccessSecret)
}
listFileReq, e := chaoxingClient.R().SetHeader("Cookie", account.AccessToken).Post(url)
resp, e := simplejson.NewJson(listFileReq.Body())
if e != nil || resp == nil {
return nil, e
}
files := make([]model.File, 0)
array, _ := resp.Get("list").Array()
for _, file := range array {
var f = model.File{}
file_ := file.(map[string]interface{})
//f.Id = file_["id"].(string)
f.Id = fmt.Sprintf("%s_%s_%s", file_["id"].(string), file_["puid"].(json.Number).String(), file_["shareid"].(json.Number).String())
f.Name = file_["name"].(string)
f_server_type, _ := file_["type"].(json.Number).Int64()
if f_server_type != TYPE_CX_SHARED_ROOT {
f.Size, _ = file_["filesize"].(json.Number).Int64()
}
// 为文件分配类型
switch f_server_type {
case TYPE_CX_FILE:
{
f.Type = utils.GetFileType(file_["suffix"].(string))
}
case TYPE_CX_FOLDER:
f.Type = conf.FOLDER
case TYPE_CX_SHARED_ROOT:
f.Type = conf.FOLDER
}
modifyDate, e := time.Parse("2006-01-02 15:04:05", file_["modifyDate"].(string))
if e == nil {
f.UpdatedAt = &modifyDate
}
f.Thumbnail = file_["thumbnail"].(string)
files = append(files, f)
}
return files, nil
}

View File

@ -227,13 +227,21 @@ func (driver FTP) Copy(src string, dst string, account *model.Account) error {
func (driver FTP) Delete(path string, account *model.Account) error {
path = utils.ParsePath(path)
file, err := driver.File(path, account)
if err != nil {
return err
}
realPath := utils.Join(account.RootFolder, path)
conn, err := driver.Login(account)
if err != nil {
return err
}
//defer func() { _ = conn.Quit() }()
err = conn.Delete(realPath)
if file.IsDir() {
err = conn.RemoveDirRecur(realPath)
} else {
err = conn.Delete(realPath)
}
return err
}

View File

@ -307,8 +307,10 @@ func (driver Onedrive) UploadBig(file *model.FileStream, account *model.Account)
res, err := base.HttpClient.Do(req)
if res.StatusCode != 201 && res.StatusCode != 202 {
data, _ := ioutil.ReadAll(res.Body)
res.Body.Close()
return errors.New(string(data))
}
res.Body.Close()
}
return nil
}

View File

@ -51,37 +51,37 @@ type SortResp struct {
type DownResp struct {
Resp
Data []struct {
Fid string `json:"fid"`
FileName string `json:"file_name"`
PdirFid string `json:"pdir_fid"`
Category int `json:"category"`
FileType int `json:"file_type"`
Size int `json:"size"`
FormatType string `json:"format_type"`
Status int `json:"status"`
Tags string `json:"tags"`
LCreatedAt int64 `json:"l_created_at"`
LUpdatedAt int64 `json:"l_updated_at"`
NameSpace int `json:"name_space"`
Thumbnail string `json:"thumbnail"`
DownloadUrl string `json:"download_url"`
Md5 string `json:"md5"`
RiskType int `json:"risk_type"`
RangeSize int `json:"range_size"`
BackupSign int `json:"backup_sign"`
ObjCategory string `json:"obj_category"`
Duration int `json:"duration"`
FileSource string `json:"file_source"`
File bool `json:"file"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
PrivateExtra struct {
} `json:"_private_extra"`
//Fid string `json:"fid"`
//FileName string `json:"file_name"`
//PdirFid string `json:"pdir_fid"`
//Category int `json:"category"`
//FileType int `json:"file_type"`
//Size int `json:"size"`
//FormatType string `json:"format_type"`
//Status int `json:"status"`
//Tags string `json:"tags"`
//LCreatedAt int64 `json:"l_created_at"`
//LUpdatedAt int64 `json:"l_updated_at"`
//NameSpace int `json:"name_space"`
//Thumbnail string `json:"thumbnail"`
DownloadUrl string `json:"download_url"`
//Md5 string `json:"md5"`
//RiskType int `json:"risk_type"`
//RangeSize int `json:"range_size"`
//BackupSign int `json:"backup_sign"`
//ObjCategory string `json:"obj_category"`
//Duration int `json:"duration"`
//FileSource string `json:"file_source"`
//File bool `json:"file"`
//CreatedAt int64 `json:"created_at"`
//UpdatedAt int64 `json:"updated_at"`
//PrivateExtra struct {
//} `json:"_private_extra"`
} `json:"data"`
Metadata struct {
Acc2 string `json:"acc2"`
Acc1 string `json:"acc1"`
} `json:"metadata"`
//Metadata struct {
// Acc2 string `json:"acc2"`
// Acc1 string `json:"acc1"`
//} `json:"metadata"`
}
type UpPreResp struct {

View File

@ -116,7 +116,7 @@ func (driver SFTP) Files(path string, account *model.Account) ([]model.File, err
if err != nil {
return nil, err
}
var files []model.File
files := make([]model.File, 0)
rawFiles, err := client.Files(remotePath)
if err != nil {
return nil, err
@ -195,7 +195,7 @@ func (driver SFTP) Delete(path string, account *model.Account) error {
if err != nil {
return err
}
return client.Remove(utils.Join(account.RootFolder, path))
return client.remove(utils.Join(account.RootFolder, path))
}
func (driver SFTP) Upload(file *model.FileStream, account *model.Account) error {

View File

@ -49,7 +49,7 @@ func (client *Client) Files(remotePath string) ([]os.FileInfo, error) {
return client.ReadDir(remotePath)
}
func (client *Client) Remove(remotePath string) error {
func (client *Client) remove(remotePath string) error {
f, err := client.Stat(remotePath)
if err != nil {
return nil

View File

@ -9,6 +9,7 @@ import (
)
type Template struct {
base.Base
}
func (driver Template) Config() base.DriverConfig {
@ -111,39 +112,40 @@ func (driver Template) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil
}
func (driver Template) Preview(path string, account *model.Account) (interface{}, error) {
//TODO preview interface if driver support
return nil, base.ErrNotImplement
}
func (driver Template) MakeDir(path string, account *model.Account) error {
//TODO make dir
return base.ErrNotImplement
}
func (driver Template) Move(src string, dst string, account *model.Account) error {
//TODO move file/dir
return base.ErrNotImplement
}
func (driver Template) Rename(src string, dst string, account *model.Account) error {
//TODO rename file/dir
return base.ErrNotImplement
}
func (driver Template) Copy(src string, dst string, account *model.Account) error {
//TODO copy file/dir
return base.ErrNotImplement
}
func (driver Template) Delete(path string, account *model.Account) error {
//TODO delete file/dir
return base.ErrNotImplement
}
func (driver Template) Upload(file *model.FileStream, account *model.Account) error {
//TODO upload file
return base.ErrNotImplement
}
// Optional function
//func (driver Template) Preview(path string, account *model.Account) (interface{}, error) {
// //TODO preview interface if driver support
// return nil, base.ErrNotImplement
//}
//
//func (driver Template) MakeDir(path string, account *model.Account) error {
// //TODO make dir
// return base.ErrNotImplement
//}
//
//func (driver Template) Move(src string, dst string, account *model.Account) error {
// //TODO move file/dir
// return base.ErrNotImplement
//}
//
//func (driver Template) Rename(src string, dst string, account *model.Account) error {
// //TODO rename file/dir
// return base.ErrNotImplement
//}
//
//func (driver Template) Copy(src string, dst string, account *model.Account) error {
// //TODO copy file/dir
// return base.ErrNotImplement
//}
//
//func (driver Template) Delete(path string, account *model.Account) error {
// //TODO delete file/dir
// return base.ErrNotImplement
//}
//
//func (driver Template) Upload(file *model.FileStream, account *model.Account) error {
// //TODO upload file
// return base.ErrNotImplement
//}
var _ base.Driver = (*Template)(nil)

View File

@ -1,21 +1,21 @@
package xunlei
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/go-resty/resty/v2"
"strings"
"time"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
)
type XunLeiCloud struct{}
@ -48,8 +48,61 @@ func (driver XunLeiCloud) Items() []base.Item {
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Name: "captcha_token",
Label: "verified captcha token",
Type: base.TypeString,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
},
{
Name: "client_version",
Label: "client version",
Default: "7.43.0.7998",
Type: base.TypeString,
Required: true,
},
{
Name: "client_id",
Label: "client id",
Default: "Xp6vsxz_7IYVw2BB",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Default: "Xp6vsy4tN9toTVdMSpomVdXpRmES",
Type: base.TypeString,
Required: true,
},
{
Name: "algorithms",
Label: "algorithms",
Default: "hrVPGbeqYPs+CIscj05VpAtjalzY5yjpvlMS8bEo,DrI0uTP,HHK0VXyMgY0xk2K0o,BBaXsExvL3GadmIacjWv7ISUJp3ifAwqbJumu,5toJ7ejB+bh1,5LsZTFAFjgvFvIl1URBgOAJ,QcJ5Ry+,hYgZVz8r7REROaCYfd9,zw6gXgkk/8TtGrmx6EGfekPESLnbZfDFwqR,gtSwLnMBa8h12nF3DU6+LwEQPHxd,fMG8TvtAYbCkxuEbIm0Xi/Lb7Z",
Type: base.TypeString,
Required: true,
},
{
Name: "package_name",
Label: "package name",
Default: "com.xunlei.downloadprovider",
Type: base.TypeString,
Required: true,
},
{
Name: "user_agent",
Label: "user agent",
Default: "ANDROID-com.xunlei.downloadprovider/7.43.0.7998 netWorkType/WIFI appid/40 deviceName/Samsung_Sm-g9810 deviceModel/SM-G9810 OSVersion/7.1.2 protocolVersion/301 platformVersion/10 sdkVersion/220200 Oauth2Client/0.9 (Linux 4_0_9+) (JAVA 0)",
Type: base.TypeString,
Required: false,
},
{
Name: "device_id",
Label: "device id",
Default: utils.GetMD5Encode(uuid.NewString()),
Type: base.TypeString,
Required: true,
},
@ -60,10 +113,18 @@ func (driver XunLeiCloud) Save(account *model.Account, old *model.Account) error
if account == nil {
return nil
}
state := GetState(account)
if state.isTokensExpires() {
return state.Login(account)
client := GetClient(account)
// 指定验证通过的captchaToken
if account.CaptchaToken != "" {
client.UpdateCaptchaToken(strings.TrimSpace(account.CaptchaToken))
account.CaptchaToken = ""
}
if client.token == "" {
return client.Login(account)
}
account.Status = "work"
model.SaveAccount(account)
return nil
@ -101,19 +162,23 @@ func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.Fi
files, _ := cache.([]model.File)
return files, nil
}
file, err := driver.File(path, account)
parentFile, err := driver.File(path, account)
if err != nil {
return nil, err
}
time.Sleep(time.Millisecond * 300)
files := make([]model.File, 0)
var pageToken string
for {
var fileList FileList
_, err = GetState(account).Request("GET", FILE_API_URL, func(r *resty.Request) {
_, err = GetClient(account).Request("GET", FILE_API_URL, func(r *resty.Request) {
r.SetQueryParams(map[string]string{
"parent_id": file.Id,
"page_token": fileList.NextPageToken,
"parent_id": parentFile.Id,
"page_token": pageToken,
"with_audit": "true",
"limit": "100",
"filters": `{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`,
})
r.SetResult(&fileList)
@ -129,6 +194,7 @@ func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.Fi
if fileList.NextPageToken == "" {
break
}
pageToken = fileList.NextPageToken
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
@ -162,8 +228,9 @@ func (driver XunLeiCloud) Link(args base.Args, account *model.Account) (*base.Li
return nil, base.ErrNotFile
}
var lFile Files
_, err = GetState(account).Request("GET", FILE_API_URL+"/{id}", func(r *resty.Request) {
r.SetPathParam("id", file.Id)
clinet := GetClient(account)
_, err = clinet.Request("GET", FILE_API_URL+"/{fileID}", func(r *resty.Request) {
r.SetPathParam("fileID", file.Id)
r.SetQueryParam("with_audit", "true")
r.SetResult(&lFile)
}, account)
@ -172,7 +239,7 @@ func (driver XunLeiCloud) Link(args base.Args, account *model.Account) (*base.Li
}
return &base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
{Name: "User-Agent", Value: clinet.userAgent},
},
Url: lFile.WebContentLink,
}, nil
@ -180,7 +247,6 @@ func (driver XunLeiCloud) Link(args base.Args, account *model.Account) (*base.Li
func (driver XunLeiCloud) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("xunlei path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
@ -199,6 +265,18 @@ func (driver XunLeiCloud) Preview(path string, account *model.Account) (interfac
return nil, base.ErrNotSupport
}
func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
_, err = GetClient(account).Request("PATCH", FILE_API_URL+"/{fileID}", func(r *resty.Request) {
r.SetPathParam("fileID", srcFile.Id)
r.SetBody(&base.Json{"name": filepath.Base(dst)})
}, account)
return err
}
func (driver XunLeiCloud) MakeDir(path string, account *model.Account) error {
dir, name := filepath.Split(path)
parentFile, err := driver.File(dir, account)
@ -208,7 +286,7 @@ func (driver XunLeiCloud) MakeDir(path string, account *model.Account) error {
if !parentFile.IsDir() {
return base.ErrNotFolder
}
_, err = GetState(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
_, err = GetClient(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
r.SetBody(&base.Json{
"kind": FOLDER,
"name": name,
@ -229,7 +307,7 @@ func (driver XunLeiCloud) Move(src string, dst string, account *model.Account) e
return err
}
_, err = GetState(account).Request("POST", FILE_API_URL+":batchMove", func(r *resty.Request) {
_, err = GetClient(account).Request("POST", FILE_API_URL+":batchMove", func(r *resty.Request) {
r.SetBody(&base.Json{
"to": base.Json{"parent_id": dstDirFile.Id},
"ids": []string{srcFile.Id},
@ -248,7 +326,7 @@ func (driver XunLeiCloud) Copy(src string, dst string, account *model.Account) e
if err != nil {
return err
}
_, err = GetState(account).Request("POST", FILE_API_URL+":batchCopy", func(r *resty.Request) {
_, err = GetClient(account).Request("POST", FILE_API_URL+":batchCopy", func(r *resty.Request) {
r.SetBody(&base.Json{
"to": base.Json{"parent_id": dstDirFile.Id},
"ids": []string{srcFile.Id},
@ -262,8 +340,8 @@ func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
if err != nil {
return err
}
_, err = GetState(account).Request("PATCH", FILE_API_URL+"/{id}/trash", func(r *resty.Request) {
r.SetPathParam("id", srcFile.Id)
_, err = GetClient(account).Request("PATCH", FILE_API_URL+"/{fileID}/trash", func(r *resty.Request) {
r.SetPathParam("fileID", srcFile.Id)
r.SetBody(&base.Json{})
}, account)
return err
@ -279,28 +357,35 @@ func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account)
return err
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
/*
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
if err != nil {
return err
}
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
if err != nil {
return err
}
tempFile.Close()
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
return err
}
*/
var resp UploadTaskResponse
_, err = GetState(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
_, err = GetClient(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
r.SetBody(&base.Json{
"kind": FILE,
"parent_id": parentFile.Id,
"name": file.Name,
"size": fmt.Sprint(file.Size),
"hash": gcid,
"size": file.Size,
"hash": "1CF254FBC456E1B012CD45C546636AA62CF8350E",
"upload_type": UPLOAD_TYPE_RESUMABLE,
})
r.SetResult(&resp)
@ -311,30 +396,24 @@ func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account)
param := resp.Resumable.Params
if resp.UploadType == UPLOAD_TYPE_RESUMABLE {
client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true))
param.Endpoint = strings.TrimLeft(param.Endpoint, param.Bucket+".")
s, err := session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(param.AccessKeyID, param.AccessKeySecret, param.SecurityToken),
Region: aws.String("xunlei"),
Endpoint: aws.String(param.Endpoint),
})
if err != nil {
return err
}
bucket, err := client.Bucket(param.Bucket)
if err != nil {
return err
}
return bucket.UploadFile(param.Key, tempFile.Name(), 1<<22, oss.Routines(3), oss.Checkpoint(true, ""), oss.Expires(param.Expiration))
_, err = s3manager.NewUploader(s).Upload(&s3manager.UploadInput{
Bucket: aws.String(param.Bucket),
Key: aws.String(param.Key),
Expires: aws.Time(param.Expiration),
Body: file,
})
return err
}
return nil
}
func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account) error {
_, dstName := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
_, err = GetState(account).Request("PATCH", FILE_API_URL+"/{id}", func(r *resty.Request) {
r.SetPathParam("id", srcFile.Id)
r.SetBody(&base.Json{"name": dstName})
}, account)
return err
}
var _ base.Driver = (*XunLeiCloud)(nil)

View File

@ -1,23 +1,35 @@
package xunlei
import (
"fmt"
"time"
)
type Erron struct {
Error string `json:"error"`
ErrorCode int64 `json:"error_code"`
ErrorMsg string `json:"error"`
ErrorDescription string `json:"error_description"`
// ErrorDetails interface{} `json:"error_details"`
}
func (e *Erron) HasError() bool {
return e.ErrorCode != 0 || e.ErrorMsg != "" || e.ErrorDescription != ""
}
func (e *Erron) Error() string {
return fmt.Sprintf("ErrorCode: %d ,Error: %s ,ErrorDescription: %s ", e.ErrorCode, e.ErrorMsg, e.ErrorDescription)
}
/*
* 验证码Token
**/
type CaptchaTokenRequest struct {
Action string `json:"action"`
CaptchaToken string `json:"captcha_token"`
ClientID string `json:"client_id"`
DeviceID string `json:"device_id"`
Meta map[string]string `json:"meta"`
//RedirectUri string `json:"redirect_uri"`
RedirectUri string `json:"redirect_uri"`
}
type CaptchaTokenResponse struct {
@ -26,6 +38,9 @@ type CaptchaTokenResponse struct {
Url string `json:"url"`
}
/*
* 登录
**/
type TokenResponse struct {
TokenType string `json:"token_type"`
AccessToken string `json:"access_token"`
@ -36,6 +51,10 @@ type TokenResponse struct {
UserID string `json:"user_id"`
}
func (t *TokenResponse) Token() string {
return fmt.Sprint(t.TokenType, " ", t.AccessToken)
}
type SignInRequest struct {
CaptchaToken string `json:"captcha_token"`
@ -46,6 +65,9 @@ type SignInRequest struct {
Password string `json:"password"`
}
/*
* 文件
**/
type FileList struct {
Kind string `json:"kind"`
NextPageToken string `json:"next_page_token"`
@ -116,6 +138,9 @@ type Files struct {
//Collection interface{} `json:"collection"`
}
/*
* 上传
**/
type UploadTaskResponse struct {
UploadType string `json:"upload_type"`

View File

@ -3,40 +3,10 @@ package xunlei
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"net/url"
"github.com/Xhofe/alist/utils"
)
const (
// 小米浏览器
CLIENT_ID = "X7MtiU0Gb5YqWv-6"
CLIENT_SECRET = "84MYEih3Eeu2HF4RrGce3Q"
CLIENT_VERSION = "5.1.0.51045"
ALG_VERSION = "1"
PACKAGE_NAME = "com.xunlei.xcloud.lib"
)
var Algorithms = []string{
"",
"BXza40wm+P4zw8rEFpHA",
"UfZLfKfYRmKTA0",
"OMBGVt/9Wcaln1XaBz",
"Jn217F4rk5FPPWyhoeV",
"w5OwkGo0pGpb0Xe/XZ5T3",
"5guM3DNiY4F78x49zQ97q75",
"QXwn4D2j884wJgrYXjGClM/IVrJX",
"NXBRosYvbHIm6w8vEB",
"2kZ8Ie1yW2ib4O2iAkNpJobP",
"11CoVJJQEc",
"xf3QWysVwnVsNv5DCxU+cgNT1rK",
"9eEfKkrqkfw",
"T78dnANexYRbiZy",
}
const (
API_URL = "https://api-pan.xunlei.com/drive/v1"
FILE_API_URL = API_URL + "/files"
@ -44,9 +14,8 @@ const (
)
const (
FOLDER = "drive#folder"
FILE = "drive#file"
FOLDER = "drive#folder"
FILE = "drive#file"
RESUMABLE = "drive#resumable"
)
@ -57,47 +26,32 @@ const (
UPLOAD_TYPE_URL = "UPLOAD_TYPE_URL"
)
func captchaSign(driverID string, time int64) string {
str := fmt.Sprint(CLIENT_ID, CLIENT_VERSION, PACKAGE_NAME, driverID, time)
for _, algorithm := range Algorithms {
str = utils.GetMD5Encode(fmt.Sprint(str, algorithm))
}
return fmt.Sprint(ALG_VERSION, ".", str)
}
func getAction(method string, u string) string {
c, _ := url.Parse(u)
return fmt.Sprint(method, ":", c.Path)
return method + ":" + c.Path
}
// 计算文件Gcid
func getGcid(r io.Reader, size int64) (string, error) {
calcBlockSize := func(j int64) int64 {
if j >= 0 && j <= 134217728 {
return 262144
if j >= 0 && j <= 0x8000000 {
return 0x40000
}
if j <= 134217728 || j > 268435456 {
if j <= 268435456 || j > 536870912 {
return 2097152
if j <= 0x8000000 || j > 0x10000000 {
if j <= 0x10000000 || j > 0x20000000 {
return 0x200000
}
return 1048576
return 0x100000
}
return 524288
return 0x80000
}
/*
calcBlockSize := func(j int64) int64 {
psize := int64(0x40000)
for j/psize > 0x200 {
psize <<= 1
}
return psize
}
*/
hash1 := sha1.New()
hash2 := sha1.New()
readSize := calcBlockSize(size)
for {
hash2.Reset()
if n, err := io.CopyN(hash2, r, calcBlockSize(size)); err != nil && n == 0 {
if n, err := io.CopyN(hash2, r, readSize); err != nil && n == 0 {
if err != io.EOF {
return "", err
}

View File

@ -3,6 +3,7 @@ package xunlei
import (
"fmt"
"net/http"
"strings"
"sync"
"time"
@ -13,281 +14,269 @@ import (
log "github.com/sirupsen/logrus"
)
var xunleiClient = resty.New().SetHeaders(map[string]string{"Accept": "application/json;charset=UTF-8"}).SetTimeout(base.DefaultTimeout)
// 缓存登录状态
var userClients sync.Map
// 一个账户只允许登陆一次
var userStateCache = struct {
func GetClient(account *model.Account) *Client {
if v, ok := userClients.Load(account.Username); ok {
return v.(*Client)
}
client := &Client{
Client: base.RestyClient,
clientID: account.ClientId,
clientSecret: account.ClientSecret,
clientVersion: account.ClientVersion,
packageName: account.PackageName,
algorithms: strings.Split(account.Algorithms, ","),
userAgent: account.UserAgent,
deviceID: account.DeviceId,
}
userClients.Store(account.Username, client)
return client
}
type Client struct {
*resty.Client
sync.Mutex
States map[string]*State
}{States: make(map[string]*State)}
func GetState(account *model.Account) *State {
userStateCache.Lock()
defer userStateCache.Unlock()
if v, ok := userStateCache.States[account.Username]; ok && v != nil {
return v
}
state := new(State).Init()
userStateCache.States[account.Username] = state
return state
clientID string
clientSecret string
clientVersion string
packageName string
algorithms []string
userAgent string
deviceID string
captchaToken string
token string
refreshToken string
userID string
}
type State struct {
sync.Mutex
captchaToken string
captchaTokenExpiresTime int64
tokenType string
accessToken string
refreshToken string
tokenExpiresTime int64 //Milli
userID string
}
func (s *State) init() *State {
s.captchaToken = ""
s.captchaTokenExpiresTime = 0
s.tokenType = ""
s.accessToken = ""
s.refreshToken = ""
s.tokenExpiresTime = 0
s.userID = "0"
return s
}
func (s *State) getToken(account *model.Account) (string, error) {
if s.isTokensExpires() {
if err := s.refreshToken_(account); err != nil {
return "", err
}
}
return fmt.Sprint(s.tokenType, " ", s.accessToken), nil
}
func (s *State) getCaptchaToken(action string, account *model.Account) (string, error) {
if s.isCaptchaTokenExpires() {
return s.newCaptchaToken(action, nil, account)
}
return s.captchaToken, nil
}
func (s *State) isCaptchaTokenExpires() bool {
return time.Now().UnixMilli() >= s.captchaTokenExpiresTime || s.captchaToken == "" || s.tokenType == ""
}
func (s *State) isTokensExpires() bool {
return time.Now().UnixMilli() >= s.tokenExpiresTime || s.accessToken == ""
}
func (s *State) newCaptchaToken(action string, meta map[string]string, account *model.Account) (string, error) {
ctime := time.Now().UnixMilli()
driverID := utils.GetMD5Encode(account.Username)
creq := CaptchaTokenRequest{
// 请求验证码token
func (c *Client) requestCaptchaToken(action string, meta map[string]string) error {
param := CaptchaTokenRequest{
Action: action,
CaptchaToken: s.captchaToken,
ClientID: CLIENT_ID,
DeviceID: driverID,
Meta: map[string]string{
"captcha_sign": captchaSign(driverID, ctime),
"client_version": CLIENT_VERSION,
"package_name": PACKAGE_NAME,
"timestamp": fmt.Sprint(ctime),
"user_id": s.userID,
},
}
for k, v := range meta {
creq.Meta[k] = v
CaptchaToken: c.captchaToken,
ClientID: c.clientID,
DeviceID: c.deviceID,
Meta: meta,
RedirectUri: "xlaccsdk01://xunlei.com/callback?state=harbor",
}
var e Erron
var resp CaptchaTokenResponse
_, err := xunleiClient.R().
SetBody(&creq).
_, err := c.Client.R().
SetBody(&param).
SetError(&e).
SetResult(&resp).
SetHeader("X-Device-Id", driverID).
SetQueryParam("client_id", CLIENT_ID).
SetHeader("X-Device-Id", c.deviceID).
SetQueryParam("client_id", c.clientID).
Post(XLUSER_API_URL + "/shield/captcha/init")
if err != nil {
return "", err
return err
}
if e.ErrorCode != 0 {
return "", fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
if e.HasError() {
return &e
}
if resp.Url != "" {
return "", fmt.Errorf("需要验证验证码")
return fmt.Errorf("need verify:%s", resp.Url)
}
s.captchaTokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000
s.captchaToken = resp.CaptchaToken
return s.captchaToken, nil
if resp.CaptchaToken == "" {
return fmt.Errorf("empty captchaToken")
}
c.captchaToken = resp.CaptchaToken
return nil
}
func (s *State) refreshToken_(account *model.Account) error {
var e Erron
var resp TokenResponse
_, err := xunleiClient.R().
SetResult(&resp).SetError(&e).
SetBody(&base.Json{
"grant_type": "refresh_token",
"refresh_token": s.refreshToken,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
}).
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).SetQueryParam("client_id", CLIENT_ID).
Post(XLUSER_API_URL + "/auth/token")
if err != nil {
return err
}
switch e.ErrorCode {
case 4122, 4121:
return s.login(account)
case 0:
s.tokenExpiresTime = (time.Now().UnixMilli() + resp.ExpiresIn*1000) - 30000
s.tokenType = resp.TokenType
s.accessToken = resp.AccessToken
s.refreshToken = resp.RefreshToken
s.userID = resp.UserID
return nil
default:
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
// 验证码签名
func (c *Client) captchaSign(time string) string {
str := fmt.Sprint(c.clientID, c.clientVersion, c.packageName, c.deviceID, time)
for _, algorithm := range c.algorithms {
str = utils.GetMD5Encode(str + algorithm)
}
return "1." + str
}
func (s *State) login(account *model.Account) error {
s.init()
ctime := time.Now().UnixMilli()
// 登录
func (c *Client) Login(account *model.Account) (err error) {
c.Lock()
defer c.Unlock()
defer func() {
if err != nil {
account.Status = err.Error()
} else {
account.Status = "work"
}
model.SaveAccount(account)
}()
meta := make(map[string]string)
if strings.Contains(account.Username, "@") {
meta["email"] = account.Username
} else if len(account.Username) >= 11 {
if !strings.Contains(account.Username, "+") {
account.Username = "+86 " + account.Username
}
meta["phone_number"] = account.Username
} else {
meta["username"] = account.Username
}
url := XLUSER_API_URL + "/auth/signin"
captchaToken, err := s.newCaptchaToken(getAction("POST", url), map[string]string{"username": account.Username}, account)
err = c.requestCaptchaToken(getAction(http.MethodPost, url), meta)
if err != nil {
return err
}
signReq := SignInRequest{
CaptchaToken: captchaToken,
ClientID: CLIENT_ID,
ClientSecret: CLIENT_SECRET,
Username: account.Username,
Password: account.Password,
}
var e Erron
var resp TokenResponse
_, err = xunleiClient.R().
_, err = c.Client.R().
SetResult(&resp).
SetError(&e).
SetBody(&signReq).
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).
SetQueryParam("client_id", CLIENT_ID).
SetBody(&SignInRequest{
CaptchaToken: c.captchaToken,
ClientID: c.clientID,
ClientSecret: c.clientSecret,
Username: account.Username,
Password: account.Password,
}).
SetHeader("X-Device-Id", c.deviceID).
SetQueryParam("client_id", c.clientID).
Post(url)
if err != nil {
return err
}
defer model.SaveAccount(account)
if e.ErrorCode != 0 {
account.Status = e.Error
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
if e.HasError() {
return &e
}
account.Status = "work"
s.tokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000
s.tokenType = resp.TokenType
s.accessToken = resp.AccessToken
s.refreshToken = resp.RefreshToken
s.userID = resp.UserID
if resp.RefreshToken == "" {
return base.ErrEmptyToken
}
c.token = resp.Token()
c.refreshToken = resp.RefreshToken
c.userID = resp.UserID
return nil
}
func (s *State) Request(method string, url string, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
s.Lock()
token, err := s.getToken(account)
if err != nil {
return nil, err
}
// 刷新验证码token
func (c *Client) RefreshCaptchaToken(action string) error {
c.Lock()
defer c.Unlock()
captchaToken, err := s.getCaptchaToken(getAction(method, url), account)
if err != nil {
return nil, err
timestamp := fmt.Sprint(time.Now().UnixMilli())
param := map[string]string{
"client_version": c.clientVersion,
"package_name": c.packageName,
"user_id": c.userID,
"captcha_sign": c.captchaSign(timestamp),
"timestamp": timestamp,
}
return c.requestCaptchaToken(action, param)
}
req := xunleiClient.R().
SetHeaders(map[string]string{
"X-Device-Id": utils.GetMD5Encode(account.Username),
"Authorization": token,
"X-Captcha-Token": captchaToken,
// 刷新token
func (c *Client) RefreshToken() error {
c.Lock()
defer c.Unlock()
var e Erron
var resp TokenResponse
_, err := c.Client.R().
SetError(&e).
SetResult(&resp).
SetBody(&base.Json{
"grant_type": "refresh_token",
"refresh_token": c.refreshToken,
"client_id": c.clientID,
"client_secret": c.clientSecret,
}).
SetQueryParam("client_id", CLIENT_ID)
callback(req)
s.Unlock()
var res *resty.Response
switch method {
case "GET":
res, err = req.Get(url)
case "POST":
res, err = req.Post(url)
case "DELETE":
res, err = req.Delete(url)
case "PATCH":
res, err = req.Patch(url)
case "PUT":
res, err = req.Put(url)
default:
return nil, base.ErrNotSupport
SetHeader("X-Device-Id", c.deviceID).
SetQueryParam("client_id", c.clientID).
Post(XLUSER_API_URL + "/auth/token")
if err != nil {
return err
}
if e.HasError() {
return &e
}
if resp.RefreshToken == "" {
return base.ErrEmptyToken
}
c.token = resp.TokenType + " " + resp.AccessToken
c.refreshToken = resp.RefreshToken
c.userID = resp.UserID
return nil
}
func (c *Client) Request(method string, url string, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
c.Lock()
req := c.Client.R().
SetHeaders(map[string]string{
"X-Device-Id": c.deviceID,
"Authorization": c.token,
"X-Captcha-Token": c.captchaToken,
"User-Agent": c.userAgent,
"client_id": c.clientID,
}).
SetQueryParam("client_id", c.clientID)
if callback != nil {
callback(req)
}
c.Unlock()
res, err := req.Execute(method, url)
if err != nil {
return nil, err
}
log.Debug(res.String())
var e Erron
err = utils.Json.Unmarshal(res.Body(), &e)
if err != nil {
if err = utils.Json.Unmarshal(res.Body(), &e); err != nil {
return nil, err
}
// 处理错误
switch e.ErrorCode {
case 9:
_, err = s.newCaptchaToken(getAction(method, url), nil, account)
if err != nil {
return nil, err
case 0:
return res, nil
case 4122, 4121, 10: // token过期
if err = c.RefreshToken(); err == nil {
break
}
fallthrough
case 4122, 4121: // Authorization expired
return s.Request(method, url, callback, account)
case 0:
if res.StatusCode() == http.StatusOK {
return res, nil
case 16: // 登录失效
if err = c.Login(account); err != nil {
return nil, err
}
case 9: // 验证码token过期
if err = c.RefreshCaptchaToken(getAction(method, url)); err != nil {
return nil, err
}
return nil, fmt.Errorf(res.String())
default:
return nil, fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
return nil, &e
}
return c.Request(method, url, callback, account)
}
func (s *State) Init() *State {
s.Lock()
defer s.Unlock()
return s.init()
}
func (c *Client) UpdateCaptchaToken(captchaToken string) bool {
c.Lock()
defer c.Unlock()
func (s *State) GetCaptchaToken(action string, account *model.Account) (string, error) {
s.Lock()
defer s.Unlock()
return s.getCaptchaToken(action, account)
}
func (s *State) GetToken(account *model.Account) (string, error) {
s.Lock()
defer s.Unlock()
return s.getToken(account)
}
func (s *State) Login(account *model.Account) error {
s.Lock()
defer s.Unlock()
return s.login(account)
if captchaToken != "" {
c.captchaToken = captchaToken
return true
}
return false
}

View File

@ -213,7 +213,8 @@ func (driver Yandex) Upload(file *model.FileStream, account *model.Account) erro
}
req.Header.Set("Content-Length", strconv.FormatUint(file.Size, 10))
req.Header.Set("Content-Type", "application/octet-stream")
_, err = base.HttpClient.Do(req)
res, err := base.HttpClient.Do(req)
res.Body.Close()
//res, err := base.RestyClient.R().
// SetHeader("Content-Length", strconv.FormatUint(file.Size, 10)).
// SetBody(file).Put(resp.Href)

16
go.mod
View File

@ -5,6 +5,7 @@ go 1.18
require (
github.com/aws/aws-sdk-go v1.27.0
github.com/caarlos0/env/v6 v6.9.1
github.com/casdoor/casdoor-go-sdk v0.7.0
github.com/eko/gocache/v2 v2.1.0
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.7.4
@ -24,18 +25,20 @@ require (
gorm.io/gorm v1.23.1
)
require github.com/kr/fs v0.1.0 // indirect
require (
github.com/golang-jwt/jwt/v4 v4.1.0 // indirect
github.com/kr/fs v0.1.0 // indirect
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect
google.golang.org/appengine v1.6.6 // indirect
)
require (
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/fatih/color v1.13.0
github.com/mattn/go-colorable v0.1.9 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
)
require (
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible
github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
@ -84,3 +87,8 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3 // indirect
)
require (
github.com/bitly/go-simplejson v0.5.0
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
)

255
go.sum
View File

@ -1,7 +1,39 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
@ -20,8 +52,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible h1:uuJIwCFhbZy+zdvLy5zrcIToPEQP0s5CFOZ0Zj03O/w=
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/allegro/bigcache/v2 v2.2.5 h1:mRc8r6GQjuJsmSKQNPsR5jQVXc8IJ1xsW5YXUYMLfqI=
github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@ -34,8 +64,6 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -46,6 +74,8 @@ github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQ
github.com/caarlos0/env/v6 v6.9.1 h1:zOkkjM0F6ltnQ5eBX6IPI41UP/KDGEK7rRPwGCNos8k=
github.com/caarlos0/env/v6 v6.9.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/casdoor/casdoor-go-sdk v0.7.0 h1:T+KkiLit0xA5aYHffyv26+qG6tcRen83CmOR41PUyME=
github.com/casdoor/casdoor-go-sdk v0.7.0/go.mod h1:MBed3ISHQfXTtoOCAk5T8l5lt4wFvsyynrw0awggydY=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
@ -54,8 +84,12 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
@ -91,7 +125,9 @@ github.com/eko/gocache/v2 v2.1.0/go.mod h1:u+EpYjCVsOpeqvDLzinOVLjLxwHJjO+NT4LS2
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -113,6 +149,9 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
@ -153,10 +192,20 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -164,11 +213,14 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@ -181,16 +233,30 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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 v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -223,6 +289,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
@ -294,6 +361,8 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@ -466,7 +535,6 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
@ -518,13 +586,20 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8=
@ -549,6 +624,7 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -561,13 +637,34 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -582,14 +679,31 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@ -598,12 +712,19 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrk
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -617,25 +738,43 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -652,7 +791,9 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -660,7 +801,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -670,16 +812,48 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -689,29 +863,87 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
@ -760,14 +992,25 @@ gorm.io/gorm v1.23.1 h1:aj5IlhDzEPsoIyOPtTRVI+SyaN1u6k613sbt4pwbxG0=
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3 h1:FErmbNIJruD5GT2oVEjtPn5Ar5+rcWJsC8/PPUkR0s4=
k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=

View File

@ -50,6 +50,13 @@ type Account struct {
CustomHost string `json:"custom_host"`
ExtractFolder string `json:"extract_folder"`
Bool1 bool `json:"bool_1"`
// for xunlei
Algorithms string `json:"algorithms"`
ClientVersion string `json:"client_version"`
PackageName string `json:"package_name"`
UserAgent string `json:"user_agent"`
CaptchaToken string `json:"captcha_token"`
DeviceId string `json:"device_id"`
}
var accountsMap = make(map[string]Account)

View File

@ -4,14 +4,9 @@ import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func Login(c *gin.Context) {
SuccessResp(c)
}
func CheckParent(path string, password string) bool {
meta, err := model.GetMetaByPath(path)
if err == nil {

View File

@ -26,7 +26,7 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *base.Link, file *model.
_ = link.Data.Close()
}()
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%s`, url.QueryEscape(file.Name)))
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.Name, url.QueryEscape(file.Name)))
w.Header().Set("Content-Length", strconv.FormatInt(file.Size, 10))
if link.Header != nil {
for h, val := range link.Header {
@ -57,7 +57,7 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *base.Link, file *model.
if err != nil {
return err
}
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%s`, url.QueryEscape(file.Name)))
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.Name, url.QueryEscape(file.Name)))
http.ServeContent(w, r, file.Name, fileStat.ModTime(), f)
return nil
} else {

View File

@ -0,0 +1,69 @@
package controllers
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
"github.com/gin-gonic/gin"
"net/http"
)
type OAuthReq struct {
Code string `json:"code"`
State string `json:"state"`
}
func Verify(c *gin.Context) {
token := c.GetHeader("Authorization")
if token != conf.Token {
common.ErrorStrResp(c, "Invalid token", 401)
return
}
common.SuccessResp(c)
}
func GetRedirectUrl(c *gin.Context) {
if !conf.GetBool("Enable Casdoor") {
common.ErrorStrResp(c, "Casdoor is not enabled", 1001)
return
}
redirectUri := generateRedirectUri(c.Request)
common.SuccessResp(c, utils.GetSignInUrl(redirectUri))
}
func generateRedirectUri(r *http.Request) string {
protocol := "http"
host := r.Host
if r.TLS != nil {
protocol = "https"
}
return fmt.Sprintf("%s://%s/@manage", protocol, host)
}
func OAuth(c *gin.Context) {
var req OAuthReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 401)
return
}
if req.Code == "" || req.State == "" {
common.ErrorStrResp(c, "Invalid code or state", 400)
return
}
token, err := casdoorsdk.GetOAuthToken(req.Code, req.State)
if err != nil {
common.ErrorResp(c, err, 401)
return
}
_, err = casdoorsdk.ParseJwtToken(token.AccessToken)
if err != nil {
common.ErrorResp(c, err, 401)
return
}
common.SuccessResp(c, conf.Token)
}

View File

@ -8,16 +8,6 @@ import (
func Auth(c *gin.Context) {
token := c.GetHeader("Authorization")
//password, err := model.GetSettingByKey("password")
//if err != nil {
// if err == gorm.ErrRecordNotFound {
// common.ErrorResp(c, fmt.Errorf("password not set"), 400)
// return
// }
// common.ErrorResp(c, err, 500)
// return
//}
//if token != utils.GetMD5Encode(password.Value) {
if token != conf.Token {
common.ErrorStrResp(c, "Invalid token", 401)
return

View File

@ -1,7 +1,6 @@
package server
import (
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/server/controllers"
"github.com/Xhofe/alist/server/controllers/file"
"github.com/Xhofe/alist/server/middlewares"
@ -35,8 +34,11 @@ func InitApiRouter(r *gin.Engine) {
admin := api.Group("/admin")
{
admin.GET("/verify", controllers.Verify)
admin.GET("/get_redirect_url", controllers.GetRedirectUrl)
admin.POST("/oauth", controllers.OAuth)
admin.Use(middlewares.Auth)
admin.Any("/login", common.Login)
admin.GET("/settings", controllers.GetSettings)
admin.POST("/settings", controllers.SaveSettings)
admin.DELETE("/setting", controllers.DeleteSetting)

View File

@ -1,14 +1,19 @@
package server
import (
"io/fs"
"os"
"io/ioutil"
"net/http"
"net/http/pprof"
"strings"
"path/filepath"
"github.com/Xhofe/alist/utils"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/public"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"io/fs"
"io/ioutil"
"net/http"
"strings"
)
func InitIndex() {
@ -17,37 +22,73 @@ func InitIndex() {
if !strings.Contains(conf.Conf.Assets, "/") {
conf.Conf.Assets = conf.DefaultConfig().Assets
}
index, err = public.Public.Open("index.html")
// if LocalAssets is local path, read local index.html.
if (utils.IsDir(filepath.Dir(conf.Conf.LocalAssets))) && utils.Exists(filepath.Join(conf.Conf.LocalAssets, "index.html")) {
index, err = os.Open(filepath.Join(conf.Conf.LocalAssets, "index.html"))
defer index.Close()
log.Infof("used local index.html")
} else {
index, err = public.Public.Open("index.html")
}
if err != nil {
log.Fatal(err.Error())
}
data, _ := ioutil.ReadAll(index)
data, _ := ioutil.ReadAll(index)
conf.RawIndexHtml = string(data)
// if exist SUB_FOLDER, replace it by config: SubFolder
subfolder := strings.Trim(conf.Conf.SubFolder, "/")
if strings.Contains(conf.RawIndexHtml, "SUB_FOLDER") {
conf.RawIndexHtml = strings.ReplaceAll(conf.RawIndexHtml, "SUB_FOLDER", subfolder)
}
cdnUrl := strings.ReplaceAll(conf.Conf.Assets, "$version", conf.WebTag)
cdnUrl = strings.TrimRight(cdnUrl, "/")
conf.RawIndexHtml = string(data)
if strings.Contains(conf.RawIndexHtml, "CDN_URL") {
conf.RawIndexHtml = strings.ReplaceAll(conf.RawIndexHtml, "/CDN_URL", cdnUrl)
conf.RawIndexHtml = strings.ReplaceAll(conf.RawIndexHtml, "assets/", cdnUrl+"/assets/")
if (cdnUrl == "") && (subfolder != "") {
conf.RawIndexHtml = strings.ReplaceAll(conf.RawIndexHtml, "CDN_URL", subfolder)
conf.RawIndexHtml = strings.ReplaceAll(conf.RawIndexHtml, "assets/", "/" + subfolder+"/assets/")
} else {
conf.RawIndexHtml = strings.ReplaceAll(conf.RawIndexHtml, "/CDN_URL", cdnUrl)
conf.RawIndexHtml = strings.ReplaceAll(conf.RawIndexHtml, "assets/", cdnUrl+"/assets/")
}
}
}
func Static(r *gin.Engine) {
var assets fs.FS
var pub fs.FS
var err error
var fsys fs.FS
//InitIndex()
assets, err := fs.Sub(public.Public, "assets")
// if LocalAssets is local path, read local assets.
fsys = os.DirFS(conf.Conf.LocalAssets)
if (utils.IsDir(filepath.Dir(conf.Conf.LocalAssets))) && utils.Exists(filepath.Join(conf.Conf.LocalAssets, "assets")) {
assets, err = fs.Sub(fsys, "assets")
log.Infof("used local assets")
} else {
assets, err = fs.Sub(public.Public, "assets")
}
if err != nil {
log.Fatalf("can't find assets folder")
}
pub, err := fs.Sub(public.Public, "public")
r.StaticFS("/assets/", http.FS(assets))
// if LocalAssets is local path, read local assets.
if (utils.IsDir(filepath.Dir(conf.Conf.LocalAssets))) && utils.Exists(filepath.Join(conf.Conf.LocalAssets, "public")) {
pub, err = fs.Sub(fsys, "public")
log.Infof("used local public")
} else {
pub, err = fs.Sub(public.Public, "public")
}
if err != nil {
log.Fatalf("can't find public folder")
}
r.StaticFS("/assets/", http.FS(assets))
r.StaticFS("/public/", http.FS(pub))
r.NoRoute(func(c *gin.Context) {
c.Header("Content-Type", "text/html")
c.Status(200)
if strings.HasPrefix(c.Request.URL.Path, "/@manage") {
_, _ = c.Writer.WriteString(conf.ManageHtml)
} else if strings.HasPrefix(c.Request.URL.Path, "/debug/pprof") && conf.Debug {
pprof.Index(c.Writer, c.Request)
} else {
_, _ = c.Writer.WriteString(conf.IndexHtml)
}

View File

@ -7,6 +7,14 @@ package webdav
import (
"context"
"fmt"
"mime"
"net"
"net/http"
"path"
"path/filepath"
"strings"
"time"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
@ -14,12 +22,6 @@ import (
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"net"
"net/http"
"path"
"path/filepath"
"strings"
"time"
)
type FileSystem struct{}
@ -197,8 +199,17 @@ func (fs *FileSystem) Upload(ctx context.Context, r *http.Request, rawPath strin
} else {
delete(upFileMap, rawPath)
}
mimeType := r.Header.Get("Content-Type")
if mimeType == "" || strings.ToLower(mimeType) == "application/octet-stream" {
mimeTypeTmp := mime.TypeByExtension(path.Ext(fileName))
if mimeTypeTmp != "" {
mimeType = mimeTypeTmp
} else {
mimeType = "application/octet-stream"
}
}
fileData := model.FileStream{
MIMEType: r.Header.Get("Content-Type"),
MIMEType: mimeType,
File: r.Body,
Size: fileSize,
Name: fileName,

9
utils/auth.go Normal file
View File

@ -0,0 +1,9 @@
package utils
import (
"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
)
func GetSignInUrl(redirectUri string) string {
return casdoorsdk.GetSigninUrl(redirectUri)
}