Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
c63e05983d | |||
678a982535 | |||
b2c02e6c5e | |||
7e05b0317f | |||
19f06dfaed | |||
1680a18578 | |||
e8f440ca5c | |||
7deff76f49 | |||
cd0afb9536 | |||
668a953cd8 | |||
8bfbaa74f6 | |||
3ccf5ee620 | |||
b44243c021 | |||
4ae81b5a79 | |||
189f4c19a5 | |||
92a3d74af5 | |||
bb73a10332 | |||
3baf1e8c7b | |||
fdb49f5fb4 | |||
2eedcc1626 | |||
6faecbd5d8 | |||
34ed05c62f | |||
ce83d6eb40 | |||
2271cb6c7c | |||
a42b30c96e | |||
ce25d16222 | |||
b392e093e3 | |||
0d5b7298db | |||
2063ebb74d | |||
0408d7ab5d | |||
d52451f9d2 | |||
ca9f77006a | |||
e8e8d925f3 | |||
623aab4c28 | |||
3bc81d471e | |||
dfddb5cfa1 | |||
80f5bde0cb | |||
9de072161e | |||
d08a7440bc | |||
7a4bb2496d | |||
f68ab40d26 | |||
796d490fb7 | |||
2964d5a6db | |||
90b57dacee | |||
6af17e2509 | |||
5193b2aa7d | |||
3f644f07db |
62
.all-contributorsrc
Normal file
62
.all-contributorsrc
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"CONTRIBUTORS.md"
|
||||||
|
],
|
||||||
|
"imageSize": 100,
|
||||||
|
"commit": false,
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"login": "Xhofe",
|
||||||
|
"name": "Xhofe",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/36558727?v=4",
|
||||||
|
"profile": "http://nn.ci",
|
||||||
|
"contributions": [
|
||||||
|
"code",
|
||||||
|
"ideas",
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "foxxorcat",
|
||||||
|
"name": "foxxorcat",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/95907542?v=4",
|
||||||
|
"profile": "https://github.com/foxxorcat",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "DaoChen6",
|
||||||
|
"name": "道辰",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/63903027?v=4",
|
||||||
|
"profile": "https://www.iflu.cf/",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "vg-land",
|
||||||
|
"name": "vg-land",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/16739728?v=4",
|
||||||
|
"profile": "https://vg-land.github.io/",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Clansty",
|
||||||
|
"name": "凌莞~(=^▽^=)",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/18461360?v=4",
|
||||||
|
"profile": "https://c5y.moe",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"contributorsPerLine": 7,
|
||||||
|
"projectName": "alist",
|
||||||
|
"projectOwner": "Xhofe",
|
||||||
|
"repoType": "github",
|
||||||
|
"repoHost": "https://github.com",
|
||||||
|
"skipCi": true
|
||||||
|
}
|
21
.github/config.yml
vendored
Normal file
21
.github/config.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Configuration for welcome - https://github.com/behaviorbot/welcome
|
||||||
|
|
||||||
|
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
|
||||||
|
|
||||||
|
# Comment to be posted to on first time issues
|
||||||
|
newIssueWelcomeComment: >
|
||||||
|
Thanks for opening your first issue here! Be sure to follow the issue template!
|
||||||
|
|
||||||
|
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
|
||||||
|
|
||||||
|
# Comment to be posted to on PRs from first time contributors in your repository
|
||||||
|
newPRWelcomeComment: >
|
||||||
|
Thanks for opening this pull request! Please check out our contributing guidelines.
|
||||||
|
|
||||||
|
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
|
||||||
|
|
||||||
|
# Comment to be posted to on pull requests merged by a first time user
|
||||||
|
firstPRMergeComment: >
|
||||||
|
Congrats on merging your first pull request! We here at behavior bot are proud of you!
|
||||||
|
|
||||||
|
# It is recommend to include as many gifs and emojis as possible
|
105
CONTRIBUTING.md
Normal file
105
CONTRIBUTING.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Setup your machine
|
||||||
|
|
||||||
|
`alist` is written in [Go](https://golang.org/) and [React](https://reactjs.org/).
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- [git](https://nodejs.org/zh-cn/)
|
||||||
|
- [Go 1.17+](https://golang.org/doc/install)
|
||||||
|
- [gcc](https://gcc.gnu.org/)
|
||||||
|
- [nodejs](https://nodejs.org/)
|
||||||
|
|
||||||
|
Clone `alist` and `alist-web` anywhere:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ git clone https://github.com/Xhofe/alist.git
|
||||||
|
$ git clone https://github.com/Xhofe/alist-web.git
|
||||||
|
```
|
||||||
|
You should switch to the dev branch for development.
|
||||||
|
|
||||||
|
## Preview your change
|
||||||
|
### backend
|
||||||
|
```shell
|
||||||
|
$ go run alist.go
|
||||||
|
```
|
||||||
|
### frontend
|
||||||
|
```shell
|
||||||
|
$ yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create a commit
|
||||||
|
|
||||||
|
Commit messages should be well formatted, and to make that "standardized".
|
||||||
|
|
||||||
|
### Commit Message Format
|
||||||
|
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
|
||||||
|
format that includes a **type**, a **scope** and a **subject**:
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
<BLANK LINE>
|
||||||
|
<body>
|
||||||
|
<BLANK LINE>
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
The **header** is mandatory and the **scope** of the header is optional.
|
||||||
|
|
||||||
|
Any line of the commit message cannot be longer than 100 characters! This allows the message to be easier
|
||||||
|
to read on GitHub as well as in various git tools.
|
||||||
|
|
||||||
|
### Revert
|
||||||
|
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header
|
||||||
|
of the reverted commit.
|
||||||
|
In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit
|
||||||
|
being reverted.
|
||||||
|
|
||||||
|
### Type
|
||||||
|
Must be one of the following:
|
||||||
|
|
||||||
|
* **feat**: A new feature
|
||||||
|
* **fix**: A bug fix
|
||||||
|
* **docs**: Documentation only changes
|
||||||
|
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
|
||||||
|
semi-colons, etc)
|
||||||
|
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||||
|
* **perf**: A code change that improves performance
|
||||||
|
* **test**: Adding missing or correcting existing tests
|
||||||
|
* **build**: Affects project builds or dependency modifications
|
||||||
|
* **revert**: Restore the previous commit
|
||||||
|
* **ci**: Continuous integration of related file modifications
|
||||||
|
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
|
||||||
|
generation
|
||||||
|
* **release**: Release a new version
|
||||||
|
* **workflow**: Workflow related file modification
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
The scope could be anything specifying place of the commit change. For example `$location`,
|
||||||
|
`$browser`, `$compile`, `$rootScope`, `ngHref`, `ngClick`, `ngView`, etc...
|
||||||
|
|
||||||
|
You can use `*` when the change affects more than a single scope.
|
||||||
|
|
||||||
|
### Subject
|
||||||
|
The subject contains succinct description of the change:
|
||||||
|
|
||||||
|
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||||
|
* don't capitalize first letter
|
||||||
|
* no dot (.) at the end
|
||||||
|
|
||||||
|
### Body
|
||||||
|
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
|
||||||
|
The body should include the motivation for the change and contrast this with previous behavior.
|
||||||
|
|
||||||
|
### Footer
|
||||||
|
The footer should contain any information about **Breaking Changes** and is also the place to
|
||||||
|
[reference GitHub issues that this commit closes](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||||
|
|
||||||
|
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines.
|
||||||
|
The rest of the commit message is then used for this.
|
||||||
|
|
||||||
|
## Submit a pull request
|
||||||
|
|
||||||
|
Push your branch to your `alist` fork and open a pull request against the
|
||||||
|
`dev` branch.
|
27
CONTRIBUTORS.md
Normal file
27
CONTRIBUTORS.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
|
[](#contributors-)
|
||||||
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
|
## Contributors ✨
|
||||||
|
|
||||||
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- 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>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- markdownlint-restore -->
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
|
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
11
README.md
11
README.md
@ -1,5 +1,5 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/assets@main/logo.svg"/></a>
|
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||||
<p><em>🗂️Another file list program that supports multiple storage, powered by Gin and React.</em></p>
|
<p><em>🗂️Another file list program that supports multiple storage, powered by Gin and React.</em></p>
|
||||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
|
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
|
||||||
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
|
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
|
||||||
@ -11,11 +11,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
English | [中文](./README_cn.md)
|
English | [中文](./README_cn.md) | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -39,6 +37,7 @@ English | [中文](./README_cn.md)
|
|||||||
- [x] [Yandex.Disk](https://disk.yandex.com/)
|
- [x] [Yandex.Disk](https://disk.yandex.com/)
|
||||||
- [x] [Baidu Disk](http://pan.baidu.com/)
|
- [x] [Baidu Disk](http://pan.baidu.com/)
|
||||||
- [x] [Quark](https://pan.quark.cn)
|
- [x] [Quark](https://pan.quark.cn)
|
||||||
|
- [x] [XunleiCloud](https://pan.xunlei.com/)
|
||||||
- [x] Easy to deploy and out-of-the-box
|
- [x] Easy to deploy and out-of-the-box
|
||||||
- [x] File preview (PDF, markdown, code, plain text, ...)
|
- [x] File preview (PDF, markdown, code, plain text, ...)
|
||||||
- [x] Image preview in gallery mode
|
- [x] Image preview in gallery mode
|
||||||
@ -64,7 +63,7 @@ Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions)
|
|||||||
|
|
||||||
Available at: <https://alist.nn.ci>.
|
Available at: <https://alist.nn.ci>.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Document
|
## Document
|
||||||
|
|
||||||
@ -76,4 +75,4 @@ The `AList` is open-source software licensed under the AGPL-3.0 license.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe)
|
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
|
10
README_cn.md
10
README_cn.md
@ -1,5 +1,5 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/assets@main/logo.svg"/></a>
|
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||||
<p><em>🗂️一个支持多存储的文件列表程序,使用 Gin 和 React 。</em></p>
|
<p><em>🗂️一个支持多存储的文件列表程序,使用 Gin 和 React 。</em></p>
|
||||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
|
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
|
||||||
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
|
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
|
||||||
@ -11,10 +11,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[English](./README.md) | 中文
|
[English](./README.md) | 中文 | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
||||||
|
|
||||||
## 支持
|
## 支持
|
||||||
|
|
||||||
@ -38,6 +37,7 @@
|
|||||||
- [x] [Yandex.Disk](https://disk.yandex.com/)
|
- [x] [Yandex.Disk](https://disk.yandex.com/)
|
||||||
- [x] [百度网盘](http://pan.baidu.com/)
|
- [x] [百度网盘](http://pan.baidu.com/)
|
||||||
- [x] [夸克网盘](https://pan.quark.cn)
|
- [x] [夸克网盘](https://pan.quark.cn)
|
||||||
|
- [x] [迅雷云盘](https://pan.xunlei.com/)
|
||||||
- [x] 部署方便,开箱即用
|
- [x] 部署方便,开箱即用
|
||||||
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
||||||
- [x] 画廊模式下的图像预览
|
- [x] 画廊模式下的图像预览
|
||||||
@ -63,7 +63,7 @@
|
|||||||
|
|
||||||
<https://alist.nn.ci>。
|
<https://alist.nn.ci>。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
@ -75,4 +75,4 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe)
|
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
|
15
build.sh
15
build.sh
@ -6,8 +6,11 @@ BUILD_WEB() {
|
|||||||
cd alist-web
|
cd alist-web
|
||||||
yarn
|
yarn
|
||||||
yarn build
|
yarn build
|
||||||
|
sed -i -e "s/\/CDN_URL\//\//g" dist/index.html
|
||||||
|
sed -i -e "s/assets/\/assets/g" dist/index.html
|
||||||
|
rm -f dist/index.html-e
|
||||||
mv dist ..
|
mv dist ..
|
||||||
cd ..
|
cd .. || exit
|
||||||
rm -rf alist-web
|
rm -rf alist-web
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,9 +74,9 @@ BUILD() {
|
|||||||
upx -9 ./alist-windows*
|
upx -9 ./alist-windows*
|
||||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||||
cat md5.txt
|
cat md5.txt
|
||||||
cd ..
|
cd .. || exit
|
||||||
fi
|
fi
|
||||||
cd ..
|
cd .. || exit
|
||||||
}
|
}
|
||||||
|
|
||||||
BUILD_MUSL() {
|
BUILD_MUSL() {
|
||||||
@ -113,12 +116,12 @@ BUILD_MUSL() {
|
|||||||
export CGO_ENABLED=1
|
export CGO_ENABLED=1
|
||||||
go build -o ./build/$appName-$os_arch -ldflags="$ldflags" -tags=jsoniter alist.go
|
go build -o ./build/$appName-$os_arch -ldflags="$ldflags" -tags=jsoniter alist.go
|
||||||
done
|
done
|
||||||
cd ..
|
cd .. || exit
|
||||||
}
|
}
|
||||||
|
|
||||||
RELEASE() {
|
RELEASE() {
|
||||||
cd alist/build
|
cd alist/build
|
||||||
upx -9 ./alist-linux*
|
upx -9 ./alist-linux-amd64
|
||||||
upx -9 ./alist-windows*
|
upx -9 ./alist-windows*
|
||||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||||
cat md5.txt
|
cat md5.txt
|
||||||
@ -133,7 +136,7 @@ RELEASE() {
|
|||||||
for i in $(find . -type f -name "$appName-windows-*"); do
|
for i in $(find . -type f -name "$appName-windows-*"); do
|
||||||
zip compress/$(echo $i | sed 's/\.[^.]*$//').zip "$i"
|
zip compress/$(echo $i | sed 's/\.[^.]*$//').zip "$i"
|
||||||
done
|
done
|
||||||
cd ../..
|
cd ../.. || exit
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ "$1" = "web" ]; then
|
if [ "$1" = "web" ]; then
|
||||||
|
@ -37,7 +37,7 @@ var (
|
|||||||
DProxyTypes = []string{"m3u8"}
|
DProxyTypes = []string{"m3u8"}
|
||||||
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
|
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"}
|
||||||
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav"}
|
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav", "opus"}
|
||||||
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
|
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -88,12 +88,6 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
|
|||||||
// return nil, ErrPathNotFound
|
// return nil, ErrPathNotFound
|
||||||
//}
|
//}
|
||||||
|
|
||||||
type Cloud189Down struct {
|
|
||||||
ResCode int `json:"res_code"`
|
|
||||||
ResMessage string `json:"res_message"`
|
|
||||||
FileDownloadUrl string `json:"fileDownloadUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoginResp struct {
|
type LoginResp struct {
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Result int `json:"result"`
|
Result int `json:"result"`
|
||||||
|
@ -6,7 +6,9 @@ import (
|
|||||||
"github.com/Xhofe/alist/drivers/base"
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
"github.com/Xhofe/alist/model"
|
"github.com/Xhofe/alist/model"
|
||||||
"github.com/Xhofe/alist/utils"
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -152,14 +154,15 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
|
|||||||
if file.Type == conf.FOLDER {
|
if file.Type == conf.FOLDER {
|
||||||
return nil, base.ErrNotFile
|
return nil, base.ErrNotFile
|
||||||
}
|
}
|
||||||
var resp Cloud189Down
|
var resp DownResp
|
||||||
u := "https://cloud.189.cn/api/open/file/getFileDownloadUrl.action"
|
u := "https://cloud.189.cn/api/portal/getFileInfo.action"
|
||||||
body, err := driver.Request(u, base.Get, map[string]string{
|
body, err := driver.Request(u, base.Get, map[string]string{
|
||||||
"fileId": file.Id,
|
"fileId": file.Id,
|
||||||
}, nil, nil, account)
|
}, nil, nil, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debugln(string(body))
|
||||||
err = utils.Json.Unmarshal(body, &resp)
|
err = utils.Json.Unmarshal(body, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -167,10 +170,19 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
|
|||||||
if resp.ResCode != 0 {
|
if resp.ResCode != 0 {
|
||||||
return nil, fmt.Errorf(resp.ResMessage)
|
return nil, fmt.Errorf(resp.ResMessage)
|
||||||
}
|
}
|
||||||
res, err := base.NoRedirectClient.R().Get(resp.FileDownloadUrl)
|
client, err := driver.getClient(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
client = resty.NewWithClient(client.GetClient()).SetRedirectPolicy(
|
||||||
|
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}))
|
||||||
|
res, err := client.R().Get("https:" + resp.FileDownloadUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debugln(res.Status())
|
||||||
link := base.Link{
|
link := base.Link{
|
||||||
Headers: []base.Header{
|
Headers: []base.Header{
|
||||||
{Name: "User-Agent", Value: base.UserAgent},
|
{Name: "User-Agent", Value: base.UserAgent},
|
||||||
@ -179,6 +191,10 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
|
|||||||
}
|
}
|
||||||
if res.StatusCode() == 302 {
|
if res.StatusCode() == 302 {
|
||||||
link.Url = res.Header().Get("location")
|
link.Url = res.Header().Get("location")
|
||||||
|
res, err = client.R().Get(link.Url)
|
||||||
|
if res.StatusCode() == 302 {
|
||||||
|
link.Url = res.Header().Get("location")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
link.Url = resp.FileDownloadUrl
|
link.Url = resp.FileDownloadUrl
|
||||||
}
|
}
|
||||||
|
@ -53,3 +53,15 @@ type Rsa struct {
|
|||||||
PkId string `json:"pkId"`
|
PkId string `json:"pkId"`
|
||||||
PubKey string `json:"pubKey"`
|
PubKey string `json:"pubKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Cloud189Down struct {
|
||||||
|
ResCode int `json:"res_code"`
|
||||||
|
ResMessage string `json:"res_message"`
|
||||||
|
FileDownloadUrl string `json:"fileDownloadUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownResp struct {
|
||||||
|
ResCode int `json:"res_code"`
|
||||||
|
ResMessage string `json:"res_message"`
|
||||||
|
FileDownloadUrl string `json:"downloadUrl"`
|
||||||
|
}
|
||||||
|
@ -2,18 +2,25 @@ package alidrive
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
"github.com/Xhofe/alist/model"
|
"github.com/Xhofe/alist/model"
|
||||||
"github.com/Xhofe/alist/utils"
|
"github.com/Xhofe/alist/utils"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AliDrive struct{}
|
type AliDrive struct{}
|
||||||
@ -376,15 +383,16 @@ type UploadResp struct {
|
|||||||
PartInfoList []struct {
|
PartInfoList []struct {
|
||||||
UploadUrl string `json:"upload_url"`
|
UploadUrl string `json:"upload_url"`
|
||||||
} `json:"part_info_list"`
|
} `json:"part_info_list"`
|
||||||
|
|
||||||
|
RapidUpload bool `json:"rapid_upload"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
|
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
|
||||||
if file == nil {
|
if file == nil {
|
||||||
return base.ErrEmptyFile
|
return base.ErrEmptyFile
|
||||||
}
|
}
|
||||||
const DEFAULT uint64 = 10485760
|
const DEFAULT int64 = 10485760
|
||||||
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||||
var finish uint64 = 0
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
parentFile, err := driver.File(file.ParentPath, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -392,32 +400,38 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
|
|||||||
if !parentFile.IsDir() {
|
if !parentFile.IsDir() {
|
||||||
return base.ErrNotFolder
|
return base.ErrNotFolder
|
||||||
}
|
}
|
||||||
var resp UploadResp
|
|
||||||
var e AliRespError
|
partInfoList := make([]base.Json, 0, count)
|
||||||
partInfoList := make([]base.Json, 0)
|
|
||||||
var i int64
|
var i int64
|
||||||
for i = 0; i < count; i++ {
|
for i = 0; i < count; i++ {
|
||||||
partInfoList = append(partInfoList, base.Json{
|
partInfoList = append(partInfoList, base.Json{
|
||||||
"part_number": i + 1,
|
"part_number": i + 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
buf := make([]byte, 1024)
|
||||||
SetBody(base.Json{
|
n, _ := file.Read(buf[:])
|
||||||
|
reqBody := base.Json{
|
||||||
"check_name_mode": "auto_rename",
|
"check_name_mode": "auto_rename",
|
||||||
// content_hash
|
|
||||||
"content_hash_name": "none",
|
|
||||||
"drive_id": account.DriveId,
|
"drive_id": account.DriveId,
|
||||||
"name": file.GetFileName(),
|
"name": file.GetFileName(),
|
||||||
"parent_file_id": parentFile.Id,
|
"parent_file_id": parentFile.Id,
|
||||||
"part_info_list": partInfoList,
|
"part_info_list": partInfoList,
|
||||||
//proof_code
|
|
||||||
"proof_version": "v1",
|
|
||||||
"size": file.GetSize(),
|
"size": file.GetSize(),
|
||||||
"type": "file",
|
"type": "file",
|
||||||
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders") // /v2/file/create_with_proof
|
"pre_hash": utils.GetSHA1Encode(string(buf[:n])),
|
||||||
//log.Debugf("%+v\n%+v", resp, e)
|
}
|
||||||
if e.Code != "" {
|
fileReader := io.MultiReader(bytes.NewReader(buf[:n]), file.File)
|
||||||
|
|
||||||
|
var resp UploadResp
|
||||||
|
var e AliRespError
|
||||||
|
client := aliClient.R().SetResult(&resp).SetError(&e).SetHeader("authorization", "Bearer\t"+account.AccessToken).SetBody(reqBody)
|
||||||
|
|
||||||
|
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Code != "" && e.Code != "PreHashMatched" {
|
||||||
if e.Code == "AccessTokenInvalid" {
|
if e.Code == "AccessTokenInvalid" {
|
||||||
err = driver.RefreshToken(account)
|
err = driver.RefreshToken(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -429,26 +443,59 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("%s", e.Message)
|
return fmt.Errorf("%s", e.Message)
|
||||||
}
|
}
|
||||||
var byteSize uint64
|
|
||||||
for i = 0; i < count; i++ {
|
if e.Code == "PreHashMatched" {
|
||||||
byteSize = file.GetSize() - finish
|
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||||
if DEFAULT < byteSize {
|
|
||||||
byteSize = DEFAULT
|
|
||||||
}
|
|
||||||
log.Debugf("%d,%d", byteSize, finish)
|
|
||||||
byteData := make([]byte, byteSize)
|
|
||||||
n, err := io.ReadFull(file, byteData)
|
|
||||||
//n, err := file.Read(byteData)
|
|
||||||
//byteData, err := io.ReadAll(file)
|
|
||||||
//n := len(byteData)
|
|
||||||
log.Debug(err, n)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
finish += uint64(n)
|
defer tempFile.Close()
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, bytes.NewBuffer(byteData))
|
delete(reqBody, "pre_hash")
|
||||||
|
h := sha1.New()
|
||||||
|
if _, err = io.Copy(tempFile, io.TeeReader(fileReader, h)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil))
|
||||||
|
reqBody["content_hash_name"] = "sha1"
|
||||||
|
reqBody["proof_version"] = "v1"
|
||||||
|
|
||||||
|
/*
|
||||||
|
js 隐性转换太坑不知道有没有bug
|
||||||
|
var n = e.access_token,
|
||||||
|
r = new BigNumber('0x'.concat(md5(n).slice(0, 16))),
|
||||||
|
i = new BigNumber(t.file.size),
|
||||||
|
o = i ? r.mod(i) : new gt.BigNumber(0);
|
||||||
|
(t.file.slice(o.toNumber(), Math.min(o.plus(8).toNumber(), t.file.size)))
|
||||||
|
*/
|
||||||
|
r, _ := new(big.Int).SetString(utils.GetMD5Encode(account.AccessToken)[:16], 16)
|
||||||
|
i := new(big.Int).SetUint64(file.Size)
|
||||||
|
o := r.Mod(r, i)
|
||||||
|
n, _ = io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
|
||||||
|
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
||||||
|
|
||||||
|
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Code != "" && e.Code != "PreHashMatched" {
|
||||||
|
return fmt.Errorf("%s", e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.RapidUpload {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileReader = tempFile
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 0; i < count; i++ {
|
||||||
|
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, io.LimitReader(fileReader, DEFAULT))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -473,6 +520,9 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
|
|||||||
"file_id": resp.FileId,
|
"file_id": resp.FileId,
|
||||||
"upload_id": resp.UploadId,
|
"upload_id": resp.UploadId,
|
||||||
}).Post("https://api.aliyundrive.com/v2/file/complete")
|
}).Post("https://api.aliyundrive.com/v2/file/complete")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if e.Code != "" {
|
if e.Code != "" {
|
||||||
//if e.Code == "AccessTokenInvalid" {
|
//if e.Code == "AccessTokenInvalid" {
|
||||||
// err = driver.RefreshToken(account)
|
// err = driver.RefreshToken(account)
|
||||||
|
@ -19,7 +19,9 @@ import (
|
|||||||
_ "github.com/Xhofe/alist/drivers/s3"
|
_ "github.com/Xhofe/alist/drivers/s3"
|
||||||
_ "github.com/Xhofe/alist/drivers/shandian"
|
_ "github.com/Xhofe/alist/drivers/shandian"
|
||||||
_ "github.com/Xhofe/alist/drivers/teambition"
|
_ "github.com/Xhofe/alist/drivers/teambition"
|
||||||
|
_ "github.com/Xhofe/alist/drivers/uss"
|
||||||
_ "github.com/Xhofe/alist/drivers/webdav"
|
_ "github.com/Xhofe/alist/drivers/webdav"
|
||||||
|
_ "github.com/Xhofe/alist/drivers/xunlei"
|
||||||
_ "github.com/Xhofe/alist/drivers/yandex"
|
_ "github.com/Xhofe/alist/drivers/yandex"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -153,6 +153,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
pg++
|
pg++
|
||||||
|
time.Sleep(time.Second)
|
||||||
files = append(files, resp.Text...)
|
files = append(files, resp.Text...)
|
||||||
}
|
}
|
||||||
return files, nil
|
return files, nil
|
||||||
|
@ -175,7 +175,7 @@ func (driver Onedrive) Link(args base.Args, account *model.Account) (*base.Link,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if file.File.MimeType == "" {
|
if file.File == nil {
|
||||||
return nil, base.ErrNotFile
|
return nil, base.ErrNotFile
|
||||||
}
|
}
|
||||||
link := base.Link{
|
link := base.Link{
|
||||||
|
@ -120,7 +120,7 @@ type OneFile struct {
|
|||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
LastModifiedDateTime *time.Time `json:"lastModifiedDateTime"`
|
LastModifiedDateTime *time.Time `json:"lastModifiedDateTime"`
|
||||||
Url string `json:"@microsoft.graph.downloadUrl"`
|
Url string `json:"@microsoft.graph.downloadUrl"`
|
||||||
File struct {
|
File *struct {
|
||||||
MimeType string `json:"mimeType"`
|
MimeType string `json:"mimeType"`
|
||||||
} `json:"file"`
|
} `json:"file"`
|
||||||
Thumbnails []struct {
|
Thumbnails []struct {
|
||||||
@ -157,7 +157,7 @@ func (driver Onedrive) FormatFile(file *OneFile) *model.File {
|
|||||||
if len(file.Thumbnails) > 0 {
|
if len(file.Thumbnails) > 0 {
|
||||||
f.Thumbnail = file.Thumbnails[0].Medium.Url
|
f.Thumbnail = file.Thumbnails[0].Medium.Url
|
||||||
}
|
}
|
||||||
if file.File.MimeType == "" {
|
if file.File == nil {
|
||||||
f.Type = conf.FOLDER
|
f.Type = conf.FOLDER
|
||||||
} else {
|
} else {
|
||||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
||||||
|
149
drivers/template/driver.go
Normal file
149
drivers/template/driver.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Template struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Config() base.DriverConfig {
|
||||||
|
return base.DriverConfig{
|
||||||
|
Name: "Template",
|
||||||
|
OnlyProxy: false,
|
||||||
|
OnlyLocal: false,
|
||||||
|
ApiProxy: false,
|
||||||
|
NoNeedSetLink: false,
|
||||||
|
NoCors: false,
|
||||||
|
LocalSort: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Items() []base.Item {
|
||||||
|
// TODO fill need info
|
||||||
|
return []base.Item{
|
||||||
|
{
|
||||||
|
Name: "refresh_token",
|
||||||
|
Label: "refresh token",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "root_folder",
|
||||||
|
Label: "root folder path",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Default: "/",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Save(account *model.Account, old *model.Account) error {
|
||||||
|
// TODO test available or init
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) 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 := 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Files(path string, account *model.Account) ([]model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
cache, err := base.GetCache(path, account)
|
||||||
|
if err == nil {
|
||||||
|
files, _ := cache.([]model.File)
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
var files []model.File
|
||||||
|
// TODO get files
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(files) > 0 {
|
||||||
|
_ = base.SetCache(path, files, account)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||||
|
// TODO get file link
|
||||||
|
return nil, base.ErrNotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) 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 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)
|
90
drivers/template/template.go
Normal file
90
drivers/template/template.go
Normal 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 Template) 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(&Template{})
|
||||||
|
}
|
18
drivers/template/types.go
Normal file
18
drivers/template/types.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// write all struct here
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
3
drivers/template/util.go
Normal file
3
drivers/template/util.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
// write util func here, such as cal sign
|
282
drivers/xunlei/driver.go
Normal file
282
drivers/xunlei/driver.go
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
package xunlei
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XunLeiCloud struct{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RegisterDriver(new(XunLeiCloud))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) Config() base.DriverConfig {
|
||||||
|
return base.DriverConfig{
|
||||||
|
Name: "XunLeiCloud",
|
||||||
|
LocalSort: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) Items() []base.Item {
|
||||||
|
return []base.Item{
|
||||||
|
{
|
||||||
|
Name: "username",
|
||||||
|
Label: "username",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "account username/phone number",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "password",
|
||||||
|
Label: "password",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "account password",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "root_folder",
|
||||||
|
Label: "root folder file_id",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) Save(account *model.Account, old *model.Account) error {
|
||||||
|
if account == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return GetState(account).Login(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) 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 := 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.File, error) {
|
||||||
|
cache, err := base.GetCache(path, account)
|
||||||
|
if err == nil {
|
||||||
|
files, _ := cache.([]model.File)
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
file, err := driver.File(utils.ParsePath(path), account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileList FileList
|
||||||
|
u := fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files?parent_id=%s&page_token=%s&with_audit=true&filters=%s", file.Id, "", url.QueryEscape(`{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`))
|
||||||
|
if err = GetState(account).Request("GET", u, nil, &fileList, account); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
files := make([]model.File, 0, len(fileList.Files))
|
||||||
|
for _, file := range fileList.Files {
|
||||||
|
if file.Kind == FOLDER || (file.Kind == FILE && file.Audit.Status == "STATUS_OK") {
|
||||||
|
files = append(files, *driver.formatFile(&file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(files) > 0 {
|
||||||
|
_ = base.SetCache(path, files, account)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) formatFile(file *Files) *model.File {
|
||||||
|
size, _ := strconv.ParseInt(file.Size, 10, 64)
|
||||||
|
tp := conf.FOLDER
|
||||||
|
if file.Kind == FILE {
|
||||||
|
tp = utils.GetFileType(file.FileExtension)
|
||||||
|
}
|
||||||
|
return &model.File{
|
||||||
|
Id: file.ID,
|
||||||
|
Name: file.Name,
|
||||||
|
Size: size,
|
||||||
|
Type: tp,
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: file.CreatedTime,
|
||||||
|
Thumbnail: file.ThumbnailLink,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||||
|
file, err := driver.File(utils.ParsePath(args.Path), account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if file.Type == conf.FOLDER {
|
||||||
|
return nil, base.ErrNotFile
|
||||||
|
}
|
||||||
|
var lFile Files
|
||||||
|
if err = GetState(account).Request("GET", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s?&with_audit=true", file.Id), nil, &lFile, account); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &base.Link{
|
||||||
|
Headers: []base.Header{
|
||||||
|
{Name: "User-Agent", Value: base.UserAgent},
|
||||||
|
},
|
||||||
|
Url: lFile.WebContentLink,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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 XunLeiCloud) Preview(path string, account *model.Account) (interface{}, error) {
|
||||||
|
return nil, base.ErrNotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) MakeDir(path string, account *model.Account) error {
|
||||||
|
dir, name := filepath.Split(path)
|
||||||
|
parentFile, err := driver.File(dir, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !parentFile.IsDir() {
|
||||||
|
return base.ErrNotFolder
|
||||||
|
}
|
||||||
|
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files", &base.Json{"kind": FOLDER, "name": name, "parent_id": parentFile.Id}, nil, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) Move(src string, dst string, account *model.Account) error {
|
||||||
|
srcFile, err := driver.File(src, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files:batchMove", &base.Json{"to": base.Json{"parent_id": dstDirFile.Id}, "ids": []string{srcFile.Id}}, nil, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) Copy(src string, dst string, account *model.Account) error {
|
||||||
|
srcFile, err := driver.File(src, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files:batchCopy", &base.Json{"to": base.Json{"parent_id": dstDirFile.Id}, "ids": []string{srcFile.Id}}, nil, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
|
||||||
|
srcFile, err := driver.File(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return GetState(account).Request("PATCH", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s/trash", srcFile.Id), &base.Json{}, nil, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver XunLeiCloud) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tempFile.Close()
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
|
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rep UploadTaskResponse
|
||||||
|
err = GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files", &base.Json{
|
||||||
|
"kind": FILE,
|
||||||
|
"parent_id": parentFile.Id,
|
||||||
|
"name": file.Name,
|
||||||
|
"size": fmt.Sprint(file.Size),
|
||||||
|
"hash": gcid,
|
||||||
|
"upload_type": UPLOAD_TYPE_RESUMABLE,
|
||||||
|
}, &rep, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
param := rep.Resumable.Params
|
||||||
|
client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true), oss.HTTPClient(xunleiClient.GetClient()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bucket, err := client.Bucket(param.Bucket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bucket.UploadFile(param.Key, tempFile.Name(), 4*1024*1024, oss.Routines(3), oss.Checkpoint(true, ""), oss.Expires(param.Expiration))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetState(account).Request("PATCH", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s", srcFile.Id), &base.Json{"name": dstName}, nil, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ base.Driver = (*XunLeiCloud)(nil)
|
154
drivers/xunlei/types.go
Normal file
154
drivers/xunlei/types.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package xunlei
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Erron struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorCode int64 `json:"error_code"`
|
||||||
|
ErrorDescription string `json:"error_description"`
|
||||||
|
// ErrorDetails interface{} `json:"error_details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CaptchaTokenResponse struct {
|
||||||
|
CaptchaToken string `json:"captcha_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenResponse struct {
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"`
|
||||||
|
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignInRequest struct {
|
||||||
|
CaptchaToken string `json:"captcha_token"`
|
||||||
|
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileList struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
NextPageToken string `json:"next_page_token"`
|
||||||
|
Files []Files `json:"files"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
VersionOutdated bool `json:"version_outdated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Files struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
ParentID string `json:"parent_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Size string `json:"size"`
|
||||||
|
Revision string `json:"revision"`
|
||||||
|
FileExtension string `json:"file_extension"`
|
||||||
|
MimeType string `json:"mime_type"`
|
||||||
|
Starred bool `json:"starred"`
|
||||||
|
WebContentLink string `json:"web_content_link"`
|
||||||
|
CreatedTime *time.Time `json:"created_time"`
|
||||||
|
ModifiedTime *time.Time `json:"modified_time"`
|
||||||
|
IconLink string `json:"icon_link"`
|
||||||
|
ThumbnailLink string `json:"thumbnail_link"`
|
||||||
|
Md5Checksum string `json:"md5_checksum"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
//Links struct{} `json:"links"`
|
||||||
|
Phase string `json:"phase"`
|
||||||
|
Audit struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
} `json:"audit"`
|
||||||
|
/* Medias []struct {
|
||||||
|
Category string `json:"category"`
|
||||||
|
IconLink string `json:"icon_link"`
|
||||||
|
IsDefault bool `json:"is_default"`
|
||||||
|
IsOrigin bool `json:"is_origin"`
|
||||||
|
IsVisible bool `json:"is_visible"`
|
||||||
|
//Link interface{} `json:"link"`
|
||||||
|
MediaID string `json:"media_id"`
|
||||||
|
MediaName string `json:"media_name"`
|
||||||
|
NeedMoreQuota bool `json:"need_more_quota"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
RedirectLink string `json:"redirect_link"`
|
||||||
|
ResolutionName string `json:"resolution_name"`
|
||||||
|
Video struct {
|
||||||
|
AudioCodec string `json:"audio_codec"`
|
||||||
|
BitRate int `json:"bit_rate"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
FrameRate int `json:"frame_rate"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
VideoCodec string `json:"video_codec"`
|
||||||
|
VideoType string `json:"video_type"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
} `json:"video"`
|
||||||
|
VipTypes []string `json:"vip_types"`
|
||||||
|
} `json:"medias"` */
|
||||||
|
Trashed bool `json:"trashed"`
|
||||||
|
DeleteTime string `json:"delete_time"`
|
||||||
|
OriginalURL string `json:"original_url"`
|
||||||
|
//Params struct{} `json:"params"`
|
||||||
|
OriginalFileIndex int `json:"original_file_index"`
|
||||||
|
Space string `json:"space"`
|
||||||
|
//Apps []interface{} `json:"apps"`
|
||||||
|
Writable bool `json:"writable"`
|
||||||
|
FolderType string `json:"folder_type"`
|
||||||
|
//Collection interface{} `json:"collection"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadTaskResponse struct {
|
||||||
|
UploadType string `json:"upload_type"`
|
||||||
|
|
||||||
|
/*//UPLOAD_TYPE_FORM
|
||||||
|
Form struct {
|
||||||
|
//Headers struct{} `json:"headers"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
MultiParts struct {
|
||||||
|
OSSAccessKeyID string `json:"OSSAccessKeyId"`
|
||||||
|
Signature string `json:"Signature"`
|
||||||
|
Callback string `json:"callback"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Policy string `json:"policy"`
|
||||||
|
XUserData string `json:"x:user_data"`
|
||||||
|
} `json:"multi_parts"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"form"`*/
|
||||||
|
|
||||||
|
//UPLOAD_TYPE_RESUMABLE
|
||||||
|
Resumable struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Params struct {
|
||||||
|
AccessKeyID string `json:"access_key_id"`
|
||||||
|
AccessKeySecret string `json:"access_key_secret"`
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
Expiration time.Time `json:"expiration"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
SecurityToken string `json:"security_token"`
|
||||||
|
} `json:"params"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
} `json:"resumable"`
|
||||||
|
|
||||||
|
File Files `json:"file"`
|
||||||
|
}
|
103
drivers/xunlei/util.go
Normal file
103
drivers/xunlei/util.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
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 (
|
||||||
|
FOLDER = "drive#folder"
|
||||||
|
FILE = "drive#file"
|
||||||
|
|
||||||
|
RESUMABLE = "drive#resumable"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UPLOAD_TYPE_UNKNOWN = "UPLOAD_TYPE_UNKNOWN"
|
||||||
|
//UPLOAD_TYPE_FORM = "UPLOAD_TYPE_FORM"
|
||||||
|
UPLOAD_TYPE_RESUMABLE = "UPLOAD_TYPE_RESUMABLE"
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGcid(r io.Reader, size int64) (string, error) {
|
||||||
|
calcBlockSize := func(j int64) int64 {
|
||||||
|
if j >= 0 && j <= 134217728 {
|
||||||
|
return 262144
|
||||||
|
}
|
||||||
|
if j <= 134217728 || j > 268435456 {
|
||||||
|
if j <= 268435456 || j > 536870912 {
|
||||||
|
return 2097152
|
||||||
|
}
|
||||||
|
return 1048576
|
||||||
|
}
|
||||||
|
return 524288
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
calcBlockSize := func(j int64) int64 {
|
||||||
|
psize := int64(0x40000)
|
||||||
|
for j/psize > 0x200 {
|
||||||
|
psize <<= 1
|
||||||
|
}
|
||||||
|
return psize
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
hash1 := sha1.New()
|
||||||
|
hash2 := sha1.New()
|
||||||
|
for {
|
||||||
|
hash2.Reset()
|
||||||
|
if n, err := io.CopyN(hash2, r, calcBlockSize(size)); err != nil && n == 0 {
|
||||||
|
if err != io.EOF {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
hash1.Write(hash2.Sum(nil))
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(hash1.Sum(nil)), nil
|
||||||
|
}
|
288
drivers/xunlei/xunlei.go
Normal file
288
drivers/xunlei/xunlei.go
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
package xunlei
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var xunleiClient = resty.New().SetTimeout(120 * time.Second)
|
||||||
|
|
||||||
|
// 一个账户只允许登陆一次
|
||||||
|
var userStateCache = struct {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var e Erron
|
||||||
|
var resp CaptchaTokenResponse
|
||||||
|
_, err := xunleiClient.R().
|
||||||
|
SetHeader("X-Device-Id", driverID).
|
||||||
|
SetBody(&creq).
|
||||||
|
SetError(&e).
|
||||||
|
SetResult(&resp).
|
||||||
|
Post("https://xluser-ssl.xunlei.com/v1/shield/captcha/init?client_id=" + CLIENT_ID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if e.ErrorCode != 0 {
|
||||||
|
log.Debugf("%+v\n %+v", e, account)
|
||||||
|
return "", fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||||
|
}
|
||||||
|
if resp.Url != "" {
|
||||||
|
return "", fmt.Errorf("需要验证验证码")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.captchaTokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000
|
||||||
|
s.captchaToken = resp.CaptchaToken
|
||||||
|
log.Debugf("%+v\n %+v", s.captchaToken, account)
|
||||||
|
return s.captchaToken, 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("https://xluser-ssl.xunlei.com/v1/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:
|
||||||
|
log.Debugf("%+v\n %+v", e, account)
|
||||||
|
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) login(account *model.Account) error {
|
||||||
|
s.init()
|
||||||
|
ctime := time.Now().UnixMilli()
|
||||||
|
url := "https://xluser-ssl.xunlei.com/v1/auth/signin"
|
||||||
|
captchaToken, err := s.newCaptchaToken(getAction("POST", url), map[string]string{"username": account.Username}, account)
|
||||||
|
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().
|
||||||
|
SetResult(&resp).
|
||||||
|
SetError(&e).
|
||||||
|
SetBody(&signReq).
|
||||||
|
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).
|
||||||
|
SetQueryParam("client_id", CLIENT_ID).
|
||||||
|
Post(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer model.SaveAccount(account)
|
||||||
|
if e.ErrorCode != 0 {
|
||||||
|
account.Status = e.Error
|
||||||
|
log.Debugf("%+v\n %+v", e, account)
|
||||||
|
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
log.Debugf("%+v\n %+v", resp, account)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) Request(method string, url string, body interface{}, resp interface{}, account *model.Account) error {
|
||||||
|
s.Lock()
|
||||||
|
token, err := s.getToken(account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
captchaToken, err := s.getCaptchaToken(getAction(method, url), account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Unlock()
|
||||||
|
var e Erron
|
||||||
|
req := xunleiClient.R().
|
||||||
|
SetError(&e).
|
||||||
|
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).
|
||||||
|
SetHeader("Authorization", token).
|
||||||
|
SetHeader("X-Captcha-Token", captchaToken).
|
||||||
|
SetQueryParam("client_id", CLIENT_ID)
|
||||||
|
|
||||||
|
if body != nil {
|
||||||
|
req.SetBody(body)
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
req.SetResult(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case "GET":
|
||||||
|
_, err = req.Get(url)
|
||||||
|
case "POST":
|
||||||
|
_, err = req.Post(url)
|
||||||
|
case "DELETE":
|
||||||
|
_, err = req.Delete(url)
|
||||||
|
case "PATCH":
|
||||||
|
_, err = req.Patch(url)
|
||||||
|
case "PUT":
|
||||||
|
_, err = req.Put(url)
|
||||||
|
default:
|
||||||
|
return base.ErrNotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e.ErrorCode {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 9:
|
||||||
|
s.newCaptchaToken(getAction(method, url), nil, account)
|
||||||
|
fallthrough
|
||||||
|
case 4122, 4121:
|
||||||
|
return s.Request(method, url, body, resp, account)
|
||||||
|
default:
|
||||||
|
log.Debugf("%+v\n %+v", e, account)
|
||||||
|
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) Init() *State {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
return s.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
6
go.mod
6
go.mod
@ -23,8 +23,14 @@ require (
|
|||||||
gorm.io/gorm v1.23.1
|
gorm.io/gorm v1.23.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
|
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/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
|
||||||
|
21
go.sum
21
go.sum
@ -20,6 +20,8 @@ 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-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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
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 h1:mRc8r6GQjuJsmSKQNPsR5jQVXc8IJ1xsW5YXUYMLfqI=
|
||||||
github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY=
|
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=
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
@ -32,6 +34,8 @@ 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 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
|
||||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
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/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 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.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
@ -229,7 +233,6 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU
|
|||||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||||
github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8=
|
|
||||||
github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||||
github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ=
|
github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ=
|
||||||
github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||||
@ -257,8 +260,6 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C
|
|||||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||||
github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
|
||||||
github.com/jackc/pgtype v1.9.1 h1:MJc2s0MFS8C3ok1wQTdQxWuXQcB6+HwAm5x1CzW7mf0=
|
|
||||||
github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||||
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
|
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
|
||||||
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||||
@ -266,8 +267,6 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08
|
|||||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||||
github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8=
|
|
||||||
github.com/jackc/pgx/v4 v4.14.1 h1:71oo1KAGI6mXhLiTMn6iDFcp3e7+zon/capWjl2OEFU=
|
|
||||||
github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
|
github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
|
||||||
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
|
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
|
||||||
github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
|
github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
|
||||||
@ -278,8 +277,6 @@ github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
|
|||||||
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
||||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b h1:Ur6QAxsHCK99Quj9PaWafoV4unb0DO/HWiKExD+TN5g=
|
github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b h1:Ur6QAxsHCK99Quj9PaWafoV4unb0DO/HWiKExD+TN5g=
|
||||||
@ -462,6 +459,7 @@ 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/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/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/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/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/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=
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
@ -555,8 +553,6 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP
|
|||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
|
|
||||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/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-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@ -659,6 +655,7 @@ 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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
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-20191024005414-555d28b269f0/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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -748,18 +745,12 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.2.3 h1:cZqzlOfg5Kf1VIdLC1D9hT6Cy9BgxhExLj/2tIgUe7Y=
|
|
||||||
gorm.io/driver/mysql v1.2.3/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
|
|
||||||
gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
|
gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
|
||||||
gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
|
gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
|
||||||
gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To=
|
|
||||||
gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs=
|
|
||||||
gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g=
|
gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g=
|
||||||
gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU=
|
gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU=
|
||||||
gorm.io/driver/sqlite v1.3.1 h1:bwfE+zTEWklBYoEodIOIBwuWHpnx52Z9zJFW5F33WLk=
|
gorm.io/driver/sqlite v1.3.1 h1:bwfE+zTEWklBYoEodIOIBwuWHpnx52Z9zJFW5F33WLk=
|
||||||
gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg=
|
gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg=
|
||||||
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
|
||||||
gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
|
|
||||||
gorm.io/gorm v1.23.1 h1:aj5IlhDzEPsoIyOPtTRVI+SyaN1u6k613sbt4pwbxG0=
|
gorm.io/gorm v1.23.1 h1:aj5IlhDzEPsoIyOPtTRVI+SyaN1u6k613sbt4pwbxG0=
|
||||||
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
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-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
@ -47,7 +47,6 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *base.Link, file *model.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
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`, url.QueryEscape(file.Name)))
|
||||||
http.ServeContent(w, r, file.Name, fileStat.ModTime(), f)
|
http.ServeContent(w, r, file.Name, fileStat.ModTime(), f)
|
||||||
return nil
|
return nil
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/Xhofe/alist/server/common"
|
"github.com/Xhofe/alist/server/common"
|
||||||
"github.com/Xhofe/alist/utils"
|
"github.com/Xhofe/alist/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,11 +25,25 @@ func Plist(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
u := string(bytes)
|
u := string(bytes)
|
||||||
|
uUrl, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
name := utils.Base(u)
|
name := utils.Base(u)
|
||||||
|
u = uUrl.String()
|
||||||
ipaIndex := strings.Index(name, ".ipa")
|
ipaIndex := strings.Index(name, ".ipa")
|
||||||
|
decodeName := name
|
||||||
if ipaIndex != -1 {
|
if ipaIndex != -1 {
|
||||||
name = name[:ipaIndex]
|
name = name[:ipaIndex]
|
||||||
|
decodeName = name
|
||||||
|
tmp, err := url.PathUnescape(name)
|
||||||
|
if err == nil {
|
||||||
|
decodeName = tmp
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
name = strings.ReplaceAll(name, "<", "[")
|
||||||
|
name = strings.ReplaceAll(name, ">", "]")
|
||||||
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
@ -58,7 +73,7 @@ func Plist(c *gin.Context) {
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>`, u, name, name)
|
</plist>`, u, name, decodeName)
|
||||||
c.Header("Content-Type", "application/xml;charset=utf-8")
|
c.Header("Content-Type", "application/xml;charset=utf-8")
|
||||||
c.Status(200)
|
c.Status(200)
|
||||||
_, _ = c.Writer.WriteString(plist)
|
_, _ = c.Writer.WriteString(plist)
|
||||||
|
@ -2,10 +2,17 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetSHA1Encode(data string) string {
|
||||||
|
h := sha1.New()
|
||||||
|
h.Write([]byte(data))
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
// GetMD5Encode
|
// GetMD5Encode
|
||||||
func GetMD5Encode(data string) string {
|
func GetMD5Encode(data string) string {
|
||||||
h := md5.New()
|
h := md5.New()
|
||||||
|
Reference in New Issue
Block a user