Compare commits
229 Commits
Author | SHA1 | Date | |
---|---|---|---|
82a5c43b94 | |||
d38f36ef44 | |||
f9533440c7 | |||
41a186b051 | |||
4e6a44253c | |||
ebda77cd43 | |||
1a1e86521f | |||
1b4740dae3 | |||
91fc8df84e | |||
e6ecf1fa30 | |||
183a6f1b3a | |||
3c2d59e272 | |||
fd80e3eaf7 | |||
4928c331a8 | |||
3ad75e54cb | |||
a2cf3ab42e | |||
e24814ee2f | |||
37b42e6e17 | |||
30ebb0f4d4 | |||
8e059c64b5 | |||
395de069c2 | |||
4c22f37d54 | |||
a73a40133d | |||
6591af58ea | |||
58568d4ef6 | |||
5295593bf8 | |||
24d031d578 | |||
7141bf0358 | |||
c5d707cf0a | |||
dfcf66b43e | |||
fa6ee62cf0 | |||
1428d90361 | |||
c413c22201 | |||
9b6adecd62 | |||
b3540cf539 | |||
f8650c9c0b | |||
bf2e5768d6 | |||
18c82e79b5 | |||
d69d24a5b2 | |||
342729179d | |||
0537449335 | |||
df90311453 | |||
876579ea3b | |||
e83081380e | |||
9daeaf7562 | |||
e6be11c17f | |||
b52e1e8be3 | |||
948bbe9136 | |||
ced61da33a | |||
a0f4383d41 | |||
5a527dfa2c | |||
49fc475f9f | |||
83c377270e | |||
7ffaef0de6 | |||
dd151480a8 | |||
ad3121d367 | |||
c1525ebc69 | |||
30277cd81f | |||
466ec27ffe | |||
85c757b035 | |||
712687370a | |||
b68ba22df3 | |||
d9652e2a0b | |||
a5b757b251 | |||
0bc05a60b0 | |||
db275f885a | |||
9e483d902f | |||
801f843f8a | |||
77ffb93cbe | |||
bf73ea7f5d | |||
9b23d0ab29 | |||
908cdd2c78 | |||
f4f61a5787 | |||
6db09a2736 | |||
b21801d505 | |||
2dbedc245c | |||
58426613f6 | |||
ef19e851e3 | |||
5a1b16a601 | |||
4eef9cd9bc | |||
79b5c018ea | |||
15651a4356 | |||
7be476cce0 | |||
bb017c5f6d | |||
c51dc4594d | |||
8e30b02efc | |||
0aa438dce4 | |||
9c2fc8e860 | |||
b1d7a980d9 | |||
19d0a88b55 | |||
40567dee0e | |||
4b540a2297 | |||
8a62d55efe | |||
10fce6c0fe | |||
d31d49a9bb | |||
2e91f5ffa5 | |||
8f19c45a81 | |||
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 | |||
d988f98b81 | |||
10634c7b77 | |||
135d505192 | |||
3f2be8a6ca | |||
79bef09ee7 | |||
3534f6afac | |||
106c1d069c | |||
8ed0afe80d | |||
6a6e3944d5 | |||
94d5b5e47e | |||
e61b0f8e34 | |||
f7fbe1de6c | |||
01de01630e | |||
f9f92e2198 | |||
7d5f50b04a | |||
72b5d25e4c | |||
cae7f36531 | |||
aa79f49e25 | |||
b4ad301d53 | |||
00ed54c4c9 | |||
ffa52794db | |||
24058d0c36 | |||
641ca67671 | |||
52ee2e0a8b | |||
724fc7f37e | |||
9d279b104b | |||
eb61f70164 | |||
b3a8201768 | |||
185795954b | |||
cc62cc99d2 | |||
270349f37c | |||
977888070a | |||
192d0f2bf3 | |||
f2ec7884ec | |||
815975a4d2 | |||
f96a0238fc | |||
f695bd0959 | |||
efe8f46e17 | |||
515daa22a9 | |||
f11e22deaf | |||
5be976169f | |||
a6e08f3bf4 | |||
944e68a979 | |||
b3a6e33ce1 | |||
cb53ddc8e8 | |||
693417be4f | |||
5c3f91bb55 | |||
8a219d0732 | |||
146a544af3 | |||
48dccc6c0b | |||
ce1740cec4 | |||
d40dbeae3e | |||
5094b673c4 | |||
228e6d10e7 | |||
e90b979d15 | |||
fb05a6ca48 | |||
e055ed3afa | |||
4371c470b3 | |||
7bb237d0ef | |||
5c42354b01 | |||
387e8af422 | |||
5dca777caf | |||
0814778a14 | |||
6827af3997 | |||
435bdea8f7 | |||
4f81735af6 | |||
bef3d2f88d | |||
ba99c7dc03 | |||
f5c5162a9b | |||
a22903533e | |||
86cda58b22 | |||
7804cf9d5c | |||
2bb7036110 | |||
ba545555cf | |||
be55ca690c | |||
9013add749 | |||
3201b6da76 | |||
feb42f1f4b | |||
6f14d0eb5c | |||
7530d8f5b2 | |||
e25fe05a53 | |||
8e0ab8f780 | |||
cb2a3c2b42 | |||
1b6ec94f33 | |||
cb23edc1fe |
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
|
||||
}
|
15
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
15
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -5,7 +5,16 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
Thanks for taking the time to fill out this bug report, please **confirm that your issue is not a duplicate issue and not because of your operation or version issues**
|
||||
感谢您花时间填写此错误报告,请**务必确认您的issue不是重复的且不是因为您的操作或版本问题**
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please make sure of the following things
|
||||
description: You may select more than one, even select all.
|
||||
options:
|
||||
- label: I have read the [documentation](https://alist-doc.nn.ci).
|
||||
- label: I'm sure there are no duplicate issues or discussions.
|
||||
- label: I'm sure it's due to `alist` and not something else(such as `Dependencies` or `Operational`).
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
@ -28,11 +37,11 @@ body:
|
||||
Please provide a link to a repo that can reproduce the problem you ran into.
|
||||
请提供能复现此问题的链接
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 日志 / Logs
|
||||
label: Logs / 日志
|
||||
description: |
|
||||
Please copy and paste any relevant log output.
|
||||
请复制粘贴错误日志,或者截图
|
||||
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Questions & Discussions & Feature request
|
||||
- name: Questions & Discussions
|
||||
url: https://github.com/Xhofe/alist/discussions
|
||||
about: Use GitHub discussions for message-board style questions and discussions or feature request.
|
||||
about: Use GitHub discussions for message-board style questions and discussions.
|
33
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
33
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
name: "Feature request"
|
||||
description: Feature request
|
||||
labels: ["enhancement: pending triage"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please make sure of the following things
|
||||
description: You may select more than one, even select all.
|
||||
options:
|
||||
- label: I have read the [documentation](https://alist-doc.nn.ci).
|
||||
- label: I'm sure there are no duplicate issues or discussions.
|
||||
- label: I'm sure this feature is not implemented.
|
||||
- label: I'm sure it's a reasonable and popular requirement.
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Description of the feature / 需求描述
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: suggested-solution
|
||||
attributes:
|
||||
label: Suggested solution / 实现思路
|
||||
description: |
|
||||
Solutions to achieve this requirement.
|
||||
实现此需求的解决思路。
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context / 附件
|
||||
description: |
|
||||
Any other context or screenshots about the feature request here, or information you find helpful.
|
||||
相关的任何其他上下文或截图,或者你觉得有帮助的信息
|
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
|
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@ -2,16 +2,16 @@ name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ v2 ]
|
||||
branches: [ '**' ]
|
||||
pull_request:
|
||||
branches: [ v2 ]
|
||||
branches: [ '**' ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
go-version: [1.17]
|
||||
go-version: [1.18]
|
||||
name: Build
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
@ -28,13 +28,12 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: v2
|
||||
path: alist
|
||||
|
||||
- name: Set up xgo
|
||||
- name: Install upx
|
||||
run: |
|
||||
docker pull techknowlogick/xgo:latest
|
||||
go install src.techknowlogick.com/xgo@latest
|
||||
docker pull crazymax/xgo:latest
|
||||
go install github.com/crazy-max/xgo@latest
|
||||
sudo apt install upx
|
||||
|
||||
- name: Build
|
||||
|
2
.github/workflows/build_docker.yml
vendored
2
.github/workflows/build_docker.yml
vendored
@ -3,8 +3,6 @@ name: build_docker
|
||||
on:
|
||||
push:
|
||||
branches: [ v2 ]
|
||||
pull_request:
|
||||
branches: [ v2 ]
|
||||
|
||||
jobs:
|
||||
build_docker:
|
||||
|
20
.github/workflows/issue_check_question.yml
vendored
Normal file
20
.github/workflows/issue_check_question.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: Check need info
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 */7 * *"
|
||||
|
||||
jobs:
|
||||
check-need-info:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: close-issues
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'close-issues'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
labels: 'question'
|
||||
inactive-day: 7
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, this issue was closed due to no activities in 7 days.
|
||||
你好 @${{ github.event.issue.user.login }},此issue因超过7天未回复被关闭。
|
25
.github/workflows/issue_duplicate.yml
vendored
Normal file
25
.github/workflows/issue_duplicate.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Issue Duplicate
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
create-comment:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'duplicate'
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, your issue is a duplicate and will be closed.
|
||||
你好 @${{ github.event.issue.user.login }},你的issue是重复的,将被关闭。
|
||||
- name: Close issue
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'close-issue'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
25
.github/workflows/issue_invalid.yml
vendored
Normal file
25
.github/workflows/issue_invalid.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Issue Invalid
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
create-comment:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'invalid'
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, your issue is invalid and will be closed.
|
||||
你好 @${{ github.event.issue.user.login }},你的issue无效,将被关闭。
|
||||
- name: Close issue
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'close-issue'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
16
.github/workflows/issue_month_statistics.yml
vendored
Normal file
16
.github/workflows/issue_month_statistics.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
name: Issue Month Statistics
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 1 1 * *"
|
||||
|
||||
jobs:
|
||||
month-statistics:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: month-statistics
|
||||
uses: actions-cool/issues-month-statistics@v1
|
||||
with:
|
||||
count-lables: true
|
||||
count-comments: true
|
||||
emoji: 'eyes'
|
20
.github/workflows/issue_question.yml
vendored
Normal file
20
.github/workflows/issue_question.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: Issue Question
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
create-comment:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'question'
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: actions-cool/issues-helper@v2.0.0
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, please input issue by template and add detail. Issues labeled by `question` will be closed if no activities in 7 days.
|
||||
你好 @${{ github.event.issue.user.login }},请按照issue模板填写, 并详细说明问题/复现步骤/复现链接/实现思路或提供更多信息等, 7天内未回复issue自动关闭。
|
25
.github/workflows/issue_wontfix.yml
vendored
Normal file
25
.github/workflows/issue_wontfix.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Issue Wontfix
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
lock-issue:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'wontfix'
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, this issue will not be worked on and will be closed.
|
||||
你好 @${{ github.event.issue.user.login }},这不会被处理,将被关闭。
|
||||
- name: Close issue
|
||||
uses: actions-cool/issues-helper@v2
|
||||
with:
|
||||
actions: 'close-issue'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
go-version: [1.17]
|
||||
go-version: [1.18]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
@ -27,10 +27,10 @@ jobs:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up xgo
|
||||
- name: Install upx
|
||||
run: |
|
||||
docker pull techknowlogick/xgo:latest
|
||||
go install src.techknowlogick.com/xgo@latest
|
||||
docker pull crazymax/xgo:latest
|
||||
go install github.com/crazy-max/xgo@latest
|
||||
sudo apt install upx
|
||||
|
||||
- name: Build
|
||||
|
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,4 +11,4 @@ VOLUME /opt/alist/data/
|
||||
WORKDIR /opt/alist/
|
||||
COPY --from=builder /app/bin/alist ./
|
||||
EXPOSE 5244
|
||||
CMD [ "./alist" ]
|
||||
CMD [ "./alist", "-docker" ]
|
31
README.md
31
README.md
@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<h3><a href="https://alist.nn.ci">Alist</a></h3>
|
||||
<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>
|
||||
<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>
|
||||
@ -11,10 +11,9 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
---
|
||||
|
||||
English | [中文](./README_cn.md)
|
||||
English | [中文](./README_cn.md) | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
||||
|
||||
## Features
|
||||
|
||||
@ -22,7 +21,7 @@ English | [中文](./README_cn.md)
|
||||
- [x] Local storage
|
||||
- [x] [Aliyundrive](https://www.aliyundrive.com/)
|
||||
- [x] OneDrive / Sharepoint ([global](https://www.office.com/), [cn](https://portal.partner.microsoftonline.cn),de,us)
|
||||
- [x] [189cloud](https://cloud.189.cn)
|
||||
- [x] [189cloud](https://cloud.189.cn) (Personal, Family)
|
||||
- [x] [GoogleDrive](https://drive.google.com/)
|
||||
- [x] [123pan](https://www.123pan.com/)
|
||||
- [x] [Lanzou](https://pc.woozooo.com/)
|
||||
@ -31,10 +30,15 @@ English | [中文](./README_cn.md)
|
||||
- [x] [PikPak](https://www.mypikpak.com/)
|
||||
- [x] [ShandianPan](https://shandianpan.com/)
|
||||
- [x] [S3](https://aws.amazon.com/s3/)
|
||||
- [x] WebDav
|
||||
- [x] WebDav(Support OneDrive/SharePoint without API)
|
||||
- [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ ))
|
||||
- [x] [Mediatrack](https://www.mediatrack.cn/)
|
||||
- [x] [139yun](https://yun.139.com/) (Personal, Family)
|
||||
- [x] [Yandex.Disk](https://disk.yandex.com/)
|
||||
- [x] [Baidu Disk](http://pan.baidu.com/)
|
||||
- [x] [Quark](https://pan.quark.cn)
|
||||
- [x] [XunleiCloud](https://pan.xunlei.com/)
|
||||
- [x] SFTP
|
||||
- [x] Easy to deploy and out-of-the-box
|
||||
- [x] File preview (PDF, markdown, code, plain text, ...)
|
||||
- [x] Image preview in gallery mode
|
||||
@ -54,22 +58,33 @@ English | [中文](./README_cn.md)
|
||||
|
||||
## Discussion
|
||||
|
||||
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports only.**
|
||||
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports and feature request only.**
|
||||
|
||||
## Demo
|
||||
|
||||
Available at: <https://alist.nn.ci>.
|
||||
|
||||

|
||||

|
||||
|
||||
## Document
|
||||
|
||||
<https://alist-doc.nn.ci/en/>
|
||||
|
||||
## Special sponsors
|
||||
- [Find Resources - Aliyundrive Resource Search Engine](https://zhaoziyuan.la/)
|
||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
||||
|
||||
## License
|
||||
|
||||
The `AList` is open-source software licensed under the AGPL-3.0 license.
|
||||
|
||||
## Disclaimer
|
||||
- This program is a free and open source project. It is designed to share files on the network disk, which is convenient for downloading and learning golang. Please abide by relevant laws and regulations when using it, and do not abuse it;
|
||||
- This program is implemented by calling the official sdk/interface, without destroying the official interface behavior;
|
||||
- This program only does 302 redirect/traffic forwarding, and does not intercept, store, or tamper with any user data;
|
||||
- Before using this program, you should understand and bear the corresponding risks, including but not limited to account ban, download speed limit, etc., which is none of this program's business;
|
||||
- If there is any infringement, please contact me by [email](mailto:i@nn.ci), and it will be dealt with in time.
|
||||
|
||||
---
|
||||
|
||||
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe)
|
||||
> [@Blog](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
|
30
README_cn.md
30
README_cn.md
@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<h3><a href="https://alist.nn.ci">Alist</a></h3>
|
||||
<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>
|
||||
<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>
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
---
|
||||
|
||||
[English](./README.md) | 中文
|
||||
[English](./README.md) | 中文 | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
||||
|
||||
## 支持
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
- [x] 本地存储
|
||||
- [x] [阿里云盘](https://www.aliyundrive.com/)
|
||||
- [x] OneDrive / Sharepoint([国际版](https://www.office.com/), [世纪互联](https://portal.partner.microsoftonline.cn),de,us)
|
||||
- [x] [天翼云盘](https://cloud.189.cn)
|
||||
- [x] [天翼云盘](https://cloud.189.cn) (个人云, 家庭云)
|
||||
- [x] [GoogleDrive](https://drive.google.com/)
|
||||
- [x] [123云盘](https://www.123pan.com/)
|
||||
- [x] [蓝奏云](https://pc.woozooo.com/)
|
||||
@ -30,10 +30,15 @@
|
||||
- [x] [PikPak](https://www.mypikpak.com/)
|
||||
- [x] [闪电盘](https://shandianpan.com/)
|
||||
- [x] [S3](https://aws.amazon.com/cn/s3/)
|
||||
- [x] WebDav
|
||||
- [x] WebDav(支持无API的OneDrive/SharePoint)
|
||||
- [x] Teambition([中国](https://www.teambition.com/ ),[国际](https://us.teambition.com/ ))
|
||||
- [x] [分秒帧](https://www.mediatrack.cn/)
|
||||
- [x] [和彩云](https://yun.139.com/) (个人云, 家庭云)
|
||||
- [x] [Yandex.Disk](https://disk.yandex.com/)
|
||||
- [x] [百度网盘](http://pan.baidu.com/)
|
||||
- [x] [夸克网盘](https://pan.quark.cn)
|
||||
- [x] [迅雷云盘](https://pan.xunlei.com/)
|
||||
- [x] SFTP
|
||||
- [x] 部署方便,开箱即用
|
||||
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
||||
- [x] 画廊模式下的图像预览
|
||||
@ -53,22 +58,33 @@
|
||||
|
||||
## 讨论
|
||||
|
||||
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) ,**issue仅针对错误报告。**
|
||||
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) ,**issue仅针对错误报告和功能请求。**
|
||||
|
||||
## 演示
|
||||
|
||||
<https://alist.nn.ci>。
|
||||
|
||||

|
||||

|
||||
|
||||
## 文档
|
||||
|
||||
<https://alist-doc.nn.ci/>
|
||||
|
||||
## 特别赞助
|
||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/)
|
||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
||||
|
||||
## 许可
|
||||
|
||||
`AList` 是在 AGPL-3.0 许可下许可的开源软件。
|
||||
|
||||
## 免责声明
|
||||
- 本程序为免费开源项目,旨在分享网盘文件,方便下载以及学习golang,使用时请遵守相关法律法规,请勿滥用;
|
||||
- 本程序通过调用官方sdk/接口实现,无破坏官方接口行为;
|
||||
- 本程序仅做302重定向/流量转发,不拦截、存储、篡改任何用户数据;
|
||||
- 在使用本程序之前,你应了解并承担相应的风险,包括但不限于账号被ban,下载限速等,与本程序无关;
|
||||
- 如有侵权,请通过[邮件](mailto:i@nn.ci)与我联系,会及时处理。
|
||||
|
||||
---
|
||||
|
||||
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe)
|
||||
> [@Blog](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
|
5
alist.go
5
alist.go
@ -22,7 +22,7 @@ func Init() bool {
|
||||
log.Errorf(err.Error())
|
||||
return false
|
||||
}
|
||||
log.Infof("current password: %s", pass.Value)
|
||||
fmt.Printf("your password: %s\n", pass.Value)
|
||||
return false
|
||||
}
|
||||
server.InitIndex()
|
||||
@ -34,7 +34,8 @@ func Init() bool {
|
||||
|
||||
func main() {
|
||||
if conf.Version {
|
||||
fmt.Printf("Built At: %s\nGo Version: %s\nAuthor: %s\nCommit ID: %s\nVersion: %s\n", conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.GitTag)
|
||||
fmt.Printf("Built At: %s\nGo Version: %s\nAuthor: %s\nCommit ID: %s\nVersion: %s\nWebVersion: %s\n",
|
||||
conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.GitTag, conf.WebTag)
|
||||
return
|
||||
}
|
||||
if !Init() {
|
||||
|
@ -1,12 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUrl(t *testing.T) {
|
||||
s,_ := url.QueryUnescape("/ali/%E7%8C%AA%E5%A4%B4%E7%9A%84%E6%96%87%E4%BB%B6%5B%E5%98%BF%E5%98%BF%5D/%E9%82%B9%E9%82%B9%E7%9A%84%E6%96%87%E4%BB%B6/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8/v2-e8f266ba17ae387eefed1cb22b2b5e4e_r.jpg")
|
||||
fmt.Print(s)
|
||||
}
|
@ -3,6 +3,7 @@ package bootstrap
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/drivers/operate"
|
||||
"github.com/Xhofe/alist/model"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -20,7 +21,8 @@ func InitAccounts() {
|
||||
log.Errorf("no [%s] driver", account.Type)
|
||||
} else {
|
||||
log.Infof("start init account: [%s], type: [%s]", account.Name, account.Type)
|
||||
err := driver.Save(&accounts[i], nil)
|
||||
//err := driver.Save(&accounts[i], nil)
|
||||
err := operate.Save(driver, &accounts[i], nil)
|
||||
if err != nil {
|
||||
log.Errorf("init account [%s] error:[%s]", account.Name, err.Error())
|
||||
} else {
|
||||
|
@ -3,9 +3,11 @@ package bootstrap
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/caarlos0/env/v6"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// InitConf init config
|
||||
@ -21,29 +23,49 @@ func InitConf() {
|
||||
if !utils.WriteToJson(conf.ConfigFile, conf.Conf) {
|
||||
log.Fatalf("failed to create default config file")
|
||||
}
|
||||
return
|
||||
} else {
|
||||
config, err := ioutil.ReadFile(conf.ConfigFile)
|
||||
if err != nil {
|
||||
log.Fatalf("reading config file error:%s", err.Error())
|
||||
}
|
||||
conf.Conf = conf.DefaultConfig()
|
||||
err = utils.Json.Unmarshal(config, conf.Conf)
|
||||
if err != nil {
|
||||
log.Fatalf("load config error: %s", err.Error())
|
||||
}
|
||||
log.Debugf("config:%+v", conf.Conf)
|
||||
// update config.json struct
|
||||
confBody, err := utils.Json.MarshalIndent(conf.Conf, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("marshal config error:%s", err.Error())
|
||||
}
|
||||
err = ioutil.WriteFile(conf.ConfigFile, confBody, 0777)
|
||||
if err != nil {
|
||||
log.Fatalf("update config struct error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
config, err := ioutil.ReadFile(conf.ConfigFile)
|
||||
if !conf.Conf.Force {
|
||||
confFromEnv()
|
||||
}
|
||||
err := os.RemoveAll(filepath.Join(conf.Conf.TempDir))
|
||||
if err != nil {
|
||||
log.Fatalf("reading config file error:%s", err.Error())
|
||||
log.Errorln("failed delete temp file:", err)
|
||||
}
|
||||
conf.Conf = new(conf.Config)
|
||||
err = utils.Json.Unmarshal(config, conf.Conf)
|
||||
if err != nil {
|
||||
log.Fatalf("load config error: %s", err.Error())
|
||||
}
|
||||
log.Debugf("config:%+v", conf.Conf)
|
||||
// update config.json struct
|
||||
confBody, err := utils.Json.MarshalIndent(conf.Conf, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("marshal config error:%s", err.Error())
|
||||
}
|
||||
err = ioutil.WriteFile(conf.ConfigFile, confBody, 0777)
|
||||
if err != nil {
|
||||
log.Fatalf("update config struct error: %s", err.Error())
|
||||
}
|
||||
err = os.MkdirAll("data/temp", 0700)
|
||||
err = os.MkdirAll(conf.Conf.TempDir, 0700)
|
||||
if err != nil {
|
||||
log.Fatalf("create temp dir error: %s", err.Error())
|
||||
}
|
||||
log.Debugf("config: %+v", conf.Conf)
|
||||
}
|
||||
|
||||
func confFromEnv() {
|
||||
prefix := "ALIST_"
|
||||
if conf.Docker {
|
||||
prefix = ""
|
||||
}
|
||||
if err := env.Parse(conf.Conf, env.Options{
|
||||
Prefix: prefix,
|
||||
}); err != nil {
|
||||
log.Fatalf("load config from env error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,9 @@ func InitLog() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.SetReportCaller(true)
|
||||
}
|
||||
if conf.Password || conf.Version {
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
//DisableColors: true,
|
||||
ForceColors: true,
|
||||
@ -27,6 +30,7 @@ func init() {
|
||||
flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
|
||||
flag.BoolVar(&conf.Version, "version", false, "print version info")
|
||||
flag.BoolVar(&conf.Password, "password", false, "print current password")
|
||||
flag.BoolVar(&conf.Docker, "docker", false, "is using docker")
|
||||
flag.Parse()
|
||||
InitLog()
|
||||
}
|
||||
}
|
||||
|
@ -50,8 +50,8 @@ func InitModel() {
|
||||
}
|
||||
case "mysql":
|
||||
{
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
databaseConfig.User, databaseConfig.Password, databaseConfig.Host, databaseConfig.Port, databaseConfig.Name)
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
|
||||
databaseConfig.User, databaseConfig.Password, databaseConfig.Host, databaseConfig.Port, databaseConfig.Name, databaseConfig.SslMode)
|
||||
db, err := gorm.Open(mysql.Open(dsn), gormConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect database:%s", err.Error())
|
||||
@ -74,11 +74,11 @@ func InitModel() {
|
||||
log.Infof("auto migrate model...")
|
||||
if databaseConfig.Type == "mysql" {
|
||||
err = conf.DB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").
|
||||
AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{})
|
||||
AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{}, &model.SearchFile{})
|
||||
} else {
|
||||
err = conf.DB.AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{})
|
||||
err = conf.DB.AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{}, &model.SearchFile{})
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("failed to auto migrate")
|
||||
log.Fatalf("failed to auto migrate: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package bootstrap
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
"strings"
|
||||
@ -27,7 +28,7 @@ func InitSettings() {
|
||||
},
|
||||
{
|
||||
Key: "password",
|
||||
Value: "alist",
|
||||
Value: utils.RandomStr(8),
|
||||
Description: "password",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
@ -35,7 +36,7 @@ func InitSettings() {
|
||||
},
|
||||
{
|
||||
Key: "logo",
|
||||
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png",
|
||||
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/can_circle.svg",
|
||||
Description: "logo",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
@ -43,7 +44,7 @@ func InitSettings() {
|
||||
},
|
||||
{
|
||||
Key: "favicon",
|
||||
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png",
|
||||
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg",
|
||||
Description: "favicon",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
@ -57,6 +58,14 @@ func InitSettings() {
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "announcement",
|
||||
Value: "This is a test announcement.",
|
||||
Description: "announcement message (support markdown)",
|
||||
Type: "text",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "text types",
|
||||
Value: strings.Join(conf.TextTypes, ","),
|
||||
@ -65,15 +74,37 @@ func InitSettings() {
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "hide readme file",
|
||||
Value: "true",
|
||||
Type: "bool",
|
||||
Description: "hide readme file? ",
|
||||
Key: "audio types",
|
||||
Value: strings.Join(conf.AudioTypes, ","),
|
||||
Type: "string",
|
||||
Description: "audio type extensions",
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "video types",
|
||||
Value: strings.Join(conf.VideoTypes, ","),
|
||||
Type: "string",
|
||||
Description: "video type extensions",
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "d_proxy types",
|
||||
Value: strings.Join(conf.DProxyTypes, ","),
|
||||
Type: "string",
|
||||
Description: "/d but proxy",
|
||||
Access: model.PRIVATE,
|
||||
Group: model.BACK,
|
||||
},
|
||||
{
|
||||
Key: "hide files",
|
||||
Value: "/\\/README.md/i",
|
||||
Type: "text",
|
||||
Description: "hide files, support RegExp, one per line",
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "music cover",
|
||||
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
|
||||
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/circle_center.svg",
|
||||
Description: "music cover image",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
@ -87,8 +118,8 @@ func InitSettings() {
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "home readme url",
|
||||
Description: "when have multiple, the readme file to show",
|
||||
Key: "global readme url",
|
||||
Description: "Default display when directory has no readme",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
@ -157,7 +188,7 @@ func InitSettings() {
|
||||
},
|
||||
{
|
||||
Key: "WebDAV username",
|
||||
Value: "alist_admin",
|
||||
Value: "admin",
|
||||
Description: "WebDAV username",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
@ -165,7 +196,7 @@ func InitSettings() {
|
||||
},
|
||||
{
|
||||
Key: "WebDAV password",
|
||||
Value: "alist_admin",
|
||||
Value: utils.RandomStr(8),
|
||||
Description: "WebDAV password",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
@ -189,7 +220,7 @@ func InitSettings() {
|
||||
},
|
||||
{
|
||||
Key: "Visitor WebDAV username",
|
||||
Value: "alist_visitor",
|
||||
Value: "guest",
|
||||
Description: "Visitor WebDAV username",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
@ -197,7 +228,7 @@ func InitSettings() {
|
||||
},
|
||||
{
|
||||
Key: "Visitor WebDAV password",
|
||||
Value: "alist_visitor",
|
||||
Value: "guest",
|
||||
Description: "Visitor WebDAV password",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
@ -227,6 +258,14 @@ func InitSettings() {
|
||||
Access: model.PRIVATE,
|
||||
Group: model.BACK,
|
||||
},
|
||||
{
|
||||
Key: "enable search",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.BACK,
|
||||
Description: "Experimental function, not recommended as it's still under development",
|
||||
},
|
||||
}
|
||||
for i, _ := range settings {
|
||||
v := settings[i]
|
||||
@ -235,6 +274,9 @@ func InitSettings() {
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
err = model.SaveSetting(v)
|
||||
if v.Key == "password" {
|
||||
log.Infof("Initial password: %s", conf.C.Sprintf(v.Value))
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("failed write setting: %s", err.Error())
|
||||
}
|
||||
@ -249,6 +291,9 @@ func InitSettings() {
|
||||
if err != nil {
|
||||
log.Fatalf("failed write setting: %s", err.Error())
|
||||
}
|
||||
if v.Key == "password" {
|
||||
log.Infof("Your password: %s", conf.C.Sprintf(v.Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
model.LoadSettings()
|
||||
|
33
build.sh
33
build.sh
@ -6,8 +6,11 @@ BUILD_WEB() {
|
||||
cd alist-web
|
||||
yarn
|
||||
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 ..
|
||||
cd ..
|
||||
cd .. || exit
|
||||
rm -rf alist-web
|
||||
}
|
||||
|
||||
@ -25,6 +28,7 @@ BUILD_DOCKER() {
|
||||
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
|
||||
gitCommit=$(git log --pretty=format:"%h" -1)
|
||||
gitTag=$(git describe --long --tags --dirty --always)
|
||||
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
||||
ldflags="\
|
||||
-w -s \
|
||||
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
|
||||
@ -32,6 +36,7 @@ BUILD_DOCKER() {
|
||||
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
|
||||
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
|
||||
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
|
||||
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
|
||||
"
|
||||
go build -o ./bin/alist -ldflags="$ldflags" -tags=jsoniter alist.go
|
||||
}
|
||||
@ -44,7 +49,9 @@ BUILD() {
|
||||
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
|
||||
gitCommit=$(git log --pretty=format:"%h" -1)
|
||||
gitTag=$(git describe --long --tags --dirty --always)
|
||||
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
||||
echo "build version: $gitTag"
|
||||
|
||||
ldflags="\
|
||||
-w -s \
|
||||
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
|
||||
@ -52,23 +59,24 @@ BUILD() {
|
||||
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
|
||||
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
|
||||
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
|
||||
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
|
||||
"
|
||||
|
||||
if [ "$1" == "release" ]; then
|
||||
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||
else
|
||||
xgo -targets=linux/amd64,windows/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||
xgo -targets=linux/amd64,windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||
fi
|
||||
mkdir -p "build"
|
||||
mv alist-* build
|
||||
if [ "$1" != "release" ]; then
|
||||
cd build
|
||||
upx -9 ./*
|
||||
upx -9 ./alist-linux*
|
||||
upx -9 ./alist-windows*
|
||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||
cat md5.txt
|
||||
cd ..
|
||||
cd .. || exit
|
||||
fi
|
||||
cd ..
|
||||
cd .. || exit
|
||||
}
|
||||
|
||||
BUILD_MUSL() {
|
||||
@ -86,13 +94,15 @@ BUILD_MUSL() {
|
||||
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
|
||||
gitCommit=$(git log --pretty=format:"%h" -1)
|
||||
gitTag=$(git describe --long --tags --dirty --always)
|
||||
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
||||
ldflags="\
|
||||
-w -s \
|
||||
-w -s --extldflags '-static -fpic' \
|
||||
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
|
||||
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
|
||||
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
|
||||
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
|
||||
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
|
||||
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
|
||||
"
|
||||
OS_ARCHES=(linux-musl-amd64 linux-musl-arm64 linux-musl-arm linux-musl-mips linux-musl-mips64 linux-musl-mips64le linux-musl-mipsle linux-musl-ppc64le linux-musl-s390x)
|
||||
CGO_ARGS=(x86_64-linux-musl-gcc aarch64-linux-musl-gcc arm-linux-musleabihf-gcc mips-linux-musl-gcc mips64-linux-musl-gcc mips64el-linux-musl-gcc mipsel-linux-musl-gcc powerpc64le-linux-musl-gcc s390x-linux-musl-gcc)
|
||||
@ -106,12 +116,13 @@ BUILD_MUSL() {
|
||||
export CGO_ENABLED=1
|
||||
go build -o ./build/$appName-$os_arch -ldflags="$ldflags" -tags=jsoniter alist.go
|
||||
done
|
||||
cd ..
|
||||
cd .. || exit
|
||||
}
|
||||
|
||||
RELEASE() {
|
||||
cd alist/build
|
||||
upx -9 ./*
|
||||
upx -9 ./alist-linux-amd64
|
||||
upx -9 ./alist-windows*
|
||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||
cat md5.txt
|
||||
mkdir compress
|
||||
@ -125,7 +136,7 @@ RELEASE() {
|
||||
for i in $(find . -type f -name "$appName-windows-*"); do
|
||||
zip compress/$(echo $i | sed 's/\.[^.]*$//').zip "$i"
|
||||
done
|
||||
cd ../..
|
||||
cd ../.. || exit
|
||||
}
|
||||
|
||||
if [ "$1" = "web" ]; then
|
||||
@ -142,4 +153,4 @@ elif [ "$1" = "release" ]; then
|
||||
RELEASE
|
||||
else
|
||||
echo -e "${RED_COLOR} Parameter error ${RES}"
|
||||
fi
|
||||
fi
|
@ -1,48 +1,50 @@
|
||||
package conf
|
||||
|
||||
type Database struct {
|
||||
Type string `json:"type"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Name string `json:"name"`
|
||||
TablePrefix string `json:"table_prefix"`
|
||||
DBFile string `json:"db_file"`
|
||||
SslMode string `json:"ssl_mode"`
|
||||
Type string `json:"type" env:"DB_TYPE"`
|
||||
Host string `json:"host" env:"DB_HOST"`
|
||||
Port int `json:"port" env:"DB_PORT"`
|
||||
User string `json:"user" env:"DB_USER"`
|
||||
Password string `json:"password" env:"DB_PASS"`
|
||||
Name string `json:"name" env:"DB_NAME"`
|
||||
DBFile string `json:"db_file" env:"DB_FILE"`
|
||||
TablePrefix string `json:"table_prefix" env:"DB_TABLE_PREFIX"`
|
||||
SslMode string `json:"ssl_mode" env:"DB_SLL_MODE"`
|
||||
}
|
||||
|
||||
type Scheme struct {
|
||||
Https bool `json:"https"`
|
||||
CertFile string `json:"cert_file"`
|
||||
KeyFile string `json:"key_file"`
|
||||
Https bool `json:"https" env:"HTTPS"`
|
||||
CertFile string `json:"cert_file" env:"CERT_FILE"`
|
||||
KeyFile string `json:"key_file" env:"KEY_FILE"`
|
||||
}
|
||||
|
||||
type CacheConfig struct {
|
||||
Expiration int64 `json:"expiration"`
|
||||
CleanupInterval int64 `json:"cleanup_interval"`
|
||||
Expiration int64 `json:"expiration" env:"CACHE_EXPIRATION"`
|
||||
CleanupInterval int64 `json:"cleanup_interval" env:"CLEANUP_INTERVAL"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Address string `json:"address"`
|
||||
Port int `json:"port"`
|
||||
Assets string `json:"assets"`
|
||||
Force bool `json:"force"`
|
||||
Address string `json:"address" env:"ADDR"`
|
||||
Port int `json:"port" env:"PORT"`
|
||||
Assets string `json:"assets" env:"ASSETS"`
|
||||
Database Database `json:"database"`
|
||||
Scheme Scheme `json:"scheme"`
|
||||
Cache CacheConfig `json:"cache"`
|
||||
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Address: "0.0.0.0",
|
||||
Port: 5244,
|
||||
Assets: "jsdelivr",
|
||||
Assets: "https://npm.elemecdn.com/alist-web@$version/dist",
|
||||
TempDir: "data/temp",
|
||||
Database: Database{
|
||||
Type: "sqlite3",
|
||||
Port: 0,
|
||||
TablePrefix: "x_",
|
||||
DBFile: "data/data.db",
|
||||
SslMode: "disable",
|
||||
},
|
||||
Cache: CacheConfig{
|
||||
Expiration: 60,
|
||||
|
11
conf/var.go
11
conf/var.go
@ -3,6 +3,7 @@ package conf
|
||||
import (
|
||||
"context"
|
||||
"github.com/eko/gocache/v2/cache"
|
||||
"github.com/fatih/color"
|
||||
"github.com/robfig/cron/v3"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
@ -14,6 +15,7 @@ var (
|
||||
GitAuthor string
|
||||
GitCommit string
|
||||
GitTag string = "dev"
|
||||
WebTag string
|
||||
)
|
||||
|
||||
var (
|
||||
@ -22,20 +24,24 @@ var (
|
||||
Debug bool
|
||||
Version bool
|
||||
Password bool
|
||||
Docker bool
|
||||
|
||||
DB *gorm.DB
|
||||
Cache *cache.Cache
|
||||
Ctx = context.TODO()
|
||||
Cron *cron.Cron
|
||||
|
||||
C = color.New(color.FgHiBlue, color.Bold, color.BgHiWhite, color.Underline)
|
||||
)
|
||||
|
||||
var (
|
||||
TextTypes = []string{"txt", "htm", "html", "xml", "java", "properties", "sql",
|
||||
"js", "md", "json", "conf", "ini", "vue", "php", "py", "bat", "gitignore", "yml",
|
||||
"go", "sh", "c", "cpp", "h", "hpp", "tsx", "vtt", "srt", "ass"}
|
||||
DProxyTypes = []string{"m3u8"}
|
||||
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
|
||||
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"}
|
||||
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"}
|
||||
)
|
||||
|
||||
@ -78,7 +84,8 @@ var (
|
||||
"check parent folder", "check down link", "WebDAV username", "WebDAV password",
|
||||
"Visitor WebDAV username", "Visitor WebDAV password",
|
||||
"default page size", "load type",
|
||||
"ocr api",
|
||||
"ocr api", "favicon",
|
||||
"enable search",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -12,58 +12,8 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BaseResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type Pan123TokenResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
Token string `json:"token"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type Pan123File struct {
|
||||
FileName string `json:"FileName"`
|
||||
Size int64 `json:"Size"`
|
||||
UpdateAt *time.Time `json:"UpdateAt"`
|
||||
FileId int64 `json:"FileId"`
|
||||
Type int `json:"Type"`
|
||||
Etag string `json:"Etag"`
|
||||
S3KeyFlag string `json:"S3KeyFlag"`
|
||||
}
|
||||
|
||||
type Pan123Files struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
InfoList []Pan123File `json:"InfoList"`
|
||||
Next string `json:"Next"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type Pan123DownResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
DownloadUrl string `json:"DownloadUrl"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type UploadResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
AccessKeyId string `json:"AccessKeyId"`
|
||||
Bucket string `json:"Bucket"`
|
||||
Key string `json:"Key"`
|
||||
SecretAccessKey string `json:"SecretAccessKey"`
|
||||
SessionToken string `json:"SessionToken"`
|
||||
FileId int64 `json:"FileId"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func (driver Pan123) Login(account *model.Account) error {
|
||||
url := "https://www.123pan.com/api/user/sign_in"
|
||||
if account.APIProxyUrl != "" {
|
||||
@ -98,11 +48,7 @@ func (driver Pan123) FormatFile(file *Pan123File) *model.File {
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: file.UpdateAt,
|
||||
}
|
||||
if file.Type == 1 {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.FileName))
|
||||
}
|
||||
f.Type = file.GetType()
|
||||
return f
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -58,6 +57,7 @@ func (driver Pan123) Items() []base.Item {
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,fileId,updateAt,createAt",
|
||||
Required: true,
|
||||
Default: "name",
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
@ -65,6 +65,7 @@ func (driver Pan123) Items() []base.Item {
|
||||
Type: base.TypeSelect,
|
||||
Values: "asc,desc",
|
||||
Required: true,
|
||||
Default: "asc",
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -139,7 +140,7 @@ func (driver Pan123) Link(args base.Args, account *model.Account) (*base.Link, e
|
||||
}
|
||||
var resp Pan123DownResp
|
||||
var headers map[string]string
|
||||
if args.IP != "" && args.IP != "::1" {
|
||||
if !utils.IsLocalIPAddr(args.IP) {
|
||||
headers = map[string]string{
|
||||
//"X-Real-IP": "1.1.1.1",
|
||||
"X-Forwarded-For": args.IP,
|
||||
@ -197,9 +198,9 @@ func (driver Pan123) Path(path string, account *model.Account) (*model.File, []m
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver Pan123) Proxy(c *gin.Context, account *model.Account) {
|
||||
c.Request.Header.Del("origin")
|
||||
}
|
||||
//func (driver Pan123) Proxy(r *http.Request, account *model.Account) {
|
||||
// r.Header.Del("origin")
|
||||
//}
|
||||
|
||||
func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
@ -299,7 +300,7 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
parentFileId, _ := strconv.Atoi(parentFile.Id)
|
||||
tempFile, err := ioutil.TempFile("data/temp", "file-*")
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
72
drivers/123/types.go
Normal file
72
drivers/123/types.go
Normal file
@ -0,0 +1,72 @@
|
||||
package _23
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Pan123File struct {
|
||||
FileName string `json:"FileName"`
|
||||
Size int64 `json:"Size"`
|
||||
UpdateAt *time.Time `json:"UpdateAt"`
|
||||
FileId int64 `json:"FileId"`
|
||||
Type int `json:"Type"`
|
||||
Etag string `json:"Etag"`
|
||||
S3KeyFlag string `json:"S3KeyFlag"`
|
||||
}
|
||||
|
||||
func (f Pan123File) GetSize() uint64 {
|
||||
return uint64(f.Size)
|
||||
}
|
||||
|
||||
func (f Pan123File) GetName() string {
|
||||
return f.FileName
|
||||
}
|
||||
|
||||
func (f Pan123File) GetType() int {
|
||||
if f.Type == 1 {
|
||||
return conf.FOLDER
|
||||
}
|
||||
return utils.GetFileType(path.Ext(f.FileName))
|
||||
}
|
||||
|
||||
type BaseResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type Pan123TokenResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
Token string `json:"token"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type Pan123Files struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
InfoList []Pan123File `json:"InfoList"`
|
||||
Next string `json:"Next"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type Pan123DownResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
DownloadUrl string `json:"DownloadUrl"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type UploadResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
AccessKeyId string `json:"AccessKeyId"`
|
||||
Bucket string `json:"Bucket"`
|
||||
Key string `json:"Key"`
|
||||
SecretAccessKey string `json:"SecretAccessKey"`
|
||||
SessionToken string `json:"SessionToken"`
|
||||
FileId int64 `json:"FileId"`
|
||||
} `json:"data"`
|
||||
}
|
@ -17,7 +17,7 @@ import (
|
||||
func (driver Cloud139) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
url := "https://yun.139.com" + pathname
|
||||
req := base.RestyClient.R()
|
||||
randStr := randomStr(16)
|
||||
randStr := utils.RandomStr(16)
|
||||
ts := time.Now().Format("2006-01-02 15:04:05")
|
||||
log.Debugf("%+v", data)
|
||||
body, err := utils.Json.Marshal(data)
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"math"
|
||||
@ -163,9 +162,9 @@ func (driver Cloud139) Path(path string, account *model.Account) (*model.File, [
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver Cloud139) Proxy(c *gin.Context, account *model.Account) {
|
||||
|
||||
}
|
||||
//func (driver Cloud139) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver Cloud139) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
@ -335,7 +334,7 @@ func (driver Cloud139) Delete(path string, account *model.Account) error {
|
||||
"taskInfo": base.Json{
|
||||
"newCatalogID": "",
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
@ -13,16 +12,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func randomStr(n int) string {
|
||||
builder := strings.Builder{}
|
||||
t := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
for i := 0; i < n; i++ {
|
||||
r := rand.Intn(len(t))
|
||||
builder.WriteString(t[r : r+1])
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func encodeURIComponent(str string) string {
|
||||
r := url.QueryEscape(str)
|
||||
r = strings.Replace(r, "+", "%20", -1)
|
||||
|
@ -16,9 +16,8 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"math"
|
||||
mathRand "math/rand"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"net/http/cookiejar"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -60,11 +59,9 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
|
||||
f.UpdatedAt = &lastOpTime
|
||||
}
|
||||
if file.Size == -1 {
|
||||
f.Type = conf.FOLDER
|
||||
f.Size = 0
|
||||
} else {
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
||||
}
|
||||
f.Type = file.GetType()
|
||||
return f
|
||||
}
|
||||
|
||||
@ -89,12 +86,6 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
|
||||
// return nil, ErrPathNotFound
|
||||
//}
|
||||
|
||||
type Cloud189Down struct {
|
||||
ResCode int `json:"res_code"`
|
||||
ResMessage string `json:"res_message"`
|
||||
FileDownloadUrl string `json:"fileDownloadUrl"`
|
||||
}
|
||||
|
||||
type LoginResp struct {
|
||||
Msg string `json:"msg"`
|
||||
Result int `json:"result"`
|
||||
@ -108,9 +99,13 @@ func (driver Cloud189) Login(account *model.Account) error {
|
||||
//cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||
client = resty.New()
|
||||
//client.SetCookieJar(cookieJar)
|
||||
client.SetTimeout(base.DefaultTimeout)
|
||||
client.SetRetryCount(3)
|
||||
client.SetHeader("Referer", "https://cloud.189.cn/")
|
||||
}
|
||||
// clear cookie
|
||||
jar, _ := cookiejar.New(nil)
|
||||
client.SetCookieJar(jar)
|
||||
url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
|
||||
b := ""
|
||||
lt := ""
|
||||
@ -134,7 +129,7 @@ func (driver Cloud189) Login(account *model.Account) error {
|
||||
}
|
||||
}
|
||||
if lt == "" {
|
||||
return fmt.Errorf("get empty login page")
|
||||
return errors.New("get page: " + b)
|
||||
}
|
||||
captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
|
||||
returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
|
||||
@ -462,7 +457,7 @@ func (driver Cloud189) UploadRequest(uri string, form map[string]string, account
|
||||
c := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||
r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
|
||||
l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
|
||||
l = l[0 : 16+int(16*mathRand.Float32())]
|
||||
l = l[0 : 16+int(16*utils.Rand.Float32())]
|
||||
|
||||
e := qs(form)
|
||||
data := AesEncrypt([]byte(e), []byte(l[0:16]))
|
||||
@ -525,7 +520,7 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
|
||||
}
|
||||
res, err := driver.UploadRequest("/person/initMultiUpload", map[string]string{
|
||||
"parentFolderId": parentFile.Id,
|
||||
"fileName": file.Name,
|
||||
"fileName": encode(file.Name),
|
||||
"fileSize": strconv.FormatInt(int64(file.Size), 10),
|
||||
"sliceSize": strconv.FormatInt(int64(DEFAULT), 10),
|
||||
"lazyCheck": "1",
|
||||
|
@ -6,9 +6,11 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Cloud189 struct{}
|
||||
@ -153,14 +155,15 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
|
||||
if file.Type == conf.FOLDER {
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
var resp Cloud189Down
|
||||
u := "https://cloud.189.cn/api/open/file/getFileDownloadUrl.action"
|
||||
var resp DownResp
|
||||
u := "https://cloud.189.cn/api/portal/getFileInfo.action"
|
||||
body, err := driver.Request(u, base.Get, map[string]string{
|
||||
"fileId": file.Id,
|
||||
}, nil, nil, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugln(string(body))
|
||||
err = utils.Json.Unmarshal(body, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -168,16 +171,35 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
|
||||
if resp.ResCode != 0 {
|
||||
return nil, fmt.Errorf(resp.ResMessage)
|
||||
}
|
||||
res, err := base.NoRedirectClient.R().Get(resp.FileDownloadUrl)
|
||||
client, err := driver.getClient(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link := base.Link{}
|
||||
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{
|
||||
Headers: []base.Header{
|
||||
{Name: "User-Agent", Value: base.UserAgent},
|
||||
//{Name: "Authorization", Value: ""},
|
||||
},
|
||||
}
|
||||
if res.StatusCode() == 302 {
|
||||
link.Url = res.Header().Get("location")
|
||||
res, err = client.R().Get(link.Url)
|
||||
if res.StatusCode() == 302 {
|
||||
link.Url = res.Header().Get("location")
|
||||
}
|
||||
} else {
|
||||
link.Url = resp.FileDownloadUrl
|
||||
}
|
||||
link.Url = strings.Replace(link.Url, "http://", "https://", 1)
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
@ -198,9 +220,9 @@ func (driver Cloud189) Path(path string, account *model.Account) (*model.File, [
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) Proxy(ctx *gin.Context, account *model.Account) {
|
||||
ctx.Request.Header.Del("Origin")
|
||||
}
|
||||
//func (driver Cloud189) Proxy(r *http.Request, account *model.Account) {
|
||||
// r.Header.Del("Origin")
|
||||
//}
|
||||
|
||||
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
|
@ -1,5 +1,11 @@
|
||||
package _89
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"path"
|
||||
)
|
||||
|
||||
type Cloud189Error struct {
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
@ -17,6 +23,24 @@ type Cloud189File struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
func (f Cloud189File) GetSize() uint64 {
|
||||
if f.Size == -1 {
|
||||
return 0
|
||||
}
|
||||
return uint64(f.Size)
|
||||
}
|
||||
|
||||
func (f Cloud189File) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func (f Cloud189File) GetType() int {
|
||||
if f.Size == -1 {
|
||||
return conf.FOLDER
|
||||
}
|
||||
return utils.GetFileType(path.Ext(f.Name))
|
||||
}
|
||||
|
||||
type Cloud189Folder struct {
|
||||
Id int64 `json:"id"`
|
||||
LastOpTime string `json:"lastOpTime"`
|
||||
@ -53,3 +77,15 @@ type Rsa struct {
|
||||
PkId string `json:"pkId"`
|
||||
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"`
|
||||
}
|
||||
|
@ -14,18 +14,17 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
mathRand "math/rand"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func random() string {
|
||||
return fmt.Sprintf("0.%17v", mathRand.New(mathRand.NewSource(time.Now().UnixNano())).Int63n(100000000000000000))
|
||||
return fmt.Sprintf("0.%17v", utils.Rand.Int63n(100000000000000000))
|
||||
}
|
||||
|
||||
func RsaEncode(origData []byte, j_rsakey string, hex bool) string {
|
||||
@ -116,12 +115,24 @@ func EncodeParam(v url.Values) string {
|
||||
}
|
||||
buf.WriteString(k)
|
||||
buf.WriteByte('=')
|
||||
//if k == "fileName" {
|
||||
// buf.WriteString(encode(v))
|
||||
//} else {
|
||||
buf.WriteString(v)
|
||||
//}
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func encode(str string) string {
|
||||
//str = strings.ReplaceAll(str, "%", "%25")
|
||||
//str = strings.ReplaceAll(str, "&", "%26")
|
||||
//str = strings.ReplaceAll(str, "+", "%2B")
|
||||
//return str
|
||||
return url.QueryEscape(str)
|
||||
}
|
||||
|
||||
func AesEncrypt(data, key []byte) []byte {
|
||||
block, _ := aes.NewCipher(key)
|
||||
if block == nil {
|
||||
@ -169,7 +180,7 @@ func Random(v string) string {
|
||||
reg := regexp.MustCompilePOSIX("[xy]")
|
||||
data := reg.ReplaceAllFunc([]byte(v), func(msg []byte) []byte {
|
||||
var i int64
|
||||
t := int64(16 * mathRand.Float32())
|
||||
t := int64(16 * utils.Rand.Float32())
|
||||
if msg[0] == 120 {
|
||||
i = t
|
||||
} else {
|
||||
|
311
drivers/189pc/189.go
Normal file
311
drivers/189pc/189.go
Normal file
@ -0,0 +1,311 @@
|
||||
package _189
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"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"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
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 := &State{client: resty.New().
|
||||
SetHeaders(map[string]string{
|
||||
"Accept": "application/json;charset=UTF-8",
|
||||
"User-Agent": base.UserAgent,
|
||||
}).SetTimeout(base.DefaultTimeout),
|
||||
}
|
||||
userStateCache.States[account.Username] = state
|
||||
return state
|
||||
}
|
||||
|
||||
type State struct {
|
||||
sync.Mutex
|
||||
client *resty.Client
|
||||
|
||||
RsaPublicKey string
|
||||
|
||||
SessionKey string
|
||||
SessionSecret string
|
||||
FamilySessionKey string
|
||||
FamilySessionSecret string
|
||||
|
||||
AccessToken string
|
||||
|
||||
//怎么刷新的???
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
func (s *State) login(account *model.Account) error {
|
||||
// 清除cookie
|
||||
jar, _ := cookiejar.New(nil)
|
||||
s.client.SetCookieJar(jar)
|
||||
|
||||
var err error
|
||||
var res *resty.Response
|
||||
defer func() {
|
||||
account.Status = "work"
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
}
|
||||
model.SaveAccount(account)
|
||||
if res != nil {
|
||||
log.Debug(res.String())
|
||||
}
|
||||
}()
|
||||
|
||||
var param *LoginParam
|
||||
param, err = s.getLoginParam()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 提交登录
|
||||
s.RsaPublicKey = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", param.jRsaKey)
|
||||
res, err = s.client.R().
|
||||
SetHeaders(map[string]string{
|
||||
"Referer": AUTH_URL,
|
||||
"REQID": param.ReqId,
|
||||
"lt": param.Lt,
|
||||
}).
|
||||
SetFormData(map[string]string{
|
||||
"appKey": APP_ID,
|
||||
"accountType": "02",
|
||||
"userName": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Username),
|
||||
"password": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Password),
|
||||
"validateCode": param.vCodeRS,
|
||||
"captchaToken": param.CaptchaToken,
|
||||
"returnUrl": RETURN_URL,
|
||||
"mailSuffix": "@189.cn",
|
||||
"dynamicCheck": "FALSE",
|
||||
"clientType": CLIENT_TYPE,
|
||||
"cb_SaveName": "1",
|
||||
"isOauth2": "false",
|
||||
"state": "",
|
||||
"paramId": param.ParamId,
|
||||
}).
|
||||
Post(AUTH_URL + "/api/logbox/oauth2/loginSubmit.do")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toUrl := utils.Json.Get(res.Body(), "toUrl").ToString()
|
||||
if toUrl == "" {
|
||||
log.Error(res.String())
|
||||
return fmt.Errorf(res.String())
|
||||
}
|
||||
|
||||
// 获取Session
|
||||
var erron Erron
|
||||
var sessionResp appSessionResp
|
||||
res, err = s.client.R().
|
||||
SetResult(&sessionResp).SetError(&erron).
|
||||
SetQueryParams(clientSuffix()).
|
||||
SetQueryParam("redirectURL", url.QueryEscape(toUrl)).
|
||||
Post(API_URL + "/getSessionForPC.action")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if erron.ResCode != "" {
|
||||
err = fmt.Errorf(erron.ResMessage)
|
||||
return err
|
||||
}
|
||||
if sessionResp.ResCode != 0 {
|
||||
err = fmt.Errorf(sessionResp.ResMessage)
|
||||
return err
|
||||
}
|
||||
s.SessionKey = sessionResp.SessionKey
|
||||
s.SessionSecret = sessionResp.SessionSecret
|
||||
s.FamilySessionKey = sessionResp.FamilySessionKey
|
||||
s.FamilySessionSecret = sessionResp.FamilySessionSecret
|
||||
s.AccessToken = sessionResp.AccessToken
|
||||
s.RefreshToken = sessionResp.RefreshToken
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *State) getLoginParam() (*LoginParam, error) {
|
||||
res, err := s.client.R().
|
||||
SetQueryParams(map[string]string{
|
||||
"appId": APP_ID,
|
||||
"clientType": CLIENT_TYPE,
|
||||
"returnURL": RETURN_URL,
|
||||
"timeStamp": fmt.Sprint(timestamp()),
|
||||
}).
|
||||
Get(WEB_URL + "/api/portal/unifyLoginForPC.action")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
param := &LoginParam{
|
||||
CaptchaToken: regexp.MustCompile(`'captchaToken' value='(.+?)'`).FindStringSubmatch(res.String())[1],
|
||||
Lt: regexp.MustCompile(`lt = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
||||
ParamId: regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
||||
ReqId: regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
||||
jRsaKey: regexp.MustCompile(`"j_rsaKey" value="(.+?)"`).FindStringSubmatch(res.String())[1],
|
||||
|
||||
vCodeID: regexp.MustCompile(`token=([A-Za-z0-9&=]+)`).FindStringSubmatch(res.String())[1],
|
||||
}
|
||||
|
||||
imgRes, err := s.client.R().Get(fmt.Sprint(AUTH_URL, "/api/logbox/oauth2/picCaptcha.do?token=", param.vCodeID, timestamp()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(imgRes.Body()) > 0 {
|
||||
vRes, err := resty.New().R().
|
||||
SetMultipartField("image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
|
||||
Post(conf.GetStr("ocr api"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if utils.Json.Get(vRes.Body(), "status").ToInt() != 200 {
|
||||
return nil, errors.New("ocr error:" + utils.Json.Get(vRes.Body(), "msg").ToString())
|
||||
}
|
||||
param.vCodeRS = utils.Json.Get(vRes.Body(), "result").ToString()
|
||||
log.Debugln("code: ", param.vCodeRS)
|
||||
}
|
||||
return param, nil
|
||||
}
|
||||
|
||||
func (s *State) refreshSession(account *model.Account) error {
|
||||
var erron Erron
|
||||
var userSessionResp UserSessionResp
|
||||
res, err := s.client.R().
|
||||
SetResult(&userSessionResp).SetError(&erron).
|
||||
SetQueryParams(clientSuffix()).
|
||||
SetQueryParams(map[string]string{
|
||||
"appId": APP_ID,
|
||||
"accessToken": s.AccessToken,
|
||||
}).
|
||||
SetHeader("X-Request-ID", uuid.NewString()).
|
||||
Get(API_URL + "/getSessionForPC.action")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if erron.ResCode != "" {
|
||||
return fmt.Errorf(erron.ResMessage)
|
||||
}
|
||||
|
||||
switch userSessionResp.ResCode {
|
||||
case 0:
|
||||
s.SessionKey = userSessionResp.SessionKey
|
||||
s.SessionSecret = userSessionResp.SessionSecret
|
||||
s.FamilySessionKey = userSessionResp.FamilySessionKey
|
||||
s.FamilySessionSecret = userSessionResp.FamilySessionSecret
|
||||
case 11, 18:
|
||||
return s.login(account)
|
||||
default:
|
||||
account.Status = userSessionResp.ResMessage
|
||||
_ = model.SaveAccount(account)
|
||||
return fmt.Errorf(userSessionResp.ResMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) IsLogin(account *model.Account) bool {
|
||||
_, err := s.Request(http.MethodGet, API_URL+"/getUserInfo.action", nil, func(r *resty.Request) { r.SetQueryParams(clientSuffix()) }, account)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (s *State) Login(account *model.Account) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
return s.login(account)
|
||||
}
|
||||
|
||||
func (s *State) RefreshSession(account *model.Account) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
return s.refreshSession(account)
|
||||
}
|
||||
|
||||
func (s *State) Request(method string, fullUrl string, params Params, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
|
||||
s.Lock()
|
||||
dateOfGmt := getHttpDateStr()
|
||||
sessionKey := s.SessionKey
|
||||
sessionSecret := s.SessionSecret
|
||||
if isFamily(account) {
|
||||
sessionKey = s.FamilySessionKey
|
||||
sessionSecret = s.FamilySessionSecret
|
||||
}
|
||||
|
||||
req := s.client.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"Date": dateOfGmt,
|
||||
"SessionKey": sessionKey,
|
||||
"X-Request-ID": uuid.NewString(),
|
||||
})
|
||||
|
||||
// 设置params
|
||||
var paramsData string
|
||||
if params != nil {
|
||||
paramsData = AesECBEncrypt(params.Encode(), s.SessionSecret[:16])
|
||||
req.SetQueryParam("params", paramsData)
|
||||
}
|
||||
req.SetHeader("Signature", signatureOfHmac(sessionSecret, sessionKey, method, fullUrl, dateOfGmt, paramsData))
|
||||
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
s.Unlock()
|
||||
|
||||
res, err := req.Execute(method, fullUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
|
||||
var erron Erron
|
||||
utils.Json.Unmarshal(res.Body(), &erron)
|
||||
if erron.ResCode != "" {
|
||||
return nil, fmt.Errorf(erron.ResMessage)
|
||||
}
|
||||
if erron.Code != "" && erron.Code != "SUCCESS" {
|
||||
if erron.Msg == "" {
|
||||
if erron.Message == "" {
|
||||
return nil, fmt.Errorf(res.String())
|
||||
}
|
||||
return nil, fmt.Errorf(erron.Message)
|
||||
}
|
||||
return nil, fmt.Errorf(erron.Msg)
|
||||
}
|
||||
if erron.ErrorCode != "" {
|
||||
return nil, fmt.Errorf(erron.ErrorMsg)
|
||||
}
|
||||
|
||||
switch utils.Json.Get(res.Body(), "res_code").ToInt64() {
|
||||
case 11, 18:
|
||||
if err := s.RefreshSession(account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.Request(method, fullUrl, params, callback, account)
|
||||
case 0:
|
||||
if res.StatusCode() == http.StatusOK {
|
||||
return res, nil
|
||||
}
|
||||
return nil, fmt.Errorf(res.String())
|
||||
default:
|
||||
return nil, fmt.Errorf(utils.Json.Get(res.Body(), "res_message").ToString())
|
||||
}
|
||||
}
|
912
drivers/189pc/driver.go
Normal file
912
drivers/189pc/driver.go
Normal file
@ -0,0 +1,912 @@
|
||||
package _189
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(new(Cloud189))
|
||||
}
|
||||
|
||||
type Cloud189 struct {
|
||||
}
|
||||
|
||||
func (driver Cloud189) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "189CloudPC",
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Cloud189) 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,
|
||||
},
|
||||
{
|
||||
Name: "internal_type",
|
||||
Label: "189cloud type",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "Personal,Family",
|
||||
},
|
||||
{
|
||||
Name: "site_id",
|
||||
Label: "family id",
|
||||
Type: base.TypeString,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "filename,filesize,lastOpTime",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "desc",
|
||||
Type: base.TypeSelect,
|
||||
Values: "true,false",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "bool_1",
|
||||
Label: "fast upload",
|
||||
Type: base.TypeBool,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Cloud189) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !isFamily(account) && account.RootFolder == "" {
|
||||
account.RootFolder = "-11"
|
||||
account.SiteId = ""
|
||||
}
|
||||
if isFamily(account) && account.RootFolder == "-11" {
|
||||
account.RootFolder = ""
|
||||
}
|
||||
|
||||
state := GetState(account)
|
||||
if !state.IsLogin(account) {
|
||||
if err := state.Login(account); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if isFamily(account) {
|
||||
list, err := driver.getFamilyInfoList(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, l := range list {
|
||||
if account.SiteId == "" {
|
||||
account.SiteId = fmt.Sprint(l.FamilyID)
|
||||
}
|
||||
log.Infof("天翼家庭云 用户名:%s FamilyID %d\n", l.RemarkName, l.FamilyID)
|
||||
}
|
||||
}
|
||||
|
||||
account.Status = "work"
|
||||
model.SaveAccount(account)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) getFamilyInfoList(account *model.Account) ([]FamilyInfoResp, error) {
|
||||
var resp FamilyInfoListResp
|
||||
_, err := GetState(account).Request(http.MethodGet, API_URL+"/family/manage/getFamilyList.action", nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix())
|
||||
r.SetResult(&resp)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.FamilyInfoResp, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) 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 Cloud189) 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
|
||||
}
|
||||
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fullUrl := API_URL
|
||||
if isFamily(account) {
|
||||
fullUrl += "/family/file"
|
||||
}
|
||||
fullUrl += "/listFiles.action"
|
||||
|
||||
files := make([]model.File, 0)
|
||||
client := GetState(account)
|
||||
for pageNum := 1; ; pageNum++ {
|
||||
var resp Cloud189FilesResp
|
||||
_, err = client.Request(http.MethodGet, fullUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix()).
|
||||
SetQueryParams(map[string]string{
|
||||
"folderId": file.Id,
|
||||
"fileType": "0",
|
||||
"mediaAttr": "0",
|
||||
"iconOption": "5",
|
||||
"pageNum": fmt.Sprint(pageNum),
|
||||
"pageSize": "130",
|
||||
})
|
||||
if isFamily(account) {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"familyId": account.SiteId,
|
||||
"orderBy": toFamilyOrderBy(account.OrderBy),
|
||||
"descending": account.OrderDirection,
|
||||
})
|
||||
} else {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"recursive": "0",
|
||||
"orderBy": account.OrderBy,
|
||||
"descending": account.OrderDirection,
|
||||
})
|
||||
}
|
||||
r.SetResult(&resp)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 获取完毕跳出
|
||||
if resp.FileListAO.Count == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, folder := range resp.FileListAO.FolderList {
|
||||
files = append(files, model.File{
|
||||
Id: fmt.Sprint(folder.ID),
|
||||
Name: folder.Name,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: MustParseTime(folder.LastOpTime),
|
||||
})
|
||||
}
|
||||
for _, file := range resp.FileListAO.FileList {
|
||||
files = append(files, model.File{
|
||||
Id: fmt.Sprint(file.ID),
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
Type: utils.GetFileType(filepath.Ext(file.Name)),
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: MustParseTime(file.LastOpTime),
|
||||
Thumbnail: file.Icon.SmallUrl,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("189PC 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 Cloud189) 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
|
||||
}
|
||||
|
||||
fullUrl := API_URL
|
||||
if isFamily(account) {
|
||||
fullUrl += "/family/file"
|
||||
}
|
||||
fullUrl += "/getFileDownloadUrl.action"
|
||||
|
||||
var downloadUrl struct {
|
||||
URL string `json:"fileDownloadUrl"`
|
||||
}
|
||||
_, err = GetState(account).Request(http.MethodGet, fullUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix()).SetQueryParam("fileId", file.Id)
|
||||
if isFamily(account) {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"familyId": account.SiteId,
|
||||
})
|
||||
} else {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"dt": "3",
|
||||
"flag": "1",
|
||||
})
|
||||
}
|
||||
r.SetResult(&downloadUrl)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &base.Link{
|
||||
Headers: []base.Header{
|
||||
{Name: "User-Agent", Value: base.UserAgent},
|
||||
},
|
||||
Url: strings.ReplaceAll(downloadUrl.URL, "&", "&"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Cloud189) 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
|
||||
}
|
||||
|
||||
fullUrl := API_URL
|
||||
if isFamily(account) {
|
||||
fullUrl += "/family/file"
|
||||
}
|
||||
fullUrl += "/createFolder.action"
|
||||
|
||||
_, err = GetState(account).Request(http.MethodPost, fullUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix()).SetQueryParams(map[string]string{
|
||||
"folderName": name,
|
||||
"relativePath": "",
|
||||
})
|
||||
if isFamily(account) {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"familyId": account.SiteId,
|
||||
"parentId": parentFile.Id,
|
||||
})
|
||||
} else {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"parentFolderId": parentFile.Id,
|
||||
})
|
||||
}
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) 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
|
||||
}
|
||||
|
||||
_, err = GetState(account).Request(http.MethodPost, API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
|
||||
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
||||
"type": "MOVE",
|
||||
"taskInfos": string(MustToBytes(utils.Json.Marshal(
|
||||
[]*BatchTaskInfo{
|
||||
{
|
||||
FileId: srcFile.Id,
|
||||
FileName: srcFile.Name,
|
||||
IsFolder: BoolToNumber(srcFile.IsDir()),
|
||||
},
|
||||
}))),
|
||||
"targetFolderId": dstDirFile.Id,
|
||||
})
|
||||
if isFamily(account) {
|
||||
r.SetFormData(map[string]string{
|
||||
"familyId": account.SiteId,
|
||||
})
|
||||
}
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
func (driver Cloud189) 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
|
||||
}
|
||||
|
||||
var queryParam map[string]string
|
||||
fullUrl := API_URL
|
||||
method := http.MethodPost
|
||||
if isFamily(account) {
|
||||
fullUrl += "/family/file"
|
||||
method = http.MethodGet
|
||||
}
|
||||
if srcFile.IsDir() {
|
||||
fullUrl += "/moveFolder.action"
|
||||
queryParam = map[string]string{
|
||||
"folderId": srcFile.Id,
|
||||
"destFolderName": srcFile.Name,
|
||||
}
|
||||
} else {
|
||||
fullUrl += "/moveFile.action"
|
||||
queryParam = map[string]string{
|
||||
"fileId": srcFile.Id,
|
||||
"destFileName": srcFile.Name,
|
||||
}
|
||||
}
|
||||
|
||||
_, err = GetState(account).Request(method, fullUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(queryParam).SetQueryParams(clientSuffix())
|
||||
if isFamily(account) {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"familyId": account.SiteId,
|
||||
"destParentId": dstDirFile.Id,
|
||||
})
|
||||
} else {
|
||||
r.SetQueryParam("destParentFolderId", dstDirFile.Id)
|
||||
}
|
||||
}, account)
|
||||
return err
|
||||
}*/
|
||||
|
||||
func (driver Cloud189) Rename(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var queryParam map[string]string
|
||||
fullUrl := API_URL
|
||||
method := http.MethodPost
|
||||
if isFamily(account) {
|
||||
fullUrl += "/family/file"
|
||||
method = http.MethodGet
|
||||
}
|
||||
if srcFile.IsDir() {
|
||||
fullUrl += "/renameFolder.action"
|
||||
queryParam = map[string]string{
|
||||
"folderId": srcFile.Id,
|
||||
"destFolderName": filepath.Base(dst),
|
||||
}
|
||||
} else {
|
||||
fullUrl += "/renameFile.action"
|
||||
queryParam = map[string]string{
|
||||
"fileId": srcFile.Id,
|
||||
"destFileName": filepath.Base(dst),
|
||||
}
|
||||
}
|
||||
|
||||
_, err = GetState(account).Request(method, fullUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(queryParam).SetQueryParams(clientSuffix())
|
||||
if isFamily(account) {
|
||||
r.SetQueryParam("familyId", account.SiteId)
|
||||
}
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) 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
|
||||
}
|
||||
|
||||
_, err = GetState(account).Request(http.MethodPost, API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
|
||||
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
||||
"type": "COPY",
|
||||
"taskInfos": string(MustToBytes(utils.Json.Marshal(
|
||||
[]*BatchTaskInfo{
|
||||
{
|
||||
FileId: srcFile.Id,
|
||||
FileName: srcFile.Name,
|
||||
IsFolder: BoolToNumber(srcFile.IsDir()),
|
||||
},
|
||||
}))),
|
||||
"targetFolderId": dstDirFile.Id,
|
||||
"targetFileName": filepath.Base(dst),
|
||||
})
|
||||
if isFamily(account) {
|
||||
r.SetFormData(map[string]string{
|
||||
"familyId": account.SiteId,
|
||||
})
|
||||
}
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) Delete(path string, account *model.Account) error {
|
||||
path = utils.ParsePath(path)
|
||||
srcFile, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = GetState(account).Request(http.MethodPost, API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
|
||||
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
||||
"type": "DELETE",
|
||||
"taskInfos": string(MustToBytes(utils.Json.Marshal(
|
||||
[]*BatchTaskInfo{
|
||||
{
|
||||
FileId: srcFile.Id,
|
||||
FileName: srcFile.Name,
|
||||
IsFolder: BoolToNumber(srcFile.IsDir()),
|
||||
},
|
||||
}))),
|
||||
})
|
||||
|
||||
if isFamily(account) {
|
||||
r.SetFormData(map[string]string{
|
||||
"familyId": account.SiteId,
|
||||
})
|
||||
}
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
|
||||
parentFile, err := driver.File(file.ParentPath, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !parentFile.IsDir() {
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
|
||||
if account.Bool1 {
|
||||
return driver.FastUpload(file, parentFile, account)
|
||||
}
|
||||
return driver.CommonUpload(file, parentFile, account)
|
||||
/*
|
||||
if isFamily(account) {
|
||||
return driver.uploadFamily(file, parentFile, account)
|
||||
}
|
||||
return driver.uploadPerson(file, parentFile, account)
|
||||
*/
|
||||
}
|
||||
|
||||
func (driver Cloud189) CommonUpload(file *model.FileStream, parentFile *model.File, account *model.Account) error {
|
||||
// 初始化上传
|
||||
state := GetState(account)
|
||||
const DEFAULT int64 = 10485760
|
||||
count := int(math.Ceil(float64(file.Size) / float64(DEFAULT)))
|
||||
|
||||
params := Params{
|
||||
"parentFolderId": parentFile.Id,
|
||||
"fileName": url.PathEscape(file.Name),
|
||||
"fileSize": fmt.Sprint(file.Size),
|
||||
"sliceSize": fmt.Sprint(DEFAULT),
|
||||
"lazyCheck": "1",
|
||||
}
|
||||
|
||||
fullUrl := UPLOAD_URL
|
||||
if isFamily(account) {
|
||||
params.Set("familyId", account.SiteId)
|
||||
fullUrl += "/family"
|
||||
} else {
|
||||
//params.Set("extend", `{"opScene":"1","relativepath":"","rootfolderid":""}`)
|
||||
fullUrl += "/person"
|
||||
}
|
||||
|
||||
var initMultiUpload InitMultiUploadResp
|
||||
_, err := state.Request(http.MethodGet, fullUrl+"/initMultiUpload", params, func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&initMultiUpload) }, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileMd5 := md5.New()
|
||||
silceMd5 := md5.New()
|
||||
silceMd5Hexs := make([]string, 0, count)
|
||||
byteData := bytes.NewBuffer(make([]byte, DEFAULT))
|
||||
for i := 1; i <= count; i++ {
|
||||
byteData.Reset()
|
||||
silceMd5.Reset()
|
||||
if n, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5, byteData), file, DEFAULT); err != io.EOF && n == 0 {
|
||||
return err
|
||||
}
|
||||
md5Bytes := silceMd5.Sum(nil)
|
||||
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Bytes)))
|
||||
silceMd5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
|
||||
|
||||
var uploadUrl UploadUrlsResp
|
||||
_, err = state.Request(http.MethodGet, fullUrl+"/getMultiUploadUrls",
|
||||
Params{"partInfo": fmt.Sprintf("%d-%s", i, silceMd5Base64), "uploadFileId": initMultiUpload.Data.UploadFileID},
|
||||
func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&uploadUrl) },
|
||||
account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uploadData := uploadUrl.UploadUrls[fmt.Sprint("partNumber_", i)]
|
||||
req, _ := http.NewRequest(http.MethodPut, uploadData.RequestURL, byteData)
|
||||
for k, v := range ParseHttpHeader(uploadData.RequestHeader) {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
for k, v := range clientSuffix() {
|
||||
req.URL.RawQuery += fmt.Sprintf("&%s=%s", k, v)
|
||||
}
|
||||
r, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
data, _ := io.ReadAll(r.Body)
|
||||
return fmt.Errorf(string(data))
|
||||
}
|
||||
}
|
||||
|
||||
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
|
||||
sliceMd5Hex := fileMd5Hex
|
||||
if int64(file.Size) > DEFAULT {
|
||||
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
|
||||
}
|
||||
|
||||
_, err = state.Request(http.MethodGet, fullUrl+"/commitMultiUploadFile",
|
||||
Params{
|
||||
"uploadFileId": initMultiUpload.Data.UploadFileID,
|
||||
"fileMd5": fileMd5Hex,
|
||||
"sliceMd5": sliceMd5Hex,
|
||||
"lazyCheck": "1",
|
||||
"isLog": "0",
|
||||
"opertype": "3",
|
||||
},
|
||||
func(r *resty.Request) { r.SetQueryParams(clientSuffix()) }, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) FastUpload(file *model.FileStream, parentFile *model.File, account *model.Account) error {
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// 初始化上传
|
||||
state := GetState(account)
|
||||
|
||||
const DEFAULT int64 = 10485760
|
||||
count := int(math.Ceil(float64(file.Size) / float64(DEFAULT)))
|
||||
|
||||
// 优先计算所需信息
|
||||
fileMd5 := md5.New()
|
||||
silceMd5 := md5.New()
|
||||
silceMd5Hexs := make([]string, 0, count)
|
||||
silceMd5Base64s := make([]string, 0, count)
|
||||
for i := 1; i <= count; i++ {
|
||||
silceMd5.Reset()
|
||||
if n, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5, tempFile), file, DEFAULT); err != nil && n == 0 {
|
||||
return err
|
||||
}
|
||||
md5Byte := silceMd5.Sum(nil)
|
||||
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Byte)))
|
||||
silceMd5Base64s = append(silceMd5Base64s, fmt.Sprint(i, "-", base64.StdEncoding.EncodeToString(md5Byte)))
|
||||
}
|
||||
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
|
||||
sliceMd5Hex := fileMd5Hex
|
||||
if int64(file.Size) > DEFAULT {
|
||||
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
|
||||
}
|
||||
|
||||
params := Params{
|
||||
"parentFolderId": parentFile.Id,
|
||||
"fileName": url.PathEscape(file.Name),
|
||||
"fileSize": fmt.Sprint(file.Size),
|
||||
"fileMd5": fileMd5Hex,
|
||||
"sliceSize": fmt.Sprint(DEFAULT),
|
||||
"sliceMd5": sliceMd5Hex,
|
||||
}
|
||||
|
||||
fullUrl := UPLOAD_URL
|
||||
if isFamily(account) {
|
||||
params.Set("familyId", account.SiteId)
|
||||
fullUrl += "/family"
|
||||
} else {
|
||||
//params.Set("extend", `{"opScene":"1","relativepath":"","rootfolderid":""}`)
|
||||
fullUrl += "/person"
|
||||
}
|
||||
|
||||
var uploadInfo InitMultiUploadResp
|
||||
_, err = state.Request(http.MethodGet, fullUrl+"/initMultiUpload", params, func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&uploadInfo) }, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if uploadInfo.Data.FileDataExists != 1 {
|
||||
var uploadUrls UploadUrlsResp
|
||||
_, err := state.Request(http.MethodGet, fullUrl+"/getMultiUploadUrls",
|
||||
Params{
|
||||
"uploadFileId": uploadInfo.Data.UploadFileID,
|
||||
"partInfo": strings.Join(silceMd5Base64s, ","),
|
||||
},
|
||||
func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&uploadUrls) },
|
||||
account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 1; i <= count; i++ {
|
||||
uploadData := uploadUrls.UploadUrls[fmt.Sprint("partNumber_", i)]
|
||||
req, _ := http.NewRequest(http.MethodPut, uploadData.RequestURL, io.NewSectionReader(tempFile, int64(i-1)*DEFAULT, DEFAULT))
|
||||
for k, v := range ParseHttpHeader(uploadData.RequestHeader) {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
for k, v := range clientSuffix() {
|
||||
req.URL.RawQuery += fmt.Sprintf("&%s=%s", k, v)
|
||||
}
|
||||
r, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
data, _ := io.ReadAll(r.Body)
|
||||
return fmt.Errorf(string(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = state.Request(http.MethodGet, fullUrl+"/commitMultiUploadFile",
|
||||
Params{
|
||||
"uploadFileId": uploadInfo.Data.UploadFileID,
|
||||
"isLog": "0",
|
||||
"opertype": "3",
|
||||
},
|
||||
func(r *resty.Request) { r.SetQueryParams(clientSuffix()) },
|
||||
account)
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
func (driver Cloud189) uploadFamily(file *model.FileStream, parentFile *model.File, account *model.Account) error {
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
fileMd5 := md5.New()
|
||||
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := GetState(account)
|
||||
var createUpload CreateUploadFileResult
|
||||
_, err = client.Request(http.MethodGet, API_URL+"/family/file/createFamilyFile.action", nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"fileMd5": hex.EncodeToString(fileMd5.Sum(nil)),
|
||||
"fileName": file.Name,
|
||||
"familyId": account.SiteId,
|
||||
"parentId": parentFile.Id,
|
||||
"resumePolicy": "1",
|
||||
"fileSize": fmt.Sprint(file.Size),
|
||||
})
|
||||
r.SetQueryParams(clientSuffix())
|
||||
r.SetResult(&createUpload)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if createUpload.FileDataExists != 1 {
|
||||
if createUpload.UploadFileId, err = driver.uploadFileData(file, tempFile, createUpload, account); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = client.Request(http.MethodGet, createUpload.FileCommitUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix())
|
||||
r.SetHeaders(map[string]string{
|
||||
"FamilyId": account.SiteId,
|
||||
"uploadFileId": fmt.Sprint(createUpload.UploadFileId),
|
||||
"ResumePolicy": "1",
|
||||
})
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) uploadPerson(file *model.FileStream, parentFile *model.File, account *model.Account) error {
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
fileMd5 := md5.New()
|
||||
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := GetState(account)
|
||||
var createUpload CreateUploadFileResult
|
||||
_, err = client.Request(http.MethodPost, API_URL+"/createUploadFile.action", nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix())
|
||||
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
||||
"parentFolderId": parentFile.Id,
|
||||
"baseFileId": "",
|
||||
"fileName": file.Name,
|
||||
"size": fmt.Sprint(file.Size),
|
||||
"md5": hex.EncodeToString(fileMd5.Sum(nil)),
|
||||
// "lastWrite": param.LastWrite,
|
||||
// "localPath": strings.ReplaceAll(file.ParentPath, "\\", "/"),
|
||||
"opertype": "1",
|
||||
"flag": "1",
|
||||
"resumePolicy": "1",
|
||||
"isLog": "0",
|
||||
"fileExt": "",
|
||||
})
|
||||
r.SetResult(&createUpload)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if createUpload.FileDataExists != 1 {
|
||||
if createUpload.UploadFileId, err = driver.uploadFileData(file, tempFile, createUpload, account); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = client.Request(http.MethodPost, createUpload.FileCommitUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix())
|
||||
r.SetFormData(map[string]string{
|
||||
"uploadFileId": fmt.Sprint(createUpload.UploadFileId),
|
||||
"opertype": "5", //5 覆盖 1 重命名
|
||||
"ResumePolicy": "1",
|
||||
"isLog": "0",
|
||||
})
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) uploadFileData(file *model.FileStream, tempFile *os.File, createUpload CreateUploadFileResult, account *model.Account) (int64, error) {
|
||||
uploadFileState, err := driver.getUploadFileState(createUpload.UploadFileId, account)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if uploadFileState.FileDataExists == 1 || uploadFileState.DataSize == int64(file.Size) {
|
||||
return uploadFileState.UploadFileId, nil
|
||||
}
|
||||
|
||||
if _, err = tempFile.Seek(uploadFileState.DataSize, io.SeekStart); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = GetState(account).Request("PUT", uploadFileState.FileUploadUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix())
|
||||
r.SetHeaders(map[string]string{
|
||||
"Content-Type": "application/octet-stream",
|
||||
"ResumePolicy": "1",
|
||||
"Edrive-UploadFileRange": fmt.Sprintf("bytes=%d-%d", uploadFileState.DataSize, file.Size),
|
||||
"Expect": "100-continue",
|
||||
})
|
||||
if isFamily(account) {
|
||||
r.SetHeaders(map[string]string{
|
||||
"familyId": account.SiteId,
|
||||
"UploadFileId": fmt.Sprint(uploadFileState.UploadFileId),
|
||||
})
|
||||
} else {
|
||||
r.SetHeader("Edrive-UploadFileId", fmt.Sprint(uploadFileState.UploadFileId))
|
||||
}
|
||||
r.SetBody(tempFile)
|
||||
}, account)
|
||||
return uploadFileState.UploadFileId, err
|
||||
}
|
||||
|
||||
func (driver Cloud189) getUploadFileState(uploadFileId int64, account *model.Account) (*UploadFileStatusResult, error) {
|
||||
fullUrl := API_URL
|
||||
if isFamily(account) {
|
||||
fullUrl += "/family/file/getFamilyFileStatus.action"
|
||||
} else {
|
||||
fullUrl += "/getUploadFileStatus.action"
|
||||
}
|
||||
var uploadFileState UploadFileStatusResult
|
||||
_, err := GetState(account).Request(http.MethodGet, fullUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix())
|
||||
r.SetQueryParams(map[string]string{
|
||||
"uploadFileId": fmt.Sprint(uploadFileId),
|
||||
"resumePolicy": "1",
|
||||
})
|
||||
if isFamily(account) {
|
||||
r.SetQueryParam("familyId", account.SiteId)
|
||||
}
|
||||
r.SetResult(&uploadFileState)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &uploadFileState, nil
|
||||
}*/
|
||||
|
||||
var _ base.Driver = (*Cloud189)(nil)
|
180
drivers/189pc/type.go
Normal file
180
drivers/189pc/type.go
Normal file
@ -0,0 +1,180 @@
|
||||
package _189
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type LoginParam struct {
|
||||
CaptchaToken string
|
||||
Lt string
|
||||
ParamId string
|
||||
ReqId string
|
||||
jRsaKey string
|
||||
|
||||
vCodeID string
|
||||
vCodeRS string
|
||||
}
|
||||
|
||||
// 居然有四种返回方式
|
||||
type Erron struct {
|
||||
ResCode string `json:"res_code"`
|
||||
ResMessage string `json:"res_message"`
|
||||
|
||||
XMLName xml.Name `xml:"error"`
|
||||
Code string `json:"code" xml:"code"`
|
||||
Message string `json:"message" xml:"message"`
|
||||
|
||||
// Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
}
|
||||
|
||||
// 刷新session返回
|
||||
type UserSessionResp struct {
|
||||
ResCode int `json:"res_code"`
|
||||
ResMessage string `json:"res_message"`
|
||||
|
||||
LoginName string `json:"loginName"`
|
||||
|
||||
KeepAlive int `json:"keepAlive"`
|
||||
GetFileDiffSpan int `json:"getFileDiffSpan"`
|
||||
GetUserInfoSpan int `json:"getUserInfoSpan"`
|
||||
|
||||
// 个人云
|
||||
SessionKey string `json:"sessionKey"`
|
||||
SessionSecret string `json:"sessionSecret"`
|
||||
// 家庭云
|
||||
FamilySessionKey string `json:"familySessionKey"`
|
||||
FamilySessionSecret string `json:"familySessionSecret"`
|
||||
}
|
||||
|
||||
//登录返回
|
||||
type appSessionResp struct {
|
||||
UserSessionResp
|
||||
|
||||
IsSaveName string `json:"isSaveName"`
|
||||
|
||||
// 会话刷新Token
|
||||
AccessToken string `json:"accessToken"`
|
||||
//Token刷新
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
}
|
||||
|
||||
type FamilyInfoListResp struct {
|
||||
FamilyInfoResp []FamilyInfoResp `json:"familyInfoResp"`
|
||||
}
|
||||
type FamilyInfoResp struct {
|
||||
Count int `json:"count"`
|
||||
CreateTime string `json:"createTime"`
|
||||
FamilyID int `json:"familyId"`
|
||||
RemarkName string `json:"remarkName"`
|
||||
Type int `json:"type"`
|
||||
UseFlag int `json:"useFlag"`
|
||||
UserRole int `json:"userRole"`
|
||||
}
|
||||
|
||||
/*文件部分*/
|
||||
// 文件
|
||||
type Cloud189File struct {
|
||||
CreateDate string `json:"createDate"`
|
||||
FileCata int64 `json:"fileCata"`
|
||||
Icon struct {
|
||||
//iconOption 5
|
||||
SmallUrl string `json:"smallUrl"`
|
||||
LargeUrl string `json:"largeUrl"`
|
||||
|
||||
// iconOption 10
|
||||
Max600 string `json:"max600"`
|
||||
MediumURL string `json:"mediumUrl"`
|
||||
} `json:"icon"`
|
||||
ID int64 `json:"id"`
|
||||
LastOpTime string `json:"lastOpTime"`
|
||||
Md5 string `json:"md5"`
|
||||
MediaType int `json:"mediaType"`
|
||||
Name string `json:"name"`
|
||||
Orientation int64 `json:"orientation"`
|
||||
Rev string `json:"rev"`
|
||||
Size int64 `json:"size"`
|
||||
StarLabel int64 `json:"starLabel"`
|
||||
}
|
||||
|
||||
// 文件夹
|
||||
type Cloud189Folder struct {
|
||||
ID int64 `json:"id"`
|
||||
ParentID int64 `json:"parentId"`
|
||||
Name string `json:"name"`
|
||||
|
||||
FileCata int64 `json:"fileCata"`
|
||||
FileCount int64 `json:"fileCount"`
|
||||
|
||||
LastOpTime string `json:"lastOpTime"`
|
||||
CreateDate string `json:"createDate"`
|
||||
|
||||
FileListSize int64 `json:"fileListSize"`
|
||||
Rev string `json:"rev"`
|
||||
StarLabel int64 `json:"starLabel"`
|
||||
}
|
||||
|
||||
type Cloud189FilesResp struct {
|
||||
//ResCode int `json:"res_code"`
|
||||
//ResMessage string `json:"res_message"`
|
||||
FileListAO struct {
|
||||
Count int `json:"count"`
|
||||
FileList []Cloud189File `json:"fileList"`
|
||||
FolderList []Cloud189Folder `json:"folderList"`
|
||||
} `json:"fileListAO"`
|
||||
}
|
||||
|
||||
// TaskInfo 任务信息
|
||||
type BatchTaskInfo struct {
|
||||
// FileId 文件ID
|
||||
FileId string `json:"fileId"`
|
||||
// FileName 文件名
|
||||
FileName string `json:"fileName"`
|
||||
// IsFolder 是否是文件夹,0-否,1-是
|
||||
IsFolder int `json:"isFolder"`
|
||||
// SrcParentId 文件所在父目录ID
|
||||
//SrcParentId string `json:"srcParentId"`
|
||||
}
|
||||
|
||||
/*
|
||||
type CreateUploadFileResult struct {
|
||||
// UploadFileId 上传文件请求ID
|
||||
UploadFileId int64 `json:"uploadFileId"`
|
||||
// FileUploadUrl 上传文件数据的URL路径
|
||||
FileUploadUrl string `json:"fileUploadUrl"`
|
||||
// FileCommitUrl 上传文件完成后确认路径
|
||||
FileCommitUrl string `json:"fileCommitUrl"`
|
||||
// FileDataExists 文件是否已存在云盘中,0-未存在,1-已存在
|
||||
FileDataExists int `json:"fileDataExists"`
|
||||
}
|
||||
|
||||
type UploadFileStatusResult struct {
|
||||
// 上传文件的ID
|
||||
UploadFileId int64 `json:"uploadFileId"`
|
||||
// 已上传的大小
|
||||
DataSize int64 `json:"dataSize"`
|
||||
FileUploadUrl string `json:"fileUploadUrl"`
|
||||
FileCommitUrl string `json:"fileCommitUrl"`
|
||||
FileDataExists int `json:"fileDataExists"`
|
||||
}
|
||||
*/
|
||||
|
||||
type InitMultiUploadResp struct {
|
||||
//Code string `json:"code"`
|
||||
Data struct {
|
||||
UploadType int `json:"uploadType"`
|
||||
UploadHost string `json:"uploadHost"`
|
||||
UploadFileID string `json:"uploadFileId"`
|
||||
FileDataExists int `json:"fileDataExists"`
|
||||
} `json:"data"`
|
||||
}
|
||||
type UploadUrlsResp struct {
|
||||
Code string `json:"code"`
|
||||
UploadUrls map[string]Part `json:"uploadUrls"`
|
||||
}
|
||||
|
||||
type Part struct {
|
||||
RequestURL string `json:"requestURL"`
|
||||
RequestHeader string `json:"requestHeader"`
|
||||
}
|
177
drivers/189pc/util.go
Normal file
177
drivers/189pc/util.go
Normal file
@ -0,0 +1,177 @@
|
||||
package _189
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
rand2 "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Xhofe/alist/model"
|
||||
)
|
||||
|
||||
const (
|
||||
APP_ID = "8025431004"
|
||||
CLIENT_TYPE = "10020"
|
||||
VERSION = "6.2"
|
||||
|
||||
WEB_URL = "https://cloud.189.cn"
|
||||
AUTH_URL = "https://open.e.189.cn"
|
||||
API_URL = "https://api.cloud.189.cn"
|
||||
UPLOAD_URL = "https://upload.cloud.189.cn"
|
||||
|
||||
RETURN_URL = "https://m.cloud.189.cn/zhuanti/2020/loginErrorPc/index.html"
|
||||
|
||||
PC = "TELEPC"
|
||||
MAC = "TELEMAC"
|
||||
|
||||
CHANNEL_ID = "web_cloud.189.cn"
|
||||
)
|
||||
|
||||
func clientSuffix() map[string]string {
|
||||
return map[string]string{
|
||||
"clientType": PC,
|
||||
"version": VERSION,
|
||||
"channelId": CHANNEL_ID,
|
||||
"rand": fmt.Sprintf("%d_%d", rand2.Int63n(1e5), rand2.Int63n(1e10)),
|
||||
}
|
||||
}
|
||||
|
||||
// 带params的SignatureOfHmac HMAC签名
|
||||
func signatureOfHmac(sessionSecret, sessionKey, operate, fullUrl, dateOfGmt, param string) string {
|
||||
u, _ := url.Parse(fullUrl)
|
||||
mac := hmac.New(sha1.New, []byte(sessionSecret))
|
||||
data := fmt.Sprintf("SessionKey=%s&Operate=%s&RequestURI=%s&Date=%s", sessionKey, operate, u.Path, dateOfGmt)
|
||||
if param != "" {
|
||||
data += fmt.Sprintf("¶ms=%s", param)
|
||||
}
|
||||
mac.Write([]byte(data))
|
||||
return strings.ToUpper(hex.EncodeToString(mac.Sum(nil)))
|
||||
}
|
||||
|
||||
// 获取http规范的时间
|
||||
func getHttpDateStr() string {
|
||||
return time.Now().UTC().Format(http.TimeFormat)
|
||||
}
|
||||
|
||||
// RAS 加密用户名密码
|
||||
func rsaEncrypt(publicKey, origData string) string {
|
||||
block, _ := pem.Decode([]byte(publicKey))
|
||||
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
data, _ := rsa.EncryptPKCS1v15(rand.Reader, pubInterface.(*rsa.PublicKey), []byte(origData))
|
||||
return base64ToHex(base64.StdEncoding.EncodeToString(data))
|
||||
}
|
||||
|
||||
// aes 加密params
|
||||
func AesECBEncrypt(data, key string) string {
|
||||
block, _ := aes.NewCipher([]byte(key))
|
||||
paddingData := PKCS7Padding([]byte(data), block.BlockSize())
|
||||
decrypted := make([]byte, len(paddingData))
|
||||
size := block.BlockSize()
|
||||
for src, dst := paddingData, decrypted; len(src) > 0; src, dst = src[size:], dst[size:] {
|
||||
block.Encrypt(dst[:size], src[:size])
|
||||
}
|
||||
return strings.ToUpper(hex.EncodeToString(decrypted))
|
||||
}
|
||||
|
||||
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(ciphertext)%blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padtext...)
|
||||
}
|
||||
|
||||
// 时间戳
|
||||
func timestamp() int64 {
|
||||
return time.Now().UTC().UnixNano() / 1e6
|
||||
}
|
||||
|
||||
func base64ToHex(a string) string {
|
||||
v, _ := base64.StdEncoding.DecodeString(a)
|
||||
return strings.ToUpper(hex.EncodeToString(v))
|
||||
}
|
||||
|
||||
func isFamily(account *model.Account) bool {
|
||||
return account.InternalType == "Family"
|
||||
}
|
||||
|
||||
func toFamilyOrderBy(o string) string {
|
||||
switch o {
|
||||
case "filename":
|
||||
return "1"
|
||||
case "filesize":
|
||||
return "2"
|
||||
case "lastOpTime":
|
||||
return "3"
|
||||
default:
|
||||
return "1"
|
||||
}
|
||||
}
|
||||
|
||||
func ParseHttpHeader(str string) map[string]string {
|
||||
header := make(map[string]string)
|
||||
for _, value := range strings.Split(str, "&") {
|
||||
i := strings.Index(value, "=")
|
||||
header[strings.TrimSpace(value[0:i])] = strings.TrimSpace(value[i+1:])
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
func MustString(str string, err error) string {
|
||||
return str
|
||||
}
|
||||
|
||||
func MustToBytes(b []byte, err error) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
func BoolToNumber(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func MustParseTime(str string) *time.Time {
|
||||
loc, _ := time.LoadLocation("Local")
|
||||
lastOpTime, _ := time.ParseInLocation("2006-01-02 15:04:05", str, loc)
|
||||
return &lastOpTime
|
||||
}
|
||||
|
||||
type Params map[string]string
|
||||
|
||||
func (p Params) Set(k, v string) {
|
||||
p[k] = v
|
||||
}
|
||||
|
||||
func (p Params) Encode() string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
var buf strings.Builder
|
||||
keys := make([]string, 0, len(p))
|
||||
for k := range p {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
buf.WriteString(k)
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(p[k])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
@ -3,44 +3,17 @@ package alidrive
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var aliClient = resty.New()
|
||||
|
||||
type AliRespError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type AliFiles struct {
|
||||
Items []AliFile `json:"items"`
|
||||
NextMarker string `json:"next_marker"`
|
||||
}
|
||||
|
||||
type AliFile struct {
|
||||
DriveId string `json:"drive_id"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
FileExtension string `json:"file_extension"`
|
||||
FileId string `json:"file_id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
ParentFileId string `json:"parent_file_id"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
Size int64 `json:"size"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
func (driver AliDrive) FormatFile(file *AliFile) *model.File {
|
||||
f := &model.File{
|
||||
Id: file.FileId,
|
||||
@ -51,17 +24,7 @@ func (driver AliDrive) FormatFile(file *AliFile) *model.File {
|
||||
Driver: driver.Config().Name,
|
||||
Url: file.Url,
|
||||
}
|
||||
if file.Type == "folder" {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(file.FileExtension)
|
||||
}
|
||||
if file.Category == "video" {
|
||||
f.Type = conf.VIDEO
|
||||
}
|
||||
if file.Category == "image" {
|
||||
f.Type = conf.IMAGE
|
||||
}
|
||||
f.Type = file.GetType()
|
||||
return f
|
||||
}
|
||||
|
||||
@ -190,7 +153,7 @@ func (driver AliDrive) rename(fileId, name string, account *model.Account) error
|
||||
return fmt.Errorf("%+v", resp)
|
||||
}
|
||||
|
||||
func (driver AliDrive) batch(srcId, dstId string, account *model.Account) error {
|
||||
func (driver AliDrive) batch(srcId, dstId string, url string, account *model.Account) error {
|
||||
var e AliRespError
|
||||
res, err := aliClient.R().SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
@ -208,6 +171,7 @@ func (driver AliDrive) batch(srcId, dstId string, account *model.Account) error
|
||||
"to_drive_id": account.DriveId,
|
||||
"to_parent_file_id": dstId,
|
||||
},
|
||||
"url": url,
|
||||
},
|
||||
},
|
||||
"resource": "file",
|
||||
@ -222,12 +186,13 @@ func (driver AliDrive) batch(srcId, dstId string, account *model.Account) error
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.batch(srcId, dstId, account)
|
||||
return driver.batch(srcId, dstId, url, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if strings.Contains(res.String(), `"status":200`) {
|
||||
status := jsoniter.Get(res.Body(), "responses", 0, "status").ToInt()
|
||||
if status < 400 && status >= 100 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(res.String())
|
||||
@ -236,6 +201,7 @@ func (driver AliDrive) batch(srcId, dstId string, account *model.Account) error
|
||||
func init() {
|
||||
base.RegisterDriver(&AliDrive{})
|
||||
aliClient.
|
||||
SetTimeout(base.DefaultTimeout).
|
||||
SetRetryCount(3).
|
||||
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36").
|
||||
SetHeader("content-type", "application/json").
|
||||
|
@ -2,19 +2,25 @@ package alidrive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/robfig/cron/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type AliDrive struct{}
|
||||
@ -60,6 +66,11 @@ func (driver AliDrive) Items() []base.Item {
|
||||
Required: false,
|
||||
Description: ">0 and <=200",
|
||||
},
|
||||
{
|
||||
Name: "bool_1",
|
||||
Label: "fast upload",
|
||||
Type: base.TypeBool,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,15 +99,15 @@ func (driver AliDrive) Save(account *model.Account, old *model.Account) error {
|
||||
log.Debugf("user info: %+v", resp)
|
||||
account.DriveId = resp["default_drive_id"].(string)
|
||||
cronId, err := conf.Cron.AddFunc("@every 2h", func() {
|
||||
name := account.Name
|
||||
log.Debugf("ali account name: %s", name)
|
||||
newAccount, ok := model.GetAccount(name)
|
||||
id := account.ID
|
||||
log.Debugf("ali account id: %d", id)
|
||||
newAccount, err := model.GetAccountById(id)
|
||||
log.Debugf("ali account: %+v", newAccount)
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = driver.RefreshToken(&newAccount)
|
||||
_ = model.SaveAccount(&newAccount)
|
||||
err = driver.RefreshToken(newAccount)
|
||||
_ = model.SaveAccount(newAccount)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -191,6 +202,12 @@ func (driver AliDrive) Link(args base.Args, account *model.Account) (*base.Link,
|
||||
return nil, fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
return &base.Link{
|
||||
Headers: []base.Header{
|
||||
{
|
||||
Name: "Referer",
|
||||
Value: "https://www.aliyundrive.com/",
|
||||
},
|
||||
},
|
||||
Url: resp["url"].(string),
|
||||
}, nil
|
||||
}
|
||||
@ -212,10 +229,10 @@ func (driver AliDrive) Path(path string, account *model.Account) (*model.File, [
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver AliDrive) Proxy(c *gin.Context, account *model.Account) {
|
||||
c.Request.Header.Del("Origin")
|
||||
c.Request.Header.Set("Referer", "https://www.aliyundrive.com/")
|
||||
}
|
||||
//func (driver AliDrive) Proxy(r *http.Request, account *model.Account) {
|
||||
// r.Header.Del("Origin")
|
||||
// r.Header.Set("Referer", "https://www.aliyundrive.com/")
|
||||
//}
|
||||
|
||||
func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
file, err := driver.GetFile(path, account)
|
||||
@ -304,7 +321,7 @@ func (driver AliDrive) Move(src string, dst string, account *model.Account) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = driver.batch(srcFile.Id, dstDirFile.Id, account)
|
||||
err = driver.batch(srcFile.Id, dstDirFile.Id, "/file/move", account)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -319,7 +336,17 @@ func (driver AliDrive) Rename(src string, dst string, account *model.Account) er
|
||||
}
|
||||
|
||||
func (driver AliDrive) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotSupport
|
||||
dstDir, _ := filepath.Split(dst)
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstDirFile, err := driver.File(dstDir, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = driver.batch(srcFile.Id, dstDirFile.Id, "/file/copy", account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver AliDrive) Delete(path string, account *model.Account) error {
|
||||
@ -349,7 +376,7 @@ func (driver AliDrive) Delete(path string, account *model.Account) error {
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if res.StatusCode() == 204 {
|
||||
if res.StatusCode() < 400 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(res.String())
|
||||
@ -361,15 +388,15 @@ type UploadResp struct {
|
||||
PartInfoList []struct {
|
||||
UploadUrl string `json:"upload_url"`
|
||||
} `json:"part_info_list"`
|
||||
|
||||
RapidUpload bool `json:"rapid_upload"`
|
||||
}
|
||||
|
||||
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
const DEFAULT uint64 = 10485760
|
||||
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||
var finish uint64 = 0
|
||||
|
||||
parentFile, err := driver.File(file.ParentPath, account)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -377,32 +404,44 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
|
||||
if !parentFile.IsDir() {
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
|
||||
const DEFAULT int64 = 10485760
|
||||
var count = int(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||
|
||||
partInfoList := make([]base.Json, 0, count)
|
||||
for i := 1; i <= count; i++ {
|
||||
partInfoList = append(partInfoList, base.Json{"part_number": i})
|
||||
}
|
||||
|
||||
reqBody := base.Json{
|
||||
"check_name_mode": "overwrite",
|
||||
"drive_id": account.DriveId,
|
||||
"name": file.GetFileName(),
|
||||
"parent_file_id": parentFile.Id,
|
||||
"part_info_list": partInfoList,
|
||||
"size": file.GetSize(),
|
||||
"type": "file",
|
||||
}
|
||||
|
||||
if account.Bool1 {
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := file.Read(buf[:])
|
||||
reqBody["pre_hash"] = utils.GetSHA1Encode(string(buf[:n]))
|
||||
file.File = io.NopCloser(io.MultiReader(bytes.NewReader(buf[:n]), file.File))
|
||||
} else {
|
||||
reqBody["content_hash_name"] = "none"
|
||||
reqBody["proof_version"] = "v1"
|
||||
}
|
||||
|
||||
var resp UploadResp
|
||||
var e AliRespError
|
||||
partInfoList := make([]base.Json, 0)
|
||||
var i int64
|
||||
for i = 0; i < count; i++ {
|
||||
partInfoList = append(partInfoList, base.Json{
|
||||
"part_number": i + 1,
|
||||
})
|
||||
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
|
||||
}
|
||||
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"check_name_mode": "auto_rename",
|
||||
// content_hash
|
||||
"content_hash_name": "none",
|
||||
"drive_id": account.DriveId,
|
||||
"name": file.GetFileName(),
|
||||
"parent_file_id": parentFile.Id,
|
||||
"part_info_list": partInfoList,
|
||||
//proof_code
|
||||
"proof_version": "v1",
|
||||
"size": file.GetSize(),
|
||||
"type": "file",
|
||||
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders") // /v2/file/create_with_proof
|
||||
//log.Debugf("%+v\n%+v", resp, e)
|
||||
if e.Code != "" {
|
||||
if e.Code != "" && e.Code != "PreHashMatched" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
@ -414,26 +453,60 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
var byteSize uint64
|
||||
for i = 0; i < count; i++ {
|
||||
byteSize = file.GetSize() - finish
|
||||
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 e.Code == "PreHashMatched" && account.Bool1 {
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
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(io.MultiWriter(tempFile, h), file.File); 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)))
|
||||
*/
|
||||
buf := make([]byte, 8)
|
||||
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
|
||||
}
|
||||
file.File = tempFile
|
||||
}
|
||||
|
||||
for _, partInfo := range resp.PartInfoList {
|
||||
req, err := http.NewRequest("PUT", partInfo.UploadUrl, io.LimitReader(file.File, DEFAULT))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -458,7 +531,10 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
|
||||
"file_id": resp.FileId,
|
||||
"upload_id": resp.UploadId,
|
||||
}).Post("https://api.aliyundrive.com/v2/file/complete")
|
||||
if e.Code != "" {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" && e.Code != "PreHashMatched" {
|
||||
//if e.Code == "AccessTokenInvalid" {
|
||||
// err = driver.RefreshToken(account)
|
||||
// if err != nil {
|
||||
|
53
drivers/alidrive/types.go
Normal file
53
drivers/alidrive/types.go
Normal file
@ -0,0 +1,53 @@
|
||||
package alidrive
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AliRespError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type AliFiles struct {
|
||||
Items []AliFile `json:"items"`
|
||||
NextMarker string `json:"next_marker"`
|
||||
}
|
||||
|
||||
type AliFile struct {
|
||||
DriveId string `json:"drive_id"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
FileExtension string `json:"file_extension"`
|
||||
FileId string `json:"file_id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
ParentFileId string `json:"parent_file_id"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
Size int64 `json:"size"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
func (f AliFile) GetSize() uint64 {
|
||||
return uint64(f.Size)
|
||||
}
|
||||
|
||||
func (f AliFile) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func (f AliFile) GetType() int {
|
||||
if f.Type == "folder" {
|
||||
return conf.FOLDER
|
||||
}
|
||||
if f.Category == "video" {
|
||||
return conf.VIDEO
|
||||
}
|
||||
if f.Category == "image" {
|
||||
return conf.IMAGE
|
||||
}
|
||||
return utils.GetFileType(f.FileExtension)
|
||||
}
|
@ -7,7 +7,6 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@ -148,7 +147,7 @@ func (driver Alist) Path(path string, account *model.Account) (*model.File, []mo
|
||||
return nil, resp.Data.Files, nil
|
||||
}
|
||||
|
||||
func (driver Alist) Proxy(c *gin.Context, account *model.Account) {}
|
||||
//func (driver Alist) Proxy(r *http.Request, account *model.Account) {}
|
||||
|
||||
func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
var resp PathResp
|
||||
|
@ -4,8 +4,10 @@ import (
|
||||
_ "github.com/Xhofe/alist/drivers/123"
|
||||
_ "github.com/Xhofe/alist/drivers/139"
|
||||
_ "github.com/Xhofe/alist/drivers/189"
|
||||
_ "github.com/Xhofe/alist/drivers/189pc"
|
||||
_ "github.com/Xhofe/alist/drivers/alidrive"
|
||||
_ "github.com/Xhofe/alist/drivers/alist"
|
||||
_ "github.com/Xhofe/alist/drivers/baidu"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
_ "github.com/Xhofe/alist/drivers/ftp"
|
||||
_ "github.com/Xhofe/alist/drivers/google"
|
||||
@ -14,10 +16,15 @@ import (
|
||||
_ "github.com/Xhofe/alist/drivers/native"
|
||||
_ "github.com/Xhofe/alist/drivers/onedrive"
|
||||
_ "github.com/Xhofe/alist/drivers/pikpak"
|
||||
_ "github.com/Xhofe/alist/drivers/quark"
|
||||
_ "github.com/Xhofe/alist/drivers/s3"
|
||||
_ "github.com/Xhofe/alist/drivers/sftp"
|
||||
_ "github.com/Xhofe/alist/drivers/shandian"
|
||||
_ "github.com/Xhofe/alist/drivers/teambition"
|
||||
_ "github.com/Xhofe/alist/drivers/uss"
|
||||
_ "github.com/Xhofe/alist/drivers/webdav"
|
||||
_ "github.com/Xhofe/alist/drivers/xunlei"
|
||||
_ "github.com/Xhofe/alist/drivers/yandex"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"strings"
|
||||
)
|
||||
|
185
drivers/baidu/baidu.go
Normal file
185
drivers/baidu/baidu.go
Normal file
@ -0,0 +1,185 @@
|
||||
package baidu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (driver Baidu) RefreshToken(account *model.Account) error {
|
||||
err := driver.refreshToken(account)
|
||||
if err != nil && err == base.ErrEmptyToken {
|
||||
err = driver.refreshToken(account)
|
||||
}
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Baidu) refreshToken(account *model.Account) error {
|
||||
u := "https://openapi.baidu.com/oauth/2.0/token"
|
||||
var resp base.TokenResp
|
||||
var e TokenErrResp
|
||||
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": account.RefreshToken,
|
||||
"client_id": account.ClientId,
|
||||
"client_secret": account.ClientSecret,
|
||||
}).Get(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Error != "" {
|
||||
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||
}
|
||||
if resp.RefreshToken == "" {
|
||||
return base.ErrEmptyToken
|
||||
}
|
||||
account.Status = "work"
|
||||
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver Baidu) Request(fullurl string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetQueryParam("access_token", account.AccessToken)
|
||||
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 res *resty.Response
|
||||
var err error
|
||||
switch method {
|
||||
case base.Get:
|
||||
res, err = req.Get(fullurl)
|
||||
case base.Post:
|
||||
res, err = req.Post(fullurl)
|
||||
case base.Patch:
|
||||
res, err = req.Patch(fullurl)
|
||||
case base.Delete:
|
||||
res, err = req.Delete(fullurl)
|
||||
case base.Put:
|
||||
res, err = req.Put(fullurl)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//log.Debug(res.String())
|
||||
errno := jsoniter.Get(res.Body(), "errno").ToInt()
|
||||
if errno != 0 {
|
||||
if errno == -6 {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver.Request(fullurl, method, headers, query, form, data, resp, account)
|
||||
}
|
||||
return nil, fmt.Errorf("errno: %d, refer to https://pan.baidu.com/union/doc/", errno)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (driver Baidu) Get(pathname string, params map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
return driver.Request("https://pan.baidu.com/rest/2.0"+pathname, base.Get, nil, params, nil, nil, resp, account)
|
||||
}
|
||||
|
||||
func (driver Baidu) Post(pathname string, params map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
return driver.Request("https://pan.baidu.com/rest/2.0"+pathname, base.Post, nil, params, nil, data, resp, account)
|
||||
}
|
||||
|
||||
func (driver Baidu) manage(opera string, filelist interface{}, account *model.Account) ([]byte, error) {
|
||||
params := map[string]string{
|
||||
"method": "filemanager",
|
||||
"opera": opera,
|
||||
}
|
||||
marshal, err := utils.Json.Marshal(filelist)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := fmt.Sprintf("async=0&filelist=%s&ondup=newcopy", string(marshal))
|
||||
return driver.Post("/xpan/file", params, data, nil, account)
|
||||
}
|
||||
|
||||
func (driver Baidu) GetFiles(dir string, account *model.Account) ([]model.File, error) {
|
||||
dir = utils.Join(account.RootFolder, dir)
|
||||
start := 0
|
||||
limit := 200
|
||||
params := map[string]string{
|
||||
"method": "list",
|
||||
"dir": dir,
|
||||
"web": "web",
|
||||
}
|
||||
if account.OrderBy != "" {
|
||||
params["order"] = account.OrderBy
|
||||
if account.OrderDirection == "desc" {
|
||||
params["desc"] = "1"
|
||||
}
|
||||
}
|
||||
res := make([]model.File, 0)
|
||||
for {
|
||||
params["start"] = strconv.Itoa(start)
|
||||
params["limit"] = strconv.Itoa(limit)
|
||||
start += limit
|
||||
var resp ListResp
|
||||
_, err := driver.Get("/xpan/file", params, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.List) == 0 {
|
||||
break
|
||||
}
|
||||
for _, f := range resp.List {
|
||||
file := model.File{
|
||||
Id: strconv.FormatInt(f.FsId, 10),
|
||||
Name: f.ServerFilename,
|
||||
Size: f.Size,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: getTime(f.ServerMtime),
|
||||
Thumbnail: f.Thumbs.Url3,
|
||||
}
|
||||
if f.Isdir == 1 {
|
||||
file.Type = conf.FOLDER
|
||||
} else {
|
||||
file.Type = utils.GetFileType(path.Ext(f.ServerFilename))
|
||||
}
|
||||
res = append(res, file)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (driver Baidu) create(path string, size uint64, isdir int, uploadid, block_list string, account *model.Account) ([]byte, error) {
|
||||
params := map[string]string{
|
||||
"method": "create",
|
||||
}
|
||||
data := fmt.Sprintf("path=%s&size=%d&isdir=%d", path, size, isdir)
|
||||
if uploadid != "" {
|
||||
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
|
||||
}
|
||||
return driver.Post("/xpan/file", params, data, nil, account)
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Baidu{})
|
||||
}
|
399
drivers/baidu/driver.go
Normal file
399
drivers/baidu/driver.go
Normal file
@ -0,0 +1,399 @@
|
||||
package baidu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"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"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Baidu struct{}
|
||||
|
||||
func (driver Baidu) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Baidu.Disk",
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Baidu) Items() []base.Item {
|
||||
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,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Default: "name",
|
||||
Values: "name,time,size",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: base.TypeSelect,
|
||||
Values: "asc,desc",
|
||||
Default: "asc",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "internal_type",
|
||||
Label: "download api",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "official,crack",
|
||||
Default: "official",
|
||||
},
|
||||
{
|
||||
Name: "client_id",
|
||||
Label: "client id",
|
||||
Default: "iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "client_secret",
|
||||
Label: "client secret",
|
||||
Default: "jXiFMOPVPCWlO2M5CwWQzffpNPaGTRBG",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Baidu) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
return driver.RefreshToken(account)
|
||||
}
|
||||
|
||||
func (driver Baidu) File(path string, account *model.Account) (*model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
if path == "/" {
|
||||
return &model.File{
|
||||
Id: account.RootFolder,
|
||||
Name: account.Name,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
dir, name := 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 Baidu) 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
|
||||
}
|
||||
files, err := driver.GetFiles(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Baidu) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
if account.InternalType == "crack" {
|
||||
return driver.LinkCrack(args, account)
|
||||
}
|
||||
return driver.LinkOfficial(args, account)
|
||||
}
|
||||
|
||||
func (driver Baidu) LinkOfficial(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(args.Path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.IsDir() {
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
var resp DownloadResp
|
||||
params := map[string]string{
|
||||
"method": "filemetas",
|
||||
"fsids": fmt.Sprintf("[%s]", file.Id),
|
||||
"dlink": "1",
|
||||
}
|
||||
_, err = driver.Get("/xpan/multimedia", params, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := fmt.Sprintf("%s&access_token=%s", resp.List[0].Dlink, account.AccessToken)
|
||||
res, err := base.NoRedirectClient.R().SetHeader("User-Agent", "pan.baidu.com").Head(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//if res.StatusCode() == 302 {
|
||||
u = res.Header().Get("location")
|
||||
//}
|
||||
return &base.Link{
|
||||
Url: u,
|
||||
Headers: []base.Header{
|
||||
{Name: "User-Agent", Value: "pan.baidu.com"},
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (driver Baidu) LinkCrack(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(args.Path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.IsDir() {
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
var resp DownloadResp2
|
||||
param := map[string]string{
|
||||
"target": fmt.Sprintf("[\"%s\"]", utils.Join(account.RootFolder, args.Path)),
|
||||
"dlink": "1",
|
||||
"web": "5",
|
||||
"origin": "dlna",
|
||||
}
|
||||
_, err = driver.Request("https://pan.baidu.com/api/filemetas", base.Get, nil, param, nil, nil, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &base.Link{
|
||||
Url: resp.Info[0].Dlink,
|
||||
Headers: []base.Header{
|
||||
{Name: "User-Agent", Value: "pan.baidu.com"},
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (driver Baidu) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !file.IsDir() {
|
||||
return file, nil, nil
|
||||
}
|
||||
files, err := driver.Files(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
//func (driver Baidu) Proxy(r *http.Request, account *model.Account) {
|
||||
// r.Header.Set("User-Agent", "pan.baidu.com")
|
||||
//}
|
||||
|
||||
func (driver Baidu) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Baidu) MakeDir(path string, account *model.Account) error {
|
||||
_, err := driver.create(utils.Join(account.RootFolder, path), 0, 1, "", "", account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Baidu) Move(src string, dst string, account *model.Account) error {
|
||||
path := utils.Join(account.RootFolder, src)
|
||||
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
|
||||
data := []base.Json{
|
||||
{
|
||||
"path": path,
|
||||
"dest": dest,
|
||||
"newname": newname,
|
||||
},
|
||||
}
|
||||
_, err := driver.manage("move", data, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Baidu) Rename(src string, dst string, account *model.Account) error {
|
||||
path := utils.Join(account.RootFolder, src)
|
||||
newname := utils.Base(dst)
|
||||
data := []base.Json{
|
||||
{
|
||||
"path": path,
|
||||
"newname": newname,
|
||||
},
|
||||
}
|
||||
_, err := driver.manage("rename", data, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Baidu) Copy(src string, dst string, account *model.Account) error {
|
||||
path := utils.Join(account.RootFolder, src)
|
||||
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
|
||||
data := []base.Json{
|
||||
{
|
||||
"path": path,
|
||||
"dest": dest,
|
||||
"newname": newname,
|
||||
},
|
||||
}
|
||||
_, err := driver.manage("copy", data, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Baidu) Delete(path string, account *model.Account) error {
|
||||
path = utils.Join(account.RootFolder, path)
|
||||
data := []string{path}
|
||||
_, err := driver.manage("delete", data, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Baidu) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = tempFile.Close()
|
||||
_ = os.Remove(tempFile.Name())
|
||||
}()
|
||||
_, err = io.Copy(tempFile, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tempFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var Default uint64 = 4 * 1024 * 1024
|
||||
defaultByteData := make([]byte, Default)
|
||||
count := int(math.Ceil(float64(file.GetSize()) / float64(Default)))
|
||||
var SliceSize uint64 = 256 * 1024
|
||||
// cal md5
|
||||
h1 := md5.New()
|
||||
h2 := md5.New()
|
||||
block_list := make([]string, 0)
|
||||
content_md5 := ""
|
||||
slice_md5 := ""
|
||||
left := file.GetSize()
|
||||
for i := 0; i < count; i++ {
|
||||
byteSize := Default
|
||||
var byteData []byte
|
||||
if left < Default {
|
||||
byteSize = left
|
||||
byteData = make([]byte, byteSize)
|
||||
} else {
|
||||
byteData = defaultByteData
|
||||
}
|
||||
left -= byteSize
|
||||
_, err = io.ReadFull(tempFile, byteData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h1.Write(byteData)
|
||||
h2.Write(byteData)
|
||||
block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil))))
|
||||
h2.Reset()
|
||||
}
|
||||
content_md5 = hex.EncodeToString(h1.Sum(nil))
|
||||
_, err = tempFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if file.GetSize() <= SliceSize {
|
||||
slice_md5 = content_md5
|
||||
} else {
|
||||
sliceData := make([]byte, SliceSize)
|
||||
_, err = io.ReadFull(tempFile, sliceData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h2.Write(sliceData)
|
||||
slice_md5 = hex.EncodeToString(h2.Sum(nil))
|
||||
_, err = tempFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
path := encodeURIComponent(utils.Join(account.RootFolder, file.ParentPath, file.Name))
|
||||
block_list_str := fmt.Sprintf("[%s]", strings.Join(block_list, ","))
|
||||
data := fmt.Sprintf("path=%s&size=%d&isdir=0&autoinit=1&block_list=%s&content-md5=%s&slice-md5=%s",
|
||||
path, file.GetSize(),
|
||||
block_list_str,
|
||||
content_md5, slice_md5)
|
||||
params := map[string]string{
|
||||
"method": "precreate",
|
||||
}
|
||||
var precreateResp PrecreateResp
|
||||
_, err = driver.Post("/xpan/file", params, data, &precreateResp, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("%+v", precreateResp)
|
||||
if precreateResp.ReturnType == 2 {
|
||||
return nil
|
||||
}
|
||||
params = map[string]string{
|
||||
"method": "upload",
|
||||
"access_token": account.AccessToken,
|
||||
"type": "tmpfile",
|
||||
"path": path,
|
||||
"uploadid": precreateResp.Uploadid,
|
||||
}
|
||||
left = file.GetSize()
|
||||
for _, partseq := range precreateResp.BlockList {
|
||||
byteSize := Default
|
||||
var byteData []byte
|
||||
if left < Default {
|
||||
byteSize = left
|
||||
byteData = make([]byte, byteSize)
|
||||
} else {
|
||||
byteData = defaultByteData
|
||||
}
|
||||
left -= byteSize
|
||||
_, err = io.ReadFull(tempFile, byteData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u := "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2"
|
||||
params["partseq"] = strconv.Itoa(partseq)
|
||||
res, err := base.RestyClient.R().SetQueryParams(params).SetFileReader("file", file.Name, bytes.NewReader(byteData)).Post(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugln(res.String())
|
||||
}
|
||||
_, err = driver.create(path, file.GetSize(), 0, precreateResp.Uploadid, block_list_str, account)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Baidu)(nil)
|
143
drivers/baidu/types.go
Normal file
143
drivers/baidu/types.go
Normal file
@ -0,0 +1,143 @@
|
||||
package baidu
|
||||
|
||||
type TokenErrResp struct {
|
||||
ErrorDescription string `json:"error_description"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
//TkbindId int `json:"tkbind_id"`
|
||||
//OwnerType int `json:"owner_type"`
|
||||
//Category int `json:"category"`
|
||||
//RealCategory string `json:"real_category"`
|
||||
FsId int64 `json:"fs_id"`
|
||||
ServerMtime int64 `json:"server_mtime"`
|
||||
//OperId int `json:"oper_id"`
|
||||
//ServerCtime int `json:"server_ctime"`
|
||||
Thumbs struct {
|
||||
//Icon string `json:"icon"`
|
||||
Url3 string `json:"url3"`
|
||||
//Url2 string `json:"url2"`
|
||||
//Url1 string `json:"url1"`
|
||||
} `json:"thumbs"`
|
||||
//Wpfile int `json:"wpfile"`
|
||||
//LocalMtime int `json:"local_mtime"`
|
||||
Size int64 `json:"size"`
|
||||
//ExtentTinyint7 int `json:"extent_tinyint7"`
|
||||
Path string `json:"path"`
|
||||
//Share int `json:"share"`
|
||||
//ServerAtime int `json:"server_atime"`
|
||||
//Pl int `json:"pl"`
|
||||
//LocalCtime int `json:"local_ctime"`
|
||||
ServerFilename string `json:"server_filename"`
|
||||
//Md5 string `json:"md5"`
|
||||
//OwnerId int `json:"owner_id"`
|
||||
//Unlist int `json:"unlist"`
|
||||
Isdir int `json:"isdir"`
|
||||
}
|
||||
|
||||
type ListResp struct {
|
||||
Errno int `json:"errno"`
|
||||
GuidInfo string `json:"guid_info"`
|
||||
List []File `json:"list"`
|
||||
RequestId int64 `json:"request_id"`
|
||||
Guid int `json:"guid"`
|
||||
}
|
||||
|
||||
type DownloadResp struct {
|
||||
Errmsg string `json:"errmsg"`
|
||||
Errno int `json:"errno"`
|
||||
List []struct {
|
||||
//Category int `json:"category"`
|
||||
//DateTaken int `json:"date_taken,omitempty"`
|
||||
Dlink string `json:"dlink"`
|
||||
//Filename string `json:"filename"`
|
||||
//FsId int64 `json:"fs_id"`
|
||||
//Height int `json:"height,omitempty"`
|
||||
//Isdir int `json:"isdir"`
|
||||
//Md5 string `json:"md5"`
|
||||
//OperId int `json:"oper_id"`
|
||||
//Path string `json:"path"`
|
||||
//ServerCtime int `json:"server_ctime"`
|
||||
//ServerMtime int `json:"server_mtime"`
|
||||
//Size int `json:"size"`
|
||||
//Thumbs struct {
|
||||
// Icon string `json:"icon,omitempty"`
|
||||
// Url1 string `json:"url1,omitempty"`
|
||||
// Url2 string `json:"url2,omitempty"`
|
||||
// Url3 string `json:"url3,omitempty"`
|
||||
//} `json:"thumbs"`
|
||||
//Width int `json:"width,omitempty"`
|
||||
} `json:"list"`
|
||||
//Names struct {
|
||||
//} `json:"names"`
|
||||
RequestId string `json:"request_id"`
|
||||
}
|
||||
|
||||
type DownloadResp2 struct {
|
||||
Errno int `json:"errno"`
|
||||
Info []struct {
|
||||
//ExtentTinyint4 int `json:"extent_tinyint4"`
|
||||
//ExtentTinyint1 int `json:"extent_tinyint1"`
|
||||
//Bitmap string `json:"bitmap"`
|
||||
//Category int `json:"category"`
|
||||
//Isdir int `json:"isdir"`
|
||||
//Videotag int `json:"videotag"`
|
||||
Dlink string `json:"dlink"`
|
||||
//OperID int64 `json:"oper_id"`
|
||||
//PathMd5 int `json:"path_md5"`
|
||||
//Wpfile int `json:"wpfile"`
|
||||
//LocalMtime int `json:"local_mtime"`
|
||||
/*Thumbs struct {
|
||||
Icon string `json:"icon"`
|
||||
URL3 string `json:"url3"`
|
||||
URL2 string `json:"url2"`
|
||||
URL1 string `json:"url1"`
|
||||
} `json:"thumbs"`*/
|
||||
//PlaySource int `json:"play_source"`
|
||||
//Share int `json:"share"`
|
||||
//FileKey string `json:"file_key"`
|
||||
//Errno int `json:"errno"`
|
||||
//LocalCtime int `json:"local_ctime"`
|
||||
//Rotate int `json:"rotate"`
|
||||
//Metadata time.Time `json:"metadata"`
|
||||
//Height int `json:"height"`
|
||||
//SampleRate int `json:"sample_rate"`
|
||||
//Width int `json:"width"`
|
||||
//OwnerType int `json:"owner_type"`
|
||||
//Privacy int `json:"privacy"`
|
||||
//ExtentInt3 int64 `json:"extent_int3"`
|
||||
//RealCategory string `json:"real_category"`
|
||||
//SrcLocation string `json:"src_location"`
|
||||
//MetaInfo string `json:"meta_info"`
|
||||
//ID string `json:"id"`
|
||||
//Duration int `json:"duration"`
|
||||
//FileSize string `json:"file_size"`
|
||||
//Channels int `json:"channels"`
|
||||
//UseSegment int `json:"use_segment"`
|
||||
//ServerCtime int `json:"server_ctime"`
|
||||
//Resolution string `json:"resolution"`
|
||||
//OwnerID int `json:"owner_id"`
|
||||
//ExtraInfo string `json:"extra_info"`
|
||||
//Size int `json:"size"`
|
||||
//FsID int64 `json:"fs_id"`
|
||||
//ExtentTinyint3 int `json:"extent_tinyint3"`
|
||||
//Md5 string `json:"md5"`
|
||||
//Path string `json:"path"`
|
||||
//FrameRate int `json:"frame_rate"`
|
||||
//ExtentTinyint2 int `json:"extent_tinyint2"`
|
||||
//ServerFilename string `json:"server_filename"`
|
||||
//ServerMtime int `json:"server_mtime"`
|
||||
//TkbindID int `json:"tkbind_id"`
|
||||
} `json:"info"`
|
||||
RequestID int64 `json:"request_id"`
|
||||
}
|
||||
|
||||
type PrecreateResp struct {
|
||||
Path string `json:"path"`
|
||||
Uploadid string `json:"uploadid"`
|
||||
ReturnType int `json:"return_type"`
|
||||
BlockList []int `json:"block_list"`
|
||||
Errno int `json:"errno"`
|
||||
RequestId int64 `json:"request_id"`
|
||||
}
|
18
drivers/baidu/util.go
Normal file
18
drivers/baidu/util.go
Normal file
@ -0,0 +1,18 @@
|
||||
package baidu
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getTime(t int64) *time.Time {
|
||||
tm := time.Unix(t, 0)
|
||||
return &tm
|
||||
}
|
||||
|
||||
func encodeURIComponent(str string) string {
|
||||
r := url.QueryEscape(str)
|
||||
r = strings.ReplaceAll(r, "+", "%20")
|
||||
return r
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
@ -9,12 +8,39 @@ import (
|
||||
)
|
||||
|
||||
func KeyCache(path string, account *model.Account) string {
|
||||
path = utils.ParsePath(path)
|
||||
return fmt.Sprintf("%s%s", account.Name, path)
|
||||
//path = utils.ParsePath(path)
|
||||
key := utils.ParsePath(utils.Join(account.Name, path))
|
||||
log.Debugln("cache key: ", key)
|
||||
return key
|
||||
}
|
||||
|
||||
func SetCache(path string, obj interface{}, account *model.Account) error {
|
||||
return conf.Cache.Set(conf.Ctx, KeyCache(path, account), obj, nil)
|
||||
func SaveSearchFiles[T model.ISearchFile](key string, obj []T) {
|
||||
err := model.DeleteSearchFilesByPath(key)
|
||||
if err != nil {
|
||||
log.Errorln("failed create search files", err)
|
||||
return
|
||||
}
|
||||
files := make([]model.SearchFile, len(obj))
|
||||
for i := 0; i < len(obj); i++ {
|
||||
files[i] = model.SearchFile{
|
||||
Path: key,
|
||||
Name: obj[i].GetName(),
|
||||
Size: obj[i].GetSize(),
|
||||
Type: obj[i].GetType(),
|
||||
}
|
||||
}
|
||||
err = model.CreateSearchFiles(files)
|
||||
if err != nil {
|
||||
log.Errorln("failed create search files", err)
|
||||
}
|
||||
}
|
||||
|
||||
func SetCache[T model.ISearchFile](path string, obj []T, account *model.Account) error {
|
||||
key := KeyCache(path, account)
|
||||
if conf.GetBool("enable search") {
|
||||
go SaveSearchFiles(key, obj)
|
||||
}
|
||||
return conf.Cache.Set(conf.Ctx, key, obj, nil)
|
||||
}
|
||||
|
||||
func GetCache(path string, account *model.Account) (interface{}, error) {
|
||||
|
@ -2,10 +2,10 @@ package base
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DriverConfig struct {
|
||||
@ -38,8 +38,8 @@ type Driver interface {
|
||||
Link(args Args, account *model.Account) (*Link, error)
|
||||
// Path 取路径(文件或文件夹)
|
||||
Path(path string, account *model.Account) (*model.File, []model.File, error)
|
||||
// Proxy 代理处理
|
||||
Proxy(c *gin.Context, account *model.Account)
|
||||
// Deprecated Proxy 代理处理
|
||||
//Proxy(r *http.Request, account *model.Account)
|
||||
// Preview 预览
|
||||
Preview(path string, account *model.Account) (interface{}, error)
|
||||
// MakeDir 创建文件夹
|
||||
@ -62,6 +62,7 @@ type Item struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type"`
|
||||
Default string `json:"default"`
|
||||
Values string `json:"values"`
|
||||
Required bool `json:"required"`
|
||||
Description string `json:"description"`
|
||||
@ -86,17 +87,19 @@ func GetDriversMap() map[string]Driver {
|
||||
func GetDrivers() map[string][]Item {
|
||||
res := make(map[string][]Item)
|
||||
for k, v := range driversMap {
|
||||
webdavDirect := Item{
|
||||
Name: "webdav_direct",
|
||||
Label: "webdav direct",
|
||||
Type: TypeBool,
|
||||
Required: true,
|
||||
Description: "Transfer the WebDAV of this account through the native",
|
||||
}
|
||||
if v.Config().OnlyProxy {
|
||||
res[k] = v.Items()
|
||||
res[k] = append([]Item{
|
||||
webdavDirect,
|
||||
}, v.Items()...)
|
||||
} else {
|
||||
res[k] = append([]Item{
|
||||
//{
|
||||
// Name: "allow_proxy",
|
||||
// Label: "allow_proxy",
|
||||
// Type: TypeBool,
|
||||
// Required: true,
|
||||
// Description: "allow proxy",
|
||||
//},
|
||||
{
|
||||
Name: "proxy",
|
||||
Label: "proxy",
|
||||
@ -111,13 +114,14 @@ func GetDrivers() map[string][]Item {
|
||||
Required: true,
|
||||
Description: "Transfer the WebDAV of this account through the server",
|
||||
},
|
||||
webdavDirect,
|
||||
}, v.Items()...)
|
||||
}
|
||||
res[k] = append([]Item{
|
||||
{
|
||||
Name: "down_proxy_url",
|
||||
Label: "down_proxy_url",
|
||||
Type: TypeString,
|
||||
Type: TypeText,
|
||||
},
|
||||
{
|
||||
Name: "extract_folder",
|
||||
@ -160,6 +164,8 @@ func GetDrivers() map[string][]Item {
|
||||
var NoRedirectClient *resty.Client
|
||||
var RestyClient = resty.New()
|
||||
var HttpClient = &http.Client{}
|
||||
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||
var DefaultTimeout = time.Second * 20
|
||||
|
||||
func init() {
|
||||
NoRedirectClient = resty.New().SetRedirectPolicy(
|
||||
@ -167,8 +173,8 @@ func init() {
|
||||
return http.ErrUseLastResponse
|
||||
}),
|
||||
)
|
||||
userAgent := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||
NoRedirectClient.SetHeader("user-agent", userAgent)
|
||||
RestyClient.SetHeader("user-agent", userAgent)
|
||||
NoRedirectClient.SetHeader("user-agent", UserAgent)
|
||||
RestyClient.SetHeader("user-agent", UserAgent)
|
||||
RestyClient.SetRetryCount(3)
|
||||
RestyClient.SetTimeout(DefaultTimeout)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ var (
|
||||
ErrNotFolder = errors.New("not a folder")
|
||||
ErrEmptyFile = errors.New("empty file")
|
||||
ErrRelativePath = errors.New("access using relative path is not allowed")
|
||||
ErrEmptyToken = errors.New("empty token")
|
||||
)
|
||||
|
||||
const (
|
||||
@ -20,6 +21,7 @@ const (
|
||||
TypeSelect = "select"
|
||||
TypeBool = "bool"
|
||||
TypeNumber = "number"
|
||||
TypeText = "text"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -43,7 +45,8 @@ type Header struct {
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
Url string `json:"url"`
|
||||
Headers []Header `json:"headers"`
|
||||
Data io.ReadCloser
|
||||
Url string `json:"url"`
|
||||
Headers []Header `json:"headers"`
|
||||
Data io.ReadCloser
|
||||
FilePath string `json:"path"` // for native
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jlaffaye/ftp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
@ -186,9 +185,9 @@ func (driver FTP) Path(path string, account *model.Account) (*model.File, []mode
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver FTP) Proxy(c *gin.Context, account *model.Account) {
|
||||
|
||||
}
|
||||
//func (driver FTP) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver FTP) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
|
@ -11,7 +11,12 @@ var connMap map[string]*ftp.ServerConn
|
||||
func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) {
|
||||
conn, ok := connMap[account.Name]
|
||||
if ok {
|
||||
return conn, nil
|
||||
_, err := conn.CurrentDir()
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
} else {
|
||||
delete(connMap, account.Name)
|
||||
}
|
||||
}
|
||||
conn, err := ftp.Connect(account.SiteUrl)
|
||||
if err != nil {
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
@ -30,12 +29,14 @@ func (driver GoogleDrive) Items() []base.Item {
|
||||
Label: "client id",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Default: "202264815644.apps.googleusercontent.com",
|
||||
},
|
||||
{
|
||||
Name: "client_secret",
|
||||
Label: "client secret",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Default: "X4Z3ca8xfWDb1Voo-F9a7ZxJ",
|
||||
},
|
||||
{
|
||||
Name: "refresh_token",
|
||||
@ -178,9 +179,9 @@ func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Proxy(c *gin.Context, account *model.Account) {
|
||||
c.Request.Header.Add("Authorization", "Bearer "+account.AccessToken)
|
||||
}
|
||||
//func (driver GoogleDrive) Proxy(r *http.Request, account *model.Account) {
|
||||
// r.Header.Add("Authorization", "Bearer "+account.AccessToken)
|
||||
//}
|
||||
|
||||
func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
|
@ -2,15 +2,10 @@ package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TokenError struct {
|
||||
@ -44,19 +39,6 @@ func (driver GoogleDrive) RefreshToken(account *model.Account) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MimeType string `json:"mimeType"`
|
||||
ModifiedTime *time.Time `json:"modifiedTime"`
|
||||
Size string `json:"size"`
|
||||
ThumbnailLink string `json:"thumbnailLink"`
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) IsDir(mimeType string) bool {
|
||||
return mimeType == "application/vnd.google-apps.folder" || mimeType == "application/vnd.google-apps.shortcut"
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) FormatFile(file *File, account *model.Account) *model.File {
|
||||
f := &model.File{
|
||||
Id: file.Id,
|
||||
@ -65,16 +47,11 @@ func (driver GoogleDrive) FormatFile(file *File, account *model.Account) *model.
|
||||
UpdatedAt: file.ModifiedTime,
|
||||
Url: "",
|
||||
}
|
||||
if driver.IsDir(file.MimeType) {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
size, _ := strconv.ParseInt(file.Size, 10, 64)
|
||||
f.Size = size
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
||||
}
|
||||
f.Size = int64(file.GetSize())
|
||||
f.Type = file.GetType()
|
||||
if file.ThumbnailLink != "" {
|
||||
if account.DownProxyUrl != "" {
|
||||
f.Thumbnail = fmt.Sprintf("%s/%s", account.DownProxyUrl, file.ThumbnailLink)
|
||||
if account.APIProxyUrl != "" {
|
||||
f.Thumbnail = fmt.Sprintf("%s/%s", account.APIProxyUrl, file.ThumbnailLink)
|
||||
} else {
|
||||
f.Thumbnail = file.ThumbnailLink
|
||||
}
|
||||
|
38
drivers/google/types.go
Normal file
38
drivers/google/types.go
Normal file
@ -0,0 +1,38 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MimeType string `json:"mimeType"`
|
||||
ModifiedTime *time.Time `json:"modifiedTime"`
|
||||
Size string `json:"size"`
|
||||
ThumbnailLink string `json:"thumbnailLink"`
|
||||
}
|
||||
|
||||
func (f File) GetSize() uint64 {
|
||||
if f.GetType() == conf.FOLDER {
|
||||
return 0
|
||||
}
|
||||
size, _ := strconv.ParseUint(f.Size, 10, 64)
|
||||
return size
|
||||
}
|
||||
|
||||
func (f File) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func (f File) GetType() int {
|
||||
mimeType := f.MimeType
|
||||
if mimeType == "application/vnd.google-apps.folder" || mimeType == "application/vnd.google-apps.shortcut" {
|
||||
return conf.FOLDER
|
||||
}
|
||||
return utils.GetFileType(path.Ext(f.Name))
|
||||
}
|
@ -5,7 +5,6 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
)
|
||||
@ -126,18 +125,27 @@ func (driver Lanzou) Link(args base.Args, account *model.Account) (*base.Link, e
|
||||
}
|
||||
log.Debugf("down file: %+v", file)
|
||||
downId := file.Id
|
||||
pwd := ""
|
||||
if account.InternalType == "cookie" {
|
||||
downId, err = driver.GetDownPageId(file.Id, account)
|
||||
downId, pwd, err = driver.GetDownPageId(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
url, err := driver.GetLink(downId, account)
|
||||
var url string
|
||||
//if pwd != "" {
|
||||
//url, err = driver.GetLinkWithPassword(downId, pwd, account)
|
||||
//} else {
|
||||
url, err = driver.GetLink(downId, pwd, account)
|
||||
//}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link := base.Link{
|
||||
Url: url,
|
||||
Headers: []base.Header{
|
||||
{Name: "User-Agent", Value: base.UserAgent},
|
||||
},
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
@ -159,9 +167,9 @@ func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []m
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver Lanzou) Proxy(c *gin.Context, account *model.Account) {
|
||||
c.Request.Header.Del("Origin")
|
||||
}
|
||||
//func (driver Lanzou) Proxy(r *http.Request, account *model.Account) {
|
||||
// r.Header.Del("Origin")
|
||||
//}
|
||||
|
||||
func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
|
@ -2,31 +2,15 @@ package lanzou
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var lanzouClient = resty.New()
|
||||
|
||||
type LanZouFile struct {
|
||||
Name string `json:"name"`
|
||||
NameAll string `json:"name_all"`
|
||||
Id string `json:"id"`
|
||||
FolId string `json:"fol_id"`
|
||||
Size string `json:"size"`
|
||||
Time string `json:"time"`
|
||||
Folder bool
|
||||
}
|
||||
|
||||
func (driver *Lanzou) FormatFile(file *LanZouFile) *model.File {
|
||||
now := time.Now()
|
||||
f := &model.File{
|
||||
@ -38,12 +22,11 @@ func (driver *Lanzou) FormatFile(file *LanZouFile) *model.File {
|
||||
UpdatedAt: &now,
|
||||
}
|
||||
if file.Folder {
|
||||
f.Type = conf.FOLDER
|
||||
f.Id = file.FolId
|
||||
} else {
|
||||
f.Name = file.NameAll
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.NameAll))
|
||||
}
|
||||
f.Type = file.GetType()
|
||||
return f
|
||||
}
|
||||
|
||||
@ -58,7 +41,7 @@ func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZo
|
||||
files := make([]LanZouFile, 0)
|
||||
var resp LanZouFilesResp
|
||||
// folders
|
||||
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
SetFormData(map[string]string{
|
||||
"task": "47",
|
||||
"folder_id": folderId,
|
||||
@ -77,7 +60,7 @@ func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZo
|
||||
// files
|
||||
pg := 1
|
||||
for {
|
||||
_, err = lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
_, err = base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
SetFormData(map[string]string{
|
||||
"task": "5",
|
||||
"folder_id": folderId,
|
||||
@ -108,7 +91,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := lanzouClient.R().Get(shareUrl)
|
||||
res, err := base.RestyClient.R().Get(shareUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -121,7 +104,10 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
||||
uid := regexp.MustCompile(`'uid':'(.+?)',`).FindStringSubmatch(res.String())[1]
|
||||
rep := regexp.MustCompile(`'rep':'(.+?)',`).FindStringSubmatch(res.String())[1]
|
||||
up := regexp.MustCompile(`'up':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
ls := regexp.MustCompile(`'ls':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
ls := ""
|
||||
if account.Password != "" {
|
||||
ls = regexp.MustCompile(`'ls':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
}
|
||||
tName := regexp.MustCompile(`'t':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
kName := regexp.MustCompile(`'k':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
t := regexp.MustCompile(`var ` + tName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
||||
@ -129,7 +115,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
||||
pg := 1
|
||||
for {
|
||||
var resp LanZouFilesResp
|
||||
res, err = lanzouClient.R().SetResult(&resp).SetFormData(map[string]string{
|
||||
res, err = base.RestyClient.R().SetResult(&resp).SetFormData(map[string]string{
|
||||
"lx": lx,
|
||||
"fid": fid,
|
||||
"uid": uid,
|
||||
@ -153,6 +139,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
||||
break
|
||||
}
|
||||
pg++
|
||||
time.Sleep(time.Second)
|
||||
files = append(files, resp.Text...)
|
||||
}
|
||||
return files, nil
|
||||
@ -164,29 +151,21 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
||||
//}
|
||||
|
||||
// GetDownPageId 获取下载页面的ID
|
||||
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, error) {
|
||||
var resp LanZouFilesResp
|
||||
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, string, error) {
|
||||
var resp DownPageResp
|
||||
res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
SetFormData(map[string]string{
|
||||
"task": "22",
|
||||
"file_id": fileId,
|
||||
}).Post("https://pc.woozooo.com/doupload.php")
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if resp.Zt != 1 {
|
||||
return "", fmt.Errorf("%v", resp.Info)
|
||||
return "", "", fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
info, ok := resp.Info.(map[string]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
fid, ok := info["f_id"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%v", info["f_id"])
|
||||
}
|
||||
return fid, nil
|
||||
return resp.Info.FId, resp.Info.Pwd, nil
|
||||
}
|
||||
|
||||
type LanzouLinkResp struct {
|
||||
@ -195,33 +174,43 @@ type LanzouLinkResp struct {
|
||||
Zt int `json:"zt"`
|
||||
}
|
||||
|
||||
func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, error) {
|
||||
func (driver *Lanzou) GetLink(downId string, pwd string, account *model.Account) (string, error) {
|
||||
shareUrl := account.SiteUrl
|
||||
u, err := url.Parse(shareUrl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res, err := lanzouClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
||||
log.Debugln(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
||||
res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
iframe := regexp.MustCompile(`<iframe class="ifr2" name=".{2,20}" src="(.+?)"`).FindStringSubmatch(res.String())
|
||||
if len(iframe) == 0 {
|
||||
return "", fmt.Errorf("get down empty page")
|
||||
return driver.GetLinkWithPassword(downId, pwd, res.String(), account)
|
||||
}
|
||||
iframeUrl := "https://wwa.lanzouo.com" + iframe[1]
|
||||
res, err = lanzouClient.R().Get(iframeUrl)
|
||||
iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1])
|
||||
res, err = base.RestyClient.R().Get(iframeUrl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Debugln(res.String())
|
||||
ajaxdata := regexp.MustCompile(`var ajaxdata = '(.+?)'`).FindStringSubmatch(res.String())
|
||||
if len(ajaxdata) == 0 {
|
||||
return "", fmt.Errorf("get iframe empty page")
|
||||
}
|
||||
signs := ajaxdata[1]
|
||||
sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
||||
websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
||||
websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
||||
//sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
||||
sign := regexp.MustCompile(`'sign':'(.+?)',`).FindStringSubmatch(res.String())[1]
|
||||
//websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
||||
websign := ""
|
||||
websignR := regexp.MustCompile(`var websign = '(.+?)'`).FindStringSubmatch(res.String())
|
||||
if len(websignR) > 1 {
|
||||
websign = websignR[1]
|
||||
}
|
||||
//websign := ""
|
||||
//websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
||||
websignkey := regexp.MustCompile(`var websignkey = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
||||
var resp LanzouLinkResp
|
||||
form := map[string]string{
|
||||
"action": "downprocess",
|
||||
@ -232,7 +221,7 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
|
||||
"websignkey": websignkey,
|
||||
}
|
||||
log.Debugf("form: %+v", form)
|
||||
res, err = lanzouClient.R().SetResult(&resp).
|
||||
res, err = base.RestyClient.R().SetResult(&resp).
|
||||
SetHeader("origin", "https://"+u.Host).
|
||||
SetHeader("referer", iframeUrl).
|
||||
SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
|
||||
@ -243,12 +232,40 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
|
||||
if resp.Zt == 1 {
|
||||
return resp.Dom + "/file/" + resp.Url, nil
|
||||
}
|
||||
return "", fmt.Errorf("can't get link")
|
||||
return "", fmt.Errorf("failed get link")
|
||||
}
|
||||
|
||||
func (driver *Lanzou) GetLinkWithPassword(downId string, pwd string, html string, account *model.Account) (string, error) {
|
||||
shareUrl := account.SiteUrl
|
||||
u, err := url.Parse(shareUrl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if html == "" {
|
||||
log.Debugln(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
||||
res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html = res.String()
|
||||
}
|
||||
|
||||
data := regexp.MustCompile(`data : '(.+?)'\+pwd,`).FindStringSubmatch(html)[1] + pwd
|
||||
var resp LanzouLinkResp
|
||||
_, err = base.RestyClient.R().SetResult(&resp).SetHeaders(map[string]string{
|
||||
"Referer": fmt.Sprintf("https://%s/%s", u.Host, downId),
|
||||
"Origin": "https://" + u.Host,
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
}).SetBody(data).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.Zt == 1 {
|
||||
return resp.Dom + "/file/" + resp.Url, nil
|
||||
}
|
||||
return "", fmt.Errorf("failed get link with password")
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Lanzou{})
|
||||
lanzouClient.
|
||||
SetRetryCount(3).
|
||||
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
|
||||
}
|
||||
|
47
drivers/lanzou/types.go
Normal file
47
drivers/lanzou/types.go
Normal file
@ -0,0 +1,47 @@
|
||||
package lanzou
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"path"
|
||||
)
|
||||
|
||||
type LanZouFile struct {
|
||||
Name string `json:"name"`
|
||||
NameAll string `json:"name_all"`
|
||||
Id string `json:"id"`
|
||||
FolId string `json:"fol_id"`
|
||||
Size string `json:"size"`
|
||||
Time string `json:"time"`
|
||||
Folder bool
|
||||
}
|
||||
|
||||
func (f LanZouFile) GetSize() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f LanZouFile) GetName() string {
|
||||
if f.Folder {
|
||||
return f.Name
|
||||
}
|
||||
return f.NameAll
|
||||
}
|
||||
|
||||
func (f LanZouFile) GetType() int {
|
||||
if f.Folder {
|
||||
return conf.FOLDER
|
||||
}
|
||||
return utils.GetFileType(path.Ext(f.NameAll))
|
||||
}
|
||||
|
||||
type DownPageResp struct {
|
||||
Zt int `json:"zt"`
|
||||
Info struct {
|
||||
Pwd string `json:"pwd"`
|
||||
Onof string `json:"onof"`
|
||||
FId string `json:"f_id"`
|
||||
Taoc string `json:"taoc"`
|
||||
IsNewd string `json:"is_newd"`
|
||||
} `json:"info"`
|
||||
Text interface{} `json:"text"`
|
||||
}
|
@ -12,7 +12,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -46,11 +45,12 @@ func (driver MediaTrack) Items() []base.Item {
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "updated_at,title,size",
|
||||
Required: true,
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "updated_at,title,size",
|
||||
Required: true,
|
||||
Description: "title",
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
@ -58,6 +58,7 @@ func (driver MediaTrack) Items() []base.Item {
|
||||
Type: base.TypeSelect,
|
||||
Values: "true,false",
|
||||
Required: true,
|
||||
Default: "false",
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -148,19 +149,15 @@ func (driver MediaTrack) Path(path string, account *model.Account) (*model.File,
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver MediaTrack) Proxy(c *gin.Context, account *model.Account) {
|
||||
|
||||
}
|
||||
//func (driver MediaTrack) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver MediaTrack) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver MediaTrack) MakeDir(path string, account *model.Account) error {
|
||||
_, err := driver.File(path, account)
|
||||
if err != base.ErrPathNotFound {
|
||||
return nil
|
||||
}
|
||||
parentFile, err := driver.File(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -266,7 +263,7 @@ func (driver MediaTrack) Upload(file *model.FileStream, account *model.Account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempFile, err := ioutil.TempFile("data/temp", "file-*")
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -72,7 +71,6 @@ func (driver Native) File(path string, account *model.Account) (*model.File, err
|
||||
time := f.ModTime()
|
||||
file := &model.File{
|
||||
Name: f.Name(),
|
||||
Size: f.Size(),
|
||||
UpdatedAt: &time,
|
||||
Driver: driver.Config().Name,
|
||||
}
|
||||
@ -80,6 +78,7 @@ func (driver Native) File(path string, account *model.Account) (*model.File, err
|
||||
file.Type = conf.FOLDER
|
||||
} else {
|
||||
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
|
||||
file.Size = f.Size()
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
@ -104,7 +103,6 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
|
||||
time := f.ModTime()
|
||||
file := model.File{
|
||||
Name: f.Name(),
|
||||
Size: f.Size(),
|
||||
Type: 0,
|
||||
UpdatedAt: &time,
|
||||
Driver: driver.Config().Name,
|
||||
@ -113,9 +111,14 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
|
||||
file.Type = conf.FOLDER
|
||||
} else {
|
||||
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
|
||||
file.Size = f.Size()
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
_, err = base.GetCache(path, account)
|
||||
if len(files) != 0 && err != nil {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
@ -133,7 +136,7 @@ func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, e
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
link := base.Link{
|
||||
Url: fullPath,
|
||||
FilePath: fullPath,
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
@ -156,9 +159,9 @@ func (driver Native) Path(path string, account *model.Account) (*model.File, []m
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver Native) Proxy(c *gin.Context, account *model.Account) {
|
||||
// unnecessary
|
||||
}
|
||||
//func (driver Native) Proxy(r *http.Request, account *model.Account) {
|
||||
// // unnecessary
|
||||
//}
|
||||
|
||||
func (driver Native) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
|
@ -6,8 +6,6 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/robfig/cron/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
)
|
||||
@ -92,9 +90,9 @@ func (driver Onedrive) Items() []base.Item {
|
||||
}
|
||||
|
||||
func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
|
||||
if old != nil {
|
||||
conf.Cron.Remove(cron.EntryID(old.CronId))
|
||||
}
|
||||
//if old != nil {
|
||||
// conf.Cron.Remove(cron.EntryID(old.CronId))
|
||||
//}
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
@ -104,28 +102,25 @@ func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
|
||||
}
|
||||
account.RootFolder = utils.ParsePath(account.RootFolder)
|
||||
err := driver.RefreshToken(account)
|
||||
_ = model.SaveAccount(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cronId, err := conf.Cron.AddFunc("@every 1h", func() {
|
||||
name := account.Name
|
||||
log.Debugf("onedrive account name: %s", name)
|
||||
newAccount, ok := model.GetAccount(name)
|
||||
log.Debugf("onedrive account: %+v", newAccount)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
err = driver.RefreshToken(&newAccount)
|
||||
_ = model.SaveAccount(&newAccount)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.CronId = int(cronId)
|
||||
err = model.SaveAccount(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//cronId, err := conf.Cron.AddFunc("@every 1h", func() {
|
||||
// name := account.Name
|
||||
// log.Debugf("onedrive account name: %s", name)
|
||||
// newAccount, ok := model.GetAccount(name)
|
||||
// log.Debugf("onedrive account: %+v", newAccount)
|
||||
// if !ok {
|
||||
// return
|
||||
// }
|
||||
// err = driver.RefreshToken(&newAccount)
|
||||
// _ = model.SaveAccount(&newAccount)
|
||||
//})
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//account.CronId = int(cronId)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -180,7 +175,7 @@ func (driver Onedrive) Link(args base.Args, account *model.Account) (*base.Link,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.File.MimeType == "" {
|
||||
if file.File == nil {
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
link := base.Link{
|
||||
@ -205,9 +200,9 @@ func (driver Onedrive) Path(path string, account *model.Account) (*model.File, [
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver Onedrive) Proxy(c *gin.Context, account *model.Account) {
|
||||
c.Request.Header.Del("Origin")
|
||||
}
|
||||
//func (driver Onedrive) Proxy(r *http.Request, account *model.Account) {
|
||||
// r.Header.Del("Origin")
|
||||
//}
|
||||
|
||||
func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
|
@ -19,8 +19,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var oneClient = resty.New()
|
||||
|
||||
type Host struct {
|
||||
Oauth string
|
||||
Api string
|
||||
@ -47,7 +45,7 @@ var onedriveHostMap = map[string]Host{
|
||||
|
||||
func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string {
|
||||
path = filepath.Join(account.RootFolder, path)
|
||||
log.Debugf(path)
|
||||
//log.Debugf(path)
|
||||
host, _ := onedriveHostMap[account.Zone]
|
||||
if auth {
|
||||
return host.Oauth
|
||||
@ -81,7 +79,7 @@ type OneTokenErr struct {
|
||||
|
||||
func (driver Onedrive) RefreshToken(account *model.Account) error {
|
||||
err := driver.refreshToken(account)
|
||||
if err != nil && err.Error() == "empty refresh_token" {
|
||||
if err != nil && err == base.ErrEmptyToken {
|
||||
return driver.refreshToken(account)
|
||||
}
|
||||
return err
|
||||
@ -91,7 +89,7 @@ func (driver Onedrive) refreshToken(account *model.Account) error {
|
||||
url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
|
||||
var resp base.TokenResp
|
||||
var e OneTokenErr
|
||||
_, err := oneClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
|
||||
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": account.ClientId,
|
||||
"client_secret": account.ClientSecret,
|
||||
@ -109,8 +107,8 @@ func (driver Onedrive) refreshToken(account *model.Account) error {
|
||||
account.Status = "work"
|
||||
}
|
||||
if resp.RefreshToken == "" {
|
||||
account.Status = "empty refresh_token"
|
||||
return errors.New("empty refresh_token")
|
||||
account.Status = base.ErrEmptyToken.Error()
|
||||
return base.ErrEmptyToken
|
||||
}
|
||||
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
|
||||
return nil
|
||||
@ -122,7 +120,7 @@ type OneFile struct {
|
||||
Size int64 `json:"size"`
|
||||
LastModifiedDateTime *time.Time `json:"lastModifiedDateTime"`
|
||||
Url string `json:"@microsoft.graph.downloadUrl"`
|
||||
File struct {
|
||||
File *struct {
|
||||
MimeType string `json:"mimeType"`
|
||||
} `json:"file"`
|
||||
Thumbnails []struct {
|
||||
@ -159,7 +157,7 @@ func (driver Onedrive) FormatFile(file *OneFile) *model.File {
|
||||
if len(file.Thumbnails) > 0 {
|
||||
f.Thumbnail = file.Thumbnails[0].Medium.Url
|
||||
}
|
||||
if file.File.MimeType == "" {
|
||||
if file.File == nil {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
||||
@ -178,16 +176,17 @@ func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile,
|
||||
}
|
||||
for nextLink != "" {
|
||||
var files OneFiles
|
||||
var e OneRespErr
|
||||
_, err := oneClient.R().SetResult(&files).SetError(&e).
|
||||
SetHeader("Authorization", "Bearer "+account.AccessToken).
|
||||
Get(nextLink)
|
||||
_, err := driver.Request(nextLink, base.Get, nil, nil, nil, nil, &files, account)
|
||||
//var e OneRespErr
|
||||
//_, err := oneClient.R().SetResult(&files).SetError(&e).
|
||||
// SetHeader("Authorization", "Bearer "+account.AccessToken).
|
||||
// Get(nextLink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Error.Code != "" {
|
||||
return nil, fmt.Errorf("%s", e.Error.Message)
|
||||
}
|
||||
//if e.Error.Code != "" {
|
||||
// return nil, fmt.Errorf("%s", e.Error.Message)
|
||||
//}
|
||||
res = append(res, files.Value...)
|
||||
nextLink = files.NextLink
|
||||
}
|
||||
@ -196,16 +195,18 @@ func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile,
|
||||
|
||||
func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) {
|
||||
var file OneFile
|
||||
var e OneRespErr
|
||||
_, err := oneClient.R().SetResult(&file).SetError(&e).
|
||||
SetHeader("Authorization", "Bearer "+account.AccessToken).
|
||||
Get(driver.GetMetaUrl(account, false, path))
|
||||
//var e OneRespErr
|
||||
u := driver.GetMetaUrl(account, false, path)
|
||||
_, err := driver.Request(u, base.Get, nil, nil, nil, nil, &file, account)
|
||||
//_, err := oneClient.R().SetResult(&file).SetError(&e).
|
||||
// SetHeader("Authorization", "Bearer "+account.AccessToken).
|
||||
// Get(driver.GetMetaUrl(account, false, path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Error.Code != "" {
|
||||
return nil, fmt.Errorf("%s", e.Error.Message)
|
||||
}
|
||||
//if e.Error.Code != "" {
|
||||
// return nil, fmt.Errorf("%s", e.Error.Message)
|
||||
//}
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
@ -252,7 +253,7 @@ func (driver Onedrive) Request(url string, method int, headers, query, form map[
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
//log.Debug(res.String())
|
||||
if e.Error.Code != "" {
|
||||
if e.Error.Code == "InvalidAuthenticationToken" {
|
||||
err = driver.RefreshToken(account)
|
||||
@ -314,5 +315,4 @@ func (driver Onedrive) UploadBig(file *model.FileStream, account *model.Account)
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Onedrive{})
|
||||
oneClient.SetRetryCount(3)
|
||||
}
|
||||
|
@ -8,6 +8,10 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
func Save(driver base.Driver, account, old *model.Account) error {
|
||||
return driver.Save(account, old)
|
||||
}
|
||||
|
||||
func Path(driver base.Driver, account *model.Account, path string) (*model.File, []model.File, error) {
|
||||
return driver.Path(path, account)
|
||||
}
|
||||
@ -29,7 +33,11 @@ func File(driver base.Driver, account *model.Account, path string) (*model.File,
|
||||
|
||||
func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error {
|
||||
log.Debugf("mkdir: %s", path)
|
||||
err := driver.MakeDir(path, account)
|
||||
_, err := Files(driver, account, path)
|
||||
if err != base.ErrPathNotFound {
|
||||
return nil
|
||||
}
|
||||
err = driver.MakeDir(path, account)
|
||||
if err == nil && clearCache {
|
||||
_ = base.DeleteCache(utils.Dir(path), account)
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/gin-gonic/gin"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
@ -120,9 +119,14 @@ func (driver PikPak) Link(args base.Args, account *model.Account) (*base.Link, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &base.Link{
|
||||
link := base.Link{
|
||||
Url: resp.WebContentLink,
|
||||
}, nil
|
||||
}
|
||||
if len(resp.Medias) > 0 && resp.Medias[0].Link.Url != "" {
|
||||
log.Debugln("use media link")
|
||||
link.Url = resp.Medias[0].Link.Url
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver PikPak) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
@ -142,9 +146,9 @@ func (driver PikPak) Path(path string, account *model.Account) (*model.File, []m
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver PikPak) Proxy(c *gin.Context, account *model.Account) {
|
||||
|
||||
}
|
||||
//func (driver PikPak) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver PikPak) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
|
@ -144,6 +144,37 @@ type File struct {
|
||||
Size string `json:"size"`
|
||||
ThumbnailLink string `json:"thumbnail_link"`
|
||||
WebContentLink string `json:"web_content_link"`
|
||||
Medias []Media `json:"medias"`
|
||||
}
|
||||
|
||||
type Media struct {
|
||||
MediaId string `json:"media_id"`
|
||||
MediaName string `json:"media_name"`
|
||||
Video struct {
|
||||
Height int `json:"height"`
|
||||
Width int `json:"width"`
|
||||
Duration int `json:"duration"`
|
||||
BitRate int `json:"bit_rate"`
|
||||
FrameRate int `json:"frame_rate"`
|
||||
VideoCodec string `json:"video_codec"`
|
||||
AudioCodec string `json:"audio_codec"`
|
||||
VideoType string `json:"video_type"`
|
||||
} `json:"video"`
|
||||
Link struct {
|
||||
Url string `json:"url"`
|
||||
Token string `json:"token"`
|
||||
Expire time.Time `json:"expire"`
|
||||
} `json:"link"`
|
||||
NeedMoreQuota bool `json:"need_more_quota"`
|
||||
VipTypes []interface{} `json:"vip_types"`
|
||||
RedirectLink string `json:"redirect_link"`
|
||||
IconLink string `json:"icon_link"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
Priority int `json:"priority"`
|
||||
IsOrigin bool `json:"is_origin"`
|
||||
ResolutionName string `json:"resolution_name"`
|
||||
IsVisible bool `json:"is_visible"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
|
||||
func (driver PikPak) FormatFile(file *File) *model.File {
|
||||
|
327
drivers/quark/driver.go
Normal file
327
drivers/quark/driver.go
Normal file
@ -0,0 +1,327 @@
|
||||
package quark
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"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"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Quark struct{}
|
||||
|
||||
func (driver Quark) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Quark",
|
||||
OnlyProxy: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Quark) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "access_token",
|
||||
Label: "Cookie",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Description: "Unknown expiration time",
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Default: "0",
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "file_type,file_name,updated_at",
|
||||
Required: true,
|
||||
Default: "file_name",
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: base.TypeSelect,
|
||||
Values: "asc,desc",
|
||||
Required: true,
|
||||
Default: "asc",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Quark) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := driver.Get("/config", nil, nil, account)
|
||||
if err == nil {
|
||||
account.Status = "work"
|
||||
} else {
|
||||
account.Status = err.Error()
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Quark) 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 Quark) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var files []model.File
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files, _ = cache.([]model.File)
|
||||
} else {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files, err = driver.GetFiles(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Quark) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
path := args.Path
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := base.Json{
|
||||
"fids": []string{file.Id},
|
||||
}
|
||||
var resp DownResp
|
||||
_, err = driver.Post("/file/download", data, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &base.Link{
|
||||
Url: resp.Data[0].DownloadUrl,
|
||||
Headers: []base.Header{
|
||||
{Name: "Cookie", Value: account.AccessToken},
|
||||
{Name: "Referer", Value: "https://pan.quark.cn"},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (driver Quark) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("quark 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 Quark) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Quark) MakeDir(path string, account *model.Account) error {
|
||||
parentFile, err := driver.File(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"dir_init_lock": false,
|
||||
"dir_path": "",
|
||||
"file_name": utils.Base(path),
|
||||
"pdir_fid": parentFile.Id,
|
||||
}
|
||||
_, err = driver.Post("/file", data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Quark) Move(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"action_type": 1,
|
||||
"exclude_fids": []string{},
|
||||
"filelist": []string{srcFile.Id},
|
||||
"to_pdir_fid": dstParentFile.Id,
|
||||
}
|
||||
_, err = driver.Post("/file/move", data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Quark) Rename(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"fid": srcFile.Id,
|
||||
"file_name": utils.Base(dst),
|
||||
}
|
||||
_, err = driver.Post("/file/rename", data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Quark) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Quark) Delete(path string, account *model.Account) error {
|
||||
srcFile, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"action_type": 1,
|
||||
"exclude_fids": []string{},
|
||||
"filelist": []string{srcFile.Id},
|
||||
}
|
||||
_, err = driver.Post("/file/delete", data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Quark) 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 func() {
|
||||
_ = tempFile.Close()
|
||||
_ = os.Remove(tempFile.Name())
|
||||
}()
|
||||
_, err = io.Copy(tempFile, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tempFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m := md5.New()
|
||||
_, err = io.Copy(m, tempFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tempFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md5Str := hex.EncodeToString(m.Sum(nil))
|
||||
s := sha1.New()
|
||||
_, err = io.Copy(s, tempFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tempFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sha1Str := hex.EncodeToString(s.Sum(nil))
|
||||
// pre
|
||||
pre, err := driver.UpPre(file, parentFile.Id, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugln("hash: ", md5Str, sha1Str)
|
||||
// hash
|
||||
finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if finish {
|
||||
return nil
|
||||
}
|
||||
// part up
|
||||
partSize := pre.Metadata.PartSize
|
||||
var bytes []byte
|
||||
md5s := make([]string, 0)
|
||||
defaultBytes := make([]byte, partSize)
|
||||
left := int64(file.GetSize())
|
||||
partNumber := 1
|
||||
for left > 0 {
|
||||
if left > int64(partSize) {
|
||||
bytes = defaultBytes
|
||||
} else {
|
||||
bytes = make([]byte, left)
|
||||
}
|
||||
_, err := io.ReadFull(tempFile, bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
left -= int64(partSize)
|
||||
log.Debugf("left: %d", left)
|
||||
m, err := driver.UpPart(pre, file.GetMIMEType(), partNumber, bytes, account)
|
||||
//m, err := driver.UpPart(pre, file.GetMIMEType(), partNumber, bytes, account, md5Str, sha1Str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m == "finish" {
|
||||
return nil
|
||||
}
|
||||
md5s = append(md5s, m)
|
||||
partNumber++
|
||||
}
|
||||
err = driver.UpCommit(pre, md5s, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return driver.UpFinish(pre, account)
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Quark)(nil)
|
272
drivers/quark/quark.go
Normal file
272
drivers/quark/quark.go
Normal file
@ -0,0 +1,272 @@
|
||||
package quark
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/Xhofe/alist/utils/cookie"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (driver Quark) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
u := "https://drive.quark.cn/1/clouddrive" + pathname
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"Cookie": account.AccessToken,
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Referer": "https://pan.quark.cn/",
|
||||
})
|
||||
req.SetQueryParam("pr", "ucpro")
|
||||
req.SetQueryParam("fr", "pc")
|
||||
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
|
||||
}
|
||||
__puus := cookie.GetCookie(res.Cookies(), "__puus")
|
||||
if __puus != nil {
|
||||
account.AccessToken = cookie.SetStr(account.AccessToken, "__puus", __puus.Value)
|
||||
_ = model.SaveAccount(account)
|
||||
}
|
||||
//log.Debugf("%s response: %s", pathname, res.String())
|
||||
if e.Status >= 400 || e.Code != 0 {
|
||||
return nil, errors.New(e.Message)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (driver Quark) Get(pathname string, query map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
return driver.Request(pathname, base.Get, nil, query, nil, nil, resp, account)
|
||||
}
|
||||
|
||||
func (driver Quark) Post(pathname string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
return driver.Request(pathname, base.Post, nil, nil, nil, data, resp, account)
|
||||
}
|
||||
|
||||
func (driver Quark) GetFiles(parent string, account *model.Account) ([]model.File, error) {
|
||||
files := make([]model.File, 0)
|
||||
page := 1
|
||||
size := 100
|
||||
query := map[string]string{
|
||||
"pdir_fid": parent,
|
||||
"_size": strconv.Itoa(size),
|
||||
"_fetch_total": "1",
|
||||
"_sort": "file_type:asc," + account.OrderBy + ":" + account.OrderDirection,
|
||||
}
|
||||
for {
|
||||
query["_page"] = strconv.Itoa(page)
|
||||
var resp SortResp
|
||||
_, err := driver.Get("/file/sort", query, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range resp.Data.List {
|
||||
files = append(files, *driver.formatFile(&f))
|
||||
}
|
||||
if page*size >= resp.Metadata.Total {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Quark) UpPre(file *model.FileStream, parentId string, account *model.Account) (UpPreResp, error) {
|
||||
now := time.Now()
|
||||
data := base.Json{
|
||||
"ccp_hash_update": true,
|
||||
"dir_name": "",
|
||||
"file_name": file.Name,
|
||||
"format_type": file.MIMEType,
|
||||
"l_created_at": now.UnixMilli(),
|
||||
"l_updated_at": now.UnixMilli(),
|
||||
"pdir_fid": parentId,
|
||||
"size": file.Size,
|
||||
}
|
||||
log.Debugf("uppre data: %+v", data)
|
||||
var resp UpPreResp
|
||||
_, err := driver.Post("/file/upload/pre", data, &resp, account)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (driver Quark) UpHash(md5, sha1, taskId string, account *model.Account) (bool, error) {
|
||||
data := base.Json{
|
||||
"md5": md5,
|
||||
"sha1": sha1,
|
||||
"task_id": taskId,
|
||||
}
|
||||
log.Debugf("hash: %+v", data)
|
||||
var resp HashResp
|
||||
_, err := driver.Post("/file/update/hash", data, &resp, account)
|
||||
return resp.Data.Finish, err
|
||||
}
|
||||
|
||||
func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account) (string, error) {
|
||||
//func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account, md5Str, sha1Str string) (string, error) {
|
||||
timeStr := time.Now().UTC().Format(http.TimeFormat)
|
||||
data := base.Json{
|
||||
"auth_info": pre.Data.AuthInfo,
|
||||
"auth_meta": fmt.Sprintf(`PUT
|
||||
|
||||
%s
|
||||
%s
|
||||
x-oss-date:%s
|
||||
x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
|
||||
/%s/%s?partNumber=%d&uploadId=%s`,
|
||||
mineType, timeStr, timeStr, pre.Data.Bucket, pre.Data.ObjKey, partNumber, pre.Data.UploadId),
|
||||
"task_id": pre.Data.TaskId,
|
||||
}
|
||||
var resp UpAuthResp
|
||||
_, err := driver.Post("/file/upload/auth", data, &resp, account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//if partNumber == 1 {
|
||||
// finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// if finish {
|
||||
// return "finish", nil
|
||||
// }
|
||||
//}
|
||||
u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey)
|
||||
res, err := base.RestyClient.R().
|
||||
SetHeaders(map[string]string{
|
||||
"Authorization": resp.Data.AuthKey,
|
||||
"Content-Type": mineType,
|
||||
"Referer": "https://pan.quark.cn/",
|
||||
"x-oss-date": timeStr,
|
||||
"x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit",
|
||||
}).
|
||||
SetQueryParams(map[string]string{
|
||||
"partNumber": strconv.Itoa(partNumber),
|
||||
"uploadId": pre.Data.UploadId,
|
||||
}).SetBody(bytes).Put(u)
|
||||
if res.StatusCode() != 200 {
|
||||
return "", fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String())
|
||||
}
|
||||
return res.Header().Get("ETag"), nil
|
||||
}
|
||||
|
||||
func (driver Quark) UpCommit(pre UpPreResp, md5s []string, account *model.Account) error {
|
||||
timeStr := time.Now().UTC().Format(http.TimeFormat)
|
||||
log.Debugf("md5s: %+v", md5s)
|
||||
bodyBuilder := strings.Builder{}
|
||||
bodyBuilder.WriteString(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CompleteMultipartUpload>
|
||||
`)
|
||||
for i, m := range md5s {
|
||||
bodyBuilder.WriteString(fmt.Sprintf(`<Part>
|
||||
<PartNumber>%d</PartNumber>
|
||||
<ETag>%s</ETag>
|
||||
</Part>
|
||||
`, i+1, m))
|
||||
}
|
||||
bodyBuilder.WriteString("</CompleteMultipartUpload>")
|
||||
body := bodyBuilder.String()
|
||||
m := md5.New()
|
||||
m.Write([]byte(body))
|
||||
contentMd5 := base64.StdEncoding.EncodeToString(m.Sum(nil))
|
||||
callbackBytes, err := utils.Json.Marshal(pre.Data.Callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
callbackBase64 := base64.StdEncoding.EncodeToString(callbackBytes)
|
||||
data := base.Json{
|
||||
"auth_info": pre.Data.AuthInfo,
|
||||
"auth_meta": fmt.Sprintf(`POST
|
||||
%s
|
||||
application/xml
|
||||
%s
|
||||
x-oss-callback:%s
|
||||
x-oss-date:%s
|
||||
x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
|
||||
/%s/%s?uploadId=%s`,
|
||||
contentMd5, timeStr, callbackBase64, timeStr,
|
||||
pre.Data.Bucket, pre.Data.ObjKey, pre.Data.UploadId),
|
||||
"task_id": pre.Data.TaskId,
|
||||
}
|
||||
log.Debugf("xml: %s", body)
|
||||
log.Debugf("auth data: %+v", data)
|
||||
var resp UpAuthResp
|
||||
_, err = driver.Post("/file/upload/auth", data, &resp, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey)
|
||||
res, err := base.RestyClient.R().
|
||||
SetHeaders(map[string]string{
|
||||
"Authorization": resp.Data.AuthKey,
|
||||
"Content-MD5": contentMd5,
|
||||
"Content-Type": "application/xml",
|
||||
"Referer": "https://pan.quark.cn/",
|
||||
"x-oss-callback": callbackBase64,
|
||||
"x-oss-date": timeStr,
|
||||
"x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit",
|
||||
}).
|
||||
SetQueryParams(map[string]string{
|
||||
"uploadId": pre.Data.UploadId,
|
||||
}).SetBody(body).Post(u)
|
||||
if res.StatusCode() != 200 {
|
||||
return fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver Quark) UpFinish(pre UpPreResp, account *model.Account) error {
|
||||
data := base.Json{
|
||||
"obj_key": pre.Data.ObjKey,
|
||||
"task_id": pre.Data.TaskId,
|
||||
}
|
||||
_, err := driver.Post("/file/upload/finish", data, nil, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Quark{})
|
||||
}
|
134
drivers/quark/types.go
Normal file
134
drivers/quark/types.go
Normal file
@ -0,0 +1,134 @@
|
||||
package quark
|
||||
|
||||
type Resp struct {
|
||||
Status int `json:"status"`
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
//ReqId string `json:"req_id"`
|
||||
//Timestamp int `json:"timestamp"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Fid string `json:"fid"`
|
||||
FileName string `json:"file_name"`
|
||||
//PdirFid string `json:"pdir_fid"`
|
||||
//Category int `json:"category"`
|
||||
//FileType int `json:"file_type"`
|
||||
Size int64 `json:"size"`
|
||||
//FormatType string `json:"format_type"`
|
||||
//Status int `json:"status"`
|
||||
//Tags string `json:"tags,omitempty"`
|
||||
//LCreatedAt int64 `json:"l_created_at"`
|
||||
LUpdatedAt int64 `json:"l_updated_at"`
|
||||
//NameSpace int `json:"name_space"`
|
||||
//IncludeItems int `json:"include_items,omitempty"`
|
||||
//RiskType int `json:"risk_type"`
|
||||
//BackupSign int `json:"backup_sign"`
|
||||
//Duration int `json:"duration"`
|
||||
//FileSource string `json:"file_source"`
|
||||
File bool `json:"file"`
|
||||
//CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
//PrivateExtra struct {} `json:"_private_extra"`
|
||||
//ObjCategory string `json:"obj_category,omitempty"`
|
||||
//Thumbnail string `json:"thumbnail,omitempty"`
|
||||
}
|
||||
|
||||
type SortResp struct {
|
||||
Resp
|
||||
Data struct {
|
||||
List []File `json:"list"`
|
||||
} `json:"data"`
|
||||
Metadata struct {
|
||||
Size int `json:"_size"`
|
||||
Page int `json:"_page"`
|
||||
Count int `json:"_count"`
|
||||
Total int `json:"_total"`
|
||||
Way string `json:"way"`
|
||||
} `json:"metadata"`
|
||||
}
|
||||
|
||||
type DownResp struct {
|
||||
Resp
|
||||
Data []struct {
|
||||
Fid string `json:"fid"`
|
||||
FileName string `json:"file_name"`
|
||||
PdirFid string `json:"pdir_fid"`
|
||||
Category int `json:"category"`
|
||||
FileType int `json:"file_type"`
|
||||
Size int `json:"size"`
|
||||
FormatType string `json:"format_type"`
|
||||
Status int `json:"status"`
|
||||
Tags string `json:"tags"`
|
||||
LCreatedAt int64 `json:"l_created_at"`
|
||||
LUpdatedAt int64 `json:"l_updated_at"`
|
||||
NameSpace int `json:"name_space"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
DownloadUrl string `json:"download_url"`
|
||||
Md5 string `json:"md5"`
|
||||
RiskType int `json:"risk_type"`
|
||||
RangeSize int `json:"range_size"`
|
||||
BackupSign int `json:"backup_sign"`
|
||||
ObjCategory string `json:"obj_category"`
|
||||
Duration int `json:"duration"`
|
||||
FileSource string `json:"file_source"`
|
||||
File bool `json:"file"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
PrivateExtra struct {
|
||||
} `json:"_private_extra"`
|
||||
} `json:"data"`
|
||||
Metadata struct {
|
||||
Acc2 string `json:"acc2"`
|
||||
Acc1 string `json:"acc1"`
|
||||
} `json:"metadata"`
|
||||
}
|
||||
|
||||
type UpPreResp struct {
|
||||
Resp
|
||||
Data struct {
|
||||
TaskId string `json:"task_id"`
|
||||
Finish bool `json:"finish"`
|
||||
UploadId string `json:"upload_id"`
|
||||
ObjKey string `json:"obj_key"`
|
||||
UploadUrl string `json:"upload_url"`
|
||||
Fid string `json:"fid"`
|
||||
Bucket string `json:"bucket"`
|
||||
Callback struct {
|
||||
CallbackUrl string `json:"callbackUrl"`
|
||||
CallbackBody string `json:"callbackBody"`
|
||||
} `json:"callback"`
|
||||
FormatType string `json:"format_type"`
|
||||
Size int `json:"size"`
|
||||
AuthInfo string `json:"auth_info"`
|
||||
} `json:"data"`
|
||||
Metadata struct {
|
||||
PartThread int `json:"part_thread"`
|
||||
Acc2 string `json:"acc2"`
|
||||
Acc1 string `json:"acc1"`
|
||||
PartSize int `json:"part_size"` // 分片大小
|
||||
} `json:"metadata"`
|
||||
}
|
||||
|
||||
type HashResp struct {
|
||||
Resp
|
||||
Data struct {
|
||||
Finish bool `json:"finish"`
|
||||
Fid string `json:"fid"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
FormatType string `json:"format_type"`
|
||||
} `json:"data"`
|
||||
Metadata struct {
|
||||
} `json:"metadata"`
|
||||
}
|
||||
|
||||
type UpAuthResp struct {
|
||||
Resp
|
||||
Data struct {
|
||||
AuthKey string `json:"auth_key"`
|
||||
Speed int `json:"speed"`
|
||||
Headers []interface{} `json:"headers"`
|
||||
} `json:"data"`
|
||||
Metadata struct {
|
||||
} `json:"metadata"`
|
||||
}
|
31
drivers/quark/util.go
Normal file
31
drivers/quark/util.go
Normal file
@ -0,0 +1,31 @@
|
||||
package quark
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getTime(t int64) *time.Time {
|
||||
tm := time.UnixMilli(t)
|
||||
//log.Debugln(tm)
|
||||
return &tm
|
||||
}
|
||||
|
||||
func (driver Quark) formatFile(f *File) *model.File {
|
||||
file := model.File{
|
||||
Id: f.Fid,
|
||||
Name: f.FileName,
|
||||
Size: f.Size,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: getTime(f.UpdatedAt),
|
||||
}
|
||||
if f.File {
|
||||
file.Type = utils.GetFileType(path.Ext(f.FileName))
|
||||
} else {
|
||||
file.Type = conf.FOLDER
|
||||
}
|
||||
return &file
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package s3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
@ -8,8 +9,8 @@ import (
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@ -77,8 +78,21 @@ func (driver S3) Items() []base.Item {
|
||||
{
|
||||
Name: "zone",
|
||||
Label: "placeholder filename",
|
||||
Type: base.TypeNumber,
|
||||
Type: base.TypeString,
|
||||
Description: "default empty string",
|
||||
Default: defaultPlaceholderName,
|
||||
},
|
||||
{
|
||||
Name: "bool_1",
|
||||
Label: "S3ForcePathStyle",
|
||||
Type: base.TypeBool,
|
||||
},
|
||||
{
|
||||
Name: "internal_type",
|
||||
Label: "ListObject Version",
|
||||
Type: base.TypeSelect,
|
||||
Values: "v1,v2",
|
||||
Default: "v1",
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -133,7 +147,11 @@ func (driver S3) Files(path string, account *model.Account) ([]model.File, error
|
||||
if err == nil {
|
||||
files, _ = cache.([]model.File)
|
||||
} else {
|
||||
files, err = driver.List(path, account)
|
||||
if account.InternalType == "v2" {
|
||||
files, err = driver.ListV2(path, account)
|
||||
} else {
|
||||
files, err = driver.List(path, account)
|
||||
}
|
||||
if err == nil && len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
@ -142,7 +160,7 @@ func (driver S3) Files(path string, account *model.Account) ([]model.File, error
|
||||
}
|
||||
|
||||
func (driver S3) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
client, err := driver.GetClient(account)
|
||||
client, err := driver.GetClient(account, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -189,17 +207,28 @@ func (driver S3) Path(path string, account *model.Account) (*model.File, []model
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver S3) Proxy(c *gin.Context, account *model.Account) {
|
||||
|
||||
}
|
||||
//func (driver S3) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver S3) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver S3) MakeDir(path string, account *model.Account) error {
|
||||
// not support, default as success
|
||||
return nil
|
||||
// not support, generate a placeholder file
|
||||
_, err := driver.File(path, account)
|
||||
// exist
|
||||
if err != base.ErrPathNotFound {
|
||||
return nil
|
||||
}
|
||||
return driver.Upload(&model.FileStream{
|
||||
File: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
||||
Size: 0,
|
||||
ParentPath: path,
|
||||
Name: getPlaceholderName(account.Zone),
|
||||
MIMEType: "application/octet-stream",
|
||||
}, account)
|
||||
}
|
||||
|
||||
func (driver S3) Move(src string, dst string, account *model.Account) error {
|
||||
@ -215,7 +244,7 @@ func (driver S3) Rename(src string, dst string, account *model.Account) error {
|
||||
}
|
||||
|
||||
func (driver S3) Copy(src string, dst string, account *model.Account) error {
|
||||
client, err := driver.GetClient(account)
|
||||
client, err := driver.GetClient(account, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -235,7 +264,7 @@ func (driver S3) Copy(src string, dst string, account *model.Account) error {
|
||||
}
|
||||
|
||||
func (driver S3) Delete(path string, account *model.Account) error {
|
||||
client, err := driver.GetClient(account)
|
||||
client, err := driver.GetClient(account, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -262,6 +291,7 @@ func (driver S3) Upload(file *model.FileStream, account *model.Account) error {
|
||||
}
|
||||
uploader := s3manager.NewUploader(s)
|
||||
key := driver.GetKey(utils.Join(file.ParentPath, file.GetFileName()), account, false)
|
||||
log.Debugln("key:", key)
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &account.Bucket,
|
||||
Key: &key,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package s3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
@ -22,20 +23,21 @@ var sessionsMap map[string]*session.Session
|
||||
|
||||
func (driver S3) NewSession(account *model.Account) (*session.Session, error) {
|
||||
cfg := &aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(account.AccessKey, account.AccessSecret, ""),
|
||||
Region: &account.Region,
|
||||
Endpoint: &account.Endpoint,
|
||||
Credentials: credentials.NewStaticCredentials(account.AccessKey, account.AccessSecret, ""),
|
||||
Region: &account.Region,
|
||||
Endpoint: &account.Endpoint,
|
||||
S3ForcePathStyle: aws.Bool(account.Bool1),
|
||||
}
|
||||
return session.NewSession(cfg)
|
||||
}
|
||||
|
||||
func (driver S3) GetClient(account *model.Account) (*s3.S3, error) {
|
||||
func (driver S3) GetClient(account *model.Account, link bool) (*s3.S3, error) {
|
||||
s, ok := sessionsMap[account.Name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can't find [%s] session", account.Name)
|
||||
}
|
||||
client := s3.New(s)
|
||||
if account.CustomHost != "" {
|
||||
if link && account.CustomHost != "" {
|
||||
cURL, err := url.Parse(account.CustomHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -54,7 +56,7 @@ func (driver S3) GetClient(account *model.Account) (*s3.S3, error) {
|
||||
func (driver S3) List(prefix string, account *model.Account) ([]model.File, error) {
|
||||
prefix = driver.GetKey(prefix, account, true)
|
||||
log.Debugf("list: %s", prefix)
|
||||
client, err := driver.GetClient(account)
|
||||
client, err := driver.GetClient(account, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -86,7 +88,7 @@ func (driver S3) List(prefix string, account *model.Account) ([]model.File, erro
|
||||
}
|
||||
for _, object := range listObjectsResult.Contents {
|
||||
name := utils.Base(*object.Key)
|
||||
if name == account.Zone {
|
||||
if name == getPlaceholderName(account.Zone) {
|
||||
continue
|
||||
}
|
||||
file := model.File{
|
||||
@ -99,6 +101,9 @@ func (driver S3) List(prefix string, account *model.Account) ([]model.File, erro
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
if listObjectsResult.IsTruncated == nil {
|
||||
return nil, errors.New("IsTruncated nil")
|
||||
}
|
||||
if *listObjectsResult.IsTruncated {
|
||||
marker = *listObjectsResult.NextMarker
|
||||
} else {
|
||||
@ -108,6 +113,73 @@ func (driver S3) List(prefix string, account *model.Account) ([]model.File, erro
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver S3) ListV2(prefix string, account *model.Account) ([]model.File, error) {
|
||||
prefix = driver.GetKey(prefix, account, true)
|
||||
//if prefix == "" {
|
||||
// prefix = "/"
|
||||
//}
|
||||
log.Debugf("list: %s", prefix)
|
||||
client, err := driver.GetClient(account, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
var continuationToken, startAfter *string
|
||||
for {
|
||||
input := &s3.ListObjectsV2Input{
|
||||
Bucket: &account.Bucket,
|
||||
ContinuationToken: continuationToken,
|
||||
Prefix: &prefix,
|
||||
Delimiter: aws.String("/"),
|
||||
StartAfter: startAfter,
|
||||
}
|
||||
listObjectsResult, err := client.ListObjectsV2(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("resp: %+v", listObjectsResult)
|
||||
for _, object := range listObjectsResult.CommonPrefixes {
|
||||
name := utils.Base(strings.Trim(*object.Prefix, "/"))
|
||||
file := model.File{
|
||||
//Id: *object.Key,
|
||||
Name: name,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
TimeStr: "-",
|
||||
Type: conf.FOLDER,
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
for _, object := range listObjectsResult.Contents {
|
||||
name := utils.Base(*object.Key)
|
||||
if name == getPlaceholderName(account.Zone) {
|
||||
continue
|
||||
}
|
||||
file := model.File{
|
||||
//Id: *object.Key,
|
||||
Name: name,
|
||||
Size: *object.Size,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: object.LastModified,
|
||||
Type: utils.GetFileType(path.Ext(*object.Key)),
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
if !aws.BoolValue(listObjectsResult.IsTruncated) {
|
||||
break
|
||||
}
|
||||
if listObjectsResult.NextContinuationToken != nil {
|
||||
continuationToken = listObjectsResult.NextContinuationToken
|
||||
continue
|
||||
}
|
||||
if len(listObjectsResult.Contents) == 0 {
|
||||
break
|
||||
}
|
||||
startAfter = listObjectsResult.Contents[len(listObjectsResult.Contents)-1].Key
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver S3) GetKey(path string, account *model.Account, dir bool) string {
|
||||
path = utils.Join(account.RootFolder, path)
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
|
10
drivers/s3/util.go
Normal file
10
drivers/s3/util.go
Normal file
@ -0,0 +1,10 @@
|
||||
package s3
|
||||
|
||||
var defaultPlaceholderName = ".placeholder"
|
||||
|
||||
func getPlaceholderName(placeholder string) string {
|
||||
if placeholder == "" {
|
||||
return defaultPlaceholderName
|
||||
}
|
||||
return placeholder
|
||||
}
|
220
drivers/sftp/driver.go
Normal file
220
drivers/sftp/driver.go
Normal file
@ -0,0 +1,220 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"io"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type SFTP struct {
|
||||
}
|
||||
|
||||
func (driver SFTP) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "SFTP",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: true,
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver SFTP) Items() []base.Item {
|
||||
// TODO fill need info
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "site_url",
|
||||
Label: "ip/host",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "limit",
|
||||
Label: "port",
|
||||
Type: base.TypeNumber,
|
||||
Required: true,
|
||||
Default: "22",
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
Label: "username",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Label: "password",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder path",
|
||||
Type: base.TypeString,
|
||||
Default: "/",
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver SFTP) Save(account *model.Account, old *model.Account) error {
|
||||
if old != nil {
|
||||
clientsMap.Lock()
|
||||
defer clientsMap.Unlock()
|
||||
delete(clientsMap.clients, old.Name)
|
||||
}
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := GetClient(account)
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
} else {
|
||||
account.Status = "work"
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver SFTP) 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 SFTP) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
remotePath := utils.Join(account.RootFolder, path)
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files, _ := cache.([]model.File)
|
||||
return files, nil
|
||||
}
|
||||
client, err := GetClient(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var files []model.File
|
||||
rawFiles, err := client.Files(remotePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < len(rawFiles); i++ {
|
||||
files = append(files, driver.formatFile(rawFiles[i]))
|
||||
}
|
||||
if len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver SFTP) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
client, err := GetClient(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remoteFileName := utils.Join(account.RootFolder, args.Path)
|
||||
remoteFile, err := client.Open(remoteFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &base.Link{
|
||||
Data: remoteFile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (driver SFTP) 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 SFTP) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
//TODO preview interface if driver support
|
||||
return nil, base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver SFTP) MakeDir(path string, account *model.Account) error {
|
||||
client, err := GetClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.MkdirAll(utils.Join(account.RootFolder, path))
|
||||
}
|
||||
|
||||
func (driver SFTP) Move(src string, dst string, account *model.Account) error {
|
||||
return driver.Rename(src, dst, account)
|
||||
}
|
||||
|
||||
func (driver SFTP) Rename(src string, dst string, account *model.Account) error {
|
||||
client, err := GetClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.Rename(utils.Join(account.RootFolder, src), utils.Join(account.RootFolder, dst))
|
||||
}
|
||||
|
||||
func (driver SFTP) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver SFTP) Delete(path string, account *model.Account) error {
|
||||
client, err := GetClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.Remove(utils.Join(account.RootFolder, path))
|
||||
}
|
||||
|
||||
func (driver SFTP) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
client, err := GetClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstFile, err := client.Create(path.Join(account.RootFolder, file.ParentPath, file.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = dstFile.Close()
|
||||
}()
|
||||
_, err = io.Copy(dstFile, file)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*SFTP)(nil)
|
110
drivers/sftp/sftp.go
Normal file
110
drivers/sftp/sftp.go
Normal file
@ -0,0 +1,110 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var clientsMap = struct {
|
||||
sync.Mutex
|
||||
clients map[string]*Client
|
||||
}{clients: make(map[string]*Client)}
|
||||
|
||||
func GetClient(account *model.Account) (*Client, error) {
|
||||
clientsMap.Lock()
|
||||
defer clientsMap.Unlock()
|
||||
if v, ok := clientsMap.clients[account.Name]; ok {
|
||||
return v, nil
|
||||
}
|
||||
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", account.SiteUrl, account.Limit), &ssh.ClientConfig{
|
||||
User: account.Username,
|
||||
Auth: []ssh.AuthMethod{ssh.Password(account.Password)},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Client{client}
|
||||
clientsMap.clients[account.Name] = c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*sftp.Client
|
||||
}
|
||||
|
||||
func (client *Client) Files(remotePath string) ([]os.FileInfo, error) {
|
||||
return client.ReadDir(remotePath)
|
||||
}
|
||||
|
||||
func (client *Client) Remove(remotePath string) error {
|
||||
f, err := client.Stat(remotePath)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if f.IsDir() {
|
||||
return client.removeDirectory(remotePath)
|
||||
} else {
|
||||
return client.removeFile(remotePath)
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) removeDirectory(remotePath string) error {
|
||||
//打不开,说明要么文件路径错误了,要么是第一次部署
|
||||
remoteFiles, err := client.ReadDir(remotePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, backupDir := range remoteFiles {
|
||||
remoteFilePath := path.Join(remotePath, backupDir.Name())
|
||||
if backupDir.IsDir() {
|
||||
err := client.removeDirectory(remoteFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := client.Remove(path.Join(remoteFilePath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return client.RemoveDirectory(remotePath)
|
||||
}
|
||||
|
||||
func (client *Client) removeFile(remotePath string) error {
|
||||
return client.Remove(utils.Join(remotePath))
|
||||
}
|
||||
|
||||
func (driver SFTP) formatFile(f os.FileInfo) model.File {
|
||||
t := f.ModTime()
|
||||
file := model.File{
|
||||
//Id: f.Id,
|
||||
Name: f.Name(),
|
||||
Size: f.Size(),
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: &t,
|
||||
}
|
||||
if f.IsDir() {
|
||||
file.Type = conf.FOLDER
|
||||
} else {
|
||||
file.Type = utils.GetFileType(path.Ext(f.Name()))
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&SFTP{})
|
||||
}
|
18
drivers/sftp/types.go
Normal file
18
drivers/sftp/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/sftp/util.go
Normal file
3
drivers/sftp/util.go
Normal file
@ -0,0 +1,3 @@
|
||||
package template
|
||||
|
||||
// write util func here, such as cal sign
|
@ -6,7 +6,6 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@ -154,9 +153,9 @@ func (driver Shandian) Path(path string, account *model.Account) (*model.File, [
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver Shandian) Proxy(c *gin.Context, account *model.Account) {
|
||||
|
||||
}
|
||||
//func (driver Shandian) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver Shandian) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
)
|
||||
@ -52,6 +51,7 @@ func (driver Teambition) Items() []base.Item {
|
||||
Type: base.TypeSelect,
|
||||
Values: "fileName,fileSize,updated,created",
|
||||
Required: true,
|
||||
Default: "fileName",
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
@ -59,6 +59,7 @@ func (driver Teambition) Items() []base.Item {
|
||||
Type: base.TypeSelect,
|
||||
Values: "Asc,Desc",
|
||||
Required: true,
|
||||
Default: "Asc",
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -149,9 +150,9 @@ func (driver Teambition) Path(path string, account *model.Account) (*model.File,
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver Teambition) Proxy(c *gin.Context, account *model.Account) {
|
||||
|
||||
}
|
||||
//func (driver Teambition) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver Teambition) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
|
@ -98,7 +98,7 @@ func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]mo
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: &collection.Updated,
|
||||
UpdatedAt: collection.Updated,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -126,7 +126,7 @@ func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]mo
|
||||
Size: work.FileSize,
|
||||
Type: utils.GetFileType(path.Ext(work.FileName)),
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: &work.Updated,
|
||||
UpdatedAt: work.Updated,
|
||||
Thumbnail: work.Thumbnail,
|
||||
Url: work.DownloadURL,
|
||||
})
|
||||
@ -157,8 +157,10 @@ func (driver Teambition) upload(file *model.FileStream, token string, account *m
|
||||
|
||||
func (driver Teambition) chunkUpload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
|
||||
prefix := "tcs"
|
||||
referer := "https://www.teambition.com/"
|
||||
if account.InternalType == "International" {
|
||||
prefix = "us-tcs"
|
||||
referer = "https://us.teambition.com/"
|
||||
}
|
||||
var newChunk ChunkUpload
|
||||
_, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token).
|
||||
@ -187,7 +189,7 @@ func (driver Teambition) chunkUpload(file *model.FileStream, token string, accou
|
||||
res, err := base.RestyClient.R().SetHeaders(map[string]string{
|
||||
"Authorization": token,
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Referer": "https://www.teambition.com/",
|
||||
"Referer": referer,
|
||||
}).SetBody(chunkData).Post(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -3,22 +3,22 @@ package teambition
|
||||
import "time"
|
||||
|
||||
type Collection struct {
|
||||
ID string `json:"_id"`
|
||||
Title string `json:"title"`
|
||||
Updated time.Time `json:"updated"`
|
||||
ID string `json:"_id"`
|
||||
Title string `json:"title"`
|
||||
Updated *time.Time `json:"updated"`
|
||||
}
|
||||
|
||||
type Work struct {
|
||||
ID string `json:"_id"`
|
||||
FileName string `json:"fileName"`
|
||||
FileSize int64 `json:"fileSize"`
|
||||
FileKey string `json:"fileKey"`
|
||||
FileCategory string `json:"fileCategory"`
|
||||
DownloadURL string `json:"downloadUrl"`
|
||||
ThumbnailURL string `json:"thumbnailUrl"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Updated time.Time `json:"updated"`
|
||||
PreviewURL string `json:"previewUrl"`
|
||||
ID string `json:"_id"`
|
||||
FileName string `json:"fileName"`
|
||||
FileSize int64 `json:"fileSize"`
|
||||
FileKey string `json:"fileKey"`
|
||||
FileCategory string `json:"fileCategory"`
|
||||
DownloadURL string `json:"downloadUrl"`
|
||||
ThumbnailURL string `json:"thumbnailUrl"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Updated *time.Time `json:"updated"`
|
||||
PreviewURL string `json:"previewUrl"`
|
||||
}
|
||||
|
||||
type FileUpload struct {
|
||||
|
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
|
237
drivers/uss/driver.go
Normal file
237
drivers/uss/driver.go
Normal file
@ -0,0 +1,237 @@
|
||||
package uss
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/upyun/go-sdk/v3/upyun"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type USS struct {
|
||||
}
|
||||
|
||||
func (driver USS) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "USS",
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver USS) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "bucket",
|
||||
Label: "Bucket",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "endpoint",
|
||||
Label: "Endpoint",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "access_key",
|
||||
Label: "Operator Name",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "access_secret",
|
||||
Label: "Operator Password",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder path",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "custom_host",
|
||||
Label: "Custom Host",
|
||||
Type: base.TypeString,
|
||||
},
|
||||
{
|
||||
Name: "limit",
|
||||
Label: "Sign url expire time(hours)",
|
||||
Type: base.TypeNumber,
|
||||
Default: "4",
|
||||
Description: "default 4 hours",
|
||||
},
|
||||
//{
|
||||
// Name: "zone",
|
||||
// Label: "placeholder filename",
|
||||
// Type: base.TypeString,
|
||||
// Description: "default empty string",
|
||||
//},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver USS) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if account.Limit == 0 {
|
||||
account.Limit = 4
|
||||
}
|
||||
client, err := driver.NewUpYun(account)
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
} else {
|
||||
clientsMap[account.Name] = client
|
||||
account.Status = "work"
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver USS) 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 USS) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var files []model.File
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files, _ = cache.([]model.File)
|
||||
} else {
|
||||
files, err = driver.List(path, account)
|
||||
if err == nil && len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
}
|
||||
return files, err
|
||||
}
|
||||
|
||||
func (driver USS) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
key := driver.GetKey(args.Path, account, false)
|
||||
host := account.CustomHost
|
||||
if host == "" {
|
||||
host = account.Endpoint
|
||||
}
|
||||
if strings.Contains(host, "://") {
|
||||
host = "https://" + host
|
||||
}
|
||||
u := fmt.Sprintf("%s/%s", host, key)
|
||||
downExp := time.Hour * time.Duration(account.Limit)
|
||||
expireAt := time.Now().Add(downExp).Unix()
|
||||
upd := url.QueryEscape(utils.Base(args.Path))
|
||||
signStr := strings.Join([]string{account.AccessSecret, fmt.Sprint(expireAt), fmt.Sprintf("/%s", key)}, "&")
|
||||
upt := utils.GetMD5Encode(signStr)[12:20] + fmt.Sprint(expireAt)
|
||||
link := fmt.Sprintf("%s?_upd=%s&_upt=%s", u, upd, upt)
|
||||
return &base.Link{Url: link}, nil
|
||||
}
|
||||
|
||||
func (driver USS) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("s3 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 USS) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver USS) MakeDir(path string, account *model.Account) error {
|
||||
client, err := driver.GetClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.Mkdir(driver.GetKey(path, account, true))
|
||||
}
|
||||
|
||||
func (driver USS) Move(src string, dst string, account *model.Account) error {
|
||||
client, err := driver.GetClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.Move(&upyun.MoveObjectConfig{
|
||||
SrcPath: driver.GetKey(src, account, false),
|
||||
DestPath: driver.GetKey(dst, account, false),
|
||||
})
|
||||
}
|
||||
|
||||
func (driver USS) Rename(src string, dst string, account *model.Account) error {
|
||||
return driver.Move(src, dst, account)
|
||||
}
|
||||
|
||||
func (driver USS) Copy(src string, dst string, account *model.Account) error {
|
||||
client, err := driver.GetClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.Copy(&upyun.CopyObjectConfig{
|
||||
SrcPath: driver.GetKey(src, account, false),
|
||||
DestPath: driver.GetKey(dst, account, false),
|
||||
})
|
||||
}
|
||||
|
||||
func (driver USS) Delete(path string, account *model.Account) error {
|
||||
client, err := driver.GetClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.Delete(&upyun.DeleteObjectConfig{
|
||||
Path: driver.GetKey(path, account, false),
|
||||
Async: false,
|
||||
})
|
||||
}
|
||||
|
||||
func (driver USS) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
client, err := driver.GetClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.Put(&upyun.PutObjectConfig{
|
||||
Path: driver.GetKey(utils.Join(file.ParentPath, file.GetFileName()), account, false),
|
||||
Reader: file,
|
||||
})
|
||||
}
|
||||
|
||||
var _ base.Driver = (*USS)(nil)
|
82
drivers/uss/uss.go
Normal file
82
drivers/uss/uss.go
Normal file
@ -0,0 +1,82 @@
|
||||
package uss
|
||||
|
||||
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/upyun/go-sdk/v3/upyun"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var clientsMap map[string]*upyun.UpYun
|
||||
|
||||
func (driver USS) NewUpYun(account *model.Account) (*upyun.UpYun, error) {
|
||||
return upyun.NewUpYun(&upyun.UpYunConfig{
|
||||
Bucket: account.Bucket,
|
||||
Operator: account.AccessKey,
|
||||
Password: account.AccessToken,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (driver USS) GetClient(account *model.Account) (*upyun.UpYun, error) {
|
||||
client, ok := clientsMap[account.Name]
|
||||
if ok {
|
||||
return client, nil
|
||||
}
|
||||
return nil, errors.New("can't get client")
|
||||
}
|
||||
|
||||
func (driver USS) List(prefix string, account *model.Account) ([]model.File, error) {
|
||||
prefix = driver.GetKey(prefix, account, true)
|
||||
client, err := driver.GetClient(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objsChan := make(chan *upyun.FileInfo, 10)
|
||||
defer close(objsChan)
|
||||
go func() {
|
||||
err = client.List(&upyun.GetObjectsConfig{
|
||||
Path: prefix,
|
||||
ObjectsChan: objsChan,
|
||||
MaxListObjects: 0,
|
||||
MaxListLevel: 1,
|
||||
})
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]model.File, 0)
|
||||
for obj := range objsChan {
|
||||
t := obj.Time
|
||||
f := model.File{
|
||||
Name: obj.Name,
|
||||
Size: obj.Size,
|
||||
UpdatedAt: &t,
|
||||
Driver: driver.Config().Name,
|
||||
}
|
||||
if obj.IsDir {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(path.Ext(obj.Name))
|
||||
}
|
||||
res = append(res, f)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (driver USS) GetKey(path string, account *model.Account, dir bool) string {
|
||||
path = utils.Join(account.RootFolder, path)
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
if dir {
|
||||
path += "/"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func init() {
|
||||
clientsMap = make(map[string]*upyun.UpYun)
|
||||
base.RegisterDriver(&USS{})
|
||||
}
|
@ -3,9 +3,9 @@ package webdav
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/drivers/webdav/odrvcookie"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@ -41,6 +41,15 @@ func (driver WebDav) Items() []base.Item {
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "internal_type",
|
||||
Label: "vendor",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Default: "other",
|
||||
Values: "sharepoint,other",
|
||||
Description: "webdav vendor",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,9 +57,17 @@ func (driver WebDav) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
account.Status = "work"
|
||||
var err error
|
||||
if isSharePoint(account) {
|
||||
_, err = odrvcookie.GetCookie(account.Username, account.Password, account.SiteUrl)
|
||||
}
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
} else {
|
||||
account.Status = "work"
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver WebDav) File(path string, account *model.Account) (*model.File, error) {
|
||||
@ -137,9 +154,9 @@ func (driver WebDav) Path(path string, account *model.Account) (*model.File, []m
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver WebDav) Proxy(c *gin.Context, account *model.Account) {
|
||||
|
||||
}
|
||||
//func (driver WebDav) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver WebDav) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
|
44
drivers/webdav/odrvcookie/cookie.go
Normal file
44
drivers/webdav/odrvcookie/cookie.go
Normal file
@ -0,0 +1,44 @@
|
||||
package odrvcookie
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/utils/cookie"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SpCookie struct {
|
||||
Cookie string
|
||||
expire time.Time
|
||||
}
|
||||
|
||||
func (sp SpCookie) IsExpire() bool {
|
||||
return time.Now().Before(sp.expire)
|
||||
}
|
||||
|
||||
var cookiesMap = struct {
|
||||
sync.Mutex
|
||||
m map[string]*SpCookie
|
||||
}{m: make(map[string]*SpCookie)}
|
||||
|
||||
func GetCookie(username, password, siteUrl string) (string, error) {
|
||||
cookiesMap.Lock()
|
||||
defer cookiesMap.Unlock()
|
||||
spCookie, ok := cookiesMap.m[username]
|
||||
if ok {
|
||||
if !spCookie.IsExpire() {
|
||||
return spCookie.Cookie, nil
|
||||
}
|
||||
}
|
||||
ca := New(username, password, siteUrl)
|
||||
tokenConf, err := ca.Cookies()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
spCookie = &SpCookie{
|
||||
Cookie: cookie.ToString([]*http.Cookie{&tokenConf.RtFa, &tokenConf.FedAuth}),
|
||||
expire: time.Now().Add(time.Hour * 12),
|
||||
}
|
||||
cookiesMap.m[username] = spCookie
|
||||
return spCookie.Cookie, nil
|
||||
}
|
206
drivers/webdav/odrvcookie/fetch.go
Normal file
206
drivers/webdav/odrvcookie/fetch.go
Normal file
@ -0,0 +1,206 @@
|
||||
// Package odrvcookie can fetch authentication cookies for a sharepoint webdav endpoint
|
||||
package odrvcookie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// CookieAuth hold the authentication information
|
||||
// These are username and password as well as the authentication endpoint
|
||||
type CookieAuth struct {
|
||||
user string
|
||||
pass string
|
||||
endpoint string
|
||||
}
|
||||
|
||||
// CookieResponse contains the requested cookies
|
||||
type CookieResponse struct {
|
||||
RtFa http.Cookie
|
||||
FedAuth http.Cookie
|
||||
}
|
||||
|
||||
// SuccessResponse hold a response from the sharepoint webdav
|
||||
type SuccessResponse struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Succ SuccessResponseBody `xml:"Body"`
|
||||
}
|
||||
|
||||
// SuccessResponseBody is the body of a success response, it holds the token
|
||||
type SuccessResponseBody struct {
|
||||
XMLName xml.Name
|
||||
Type string `xml:"RequestSecurityTokenResponse>TokenType"`
|
||||
Created time.Time `xml:"RequestSecurityTokenResponse>Lifetime>Created"`
|
||||
Expires time.Time `xml:"RequestSecurityTokenResponse>Lifetime>Expires"`
|
||||
Token string `xml:"RequestSecurityTokenResponse>RequestedSecurityToken>BinarySecurityToken"`
|
||||
}
|
||||
|
||||
// reqString is a template that gets populated with the user data in order to retrieve a "BinarySecurityToken"
|
||||
const reqString = `<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
|
||||
xmlns:a="http://www.w3.org/2005/08/addressing"
|
||||
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
|
||||
<s:Header>
|
||||
<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
|
||||
<a:ReplyTo>
|
||||
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
|
||||
</a:ReplyTo>
|
||||
<a:To s:mustUnderstand="1">{{ .LoginUrl }}</a:To>
|
||||
<o:Security s:mustUnderstand="1"
|
||||
xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
||||
<o:UsernameToken>
|
||||
<o:Username>{{ .Username }}</o:Username>
|
||||
<o:Password>{{ .Password }}</o:Password>
|
||||
</o:UsernameToken>
|
||||
</o:Security>
|
||||
</s:Header>
|
||||
<s:Body>
|
||||
<t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
|
||||
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
|
||||
<a:EndpointReference>
|
||||
<a:Address>{{ .Address }}</a:Address>
|
||||
</a:EndpointReference>
|
||||
</wsp:AppliesTo>
|
||||
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
|
||||
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
|
||||
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
|
||||
</t:RequestSecurityToken>
|
||||
</s:Body>
|
||||
</s:Envelope>`
|
||||
|
||||
// New creates a new CookieAuth struct
|
||||
func New(pUser, pPass, pEndpoint string) CookieAuth {
|
||||
retStruct := CookieAuth{
|
||||
user: pUser,
|
||||
pass: pPass,
|
||||
endpoint: pEndpoint,
|
||||
}
|
||||
|
||||
return retStruct
|
||||
}
|
||||
|
||||
// Cookies creates a CookieResponse. It fetches the auth token and then
|
||||
// retrieves the Cookies
|
||||
func (ca *CookieAuth) Cookies() (CookieResponse, error) {
|
||||
spToken, err := ca.getSPToken()
|
||||
if err != nil {
|
||||
return CookieResponse{}, err
|
||||
}
|
||||
return ca.getSPCookie(spToken)
|
||||
}
|
||||
|
||||
func (ca *CookieAuth) getSPCookie(conf *SuccessResponse) (CookieResponse, error) {
|
||||
spRoot, err := url.Parse(ca.endpoint)
|
||||
if err != nil {
|
||||
return CookieResponse{}, err
|
||||
}
|
||||
|
||||
u, err := url.Parse("https://" + spRoot.Host + "/_forms/default.aspx?wa=wsignin1.0")
|
||||
if err != nil {
|
||||
return CookieResponse{}, err
|
||||
}
|
||||
|
||||
// To authenticate with davfs or anything else we need two cookies (rtFa and FedAuth)
|
||||
// In order to get them we use the token we got earlier and a cookieJar
|
||||
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||
if err != nil {
|
||||
return CookieResponse{}, err
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Jar: jar,
|
||||
}
|
||||
|
||||
// Send the previously aquired Token as a Post parameter
|
||||
if _, err = client.Post(u.String(), "text/xml", strings.NewReader(conf.Succ.Token)); err != nil {
|
||||
return CookieResponse{}, err
|
||||
}
|
||||
|
||||
cookieResponse := CookieResponse{}
|
||||
for _, cookie := range jar.Cookies(u) {
|
||||
if (cookie.Name == "rtFa") || (cookie.Name == "FedAuth") {
|
||||
switch cookie.Name {
|
||||
case "rtFa":
|
||||
cookieResponse.RtFa = *cookie
|
||||
case "FedAuth":
|
||||
cookieResponse.FedAuth = *cookie
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieResponse, err
|
||||
}
|
||||
|
||||
var loginUrlsMap = map[string]string{
|
||||
"com": "https://login.microsoftonline.com",
|
||||
"cn": "https://login.chinacloudapi.cn",
|
||||
"us": "https://login.microsoftonline.us",
|
||||
"de": "https://login.microsoftonline.de",
|
||||
}
|
||||
|
||||
func getLoginUrl(endpoint string) (string, error) {
|
||||
spRoot, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
domains := strings.Split(spRoot.Host, ".")
|
||||
tld := domains[len(domains)-1]
|
||||
loginUrl, ok := loginUrlsMap[tld]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("tld %s is not supported", tld)
|
||||
}
|
||||
return loginUrl + "/extSTS.srf", nil
|
||||
}
|
||||
|
||||
func (ca *CookieAuth) getSPToken() (*SuccessResponse, error) {
|
||||
loginUrl, err := getLoginUrl(ca.endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqData := map[string]string{
|
||||
"Username": ca.user,
|
||||
"Password": ca.pass,
|
||||
"Address": ca.endpoint,
|
||||
"LoginUrl": loginUrl,
|
||||
}
|
||||
|
||||
t := template.Must(template.New("authXML").Parse(reqString))
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
if err := t.Execute(buf, reqData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Execute the first request which gives us an auth token for the sharepoint service
|
||||
// With this token we can authenticate on the login page and save the returned cookies
|
||||
req, err := http.NewRequest("POST", loginUrl, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBuf := bytes.Buffer{}
|
||||
respBuf.ReadFrom(resp.Body)
|
||||
s := respBuf.Bytes()
|
||||
|
||||
var conf SuccessResponse
|
||||
err = xml.Unmarshal(s, &conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &conf, err
|
||||
}
|
7
drivers/webdav/util.go
Normal file
7
drivers/webdav/util.go
Normal file
@ -0,0 +1,7 @@
|
||||
package webdav
|
||||
|
||||
import "github.com/Xhofe/alist/model"
|
||||
|
||||
func isSharePoint(account *model.Account) bool {
|
||||
return account.InternalType == "sharepoint"
|
||||
}
|
@ -2,14 +2,30 @@ package webdav
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/drivers/webdav/odrvcookie"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/studio-b12/gowebdav"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (driver WebDav) NewClient(account *model.Account) *gowebdav.Client {
|
||||
return gowebdav.NewClient(account.SiteUrl, account.Username, account.Password)
|
||||
c := gowebdav.NewClient(account.SiteUrl, account.Username, account.Password)
|
||||
if isSharePoint(account) {
|
||||
cookie, err := odrvcookie.GetCookie(account.Username, account.Password, account.SiteUrl)
|
||||
log.Debugln(cookie, err)
|
||||
if err == nil {
|
||||
log.Debugln("set interceptor")
|
||||
c.SetInterceptor(func(method string, rq *http.Request) {
|
||||
rq.Header.Del("Authorization")
|
||||
rq.Header.Set("Cookie", cookie)
|
||||
log.Debugf("sp webdav req: %+v", rq)
|
||||
})
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (driver WebDav) WebDavPath(path string) string {
|
||||
|
340
drivers/xunlei/driver.go
Normal file
340
drivers/xunlei/driver.go
Normal file
@ -0,0 +1,340 @@
|
||||
package xunlei
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"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
|
||||
}
|
||||
state := GetState(account)
|
||||
if state.isTokensExpires() {
|
||||
return state.Login(account)
|
||||
}
|
||||
account.Status = "work"
|
||||
model.SaveAccount(account)
|
||||
return nil
|
||||
}
|
||||
|
||||
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) {
|
||||
path = utils.ParsePath(path)
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files, _ := cache.([]model.File)
|
||||
return files, nil
|
||||
}
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make([]model.File, 0)
|
||||
for {
|
||||
var fileList FileList
|
||||
_, err = GetState(account).Request("GET", FILE_API_URL, func(r *resty.Request) {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"parent_id": file.Id,
|
||||
"page_token": fileList.NextPageToken,
|
||||
"with_audit": "true",
|
||||
"filters": `{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`,
|
||||
})
|
||||
r.SetResult(&fileList)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range fileList.Files {
|
||||
if file.Kind == FOLDER || (file.Kind == FILE && file.Audit.Status == "STATUS_OK") {
|
||||
files = append(files, *driver.formatFile(&file))
|
||||
}
|
||||
}
|
||||
if fileList.NextPageToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
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
|
||||
_, err = GetState(account).Request("GET", FILE_API_URL+"/{id}", func(r *resty.Request) {
|
||||
r.SetPathParam("id", file.Id)
|
||||
r.SetQueryParam("with_audit", "true")
|
||||
r.SetResult(&lFile)
|
||||
}, account)
|
||||
if 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
|
||||
}
|
||||
_, err = GetState(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
|
||||
r.SetBody(&base.Json{
|
||||
"kind": FOLDER,
|
||||
"name": name,
|
||||
"parent_id": parentFile.Id,
|
||||
})
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
_, err = GetState(account).Request("POST", FILE_API_URL+":batchMove", func(r *resty.Request) {
|
||||
r.SetBody(&base.Json{
|
||||
"to": base.Json{"parent_id": dstDirFile.Id},
|
||||
"ids": []string{srcFile.Id},
|
||||
})
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
_, err = GetState(account).Request("POST", FILE_API_URL+":batchCopy", func(r *resty.Request) {
|
||||
r.SetBody(&base.Json{
|
||||
"to": base.Json{"parent_id": dstDirFile.Id},
|
||||
"ids": []string{srcFile.Id},
|
||||
})
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
|
||||
srcFile, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = GetState(account).Request("PATCH", FILE_API_URL+"/{id}/trash", func(r *resty.Request) {
|
||||
r.SetPathParam("id", srcFile.Id)
|
||||
r.SetBody(&base.Json{})
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
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 os.Remove(tempFile.Name())
|
||||
|
||||
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tempFile.Close()
|
||||
|
||||
var resp UploadTaskResponse
|
||||
_, err = GetState(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
|
||||
r.SetBody(&base.Json{
|
||||
"kind": FILE,
|
||||
"parent_id": parentFile.Id,
|
||||
"name": file.Name,
|
||||
"size": fmt.Sprint(file.Size),
|
||||
"hash": gcid,
|
||||
"upload_type": UPLOAD_TYPE_RESUMABLE,
|
||||
})
|
||||
r.SetResult(&resp)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
param := resp.Resumable.Params
|
||||
if resp.UploadType == UPLOAD_TYPE_RESUMABLE {
|
||||
client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bucket, err := client.Bucket(param.Bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.UploadFile(param.Key, tempFile.Name(), 1<<22, oss.Routines(3), oss.Checkpoint(true, ""), oss.Expires(param.Expiration))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account) error {
|
||||
_, dstName := filepath.Split(dst)
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = GetState(account).Request("PATCH", FILE_API_URL+"/{id}", func(r *resty.Request) {
|
||||
r.SetPathParam("id", srcFile.Id)
|
||||
r.SetBody(&base.Json{"name": dstName})
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*XunLeiCloud)(nil)
|
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"`
|
||||
}
|
109
drivers/xunlei/util.go
Normal file
109
drivers/xunlei/util.go
Normal file
@ -0,0 +1,109 @@
|
||||
package xunlei
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"github.com/Xhofe/alist/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// 小米浏览器
|
||||
CLIENT_ID = "X7MtiU0Gb5YqWv-6"
|
||||
CLIENT_SECRET = "84MYEih3Eeu2HF4RrGce3Q"
|
||||
CLIENT_VERSION = "5.1.0.51045"
|
||||
|
||||
ALG_VERSION = "1"
|
||||
PACKAGE_NAME = "com.xunlei.xcloud.lib"
|
||||
)
|
||||
|
||||
var Algorithms = []string{
|
||||
"",
|
||||
"BXza40wm+P4zw8rEFpHA",
|
||||
"UfZLfKfYRmKTA0",
|
||||
"OMBGVt/9Wcaln1XaBz",
|
||||
"Jn217F4rk5FPPWyhoeV",
|
||||
"w5OwkGo0pGpb0Xe/XZ5T3",
|
||||
"5guM3DNiY4F78x49zQ97q75",
|
||||
"QXwn4D2j884wJgrYXjGClM/IVrJX",
|
||||
"NXBRosYvbHIm6w8vEB",
|
||||
"2kZ8Ie1yW2ib4O2iAkNpJobP",
|
||||
"11CoVJJQEc",
|
||||
"xf3QWysVwnVsNv5DCxU+cgNT1rK",
|
||||
"9eEfKkrqkfw",
|
||||
"T78dnANexYRbiZy",
|
||||
}
|
||||
|
||||
const (
|
||||
API_URL = "https://api-pan.xunlei.com/drive/v1"
|
||||
FILE_API_URL = API_URL + "/files"
|
||||
XLUSER_API_URL = "https://xluser-ssl.xunlei.com/v1"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
287
drivers/xunlei/xunlei.go
Normal file
287
drivers/xunlei/xunlei.go
Normal file
@ -0,0 +1,287 @@
|
||||
package xunlei
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"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().SetHeaders(map[string]string{"Accept": "application/json;charset=UTF-8"}).SetTimeout(base.DefaultTimeout)
|
||||
|
||||
// 一个账户只允许登陆一次
|
||||
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().
|
||||
SetBody(&creq).
|
||||
SetError(&e).
|
||||
SetResult(&resp).
|
||||
SetHeader("X-Device-Id", driverID).
|
||||
SetQueryParam("client_id", CLIENT_ID).
|
||||
Post(XLUSER_API_URL + "/shield/captcha/init")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if e.ErrorCode != 0 {
|
||||
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
|
||||
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(XLUSER_API_URL + "/auth/token")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch e.ErrorCode {
|
||||
case 4122, 4121:
|
||||
return s.login(account)
|
||||
case 0:
|
||||
s.tokenExpiresTime = (time.Now().UnixMilli() + resp.ExpiresIn*1000) - 30000
|
||||
s.tokenType = resp.TokenType
|
||||
s.accessToken = resp.AccessToken
|
||||
s.refreshToken = resp.RefreshToken
|
||||
s.userID = resp.UserID
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) login(account *model.Account) error {
|
||||
s.init()
|
||||
ctime := time.Now().UnixMilli()
|
||||
url := XLUSER_API_URL + "/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
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) Request(method string, url string, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
|
||||
s.Lock()
|
||||
token, err := s.getToken(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
captchaToken, err := s.getCaptchaToken(getAction(method, url), account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := xunleiClient.R().
|
||||
SetHeaders(map[string]string{
|
||||
"X-Device-Id": utils.GetMD5Encode(account.Username),
|
||||
"Authorization": token,
|
||||
"X-Captcha-Token": captchaToken,
|
||||
}).
|
||||
SetQueryParam("client_id", CLIENT_ID)
|
||||
|
||||
callback(req)
|
||||
s.Unlock()
|
||||
|
||||
var res *resty.Response
|
||||
switch method {
|
||||
case "GET":
|
||||
res, err = req.Get(url)
|
||||
case "POST":
|
||||
res, err = req.Post(url)
|
||||
case "DELETE":
|
||||
res, err = req.Delete(url)
|
||||
case "PATCH":
|
||||
res, err = req.Patch(url)
|
||||
case "PUT":
|
||||
res, err = req.Put(url)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
|
||||
var e Erron
|
||||
utils.Json.Unmarshal(res.Body(), &e)
|
||||
switch e.ErrorCode {
|
||||
case 9:
|
||||
s.newCaptchaToken(getAction(method, url), nil, account)
|
||||
fallthrough
|
||||
case 4122, 4121:
|
||||
return s.Request(method, url, callback, account)
|
||||
case 0:
|
||||
if res.StatusCode() == http.StatusOK {
|
||||
return res, nil
|
||||
}
|
||||
return nil, fmt.Errorf(res.String())
|
||||
default:
|
||||
return nil, 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)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user