Compare commits
238 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
6fd05d7d72 | |||
f26ac57569 | |||
2434ac54d0 | |||
f25b557327 | |||
81a0706d01 | |||
5f6b576cbf | |||
549877f71e | |||
c6a5ba9b91 | |||
1a69d80489 | |||
b797f4302c | |||
bf9aa5c3d3 | |||
7390e19a7a | |||
b31a12a0cc | |||
26ce001782 | |||
a2c7ff3262 | |||
8fc7c716c0 | |||
c70fc3fc4b | |||
df513b7dc0 | |||
2a9598f4c6 | |||
224c20779c | |||
5d722298cb | |||
4bcc6359e3 | |||
4144afcc92 | |||
2ad27046fb | |||
9516ac6718 | |||
de638c7c36 | |||
c6b34a033b | |||
31de3399d2 | |||
0dc2ca019f | |||
04724f7f0f | |||
75a983a965 | |||
e12d8bb8ca | |||
68f1ccfed4 | |||
54272db59c | |||
6d34e88360 | |||
0a901a2eb0 | |||
e1671a0511 | |||
dcb4ec695f | |||
4a21b6fe1d | |||
96a237902b | |||
cfb51e9f80 | |||
e952f1c243 | |||
07d6ca27db | |||
8245da485a | |||
5c759217cf | |||
0648fdebc2 | |||
ed670e528f | |||
2473309a51 |
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
|
||||
}
|
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -5,7 +5,8 @@ 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: input
|
||||
id: version
|
||||
attributes:
|
||||
@ -28,11 +29,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.
|
24
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: "Feature request"
|
||||
description: Feature request
|
||||
labels: ["enhancement: pending triage"]
|
||||
body:
|
||||
- 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
|
44
.github/workflows/build.yml
vendored
44
.github/workflows/build.yml
vendored
@ -2,9 +2,9 @@ name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ v2 ]
|
||||
branches: [ '**' ]
|
||||
pull_request:
|
||||
branches: [ v2 ]
|
||||
branches: [ '**' ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -28,10 +28,9 @@ 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
|
||||
@ -49,40 +48,3 @@ jobs:
|
||||
with:
|
||||
name: artifact
|
||||
path: alist/build
|
||||
docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: xhofe/alist
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Build web
|
||||
run: |
|
||||
bash build.sh web
|
||||
mv dist/* public
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: xhofe
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
44
.github/workflows/build_docker.yml
vendored
Normal file
44
.github/workflows/build_docker.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: build_docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ v2 ]
|
||||
|
||||
jobs:
|
||||
build_docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: xhofe/alist
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Build web
|
||||
run: |
|
||||
bash build.sh web
|
||||
mv dist/* public
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: xhofe
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
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 }}
|
39
.github/workflows/release.yml
vendored
39
.github/workflows/release.yml
vendored
@ -27,7 +27,7 @@ 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
|
||||
@ -44,40 +44,3 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: alist/build/compress/*
|
||||
docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: xhofe/alist
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Build web
|
||||
run: |
|
||||
bash build.sh cdn
|
||||
mv dist/* public
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: xhofe
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
45
.github/workflows/release_docker.yml
vendored
Normal file
45
.github/workflows/release_docker.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: release_docker
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
docker_release:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: xhofe/alist
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Build web
|
||||
run: |
|
||||
bash build.sh cdn
|
||||
mv dist/* public
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: xhofe
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
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!
|
@ -3,7 +3,7 @@ LABEL stage=go-builder
|
||||
WORKDIR /app/
|
||||
COPY ./ ./
|
||||
RUN apk add --no-cache bash git go gcc musl-dev; \
|
||||
sh build.sh docker
|
||||
bash build.sh docker
|
||||
|
||||
FROM alpine:edge
|
||||
LABEL MAINTAINER="i@nn.ci"
|
||||
@ -11,4 +11,4 @@ VOLUME /opt/alist/data/
|
||||
WORKDIR /opt/alist/
|
||||
COPY --from=builder /app/bin/alist ./
|
||||
EXPOSE 5244
|
||||
CMD [ "./alist" ]
|
||||
CMD [ "./alist", "-docker" ]
|
30
README.md
30
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/)
|
||||
@ -35,6 +34,10 @@ English | [中文](./README_cn.md)
|
||||
- [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] Easy to deploy and out-of-the-box
|
||||
- [x] File preview (PDF, markdown, code, plain text, ...)
|
||||
- [x] Image preview in gallery mode
|
||||
@ -50,26 +53,37 @@ English | [中文](./README_cn.md)
|
||||
- [x] Cloudflare workers proxy
|
||||
- [x] File/Folder package download
|
||||
- [x] Support video list playback and subtitles(ass,srt,vtt)
|
||||
- [x] Web upload(Can allow visitors to upload) and delete
|
||||
- [x] Web upload(Can allow visitors to upload), delete, mkdir, rename, move and copy
|
||||
|
||||
## 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)
|
29
README_cn.md
29
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/)
|
||||
@ -34,6 +34,10 @@
|
||||
- [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] 部署方便,开箱即用
|
||||
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
||||
- [x] 画廊模式下的图像预览
|
||||
@ -49,26 +53,37 @@
|
||||
- [x] Cloudflare workers 中转
|
||||
- [x] 文件/文件夹打包下载
|
||||
- [x] 支持视频列表播放和字幕(ass,srt,vtt)
|
||||
- [x] 网页上传(可以允许访客上传),删除
|
||||
- [x] 网页上传(可以允许访客上传),删除,新建文件夹,重命名,移动,复制
|
||||
|
||||
## 讨论
|
||||
|
||||
一般问题请到[讨论论坛](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() {
|
||||
|
@ -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"
|
||||
)
|
||||
@ -19,7 +20,9 @@ func InitAccounts() {
|
||||
if !ok {
|
||||
log.Errorf("no [%s] driver", account.Type)
|
||||
} else {
|
||||
err := driver.Save(&accounts[i], nil)
|
||||
log.Infof("start init account: [%s], type: [%s]", account.Name, account.Type)
|
||||
//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,8 +3,10 @@ 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"
|
||||
)
|
||||
|
||||
// InitConf init config
|
||||
@ -20,13 +22,12 @@ 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 = new(conf.Config)
|
||||
conf.Conf = conf.DefaultConfig()
|
||||
err = utils.Json.Unmarshal(config, conf.Conf)
|
||||
if err != nil {
|
||||
log.Fatalf("load config error: %s", err.Error())
|
||||
@ -42,3 +43,24 @@ func InitConf() {
|
||||
log.Fatalf("update config struct error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
if !conf.Conf.Force {
|
||||
confFromEnv()
|
||||
}
|
||||
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())
|
||||
@ -60,14 +60,13 @@ func InitModel() {
|
||||
}
|
||||
case "postgres":
|
||||
{
|
||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai",
|
||||
databaseConfig.Host, databaseConfig.User, databaseConfig.Password, databaseConfig.Name, databaseConfig.Port)
|
||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=Asia/Shanghai",
|
||||
databaseConfig.Host, databaseConfig.User, databaseConfig.Password, databaseConfig.Name, databaseConfig.Port, databaseConfig.SslMode)
|
||||
db, err := gorm.Open(postgres.Open(dsn), gormConfig)
|
||||
if err != nil {
|
||||
log.Errorf("failed to connect database:%s", err.Error())
|
||||
}
|
||||
conf.DB = db
|
||||
|
||||
}
|
||||
default:
|
||||
log.Fatalf("not supported database type: %s", databaseConfig.Type)
|
||||
@ -80,6 +79,6 @@ func InitModel() {
|
||||
err = conf.DB.AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{})
|
||||
}
|
||||
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,
|
||||
@ -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,
|
||||
@ -219,6 +250,14 @@ func InitSettings() {
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "ocr api",
|
||||
Value: "https://api.xhofe.top/ocr/file/json",
|
||||
Description: "Used to identify verification codes",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
Group: model.BACK,
|
||||
},
|
||||
}
|
||||
for i, _ := range settings {
|
||||
v := settings[i]
|
||||
@ -227,6 +266,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())
|
||||
}
|
||||
@ -241,6 +283,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()
|
||||
|
70
build.sh
70
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,6 +49,7 @@ BUILD() {
|
||||
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
|
||||
gitCommit=$(git log --pretty=format:"%h" -1)
|
||||
gitTag=$(git describe --long --tags --dirty --always)
|
||||
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
||||
echo "build version: $gitTag"
|
||||
ldflags="\
|
||||
-w -s \
|
||||
@ -52,24 +58,73 @@ 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 alist -ldflags="$ldflags" -tags=jsoniter .
|
||||
xgo -targets=linux/amd64,windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||
fi
|
||||
mkdir "build"
|
||||
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 .. || exit
|
||||
}
|
||||
|
||||
BUILD_MUSL() {
|
||||
BASE="https://musl.cc/"
|
||||
FILES=(x86_64-linux-musl-cross aarch64-linux-musl-cross arm-linux-musleabihf-cross mips-linux-musl-cross mips64-linux-musl-cross mips64el-linux-musl-cross mipsel-linux-musl-cross powerpc64le-linux-musl-cross s390x-linux-musl-cross)
|
||||
for i in "${FILES[@]}"; do
|
||||
url="${BASE}${i}.tgz"
|
||||
curl -L -o "${i}.tgz" "${url}"
|
||||
sudo tar xf "${i}.tgz" --strip-components 1 -C /usr/local
|
||||
done
|
||||
cd alist
|
||||
appName="alist"
|
||||
builtAt="$(date +'%F %T %z')"
|
||||
goVersion=$(go version | sed 's/go version //')
|
||||
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' \
|
||||
-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)
|
||||
for i in "${!OS_ARCHES[@]}"; do
|
||||
os_arch=${OS_ARCHES[$i]}
|
||||
cgo_cc=${CGO_ARGS[$i]}
|
||||
echo building for ${os_arch}
|
||||
export GOOS=${os_arch%%-*}
|
||||
export GOARCH=${os_arch##*-}
|
||||
export CC=${cgo_cc}
|
||||
export CGO_ENABLED=1
|
||||
go build -o ./build/$appName-$os_arch -ldflags="$ldflags" -tags=jsoniter alist.go
|
||||
done
|
||||
cd .. || exit
|
||||
}
|
||||
|
||||
RELEASE() {
|
||||
cd alist/build
|
||||
upx -9 ./alist-linux-amd64
|
||||
upx -9 ./alist-windows*
|
||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||
cat md5.txt
|
||||
mkdir compress
|
||||
mv md5.txt compress
|
||||
for i in $(find . -type f -name "$appName-linux-*"); do
|
||||
@ -81,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
|
||||
@ -94,7 +149,8 @@ elif [ "$1" = "build" ]; then
|
||||
BUILD build
|
||||
elif [ "$1" = "release" ]; then
|
||||
BUILD release
|
||||
BUILD_MUSL
|
||||
RELEASE
|
||||
else
|
||||
echo -e "${RED_COLOR} 错误的命令${RES}"
|
||||
echo -e "${RED_COLOR} Parameter error ${RES}"
|
||||
fi
|
@ -1,41 +1,45 @@
|
||||
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"`
|
||||
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: "zhimg",
|
||||
Assets: "https://npm.elemecdn.com/alist-web@$version/dist",
|
||||
TempDir: "data/temp",
|
||||
Database: Database{
|
||||
Type: "sqlite3",
|
||||
Port: 0,
|
||||
|
13
conf/var.go
13
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,24 +24,28 @@ 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"}
|
||||
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico"}
|
||||
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav", "opus"}
|
||||
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
|
||||
)
|
||||
|
||||
var settingsMap = make(map[string]string, 0)
|
||||
var settingsMap = make(map[string]string)
|
||||
|
||||
func Set(key string, value string) {
|
||||
settingsMap[key] = value
|
||||
@ -78,6 +84,7 @@ var (
|
||||
"check parent folder", "check down link", "WebDAV username", "WebDAV password",
|
||||
"Visitor WebDAV username", "Visitor WebDAV password",
|
||||
"default page size", "load type",
|
||||
"ocr api", "favicon",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package _23
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
@ -10,9 +12,11 @@ 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"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
@ -53,6 +57,7 @@ func (driver Pan123) Items() []base.Item {
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,fileId,updateAt,createAt",
|
||||
Required: true,
|
||||
Default: "name",
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
@ -60,11 +65,15 @@ func (driver Pan123) Items() []base.Item {
|
||||
Type: base.TypeSelect,
|
||||
Values: "asc,desc",
|
||||
Required: true,
|
||||
Default: "asc",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Pan123) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "0"
|
||||
}
|
||||
@ -131,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,
|
||||
@ -189,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
|
||||
@ -234,7 +243,7 @@ func (driver Pan123) Move(src string, dst string, account *model.Account) error
|
||||
}
|
||||
parentFileId, _ := strconv.Atoi(dstDirFile.Id)
|
||||
data := base.Json{
|
||||
"fileId": fileId,
|
||||
"fileIdList": []base.Json{{"FileId": fileId}},
|
||||
"parentFileId": parentFileId,
|
||||
}
|
||||
_, err = driver.Request("https://www.123pan.com/api/file/mod_pid",
|
||||
@ -291,10 +300,37 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
parentFileId, _ := strconv.Atoi(parentFile.Id)
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = tempFile.Close()
|
||||
_ = os.Remove(tempFile.Name())
|
||||
}()
|
||||
_, err = io.Copy(tempFile, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tempFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := md5.New()
|
||||
_, err = io.Copy(h, tempFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
etag := hex.EncodeToString(h.Sum(nil))
|
||||
log.Debugln("md5:", etag)
|
||||
_, err = tempFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"driveId": 0,
|
||||
"duplicate": true,
|
||||
"etag": "836aae6cac845e17fce51919594737d0", //maybe file's md5
|
||||
"etag": etag,
|
||||
"fileName": file.GetFileName(),
|
||||
"parentFileId": parentFileId,
|
||||
"size": file.GetSize(),
|
||||
@ -307,6 +343,9 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Data.Key == "" {
|
||||
return nil
|
||||
}
|
||||
cfg := &aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken),
|
||||
Region: aws.String("123pan"),
|
||||
@ -321,7 +360,7 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &resp.Data.Bucket,
|
||||
Key: &resp.Data.Key,
|
||||
Body: file,
|
||||
Body: tempFile,
|
||||
}
|
||||
_, err = uploader.Upload(input)
|
||||
if err != nil {
|
||||
|
@ -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)
|
||||
@ -136,7 +136,7 @@ func (driver Cloud139) GetFiles(catalogID string, account *model.Account) ([]mod
|
||||
f := model.File{
|
||||
Id: content.ContentID,
|
||||
Name: content.ContentName,
|
||||
Size: int64(content.ContentSize),
|
||||
Size: content.ContentSize,
|
||||
Type: utils.GetFileType(path.Ext(content.ContentName)),
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: getTime(content.UpdateTime),
|
||||
|
@ -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"
|
||||
@ -64,6 +63,9 @@ func (driver Cloud139) Items() []base.Item {
|
||||
}
|
||||
|
||||
func (driver Cloud139) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := driver.Request("/orchestration/personalCloud/user/v1.0/qryUserExternInfo", base.Post, nil, nil, nil, base.Json{
|
||||
"qryUserExternInfoReq": base.Json{
|
||||
"commonAccountInfo": base.Json{
|
||||
@ -160,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
|
||||
@ -229,7 +231,7 @@ func (driver Cloud139) Move(src string, dst string, account *model.Account) erro
|
||||
"newCatalogID": dstParentFile.Id,
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": "18627147660",
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
@ -254,7 +256,7 @@ func (driver Cloud139) Rename(src string, dst string, account *model.Account) er
|
||||
"catalogID": srcFile.Id,
|
||||
"catalogName": utils.Base(dst),
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": "18627147660",
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
@ -264,7 +266,7 @@ func (driver Cloud139) Rename(src string, dst string, account *model.Account) er
|
||||
"contentID": srcFile.Id,
|
||||
"contentName": utils.Base(dst),
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": "18627147660",
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
@ -303,7 +305,7 @@ func (driver Cloud139) Copy(src string, dst string, account *model.Account) erro
|
||||
"newCatalogID": dstParentFile.Id,
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": "18627147660",
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
@ -332,10 +334,10 @@ func (driver Cloud139) Delete(path string, account *model.Account) error {
|
||||
"taskInfo": base.Json{
|
||||
"newCatalogID": "",
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": "18627147660",
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
@ -346,7 +348,7 @@ func (driver Cloud139) Delete(path string, account *model.Account) error {
|
||||
"catalogList": catalogInfoList,
|
||||
"contentList": contentInfoList,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": "18627147660",
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
"sourceCatalogType": 1002,
|
||||
@ -382,7 +384,7 @@ func (driver Cloud139) Upload(file *model.FileStream, account *model.Account) er
|
||||
"parentCatalogID": parentFile.Id,
|
||||
"newCatalogName": "",
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": "18627147660",
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ func (driver Cloud139) familyGetFiles(catalogID string, account *model.Account)
|
||||
f := model.File{
|
||||
Id: content.ContentID,
|
||||
Name: content.ContentName,
|
||||
Size: int64(content.ContentSize),
|
||||
Size: content.ContentSize,
|
||||
Type: utils.GetFileType(path.Ext(content.ContentName)),
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: getTime(content.LastUpdateTime),
|
||||
|
@ -40,7 +40,7 @@ type Content struct {
|
||||
ContentID string `json:"contentID"`
|
||||
ContentName string `json:"contentName"`
|
||||
//ContentSuffix string `json:"contentSuffix"`
|
||||
ContentSize int `json:"contentSize"`
|
||||
ContentSize int64 `json:"contentSize"`
|
||||
//ContentDesc string `json:"contentDesc"`
|
||||
//ContentType int `json:"contentType"`
|
||||
//ContentOrigin int `json:"contentOrigin"`
|
||||
@ -133,42 +133,42 @@ type UploadResp struct {
|
||||
|
||||
type CloudContent struct {
|
||||
ContentID string `json:"contentID"`
|
||||
Modifier string `json:"modifier"`
|
||||
Nickname string `json:"nickname"`
|
||||
CloudNickName string `json:"cloudNickName"`
|
||||
//Modifier string `json:"modifier"`
|
||||
//Nickname string `json:"nickname"`
|
||||
//CloudNickName string `json:"cloudNickName"`
|
||||
ContentName string `json:"contentName"`
|
||||
ContentType int `json:"contentType"`
|
||||
ContentSuffix string `json:"contentSuffix"`
|
||||
ContentSize int `json:"contentSize"`
|
||||
ContentDesc string `json:"contentDesc"`
|
||||
CreateTime string `json:"createTime"`
|
||||
Shottime interface{} `json:"shottime"`
|
||||
//ContentType int `json:"contentType"`
|
||||
//ContentSuffix string `json:"contentSuffix"`
|
||||
ContentSize int64 `json:"contentSize"`
|
||||
//ContentDesc string `json:"contentDesc"`
|
||||
//CreateTime string `json:"createTime"`
|
||||
//Shottime interface{} `json:"shottime"`
|
||||
LastUpdateTime string `json:"lastUpdateTime"`
|
||||
ThumbnailURL string `json:"thumbnailURL"`
|
||||
MidthumbnailURL string `json:"midthumbnailURL"`
|
||||
BigthumbnailURL string `json:"bigthumbnailURL"`
|
||||
PresentURL string `json:"presentURL"`
|
||||
PresentLURL string `json:"presentLURL"`
|
||||
PresentHURL string `json:"presentHURL"`
|
||||
ParentCatalogID string `json:"parentCatalogID"`
|
||||
Uploader string `json:"uploader"`
|
||||
UploaderNickName string `json:"uploaderNickName"`
|
||||
TreeInfo interface{} `json:"treeInfo"`
|
||||
UpdateTime interface{} `json:"updateTime"`
|
||||
ExtInfo struct {
|
||||
Uploader string `json:"uploader"`
|
||||
} `json:"extInfo"`
|
||||
EtagOprType interface{} `json:"etagOprType"`
|
||||
//MidthumbnailURL string `json:"midthumbnailURL"`
|
||||
//BigthumbnailURL string `json:"bigthumbnailURL"`
|
||||
//PresentURL string `json:"presentURL"`
|
||||
//PresentLURL string `json:"presentLURL"`
|
||||
//PresentHURL string `json:"presentHURL"`
|
||||
//ParentCatalogID string `json:"parentCatalogID"`
|
||||
//Uploader string `json:"uploader"`
|
||||
//UploaderNickName string `json:"uploaderNickName"`
|
||||
//TreeInfo interface{} `json:"treeInfo"`
|
||||
//UpdateTime interface{} `json:"updateTime"`
|
||||
//ExtInfo struct {
|
||||
// Uploader string `json:"uploader"`
|
||||
//} `json:"extInfo"`
|
||||
//EtagOprType interface{} `json:"etagOprType"`
|
||||
}
|
||||
|
||||
type CloudCatalog struct {
|
||||
CatalogID string `json:"catalogID"`
|
||||
CatalogName string `json:"catalogName"`
|
||||
CloudID string `json:"cloudID"`
|
||||
CreateTime string `json:"createTime"`
|
||||
//CloudID string `json:"cloudID"`
|
||||
//CreateTime string `json:"createTime"`
|
||||
LastUpdateTime string `json:"lastUpdateTime"`
|
||||
Creator string `json:"creator"`
|
||||
CreatorNickname string `json:"creatorNickname"`
|
||||
//Creator string `json:"creator"`
|
||||
//CreatorNickname string `json:"creatorNickname"`
|
||||
}
|
||||
|
||||
type QueryContentListResp struct {
|
||||
|
@ -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)
|
||||
|
@ -2,16 +2,9 @@ package _89
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
@ -19,14 +12,12 @@ import (
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/google/uuid"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"math"
|
||||
mathRand "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"net/http/cookiejar"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@ -35,6 +26,23 @@ import (
|
||||
)
|
||||
|
||||
var client189Map map[string]*resty.Client
|
||||
var infoMap = make(map[string]Rsa)
|
||||
|
||||
func (driver Cloud189) getClient(account *model.Account) (*resty.Client, error) {
|
||||
client, ok := client189Map[account.Name]
|
||||
if ok {
|
||||
return client, nil
|
||||
}
|
||||
err := driver.Login(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, ok = client189Map[account.Name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can't find [%s] client", account.Name)
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
|
||||
f := &model.File{
|
||||
@ -81,12 +89,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"`
|
||||
@ -100,9 +102,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 := ""
|
||||
@ -137,9 +143,33 @@ func (driver Cloud189) Login(account *model.Account) error {
|
||||
vCodeRS := ""
|
||||
if vCodeID != "" {
|
||||
// need ValidateCode
|
||||
log.Debugf("try to identify verification codes")
|
||||
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
|
||||
u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
|
||||
imgRes, err := client.R().SetHeaders(map[string]string{
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0",
|
||||
"Referer": "https://open.e.189.cn/api/logbox/oauth2/unifyAccountLogin.do",
|
||||
"Sec-Fetch-Dest": "image",
|
||||
"Sec-Fetch-Mode": "no-cors",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
}).Get(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userRsa := RsaEncode([]byte(account.Username), jRsakey)
|
||||
passwordRsa := RsaEncode([]byte(account.Password), jRsakey)
|
||||
vRes, err := client.R().SetMultipartField(
|
||||
"image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
|
||||
Post(conf.GetStr("ocr api"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
|
||||
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
|
||||
}
|
||||
vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
|
||||
log.Debugln("code: ", vCodeRS)
|
||||
}
|
||||
userRsa := RsaEncode([]byte(account.Username), jRsakey, true)
|
||||
passwordRsa := RsaEncode([]byte(account.Password), jRsakey, true)
|
||||
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
|
||||
var loginResp LoginResp
|
||||
res, err := client.R().
|
||||
@ -183,39 +213,6 @@ func (driver Cloud189) Login(account *model.Account) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Cloud189Error struct {
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
}
|
||||
|
||||
type Cloud189File struct {
|
||||
Id int64 `json:"id"`
|
||||
LastOpTime string `json:"lastOpTime"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Icon struct {
|
||||
SmallUrl string `json:"smallUrl"`
|
||||
//LargeUrl string `json:"largeUrl"`
|
||||
} `json:"icon"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type Cloud189Folder struct {
|
||||
Id int64 `json:"id"`
|
||||
LastOpTime string `json:"lastOpTime"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Cloud189Files 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"`
|
||||
}
|
||||
|
||||
func (driver Cloud189) isFamily(account *model.Account) bool {
|
||||
return account.InternalType == "Family"
|
||||
}
|
||||
@ -233,8 +230,8 @@ func (driver Cloud189) GetFiles(fileId string, account *model.Account) ([]Cloud1
|
||||
"mediaType": "0",
|
||||
"folderId": fileId,
|
||||
"iconOption": "5",
|
||||
"orderBy": account.OrderBy,
|
||||
"descending": account.OrderDirection,
|
||||
"orderBy": "lastOpTime", //account.OrderBy
|
||||
"descending": "true", //account.OrderDirection
|
||||
}, nil, nil, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -264,9 +261,9 @@ func (driver Cloud189) GetFiles(fileId string, account *model.Account) ([]Cloud1
|
||||
}
|
||||
|
||||
func (driver Cloud189) Request(url string, method int, query, form map[string]string, headers map[string]string, account *model.Account) ([]byte, error) {
|
||||
client, ok := client189Map[account.Name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can't find [%s] client", account.Name)
|
||||
client, err := driver.getClient(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//var resp base.Json
|
||||
if driver.isFamily(account) {
|
||||
@ -293,7 +290,6 @@ func (driver Cloud189) Request(url string, method int, query, form map[string]st
|
||||
if headers != nil {
|
||||
req = req.SetHeaders(headers)
|
||||
}
|
||||
var err error
|
||||
var res *resty.Response
|
||||
switch method {
|
||||
case base.Get:
|
||||
@ -306,7 +302,7 @@ func (driver Cloud189) Request(url string, method int, query, form map[string]st
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
//log.Debug(res.String())
|
||||
if e.ErrorCode != "" {
|
||||
if e.ErrorCode == "InvalidSessionKey" {
|
||||
err = driver.Login(account)
|
||||
@ -323,61 +319,198 @@ func (driver Cloud189) Request(url string, method int, query, form map[string]st
|
||||
}
|
||||
|
||||
func (driver Cloud189) GetSessionKey(account *model.Account) (string, error) {
|
||||
//info, ok := infoMap[account.Name]
|
||||
//if !ok {
|
||||
// info = Info{}
|
||||
// infoMap[account.Name] = info
|
||||
//} else {
|
||||
// log.Debugf("hit")
|
||||
//}
|
||||
//if info.SessionKey != "" {
|
||||
// return info.SessionKey, nil
|
||||
//}
|
||||
resp, err := driver.Request("https://cloud.189.cn/v2/getUserBriefInfo.action", base.Get, nil, nil, nil, account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return jsoniter.Get(resp, "sessionKey").ToString(), nil
|
||||
sessionKey := jsoniter.Get(resp, "sessionKey").ToString()
|
||||
//info.SessionKey = sessionKey
|
||||
return sessionKey, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) GetResKey(account *model.Account) (string, string, error) {
|
||||
rsa, ok := infoMap[account.Name]
|
||||
if !ok {
|
||||
rsa = Rsa{}
|
||||
infoMap[account.Name] = rsa
|
||||
}
|
||||
now := time.Now().UnixMilli()
|
||||
if rsa.Expire > now {
|
||||
return rsa.PubKey, rsa.PkId, nil
|
||||
}
|
||||
resp, err := driver.Request("https://cloud.189.cn/api/security/generateRsaKey.action", base.Get, nil, nil, nil, account)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return jsoniter.Get(resp, "pubKey").ToString(), jsoniter.Get(resp, "pkId").ToString(), nil
|
||||
pubKey, pkId := jsoniter.Get(resp, "pubKey").ToString(), jsoniter.Get(resp, "pkId").ToString()
|
||||
rsa.PubKey, rsa.PkId = pubKey, pkId
|
||||
rsa.Expire = jsoniter.Get(resp, "expire").ToInt64()
|
||||
return pubKey, pkId, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) UploadRequest(url string, form map[string]string, account *model.Account) ([]byte, error) {
|
||||
sessionKey, err := driver.GetSessionKey(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//func (driver Cloud189) UploadRequest1(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
|
||||
// //sessionKey, err := driver.GetSessionKey(account)
|
||||
// //if err != nil {
|
||||
// // return nil, err
|
||||
// //}
|
||||
// sessionKey := account.DriveId
|
||||
// pubKey, pkId, err := driver.GetResKey(account)
|
||||
// log.Debugln(sessionKey, pubKey, pkId)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// xRId := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
|
||||
// pkey := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")[0 : 16+int(16*mathRand.Float32())]
|
||||
// params := hex.EncodeToString(AesEncrypt([]byte(qs(form)), []byte(pkey[0:16])))
|
||||
// date := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
// a := make(url.Values)
|
||||
// a.Set("SessionKey", sessionKey)
|
||||
// a.Set("Operate", http.MethodGet)
|
||||
// a.Set("RequestURI", uri)
|
||||
// a.Set("Date", date)
|
||||
// a.Set("params", params)
|
||||
// signature := hex.EncodeToString(SHA1(EncodeParam(a), pkey))
|
||||
// encryptionText := RsaEncode([]byte(pkey), pubKey, false)
|
||||
// headers := map[string]string{
|
||||
// "signature": signature,
|
||||
// "sessionKey": sessionKey,
|
||||
// "encryptionText": encryptionText,
|
||||
// "pkId": pkId,
|
||||
// "x-request-id": xRId,
|
||||
// "x-request-date": date,
|
||||
// }
|
||||
// req := base.RestyClient.R().SetHeaders(headers).SetQueryParam("params", params)
|
||||
// if resp != nil {
|
||||
// req.SetResult(resp)
|
||||
// }
|
||||
// res, err := req.Get("https://upload.cloud.189.cn" + uri)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// //log.Debug(res.String())
|
||||
// data := res.Body()
|
||||
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
|
||||
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
|
||||
// }
|
||||
// return data, nil
|
||||
//}
|
||||
//
|
||||
//func (driver Cloud189) UploadRequest2(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
|
||||
// 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())]
|
||||
//
|
||||
// e := qs(form)
|
||||
// data := AesEncrypt([]byte(e), []byte(l[0:16]))
|
||||
// h := hex.EncodeToString(data)
|
||||
//
|
||||
// sessionKey := account.DriveId
|
||||
// a := make(url.Values)
|
||||
// a.Set("SessionKey", sessionKey)
|
||||
// a.Set("Operate", http.MethodGet)
|
||||
// a.Set("RequestURI", uri)
|
||||
// a.Set("Date", c)
|
||||
// a.Set("params", h)
|
||||
// g := SHA1(EncodeParam(a), l)
|
||||
//
|
||||
// pubKey, pkId, err := driver.GetResKey(account)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// b := RsaEncode([]byte(l), pubKey, false)
|
||||
// client, err := driver.getClient(account)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// req := client.R()
|
||||
// req.Header.Set("accept", "application/json;charset=UTF-8")
|
||||
// req.Header.Set("SessionKey", sessionKey)
|
||||
// req.Header.Set("Signature", hex.EncodeToString(g))
|
||||
// req.Header.Set("X-Request-Date", c)
|
||||
// req.Header.Set("X-Request-ID", r)
|
||||
// req.Header.Set("EncryptionText", b)
|
||||
// req.Header.Set("PkId", pkId)
|
||||
// if resp != nil {
|
||||
// req.SetResult(resp)
|
||||
// }
|
||||
// res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// //log.Debug(res.String())
|
||||
// data = res.Body()
|
||||
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
|
||||
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
|
||||
// }
|
||||
// return data, nil
|
||||
//}
|
||||
|
||||
func (driver Cloud189) UploadRequest(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
|
||||
c := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||
r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
|
||||
l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
|
||||
l = l[0 : 16+int(16*utils.Rand.Float32())]
|
||||
|
||||
e := qs(form)
|
||||
data := AesEncrypt([]byte(e), []byte(l[0:16]))
|
||||
h := hex.EncodeToString(data)
|
||||
|
||||
sessionKey := account.DriveId
|
||||
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s¶ms=%s", sessionKey, uri, c, h), l)
|
||||
|
||||
pubKey, pkId, err := driver.GetResKey(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xRId := uuid.New().String()
|
||||
pkey := strings.ReplaceAll(xRId, "-", "")[:mathRand.Intn(16)+16]
|
||||
params := aesEncrypt(qs(form), pkey[:16])
|
||||
date := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s¶ms=%s", sessionKey, url, date, params), pkey)
|
||||
encryptionText := RsaEncode([]byte(pkey), pubKey)
|
||||
headers := map[string]string{
|
||||
"signature": signature,
|
||||
"sessionKey": sessionKey,
|
||||
"encryptionText": encryptionText,
|
||||
"pkId": pkId,
|
||||
"x-request-id": xRId,
|
||||
"x-request-date": date,
|
||||
"origin": "https://cloud.189.cn",
|
||||
"referer": "https://cloud.189.cn/",
|
||||
}
|
||||
log.Debugf("%+v\n%s", headers, params)
|
||||
res, err := base.RestyClient.R().SetHeaders(headers).SetQueryParam("params", params).Get("https://upload.cloud.189.cn" + url)
|
||||
b := RsaEncode([]byte(l), pubKey, false)
|
||||
client, err := driver.getClient(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
data := res.Body()
|
||||
req := client.R()
|
||||
req.Header.Set("accept", "application/json;charset=UTF-8")
|
||||
req.Header.Set("SessionKey", sessionKey)
|
||||
req.Header.Set("Signature", signature)
|
||||
req.Header.Set("X-Request-Date", c)
|
||||
req.Header.Set("X-Request-ID", r)
|
||||
req.Header.Set("EncryptionText", b)
|
||||
req.Header.Set("PkId", pkId)
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//log.Debug(res.String())
|
||||
data = res.Body()
|
||||
if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
|
||||
return nil, errors.New(jsoniter.Get(data, "msg").ToString())
|
||||
return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Upload Error: decrypt encryptionText failed
|
||||
// NewUpload Error: signature check false
|
||||
func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account) error {
|
||||
sessionKey, err := driver.GetSessionKey(account)
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
} else {
|
||||
account.Status = "work"
|
||||
account.DriveId = sessionKey
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
const DEFAULT uint64 = 10485760
|
||||
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||
var finish uint64 = 0
|
||||
@ -390,15 +523,18 @@ 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",
|
||||
}, account)
|
||||
}, account, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploadFileId := jsoniter.Get(res, "data.uploadFileId").ToString()
|
||||
uploadFileId := jsoniter.Get(res, "data", "uploadFileId").ToString()
|
||||
//_, err = driver.UploadRequest("/person/getUploadedPartsInfo", map[string]string{
|
||||
// "uploadFileId": uploadFileId,
|
||||
//}, account, nil)
|
||||
var i int64
|
||||
var byteSize uint64
|
||||
md5s := make([]string, 0)
|
||||
@ -408,180 +544,82 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
|
||||
if DEFAULT < byteSize {
|
||||
byteSize = DEFAULT
|
||||
}
|
||||
log.Debugf("%d,%d", byteSize, finish)
|
||||
//log.Debugf("%d,%d", byteSize, finish)
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(file, byteData)
|
||||
log.Debug(err, n)
|
||||
//log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
finish += uint64(n)
|
||||
md5Bytes := getMd5(byteData)
|
||||
md5Str := hex.EncodeToString(md5Bytes)
|
||||
md5Hex := hex.EncodeToString(md5Bytes)
|
||||
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
|
||||
md5s = append(md5s, md5Str)
|
||||
md5s = append(md5s, strings.ToUpper(md5Hex))
|
||||
md5Sum.Write(byteData)
|
||||
//log.Debugf("md5Bytes: %+v,md5Str:%s,md5Base64:%s", md5Bytes, md5Hex, md5Base64)
|
||||
var resp UploadUrlsResp
|
||||
res, err = driver.UploadRequest("/person/getMultiUploadUrls", map[string]string{
|
||||
"partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64),
|
||||
"uploadFileId": uploadFileId,
|
||||
}, account)
|
||||
}, account, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploadData := jsoniter.Get(res, "uploadUrls.partNumber_"+strconv.FormatInt(i, 10))
|
||||
headers := strings.Split(uploadData.Get("requestHeader").ToString(), "&")
|
||||
req, err := http.NewRequest("PUT", uploadData.Get("requestURL").ToString(), bytes.NewBuffer(byteData))
|
||||
uploadData := resp.UploadUrls["partNumber_"+strconv.FormatInt(i, 10)]
|
||||
log.Debugf("uploadData: %+v", uploadData)
|
||||
requestURL := uploadData.RequestURL
|
||||
uploadHeaders := strings.Split(decodeURIComponent(uploadData.RequestHeader), "&")
|
||||
req, _ := http.NewRequest(http.MethodPut, requestURL, bytes.NewReader(byteData))
|
||||
for _, v := range uploadHeaders {
|
||||
i := strings.Index(v, "=")
|
||||
req.Header.Set(v[0:i], v[i+1:])
|
||||
}
|
||||
|
||||
r, err := base.HttpClient.Do(req)
|
||||
log.Debugf("%+v %+v", r, r.Request.Header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, header := range headers {
|
||||
kv := strings.Split(header, "=")
|
||||
req.Header.Set(kv[0], strings.Join(kv[1:], "="))
|
||||
}
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
fileMd5 := hex.EncodeToString(md5Sum.Sum(nil))
|
||||
sliceMd5 := fileMd5
|
||||
if file.GetSize() > DEFAULT {
|
||||
sliceMd5 = utils.GetMD5Encode(strings.Join(md5s, "\n"))
|
||||
}
|
||||
log.Debugf("%+v", res)
|
||||
}
|
||||
id := md5Sum.Sum(nil)
|
||||
res, err = driver.UploadRequest("/person/commitMultiUploadFile", map[string]string{
|
||||
"uploadFileId": uploadFileId,
|
||||
"fileMd5": hex.EncodeToString(id),
|
||||
"sliceMd5": utils.GetMD5Encode(strings.Join(md5s, "\n")),
|
||||
"fileMd5": fileMd5,
|
||||
"sliceMd5": sliceMd5,
|
||||
"lazyCheck": "1",
|
||||
}, account)
|
||||
}, account, nil)
|
||||
account.DriveId, _ = driver.GetSessionKey(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func random() string {
|
||||
return fmt.Sprintf("0.%17v", mathRand.New(mathRand.NewSource(time.Now().UnixNano())).Int63n(100000000000000000))
|
||||
}
|
||||
|
||||
func RsaEncode(origData []byte, j_rsakey string) string {
|
||||
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
|
||||
block, _ := pem.Decode(publicKey)
|
||||
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
pub := pubInterface.(*rsa.PublicKey)
|
||||
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
|
||||
func (driver Cloud189) OldUpload(file *model.FileStream, account *model.Account) error {
|
||||
//return base.ErrNotImplement
|
||||
client, err := driver.getClient(account)
|
||||
if err != nil {
|
||||
log.Errorf("err: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
return b64tohex(base64.StdEncoding.EncodeToString(b))
|
||||
parentFile, err := driver.File(file.ParentPath, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
func int2char(a int) string {
|
||||
return strings.Split(BI_RM, "")[a]
|
||||
// api refer to PanIndex
|
||||
res, err := client.R().SetMultipartFormData(map[string]string{
|
||||
"parentId": parentFile.Id,
|
||||
"sessionKey": account.DriveId,
|
||||
"opertype": "1",
|
||||
"fname": file.GetFileName(),
|
||||
}).SetMultipartField("Filedata", file.GetFileName(), file.GetMIMEType(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func b64tohex(a string) string {
|
||||
d := ""
|
||||
e := 0
|
||||
c := 0
|
||||
for i := 0; i < len(a); i++ {
|
||||
m := strings.Split(a, "")[i]
|
||||
if m != "=" {
|
||||
v := strings.Index(b64map, m)
|
||||
if 0 == e {
|
||||
e = 1
|
||||
d += int2char(v >> 2)
|
||||
c = 3 & v
|
||||
} else if 1 == e {
|
||||
e = 2
|
||||
d += int2char(c<<2 | v>>4)
|
||||
c = 15 & v
|
||||
} else if 2 == e {
|
||||
e = 3
|
||||
d += int2char(c)
|
||||
d += int2char(v >> 2)
|
||||
c = 3 & v
|
||||
} else {
|
||||
e = 0
|
||||
d += int2char(c<<2 | v>>4)
|
||||
d += int2char(15 & v)
|
||||
if jsoniter.Get(res.Body(), "MD5").ToString() != "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if e == 1 {
|
||||
d += int2char(c << 2)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func qs(form map[string]string) string {
|
||||
strList := make([]string, 0)
|
||||
for k, v := range form {
|
||||
strList = append(strList, fmt.Sprintf("%s=%s", k, url.QueryEscape(v)))
|
||||
}
|
||||
return strings.Join(strList, "&")
|
||||
}
|
||||
|
||||
func aesEncrypt(data, key string) string {
|
||||
encrypted := AesEncryptECB([]byte(data), []byte(key))
|
||||
//return string(encrypted)
|
||||
return hex.EncodeToString(encrypted)
|
||||
}
|
||||
|
||||
func hmacSha1(data string, secret string) string {
|
||||
h := hmac.New(sha1.New, []byte(secret))
|
||||
h.Write([]byte(data))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func AesEncryptECB(origData []byte, key []byte) (encrypted []byte) {
|
||||
cipher, _ := aes.NewCipher(generateKey(key))
|
||||
length := (len(origData) + aes.BlockSize) / aes.BlockSize
|
||||
plain := make([]byte, length*aes.BlockSize)
|
||||
copy(plain, origData)
|
||||
pad := byte(len(plain) - len(origData))
|
||||
for i := len(origData); i < len(plain); i++ {
|
||||
plain[i] = pad
|
||||
}
|
||||
encrypted = make([]byte, len(plain))
|
||||
// 分组分块加密
|
||||
for bs, be := 0, cipher.BlockSize(); bs <= len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
||||
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
|
||||
}
|
||||
|
||||
return encrypted
|
||||
}
|
||||
func AesDecryptECB(encrypted []byte, key []byte) (decrypted []byte) {
|
||||
cipher, _ := aes.NewCipher(generateKey(key))
|
||||
decrypted = make([]byte, len(encrypted))
|
||||
//
|
||||
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
|
||||
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
|
||||
}
|
||||
|
||||
trim := 0
|
||||
if len(decrypted) > 0 {
|
||||
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
|
||||
}
|
||||
|
||||
return decrypted[:trim]
|
||||
}
|
||||
func generateKey(key []byte) (genKey []byte) {
|
||||
genKey = make([]byte, 16)
|
||||
copy(genKey, key)
|
||||
for i := 16; i < len(key); {
|
||||
for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
|
||||
genKey[j] ^= key[i]
|
||||
}
|
||||
}
|
||||
return genKey
|
||||
}
|
||||
|
||||
func getMd5(data []byte) []byte {
|
||||
h := md5.New()
|
||||
h.Write(data)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Cloud189{})
|
||||
client189Map = make(map[string]*resty.Client, 0)
|
||||
log.Debugf(res.String())
|
||||
return errors.New(res.String())
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
package _89
|
||||
|
||||
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/gin-gonic/gin"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Cloud189 struct{}
|
||||
@ -18,6 +18,7 @@ type Cloud189 struct{}
|
||||
func (driver Cloud189) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "189Cloud",
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,27 +56,30 @@ func (driver Cloud189) Items() []base.Item {
|
||||
// Label: "family id",
|
||||
// Type: base.TypeString,
|
||||
//},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,size,lastOpTime,createdDate",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "desc",
|
||||
Type: base.TypeSelect,
|
||||
Values: "true,false",
|
||||
Required: true,
|
||||
},
|
||||
//{
|
||||
// Name: "order_by",
|
||||
// Label: "order_by",
|
||||
// Type: base.TypeSelect,
|
||||
// Values: "name,size,lastOpTime,createdDate",
|
||||
// Required: true,
|
||||
//},
|
||||
//{
|
||||
// Name: "order_direction",
|
||||
// Label: "desc",
|
||||
// Type: base.TypeSelect,
|
||||
// Values: "true,false",
|
||||
// Required: true,
|
||||
//},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Cloud189) Save(account *model.Account, old *model.Account) error {
|
||||
if old != nil && old.Name != account.Name {
|
||||
if old != nil {
|
||||
delete(client189Map, old.Name)
|
||||
}
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if err := driver.Login(account); err != nil {
|
||||
account.Status = err.Error()
|
||||
_ = model.SaveAccount(account)
|
||||
@ -151,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
|
||||
@ -166,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
|
||||
}
|
||||
|
||||
@ -196,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
|
||||
@ -346,29 +370,8 @@ func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) er
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
client, ok := client189Map[account.Name]
|
||||
if !ok {
|
||||
return fmt.Errorf("can't find [%s] client", account.Name)
|
||||
}
|
||||
parentFile, err := driver.File(file.ParentPath, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// api refer to PanIndex
|
||||
res, err := client.R().SetMultipartFormData(map[string]string{
|
||||
"parentId": parentFile.Id,
|
||||
"sessionKey": account.DriveId,
|
||||
"opertype": "1",
|
||||
"fname": file.GetFileName(),
|
||||
}).SetMultipartField("Filedata", file.GetFileName(), file.GetMIMEType(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if jsoniter.Get(res.Body(), "MD5").ToString() != "" {
|
||||
return nil
|
||||
}
|
||||
log.Debugf(res.String())
|
||||
return errors.New(res.String())
|
||||
return driver.NewUpload(file, account)
|
||||
//return driver.OldUpload(file, account)
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Cloud189)(nil)
|
||||
|
67
drivers/189/types.go
Normal file
67
drivers/189/types.go
Normal file
@ -0,0 +1,67 @@
|
||||
package _89
|
||||
|
||||
type Cloud189Error struct {
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
}
|
||||
|
||||
type Cloud189File struct {
|
||||
Id int64 `json:"id"`
|
||||
LastOpTime string `json:"lastOpTime"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Icon struct {
|
||||
SmallUrl string `json:"smallUrl"`
|
||||
//LargeUrl string `json:"largeUrl"`
|
||||
} `json:"icon"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type Cloud189Folder struct {
|
||||
Id int64 `json:"id"`
|
||||
LastOpTime string `json:"lastOpTime"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Cloud189Files 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"`
|
||||
}
|
||||
|
||||
type UploadUrlsResp struct {
|
||||
Code string `json:"code"`
|
||||
UploadUrls map[string]Part `json:"uploadUrls"`
|
||||
}
|
||||
|
||||
type Part struct {
|
||||
RequestURL string `json:"requestURL"`
|
||||
RequestHeader string `json:"requestHeader"`
|
||||
}
|
||||
|
||||
//type Info struct {
|
||||
// SessionKey string
|
||||
// Rsa Rsa
|
||||
//}
|
||||
|
||||
type Rsa struct {
|
||||
Expire int64 `json:"expire"`
|
||||
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"`
|
||||
}
|
199
drivers/189/util.go
Normal file
199
drivers/189/util.go
Normal file
@ -0,0 +1,199 @@
|
||||
package _89
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"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"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func random() string {
|
||||
return fmt.Sprintf("0.%17v", utils.Rand.Int63n(100000000000000000))
|
||||
}
|
||||
|
||||
func RsaEncode(origData []byte, j_rsakey string, hex bool) string {
|
||||
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
|
||||
block, _ := pem.Decode(publicKey)
|
||||
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
pub := pubInterface.(*rsa.PublicKey)
|
||||
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
|
||||
if err != nil {
|
||||
log.Errorf("err: %s", err.Error())
|
||||
}
|
||||
res := base64.StdEncoding.EncodeToString(b)
|
||||
if hex {
|
||||
return b64tohex(res)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
func int2char(a int) string {
|
||||
return strings.Split(BI_RM, "")[a]
|
||||
}
|
||||
|
||||
func b64tohex(a string) string {
|
||||
d := ""
|
||||
e := 0
|
||||
c := 0
|
||||
for i := 0; i < len(a); i++ {
|
||||
m := strings.Split(a, "")[i]
|
||||
if m != "=" {
|
||||
v := strings.Index(b64map, m)
|
||||
if 0 == e {
|
||||
e = 1
|
||||
d += int2char(v >> 2)
|
||||
c = 3 & v
|
||||
} else if 1 == e {
|
||||
e = 2
|
||||
d += int2char(c<<2 | v>>4)
|
||||
c = 15 & v
|
||||
} else if 2 == e {
|
||||
e = 3
|
||||
d += int2char(c)
|
||||
d += int2char(v >> 2)
|
||||
c = 3 & v
|
||||
} else {
|
||||
e = 0
|
||||
d += int2char(c<<2 | v>>4)
|
||||
d += int2char(15 & v)
|
||||
}
|
||||
}
|
||||
}
|
||||
if e == 1 {
|
||||
d += int2char(c << 2)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func qs(form map[string]string) string {
|
||||
f := make(url.Values)
|
||||
for k, v := range form {
|
||||
f.Set(k, v)
|
||||
}
|
||||
return EncodeParam(f)
|
||||
//strList := make([]string, 0)
|
||||
//for k, v := range form {
|
||||
// strList = append(strList, fmt.Sprintf("%s=%s", k, url.QueryEscape(v)))
|
||||
//}
|
||||
//return strings.Join(strList, "&")
|
||||
}
|
||||
|
||||
func EncodeParam(v url.Values) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
var buf strings.Builder
|
||||
keys := make([]string, 0, len(v))
|
||||
for k := range v {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
for _, k := range keys {
|
||||
vs := v[k]
|
||||
for _, v := range vs {
|
||||
if buf.Len() > 0 {
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
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 {
|
||||
return []byte{}
|
||||
}
|
||||
data = PKCS7Padding(data, block.BlockSize())
|
||||
decrypted := make([]byte, len(data))
|
||||
size := block.BlockSize()
|
||||
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
|
||||
block.Encrypt(decrypted[bs:be], data[bs:be])
|
||||
}
|
||||
return 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 hmacSha1(data string, secret string) string {
|
||||
h := hmac.New(sha1.New, []byte(secret))
|
||||
h.Write([]byte(data))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func getMd5(data []byte) []byte {
|
||||
h := md5.New()
|
||||
h.Write(data)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Cloud189{})
|
||||
client189Map = make(map[string]*resty.Client)
|
||||
}
|
||||
|
||||
func decodeURIComponent(str string) string {
|
||||
r, _ := url.PathUnescape(str)
|
||||
//r = strings.ReplaceAll(r, " ", "+")
|
||||
return r
|
||||
}
|
||||
|
||||
func Random(v string) string {
|
||||
reg := regexp.MustCompilePOSIX("[xy]")
|
||||
data := reg.ReplaceAllFunc([]byte(v), func(msg []byte) []byte {
|
||||
var i int64
|
||||
t := int64(16 * utils.Rand.Float32())
|
||||
if msg[0] == 120 {
|
||||
i = t
|
||||
} else {
|
||||
i = 3&t | 8
|
||||
}
|
||||
return []byte(strconv.FormatInt(i, 16))
|
||||
})
|
||||
return string(data)
|
||||
}
|
||||
|
||||
//func SHA1(v, l string) []byte {
|
||||
// key := []byte(l)
|
||||
// mac := hmac.New(sha1.New, key)
|
||||
// mac.Write([]byte(v))
|
||||
// return mac.Sum(nil)
|
||||
//}
|
330
drivers/189pc/189.go
Normal file
330
drivers/189pc/189.go
Normal file
@ -0,0 +1,330 @@
|
||||
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,
|
||||
}),
|
||||
}
|
||||
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("https://api.cloud.189.cn/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() bool {
|
||||
_, err := s.Request("GET", API_URL+"/getUserInfo.action", nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix())
|
||||
}, nil)
|
||||
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 url.Values, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
|
||||
s.Lock()
|
||||
dateOfGmt := getHttpDateStr()
|
||||
sessionKey := s.SessionKey
|
||||
sessionSecret := s.SessionSecret
|
||||
if account != nil && 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))
|
||||
|
||||
callback(req)
|
||||
s.Unlock()
|
||||
|
||||
var err error
|
||||
var res *resty.Response
|
||||
switch method {
|
||||
case "GET":
|
||||
res, err = req.Get(fullUrl)
|
||||
case "POST":
|
||||
res, err = req.Post(fullUrl)
|
||||
case "DELETE":
|
||||
res, err = req.Delete(fullUrl)
|
||||
case "PATCH":
|
||||
res, err = req.Patch(fullUrl)
|
||||
case "PUT":
|
||||
res, err = req.Put(fullUrl)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
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 == "" {
|
||||
return nil, fmt.Errorf(erron.Message)
|
||||
}
|
||||
return nil, fmt.Errorf(erron.Msg)
|
||||
}
|
||||
if erron.ErrorCode != "" {
|
||||
return nil, fmt.Errorf(erron.ErrorMsg)
|
||||
}
|
||||
|
||||
if account != nil {
|
||||
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
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
return nil, fmt.Errorf(res.String())
|
||||
}
|
||||
}
|
||||
|
||||
if utils.Json.Get(res.Body(), "res_code").ToInt64() != 0 {
|
||||
return res, fmt.Errorf(utils.Json.Get(res.Body(), "res_message").ToString())
|
||||
}
|
||||
return res, nil
|
||||
}
|
831
drivers/189pc/driver.go
Normal file
831
drivers/189pc/driver.go
Normal file
@ -0,0 +1,831 @@
|
||||
package _189
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "internal_type",
|
||||
Label: "189cloud type",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "Personal,Family",
|
||||
},
|
||||
{
|
||||
Name: "site_id",
|
||||
Label: "family id",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Cloud189) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !isFamily(account) && account.RootFolder == "" {
|
||||
account.RootFolder = "-11"
|
||||
}
|
||||
|
||||
state := GetState(account)
|
||||
if !state.IsLogin() {
|
||||
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("GET", 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
|
||||
queryparam := map[string]string{
|
||||
"folderId": file.Id,
|
||||
"fileType": "0",
|
||||
"mediaAttr": "0",
|
||||
"iconOption": "5",
|
||||
"pageNum": fmt.Sprint(pageNum),
|
||||
"pageSize": "130",
|
||||
}
|
||||
_, err = client.Request("GET", fullUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix()).SetQueryParams(queryparam)
|
||||
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
|
||||
}
|
||||
|
||||
mustTime := func(str string) *time.Time {
|
||||
time, _ := http.ParseTime(str)
|
||||
return &time
|
||||
}
|
||||
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: mustTime(folder.CreateDate),
|
||||
})
|
||||
}
|
||||
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: mustTime(file.CreateDate),
|
||||
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("GET", 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("POST", 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("POST", 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 := "POST"
|
||||
if isFamily(account) {
|
||||
fullUrl += "/family/file"
|
||||
method = "GET"
|
||||
}
|
||||
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 := "POST"
|
||||
if isFamily(account) {
|
||||
fullUrl += "/family/file"
|
||||
method = "GET"
|
||||
}
|
||||
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("POST", 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("POST", 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 isFamily(account) {
|
||||
return driver.uploadFamily(file, parentFile, account)
|
||||
}
|
||||
return driver.uploadPerson(file, parentFile, account)
|
||||
}
|
||||
|
||||
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("GET", 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("GET", 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("POST", 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("POST", 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("GET", 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
|
||||
}
|
||||
|
||||
/*
|
||||
暂时未解决
|
||||
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
|
||||
}
|
||||
|
||||
fullUrl := UPLOAD_URL
|
||||
if isFamily(account) {
|
||||
fullUrl += "/family"
|
||||
} else {
|
||||
fullUrl += "/person"
|
||||
}
|
||||
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// 初始化上传
|
||||
const DEFAULT int64 = 10485760
|
||||
count := int64(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 := int64(1); i <= count; i++ {
|
||||
if _, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5, tempFile), file, DEFAULT); err != io.EOF {
|
||||
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")))
|
||||
}
|
||||
|
||||
qID := uuid.NewString()
|
||||
client := GetState(account)
|
||||
param := MapToUrlValues(map[string]interface{}{
|
||||
"parentFolderId": parentFile.Id,
|
||||
"fileName": url.QueryEscape(file.Name),
|
||||
"fileMd5": fileMd5Hex,
|
||||
"fileSize": fmt.Sprint(file.Size),
|
||||
"sliceMd5": sliceMd5Hex,
|
||||
"sliceSize": fmt.Sprint(DEFAULT),
|
||||
})
|
||||
if isFamily(account) {
|
||||
param.Set("familyId", account.SiteId)
|
||||
}
|
||||
|
||||
var uploadInfo InitMultiUploadResp
|
||||
_, err = client.Request("GET", fullUrl+"/initMultiUpload", param, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix())
|
||||
r.SetHeader("X-Request-ID", qID)
|
||||
r.SetResult(&uploadInfo)
|
||||
}, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if uploadInfo.Data.FileDataExists != 1 {
|
||||
param = MapToUrlValues(map[string]interface{}{
|
||||
"uploadFileId": uploadInfo.Data.UploadFileID,
|
||||
"partInfo": strings.Join(silceMd5Base64s, ","),
|
||||
})
|
||||
if isFamily(account) {
|
||||
param.Set("familyId", account.SiteId)
|
||||
}
|
||||
var uploadUrls UploadUrlsResp
|
||||
_, err := client.Request("GET", fullUrl+"/getMultiUploadUrls", param, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix())
|
||||
r.SetHeader("X-Request-ID", qID).SetHeader("content-type", "application/x-www-form-urlencoded")
|
||||
r.SetResult(&uploadUrls)
|
||||
|
||||
}, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var i int64
|
||||
for _, uploadurl := range uploadUrls.UploadUrls {
|
||||
req := resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).SetProxy("http://192.168.0.30:8888").R()
|
||||
for _, header := range strings.Split(decodeURIComponent(uploadurl.RequestHeader), "&") {
|
||||
i := strings.Index(header, "=")
|
||||
req.SetHeader(header[0:i], header[i+1:])
|
||||
}
|
||||
_, err := req.SetBody(io.NewSectionReader(tempFile, i*DEFAULT, DEFAULT)).Put(uploadurl.RequestURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
param = MapToUrlValues(map[string]interface{}{
|
||||
"uploadFileId": uploadInfo.Data.UploadFileID,
|
||||
"isLog": "0",
|
||||
"opertype": "1",
|
||||
})
|
||||
if isFamily(account) {
|
||||
param.Set("familyId", account.SiteId)
|
||||
}
|
||||
_, err = client.Request("GET", fullUrl+"/commitMultiUploadFile", param, func(r *resty.Request) {
|
||||
r.SetHeader("X-Request-ID", qID)
|
||||
r.SetQueryParams(clientSuffix())
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
*/
|
||||
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"`
|
||||
}
|
||||
*/
|
145
drivers/189pc/util.go
Normal file
145
drivers/189pc/util.go
Normal file
@ -0,0 +1,145 @@
|
||||
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"
|
||||
"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 MapToUrlValues(m map[string]interface{}) url.Values {
|
||||
url := make(url.Values, len(m))
|
||||
for k, v := range m {
|
||||
url.Add(k, fmt.Sprint(v))
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
func decodeURIComponent(str string) string {
|
||||
r, _ := url.QueryUnescape(str)
|
||||
//r, _ := url.PathUnescape(str)
|
||||
//r = strings.ReplaceAll(r, " ", "+")
|
||||
return r
|
||||
}
|
||||
|
||||
func MustToBytes(b []byte, err error) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
func BoolToNumber(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
@ -8,9 +8,9 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -190,7 +190,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 +208,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 +223,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 +238,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{}
|
||||
@ -67,6 +73,9 @@ func (driver AliDrive) Save(account *model.Account, old *model.Account) error {
|
||||
if old != nil {
|
||||
conf.Cron.Remove(cron.EntryID(old.CronId))
|
||||
}
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "root"
|
||||
}
|
||||
@ -85,15 +94,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
|
||||
@ -188,6 +197,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
|
||||
}
|
||||
@ -209,10 +224,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)
|
||||
@ -301,7 +316,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
|
||||
}
|
||||
|
||||
@ -316,7 +331,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 {
|
||||
@ -358,15 +383,16 @@ 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
|
||||
const DEFAULT int64 = 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
|
||||
@ -374,32 +400,38 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
|
||||
if !parentFile.IsDir() {
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
var resp UploadResp
|
||||
var e AliRespError
|
||||
partInfoList := make([]base.Json, 0)
|
||||
|
||||
partInfoList := make([]base.Json, 0, count)
|
||||
var i int64
|
||||
for i = 0; i < count; i++ {
|
||||
partInfoList = append(partInfoList, base.Json{
|
||||
"part_number": i + 1,
|
||||
})
|
||||
}
|
||||
_, 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",
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := file.Read(buf[:])
|
||||
reqBody := base.Json{
|
||||
"check_name_mode": "overwrite",
|
||||
"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 != "" {
|
||||
"pre_hash": utils.GetSHA1Encode(string(buf[:n])),
|
||||
}
|
||||
fileReader := io.MultiReader(bytes.NewReader(buf[:n]), file.File)
|
||||
|
||||
var resp UploadResp
|
||||
var e AliRespError
|
||||
client := aliClient.R().SetResult(&resp).SetError(&e).SetHeader("authorization", "Bearer\t"+account.AccessToken).SetBody(reqBody)
|
||||
|
||||
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" && e.Code != "PreHashMatched" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
@ -411,26 +443,59 @@ 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" {
|
||||
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(tempFile, io.TeeReader(fileReader, h)); err != nil {
|
||||
return err
|
||||
}
|
||||
reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil))
|
||||
reqBody["content_hash_name"] = "sha1"
|
||||
reqBody["proof_version"] = "v1"
|
||||
|
||||
/*
|
||||
js 隐性转换太坑不知道有没有bug
|
||||
var n = e.access_token,
|
||||
r = new BigNumber('0x'.concat(md5(n).slice(0, 16))),
|
||||
i = new BigNumber(t.file.size),
|
||||
o = i ? r.mod(i) : new gt.BigNumber(0);
|
||||
(t.file.slice(o.toNumber(), Math.min(o.plus(8).toNumber(), t.file.size)))
|
||||
*/
|
||||
r, _ := new(big.Int).SetString(utils.GetMD5Encode(account.AccessToken)[:16], 16)
|
||||
i := new(big.Int).SetUint64(file.Size)
|
||||
o := r.Mod(r, i)
|
||||
n, _ = io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
|
||||
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
||||
|
||||
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" && e.Code != "PreHashMatched" {
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
|
||||
if resp.RapidUpload {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
fileReader = tempFile
|
||||
}
|
||||
|
||||
for i = 0; i < count; i++ {
|
||||
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, io.LimitReader(fileReader, DEFAULT))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -455,6 +520,9 @@ 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 err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" {
|
||||
//if e.Code == "AccessTokenInvalid" {
|
||||
// err = driver.RefreshToken(account)
|
||||
|
@ -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"
|
||||
@ -48,6 +47,9 @@ func (driver Alist) Items() []base.Item {
|
||||
}
|
||||
|
||||
func (driver Alist) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
account.SiteUrl = strings.TrimRight(account.SiteUrl, "/")
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "/"
|
||||
@ -145,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,14 @@ 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/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"
|
||||
)
|
||||
|
186
drivers/baidu/baidu.go
Normal file
186
drivers/baidu/baidu.go
Normal file
@ -0,0 +1,186 @@
|
||||
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(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
u := "https://pan.baidu.com/rest/2.0" + pathname
|
||||
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(u)
|
||||
case base.Post:
|
||||
res, err = req.Post(u)
|
||||
case base.Patch:
|
||||
res, err = req.Patch(u)
|
||||
case base.Delete:
|
||||
res, err = req.Delete(u)
|
||||
case base.Put:
|
||||
res, err = req.Put(u)
|
||||
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(pathname, 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(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(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{})
|
||||
}
|
358
drivers/baidu/driver.go
Normal file
358
drivers/baidu/driver.go
Normal file
@ -0,0 +1,358 @@
|
||||
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: "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) {
|
||||
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) 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)
|
84
drivers/baidu/types.go
Normal file
84
drivers/baidu/types.go
Normal file
@ -0,0 +1,84 @@
|
||||
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 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
|
||||
}
|
@ -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"`
|
||||
@ -84,19 +85,21 @@ func GetDriversMap() map[string]Driver {
|
||||
}
|
||||
|
||||
func GetDrivers() map[string][]Item {
|
||||
res := make(map[string][]Item, 0)
|
||||
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,20 @@ 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",
|
||||
Label: "extract_folder",
|
||||
Values: "front,back",
|
||||
Type: TypeSelect,
|
||||
},
|
||||
}, res[k]...)
|
||||
if v.Config().ApiProxy {
|
||||
@ -154,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(
|
||||
@ -161,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)
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ var (
|
||||
ErrNotSupport = errors.New("not support")
|
||||
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 (
|
||||
@ -19,6 +21,7 @@ const (
|
||||
TypeSelect = "select"
|
||||
TypeBool = "bool"
|
||||
TypeNumber = "number"
|
||||
TypeText = "text"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -45,4 +48,5 @@ type Link struct {
|
||||
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"
|
||||
@ -61,6 +60,9 @@ func (driver FTP) Save(account *model.Account, old *model.Account) error {
|
||||
delete(connMap, old.Name)
|
||||
}
|
||||
}
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "/"
|
||||
}
|
||||
@ -183,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 {
|
||||
_, err := conn.CurrentDir()
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
} else {
|
||||
delete(connMap, account.Name)
|
||||
}
|
||||
}
|
||||
conn, err := ftp.Connect(account.SiteUrl)
|
||||
if err != nil {
|
||||
@ -21,9 +26,11 @@ func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connMap[account.Name] = conn
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&FTP{})
|
||||
connMap = make(map[string]*ftp.ServerConn)
|
||||
}
|
||||
|
@ -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",
|
||||
@ -67,6 +68,9 @@ func (driver GoogleDrive) Items() []base.Item {
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
account.Proxy = true
|
||||
err := driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
@ -175,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
|
||||
|
@ -73,8 +73,8 @@ func (driver GoogleDrive) FormatFile(file *File, account *model.Account) *model.
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
@ -33,16 +32,18 @@ func (driver Lanzou) Items() []base.Item {
|
||||
Label: "cookie",
|
||||
Type: base.TypeString,
|
||||
Description: "about 15 days valid",
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "site_url",
|
||||
Label: "share url",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
@ -53,6 +54,9 @@ func (driver Lanzou) Items() []base.Item {
|
||||
}
|
||||
|
||||
func (driver Lanzou) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if account.InternalType == "cookie" {
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "-1"
|
||||
@ -121,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)
|
||||
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
|
||||
}
|
||||
@ -154,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
|
||||
|
@ -6,16 +6,14 @@ import (
|
||||
"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"`
|
||||
@ -57,7 +55,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,
|
||||
@ -76,7 +74,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,
|
||||
@ -103,7 +101,11 @@ func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZo
|
||||
func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error) {
|
||||
files := make([]LanZouFile, 0)
|
||||
shareUrl := account.SiteUrl
|
||||
res, err := lanzouClient.R().Get(shareUrl)
|
||||
u, err := url.Parse(shareUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := base.RestyClient.R().Get(shareUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -116,7 +118,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]
|
||||
@ -124,7 +129,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,
|
||||
@ -135,7 +140,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
||||
"up": up,
|
||||
"ls": ls,
|
||||
"pwd": account.Password,
|
||||
}).Post("https://wwa.lanzouo.com/filemoreajax.php")
|
||||
}).Post(fmt.Sprintf("https://%s/filemoreajax.php", u.Host))
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
break
|
||||
@ -148,6 +153,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
||||
break
|
||||
}
|
||||
pg++
|
||||
time.Sleep(time.Second)
|
||||
files = append(files, resp.Text...)
|
||||
}
|
||||
return files, nil
|
||||
@ -158,30 +164,22 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
||||
// IsNewd string `json:"is_newd"`
|
||||
//}
|
||||
|
||||
// 获取下载页面的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).
|
||||
// GetDownPageId 获取下载页面的ID
|
||||
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 {
|
||||
@ -190,50 +188,98 @@ type LanzouLinkResp struct {
|
||||
Zt int `json:"zt"`
|
||||
}
|
||||
|
||||
func (driver *Lanzou) GetLink(downId string) (string, error) {
|
||||
res, err := lanzouClient.R().Get("https://wwa.lanzouo.com/" + downId)
|
||||
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
|
||||
}
|
||||
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]
|
||||
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",
|
||||
"signs": signs,
|
||||
"sign": sign,
|
||||
"ves": "1",
|
||||
"websign": "",
|
||||
"websign": websign,
|
||||
"websignkey": websignkey,
|
||||
}
|
||||
log.Debugf("form: %+v", form)
|
||||
_, err = lanzouClient.R().SetResult(&resp).
|
||||
SetHeader("origin", "https://wwa.lanzouo.com").
|
||||
res, err = base.RestyClient.R().SetResult(&resp).
|
||||
SetHeader("origin", "https://"+u.Host).
|
||||
SetHeader("referer", iframeUrl).
|
||||
SetFormData(form).Post("https://wwa.lanzouo.com/ajaxm.php")
|
||||
SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
|
||||
log.Debug(res.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
13
drivers/lanzou/types.go
Normal file
13
drivers/lanzou/types.go
Normal file
@ -0,0 +1,13 @@
|
||||
package lanzou
|
||||
|
||||
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"`
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package mediatrack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
@ -13,11 +12,12 @@ 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"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@ -50,6 +50,7 @@ func (driver MediaTrack) Items() []base.Item {
|
||||
Type: base.TypeSelect,
|
||||
Values: "updated_at,title,size",
|
||||
Required: true,
|
||||
Description: "title",
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
@ -57,11 +58,15 @@ func (driver MediaTrack) Items() []base.Item {
|
||||
Type: base.TypeSelect,
|
||||
Values: "true,false",
|
||||
Required: true,
|
||||
Default: "false",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver MediaTrack) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -144,9 +149,9 @@ 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
|
||||
@ -258,21 +263,39 @@ func (driver MediaTrack) Upload(file *model.FileStream, account *model.Account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
read := io.TeeReader(file, &buf)
|
||||
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
|
||||
}
|
||||
uploader := s3manager.NewUploader(s)
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &resp.Data.Bucket,
|
||||
Key: &resp.Data.Object,
|
||||
Body: read,
|
||||
Body: tempFile,
|
||||
}
|
||||
_, err = uploader.Upload(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id)
|
||||
_, err = tempFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := md5.New()
|
||||
_, err = io.Copy(h, &buf)
|
||||
_, err = io.Copy(h, tempFile)
|
||||
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"
|
||||
@ -39,6 +38,9 @@ func (driver Native) Items() []base.Item {
|
||||
}
|
||||
|
||||
func (driver Native) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
log.Debugf("save a account: [%s]", account.Name)
|
||||
if !utils.Exists(account.RootFolder) {
|
||||
account.Status = fmt.Sprintf("[%s] not exist", account.RootFolder)
|
||||
@ -55,6 +57,9 @@ func (driver Native) Save(account *model.Account, old *model.Account) error {
|
||||
}
|
||||
|
||||
func (driver Native) File(path string, account *model.Account) (*model.File, error) {
|
||||
if utils.IsContain(strings.Split(path, "/"), "..") {
|
||||
return nil, base.ErrRelativePath
|
||||
}
|
||||
fullPath := filepath.Join(account.RootFolder, path)
|
||||
if !utils.Exists(fullPath) {
|
||||
return nil, base.ErrPathNotFound
|
||||
@ -79,6 +84,9 @@ func (driver Native) File(path string, account *model.Account) (*model.File, err
|
||||
}
|
||||
|
||||
func (driver Native) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
if utils.IsContain(strings.Split(path, "/"), "..") {
|
||||
return nil, base.ErrRelativePath
|
||||
}
|
||||
fullPath := filepath.Join(account.RootFolder, path)
|
||||
if !utils.Exists(fullPath) {
|
||||
return nil, base.ErrPathNotFound
|
||||
@ -107,11 +115,14 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
model.SortFiles(files, account)
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
_, err := driver.File(args.Path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fullPath := filepath.Join(account.RootFolder, args.Path)
|
||||
s, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
@ -121,7 +132,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
|
||||
}
|
||||
@ -144,21 +155,27 @@ 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
|
||||
}
|
||||
|
||||
func (driver Native) MakeDir(path string, account *model.Account) error {
|
||||
if utils.IsContain(strings.Split(path, "/"), "..") {
|
||||
return base.ErrRelativePath
|
||||
}
|
||||
fullPath := filepath.Join(account.RootFolder, path)
|
||||
err := os.MkdirAll(fullPath, 0700)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Native) Move(src string, dst string, account *model.Account) error {
|
||||
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
|
||||
return base.ErrRelativePath
|
||||
}
|
||||
fullSrc := filepath.Join(account.RootFolder, src)
|
||||
fullDst := filepath.Join(account.RootFolder, dst)
|
||||
return os.Rename(fullSrc, fullDst)
|
||||
@ -169,6 +186,9 @@ func (driver Native) Rename(src string, dst string, account *model.Account) erro
|
||||
}
|
||||
|
||||
func (driver Native) Copy(src string, dst string, account *model.Account) error {
|
||||
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
|
||||
return base.ErrRelativePath
|
||||
}
|
||||
fullSrc := filepath.Join(account.RootFolder, src)
|
||||
fullDst := filepath.Join(account.RootFolder, dst)
|
||||
srcFile, err := driver.File(src, account)
|
||||
@ -188,6 +208,9 @@ func (driver Native) Copy(src string, dst string, account *model.Account) error
|
||||
}
|
||||
|
||||
func (driver Native) Delete(path string, account *model.Account) error {
|
||||
if utils.IsContain(strings.Split(path, "/"), "..") {
|
||||
return base.ErrRelativePath
|
||||
}
|
||||
fullPath := filepath.Join(account.RootFolder, path)
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
@ -203,6 +226,9 @@ func (driver Native) Upload(file *model.FileStream, account *model.Account) erro
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
if utils.IsContain(strings.Split(file.ParentPath, "/"), "..") {
|
||||
return base.ErrRelativePath
|
||||
}
|
||||
fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name)
|
||||
_, err := driver.File(filepath.Join(file.ParentPath, file.Name), account)
|
||||
if err == nil {
|
||||
@ -222,6 +248,16 @@ func (driver Native) Upload(file *model.FileStream, account *model.Account) erro
|
||||
defer func() {
|
||||
_ = out.Close()
|
||||
}()
|
||||
//var buf bytes.Buffer
|
||||
//reader := io.TeeReader(file, &buf)
|
||||
//h := md5.New()
|
||||
//_, err = io.Copy(h, reader)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//hash := hex.EncodeToString(h.Sum(nil))
|
||||
//log.Debugln("md5:", hash)
|
||||
//_, err = io.Copy(out, &buf)
|
||||
_, err = io.Copy(out, file)
|
||||
return err
|
||||
}
|
||||
|
@ -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,37 +90,37 @@ 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 account == nil {
|
||||
return nil
|
||||
}
|
||||
_, ok := onedriveHostMap[account.Zone]
|
||||
if !ok {
|
||||
return fmt.Errorf("no [%s] zone", account.Zone)
|
||||
}
|
||||
if old != nil {
|
||||
conf.Cron.Remove(cron.EntryID(old.CronId))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@ -177,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{
|
||||
@ -202,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)
|
||||
}
|
||||
|
@ -5,10 +5,39 @@ import (
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"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)
|
||||
}
|
||||
|
||||
func Files(driver base.Driver, account *model.Account, path string) ([]model.File, error) {
|
||||
_, files, err := Path(driver, account, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if files == nil {
|
||||
return nil, base.ErrNotFolder
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func File(driver base.Driver, account *model.Account, path string) (*model.File, error) {
|
||||
return driver.File(path, account)
|
||||
}
|
||||
|
||||
func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error {
|
||||
err := driver.MakeDir(path, account)
|
||||
log.Debugf("mkdir: %s", path)
|
||||
_, 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)
|
||||
}
|
||||
@ -19,6 +48,7 @@ func MakeDir(driver base.Driver, account *model.Account, path string, clearCache
|
||||
}
|
||||
|
||||
func Move(driver base.Driver, account *model.Account, src, dst string, clearCache bool) error {
|
||||
log.Debugf("move %s to %s", src, dst)
|
||||
rename := false
|
||||
if utils.Dir(src) == utils.Dir(dst) {
|
||||
rename = true
|
||||
@ -42,6 +72,7 @@ func Move(driver base.Driver, account *model.Account, src, dst string, clearCach
|
||||
}
|
||||
|
||||
func Copy(driver base.Driver, account *model.Account, src, dst string, clearCache bool) error {
|
||||
log.Debugf("copy %s to %s", src, dst)
|
||||
err := driver.Copy(src, dst, account)
|
||||
if err == nil && clearCache {
|
||||
_ = base.DeleteCache(utils.Dir(dst), account)
|
||||
@ -53,6 +84,7 @@ func Copy(driver base.Driver, account *model.Account, src, dst string, clearCach
|
||||
}
|
||||
|
||||
func Delete(driver base.Driver, account *model.Account, path string, clearCache bool) error {
|
||||
log.Debugf("delete %s", path)
|
||||
err := driver.Delete(path, account)
|
||||
if err == nil && clearCache {
|
||||
_ = base.DeleteCache(utils.Dir(path), account)
|
||||
@ -74,5 +106,6 @@ func Upload(driver base.Driver, account *model.Account, file *model.FileStream,
|
||||
if err != nil {
|
||||
log.Errorf("upload error: %s", err.Error())
|
||||
}
|
||||
debug.FreeOSMemory()
|
||||
return err
|
||||
}
|
||||
|
@ -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"
|
||||
@ -51,6 +50,9 @@ func (driver PikPak) Items() []base.Item {
|
||||
}
|
||||
|
||||
func (driver PikPak) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
err := driver.Login(account)
|
||||
return err
|
||||
}
|
||||
@ -117,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) {
|
||||
@ -139,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 {
|
||||
|
326
drivers/quark/driver.go
Normal file
326
drivers/quark/driver.go
Normal file
@ -0,0 +1,326 @@
|
||||
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},
|
||||
},
|
||||
}, 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.Count {
|
||||
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,11 +9,10 @@ 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"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -71,14 +71,36 @@ func (driver S3) Items() []base.Item {
|
||||
},
|
||||
{
|
||||
Name: "limit",
|
||||
Label: "url expire time(hours)",
|
||||
Label: "Sign url expire time(hours)",
|
||||
Type: base.TypeNumber,
|
||||
Description: "default 4 hours",
|
||||
},
|
||||
{
|
||||
Name: "zone",
|
||||
Label: "placeholder filename",
|
||||
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",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver S3) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if account.Limit == 0 {
|
||||
account.Limit = 4
|
||||
}
|
||||
@ -124,8 +146,12 @@ func (driver S3) Files(path string, account *model.Account) ([]model.File, error
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files, _ = cache.([]model.File)
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
@ -134,19 +160,28 @@ 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
|
||||
}
|
||||
path := strings.TrimPrefix(args.Path, "/")
|
||||
path := driver.GetKey(args.Path, account, false)
|
||||
disposition := fmt.Sprintf(`attachment;filename="%s"`, url.QueryEscape(utils.Base(path)))
|
||||
input := &s3.GetObjectInput{
|
||||
Bucket: &account.Bucket,
|
||||
Key: &path,
|
||||
ResponseContentDisposition: &disposition,
|
||||
//ResponseContentDisposition: &disposition,
|
||||
}
|
||||
if account.CustomHost == "" {
|
||||
input.ResponseContentDisposition = &disposition
|
||||
}
|
||||
req, _ := client.GetObjectRequest(input)
|
||||
link, err := req.Presign(time.Hour * time.Duration(account.Limit))
|
||||
var link string
|
||||
if account.CustomHost != "" {
|
||||
err = req.Build()
|
||||
link = req.HTTPRequest.URL.String()
|
||||
} else {
|
||||
link, err = req.Presign(time.Hour * time.Duration(account.Limit))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -172,18 +207,29 @@ 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
|
||||
// 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 {
|
||||
err := driver.Copy(src, dst, account)
|
||||
@ -198,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
|
||||
}
|
||||
@ -218,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
|
||||
}
|
||||
@ -245,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"
|
||||
@ -25,17 +26,18 @@ func (driver S3) NewSession(account *model.Account) (*session.Session, error) {
|
||||
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
|
||||
}
|
||||
@ -73,9 +75,10 @@ func (driver S3) List(prefix string, account *model.Account) ([]model.File, erro
|
||||
return nil, err
|
||||
}
|
||||
for _, object := range listObjectsResult.CommonPrefixes {
|
||||
name := utils.Base(strings.Trim(*object.Prefix, "/"))
|
||||
file := model.File{
|
||||
//Id: *object.Key,
|
||||
Name: utils.Base(strings.Trim(*object.Prefix, "/")),
|
||||
Name: name,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
TimeStr: "-",
|
||||
@ -84,9 +87,13 @@ func (driver S3) List(prefix string, account *model.Account) ([]model.File, erro
|
||||
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: utils.Base(*object.Key),
|
||||
Name: name,
|
||||
Size: *object.Size,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: object.LastModified,
|
||||
@ -94,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 {
|
||||
@ -103,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, "/")
|
||||
@ -113,6 +190,6 @@ func (driver S3) GetKey(path string, account *model.Account, dir bool) string {
|
||||
}
|
||||
|
||||
func init() {
|
||||
sessionsMap = make(map[string]*session.Session, 0)
|
||||
sessionsMap = make(map[string]*session.Session)
|
||||
base.RegisterDriver(&S3{})
|
||||
}
|
||||
|
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
|
||||
}
|
@ -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"
|
||||
@ -48,6 +47,9 @@ func (driver Shandian) Items() []base.Item {
|
||||
}
|
||||
|
||||
func (driver Shandian) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "0"
|
||||
}
|
||||
@ -151,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,11 +59,15 @@ func (driver Teambition) Items() []base.Item {
|
||||
Type: base.TypeSelect,
|
||||
Values: "Asc,Desc",
|
||||
Required: true,
|
||||
Default: "Asc",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Teambition) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := driver.Request("/api/v2/roles", base.Get, nil, nil, nil, nil, nil, account)
|
||||
return err
|
||||
}
|
||||
@ -146,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
|
||||
@ -249,7 +253,34 @@ func (driver Teambition) Delete(path string, account *model.Account) error {
|
||||
}
|
||||
|
||||
func (driver Teambition) Upload(file *model.FileStream, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
parentFile, err := driver.File(file.ParentPath, account)
|
||||
if !parentFile.IsDir() {
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := driver.Request("/projects", base.Get, nil, nil, nil, nil, nil, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token := GetBetweenStr(string(res), "strikerAuth":"", "","phoneForLogin")
|
||||
var newFile *FileUpload
|
||||
if file.Size <= 20971520 {
|
||||
// post upload
|
||||
newFile, err = driver.upload(file, token, account)
|
||||
} else {
|
||||
// chunk upload
|
||||
//err = base.ErrNotImplement
|
||||
newFile, err = driver.chunkUpload(file, token, account)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return driver.finishUpload(newFile, parentFile.Id, account)
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Teambition)(nil)
|
||||
|
@ -2,11 +2,14 @@ package teambition
|
||||
|
||||
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"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -66,25 +69,6 @@ func (driver Teambition) Request(pathname string, method int, headers, query, fo
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
type Collection struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]model.File, error) {
|
||||
files := make([]model.File, 0)
|
||||
page := 1
|
||||
@ -114,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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -142,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,
|
||||
})
|
||||
@ -151,6 +135,103 @@ func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]mo
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Teambition) upload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
|
||||
prefix := "tcs"
|
||||
if account.InternalType == "International" {
|
||||
prefix = "us-tcs"
|
||||
}
|
||||
var newFile FileUpload
|
||||
_, err := base.RestyClient.R().SetResult(&newFile).SetHeader("Authorization", token).
|
||||
SetMultipartFormData(map[string]string{
|
||||
"name": file.GetFileName(),
|
||||
"type": file.GetMIMEType(),
|
||||
"size": strconv.FormatUint(file.GetSize(), 10),
|
||||
//"lastModifiedDate": "",
|
||||
}).SetMultipartField("file", file.GetFileName(), file.GetMIMEType(), file).
|
||||
Post(fmt.Sprintf("https://%s.teambition.net/upload", prefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &newFile, nil
|
||||
}
|
||||
|
||||
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).
|
||||
SetBody(base.Json{
|
||||
"fileName": file.GetFileName(),
|
||||
"fileSize": file.GetSize(),
|
||||
"lastUpdated": time.Now(),
|
||||
}).Post(fmt.Sprintf("https://%s.teambition.net/upload/chunk", prefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < newChunk.Chunks; i++ {
|
||||
chunkSize := newChunk.ChunkSize
|
||||
if i == newChunk.Chunks-1 {
|
||||
chunkSize = int(file.GetSize()) - i*chunkSize
|
||||
}
|
||||
log.Debugf("%d : %d", i, chunkSize)
|
||||
chunkData := make([]byte, chunkSize)
|
||||
_, err = io.ReadFull(file, chunkData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s?chunk=%d&chunks=%d",
|
||||
prefix, newChunk.FileKey, i+1, newChunk.Chunks)
|
||||
log.Debugf("url: %s", u)
|
||||
res, err := base.RestyClient.R().SetHeaders(map[string]string{
|
||||
"Authorization": token,
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Referer": referer,
|
||||
}).SetBody(chunkData).Post(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.Status(), res.String())
|
||||
//req, err := http.NewRequest("POST",
|
||||
// u,
|
||||
// bytes.NewBuffer(chunkData))
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//req.Header.Set("Authorization", token)
|
||||
//req.Header.Set("Content-Type", "application/octet-stream")
|
||||
//req.Header.Set("Referer", "https://www.teambition.com/")
|
||||
//resp, err := base.HttpClient.Do(req)
|
||||
//res, _ := ioutil.ReadAll(resp.Body)
|
||||
//log.Debugf("chunk upload status: %s, res: %s", resp.Status, string(res))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
res, err := base.RestyClient.R().SetHeader("Authorization", token).Post(
|
||||
fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s",
|
||||
prefix, newChunk.FileKey))
|
||||
log.Debug(res.Status(), res.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &newChunk.FileUpload, nil
|
||||
}
|
||||
|
||||
func (driver Teambition) finishUpload(file *FileUpload, parentId string, account *model.Account) error {
|
||||
file.InvolveMembers = []interface{}{}
|
||||
file.Visible = "members"
|
||||
file.ParentId = parentId
|
||||
_, err := driver.Request("/api/works", base.Post, nil, nil, nil, base.Json{
|
||||
"works": []FileUpload{*file},
|
||||
"_parentId": parentId,
|
||||
}, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Teambition{})
|
||||
}
|
||||
|
63
drivers/teambition/types.go
Normal file
63
drivers/teambition/types.go
Normal file
@ -0,0 +1,63 @@
|
||||
package teambition
|
||||
|
||||
import "time"
|
||||
|
||||
type Collection struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
type FileUpload struct {
|
||||
FileKey string `json:"fileKey"`
|
||||
FileName string `json:"fileName"`
|
||||
FileType string `json:"fileType"`
|
||||
FileSize int `json:"fileSize"`
|
||||
FileCategory string `json:"fileCategory"`
|
||||
ImageWidth int `json:"imageWidth"`
|
||||
ImageHeight int `json:"imageHeight"`
|
||||
InvolveMembers []interface{} `json:"involveMembers"`
|
||||
Source string `json:"source"`
|
||||
Visible string `json:"visible"`
|
||||
ParentId string `json:"_parentId"`
|
||||
}
|
||||
|
||||
type ChunkUpload struct {
|
||||
FileUpload
|
||||
Storage string `json:"storage"`
|
||||
MimeType string `json:"mimeType"`
|
||||
Chunks int `json:"chunks"`
|
||||
ChunkSize int `json:"chunkSize"`
|
||||
Created time.Time `json:"created"`
|
||||
FileMD5 string `json:"fileMD5"`
|
||||
LastUpdated time.Time `json:"lastUpdated"`
|
||||
UploadedChunks []interface{} `json:"uploadedChunks"`
|
||||
Token struct {
|
||||
AppID string `json:"AppID"`
|
||||
OrganizationID string `json:"OrganizationID"`
|
||||
UserID string `json:"UserID"`
|
||||
Exp time.Time `json:"Exp"`
|
||||
Storage string `json:"Storage"`
|
||||
Resource string `json:"Resource"`
|
||||
Speed int `json:"Speed"`
|
||||
} `json:"token"`
|
||||
DownloadUrl string `json:"downloadUrl"`
|
||||
ThumbnailUrl string `json:"thumbnailUrl"`
|
||||
PreviewUrl string `json:"previewUrl"`
|
||||
ImmPreviewUrl string `json:"immPreviewUrl"`
|
||||
PreviewExt string `json:"previewExt"`
|
||||
LastUploadTime interface{} `json:"lastUploadTime"`
|
||||
}
|
18
drivers/teambition/util.go
Normal file
18
drivers/teambition/util.go
Normal file
@ -0,0 +1,18 @@
|
||||
package teambition
|
||||
|
||||
import "strings"
|
||||
|
||||
func GetBetweenStr(str, start, end string) string {
|
||||
n := strings.Index(str, start)
|
||||
if n == -1 {
|
||||
return ""
|
||||
}
|
||||
n = n + len(start)
|
||||
str = string([]byte(str)[n:])
|
||||
m := strings.Index(str, end)
|
||||
if m == -1 {
|
||||
return ""
|
||||
}
|
||||
str = string([]byte(str)[:m])
|
||||
return str
|
||||
}
|
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{})
|
||||
}
|
@ -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"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@ -45,6 +44,9 @@ func (driver WebDav) Items() []base.Item {
|
||||
}
|
||||
|
||||
func (driver WebDav) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
account.Status = "work"
|
||||
_ = model.SaveAccount(account)
|
||||
return nil
|
||||
@ -134,9 +136,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
|
||||
|
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)
|
||||
}
|
224
drivers/yandex/driver.go
Normal file
224
drivers/yandex/driver.go
Normal file
@ -0,0 +1,224 @@
|
||||
package yandex
|
||||
|
||||
import (
|
||||
"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"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Yandex struct{}
|
||||
|
||||
func (driver Yandex) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Yandex.Disk",
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Yandex) 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,path,created,modified,size",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: base.TypeSelect,
|
||||
Values: "asc,desc",
|
||||
Default: "asc",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "client_id",
|
||||
Label: "client id",
|
||||
Default: "a78d5a69054042fa936f6c77f9a0ae8b",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "client_secret",
|
||||
Label: "client secret",
|
||||
Default: "9c119bbb04b346d2a52aa64401936b2b",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Yandex) Save(account *model.Account, old *model.Account) error {
|
||||
return driver.RefreshToken(account)
|
||||
}
|
||||
|
||||
func (driver Yandex) 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 Yandex) 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 Yandex) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
path := utils.Join(account.RootFolder, args.Path)
|
||||
log.Debugln("down path:", path)
|
||||
var resp DownResp
|
||||
_, err := driver.Request("/download", base.Get, nil, map[string]string{
|
||||
"path": path,
|
||||
}, nil, nil, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link := base.Link{
|
||||
Url: resp.Href,
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Yandex) 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 Yandex) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver Yandex) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Yandex) MakeDir(path string, account *model.Account) error {
|
||||
path = utils.Join(account.RootFolder, path)
|
||||
_, err := driver.Request("", base.Put, nil, map[string]string{
|
||||
"path": path,
|
||||
}, nil, nil, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Yandex) Move(src string, dst string, account *model.Account) error {
|
||||
from := utils.Join(account.RootFolder, src)
|
||||
path := utils.Join(account.RootFolder, dst)
|
||||
_, err := driver.Request("/move", base.Post, nil, map[string]string{
|
||||
"from": from,
|
||||
"path": path,
|
||||
"overwrite": "true",
|
||||
}, nil, nil, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Yandex) Rename(src string, dst string, account *model.Account) error {
|
||||
return driver.Move(src, dst, account)
|
||||
}
|
||||
|
||||
func (driver Yandex) Copy(src string, dst string, account *model.Account) error {
|
||||
from := utils.Join(account.RootFolder, src)
|
||||
path := utils.Join(account.RootFolder, dst)
|
||||
_, err := driver.Request("/copy", base.Post, nil, map[string]string{
|
||||
"from": from,
|
||||
"path": path,
|
||||
"overwrite": "true",
|
||||
}, nil, nil, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Yandex) Delete(path string, account *model.Account) error {
|
||||
path = utils.Join(account.RootFolder, path)
|
||||
_, err := driver.Request("", base.Delete, nil, map[string]string{
|
||||
"path": path,
|
||||
}, nil, nil, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Yandex) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
path := utils.Join(account.RootFolder, file.ParentPath, file.Name)
|
||||
var resp UploadResp
|
||||
_, err := driver.Request("/upload", base.Get, nil, map[string]string{
|
||||
"path": path,
|
||||
"overwrite": "true",
|
||||
}, nil, nil, &resp, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest(resp.Method, resp.Href, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Length", strconv.FormatUint(file.Size, 10))
|
||||
req.Header.Set("Content-Type", "application/octet-stream")
|
||||
_, err = base.HttpClient.Do(req)
|
||||
//res, err := base.RestyClient.R().
|
||||
// SetHeader("Content-Length", strconv.FormatUint(file.Size, 10)).
|
||||
// SetBody(file).Put(resp.Href)
|
||||
//log.Debugln(res.Status(), res.String())
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Yandex)(nil)
|
74
drivers/yandex/types.go
Normal file
74
drivers/yandex/types.go
Normal file
@ -0,0 +1,74 @@
|
||||
package yandex
|
||||
|
||||
import "time"
|
||||
|
||||
type TokenErrResp struct {
|
||||
ErrorDescription string `json:"error_description"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type ErrResp struct {
|
||||
Message string `json:"message"`
|
||||
Description string `json:"description"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
//AntivirusStatus string `json:"antivirus_status"`
|
||||
Size int64 `json:"size"`
|
||||
//CommentIds struct {
|
||||
// PrivateResource string `json:"private_resource"`
|
||||
// PublicResource string `json:"public_resource"`
|
||||
//} `json:"comment_ids"`
|
||||
Name string `json:"name"`
|
||||
//Exif struct {
|
||||
// DateTime time.Time `json:"date_time"`
|
||||
//} `json:"exif"`
|
||||
//Created time.Time `json:"created"`
|
||||
//ResourceId string `json:"resource_id"`
|
||||
Modified *time.Time `json:"modified"`
|
||||
//MimeType string `json:"mime_type"`
|
||||
File string `json:"file"`
|
||||
//MediaType string `json:"media_type"`
|
||||
Preview string `json:"preview"`
|
||||
Path string `json:"path"`
|
||||
//Sha256 string `json:"sha256"`
|
||||
Type string `json:"type"`
|
||||
//Md5 string `json:"md5"`
|
||||
//Revision int64 `json:"revision"`
|
||||
}
|
||||
|
||||
type FilesResp struct {
|
||||
Embedded struct {
|
||||
Sort string `json:"sort"`
|
||||
Items []File `json:"items"`
|
||||
Limit int `json:"limit"`
|
||||
Offset int `json:"offset"`
|
||||
Path string `json:"path"`
|
||||
Total int `json:"total"`
|
||||
} `json:"_embedded"`
|
||||
Name string `json:"name"`
|
||||
Exif struct {
|
||||
} `json:"exif"`
|
||||
ResourceId string `json:"resource_id"`
|
||||
Created time.Time `json:"created"`
|
||||
Modified time.Time `json:"modified"`
|
||||
Path string `json:"path"`
|
||||
CommentIds struct {
|
||||
} `json:"comment_ids"`
|
||||
Type string `json:"type"`
|
||||
Revision int64 `json:"revision"`
|
||||
}
|
||||
|
||||
type DownResp struct {
|
||||
Href string `json:"href"`
|
||||
Method string `json:"method"`
|
||||
Templated bool `json:"templated"`
|
||||
}
|
||||
|
||||
type UploadResp struct {
|
||||
OperationId string `json:"operation_id"`
|
||||
Href string `json:"href"`
|
||||
Method string `json:"method"`
|
||||
Templated bool `json:"templated"`
|
||||
}
|
1
drivers/yandex/util.go
Normal file
1
drivers/yandex/util.go
Normal file
@ -0,0 +1 @@
|
||||
package yandex
|
154
drivers/yandex/yandex.go
Normal file
154
drivers/yandex/yandex.go
Normal file
@ -0,0 +1,154 @@
|
||||
package yandex
|
||||
|
||||
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"
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (driver Yandex) 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 Yandex) refreshToken(account *model.Account) error {
|
||||
u := "https://oauth.yandex.com/token"
|
||||
var resp base.TokenResp
|
||||
var e TokenErrResp
|
||||
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": account.RefreshToken,
|
||||
"client_id": account.ClientId,
|
||||
"client_secret": account.ClientSecret,
|
||||
}).Post(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 Yandex) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
u := "https://cloud-api.yandex.net/v1/disk/resources" + pathname
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Authorization", "OAuth "+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
|
||||
var e ErrResp
|
||||
req.SetError(&e)
|
||||
switch method {
|
||||
case base.Get:
|
||||
res, err = req.Get(u)
|
||||
case base.Post:
|
||||
res, err = req.Post(u)
|
||||
case base.Patch:
|
||||
res, err = req.Patch(u)
|
||||
case base.Delete:
|
||||
res, err = req.Delete(u)
|
||||
case base.Put:
|
||||
res, err = req.Put(u)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//log.Debug(res.String())
|
||||
if e.Error != "" {
|
||||
if e.Error == "UnauthorizedError" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver.Request(pathname, method, headers, query, form, data, resp, account)
|
||||
}
|
||||
return nil, errors.New(e.Description)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (driver Yandex) GetFiles(rawPath string, account *model.Account) ([]model.File, error) {
|
||||
path_ := utils.Join(account.RootFolder, rawPath)
|
||||
limit := 100
|
||||
page := 1
|
||||
res := make([]model.File, 0)
|
||||
for {
|
||||
offset := (page - 1) * limit
|
||||
query := map[string]string{
|
||||
"path": path_,
|
||||
"limit": strconv.Itoa(limit),
|
||||
"offset": strconv.Itoa(offset),
|
||||
}
|
||||
if account.OrderBy != "" {
|
||||
if account.OrderDirection == "desc" {
|
||||
query["sort"] = "-" + account.OrderBy
|
||||
} else {
|
||||
query["sort"] = account.OrderBy
|
||||
}
|
||||
}
|
||||
var resp FilesResp
|
||||
_, err := driver.Request("", base.Get, nil, query, nil, nil, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range resp.Embedded.Items {
|
||||
f := model.File{
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: file.Modified,
|
||||
Thumbnail: file.Preview,
|
||||
Url: file.File,
|
||||
}
|
||||
if file.Type == "dir" {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(path.Ext(file.Name))
|
||||
}
|
||||
res = append(res, f)
|
||||
}
|
||||
if resp.Embedded.Total <= offset+limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Yandex{})
|
||||
}
|
34
go.mod
34
go.mod
@ -4,25 +4,36 @@ go 1.17
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go v1.27.0
|
||||
github.com/caarlos0/env/v6 v6.9.1
|
||||
github.com/eko/gocache/v2 v2.1.0
|
||||
github.com/gin-contrib/cors v1.3.1
|
||||
github.com/gin-gonic/gin v1.7.4
|
||||
github.com/go-resty/resty/v2 v2.6.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/robfig/cron/v3 v3.0.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f
|
||||
github.com/upyun/go-sdk/v3 v3.0.2
|
||||
golang.org/x/text v0.3.7
|
||||
gorm.io/driver/mysql v1.1.2
|
||||
gorm.io/driver/postgres v1.1.2
|
||||
gorm.io/driver/sqlite v1.1.6
|
||||
gorm.io/gorm v1.21.16
|
||||
gorm.io/driver/mysql v1.3.2
|
||||
gorm.io/driver/postgres v1.3.1
|
||||
gorm.io/driver/sqlite v1.3.1
|
||||
gorm.io/gorm v1.23.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
|
||||
@ -35,21 +46,20 @@ require (
|
||||
github.com/go-redis/redis/v8 v8.9.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.10.0 // indirect
|
||||
github.com/jackc/pgconn v1.11.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.1.1 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.8.1 // indirect
|
||||
github.com/jackc/pgx/v4 v4.13.0 // indirect
|
||||
github.com/jackc/pgtype v1.10.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.15.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.2 // indirect
|
||||
github.com/jinzhu/now v1.1.4 // indirect
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.9 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.11 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@ -63,7 +73,7 @@ require (
|
||||
go.opentelemetry.io/otel v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
|
||||
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
|
64
go.sum
64
go.sum
@ -20,6 +20,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible h1:uuJIwCFhbZy+zdvLy5zrcIToPEQP0s5CFOZ0Zj03O/w=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/allegro/bigcache/v2 v2.2.5 h1:mRc8r6GQjuJsmSKQNPsR5jQVXc8IJ1xsW5YXUYMLfqI=
|
||||
github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
@ -32,6 +34,8 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
|
||||
github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@ -39,6 +43,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/caarlos0/env/v6 v6.9.1 h1:zOkkjM0F6ltnQ5eBX6IPI41UP/KDGEK7rRPwGCNos8k=
|
||||
github.com/caarlos0/env/v6 v6.9.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
@ -90,6 +96,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
@ -229,8 +237,9 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU=
|
||||
github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ=
|
||||
github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
@ -246,29 +255,34 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
|
||||
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||
github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
|
||||
github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
|
||||
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
|
||||
github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
|
||||
github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
|
||||
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
|
||||
github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b h1:Ur6QAxsHCK99Quj9PaWafoV4unb0DO/HWiKExD+TN5g=
|
||||
github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
@ -319,6 +333,8 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
@ -328,9 +344,9 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
@ -449,6 +465,7 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
@ -497,6 +514,8 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
|
||||
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
|
||||
github.com/upyun/go-sdk/v3 v3.0.2 h1:Ke+iOipK5CT0xzMwsgJsi7faJV7ID4lAs+wrH1RH0dA=
|
||||
github.com/upyun/go-sdk/v3 v3.0.2/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
@ -539,8 +558,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@ -575,6 +595,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -640,6 +661,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -729,16 +751,14 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M=
|
||||
gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM=
|
||||
gorm.io/driver/postgres v1.1.2 h1:Amy3hCvLqM+/ICzjCnQr8wKFLVJTeOTdlMT7kCP+J1Q=
|
||||
gorm.io/driver/postgres v1.1.2/go.mod h1:/AGV0zvqF3mt9ZtzLzQmXWQ/5vr+1V1TyHZGZVjzmwI=
|
||||
gorm.io/driver/sqlite v1.1.6 h1:p3U8WXkVFTOLPED4JjrZExfndjOtya3db8w9/vEMNyI=
|
||||
gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8=
|
||||
gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
gorm.io/gorm v1.21.16 h1:YBIQLtP5PLfZQz59qfrq7xbrK7KWQ+JsXXCH/THlMqs=
|
||||
gorm.io/gorm v1.21.16/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||
gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
|
||||
gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
|
||||
gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g=
|
||||
gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU=
|
||||
gorm.io/driver/sqlite v1.3.1 h1:bwfE+zTEWklBYoEodIOIBwuWHpnx52Z9zJFW5F33WLk=
|
||||
gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg=
|
||||
gorm.io/gorm v1.23.1 h1:aj5IlhDzEPsoIyOPtTRVI+SyaN1u6k613sbt4pwbxG0=
|
||||
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
183
model/account.go
183
model/account.go
@ -2,7 +2,11 @@ package model
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -33,6 +37,7 @@ type Account struct {
|
||||
InternalType string `json:"internal_type"`
|
||||
WebdavProxy bool `json:"webdav_proxy"` // 开启之后只会webdav走中转
|
||||
Proxy bool `json:"proxy"` // 是否中转,开启之后web和webdav都会走中转
|
||||
WebdavDirect bool `json:"webdav_direct"` // webdav 下载不跳转
|
||||
//AllowProxy bool `json:"allow_proxy"` // 是否允许中转下载
|
||||
DownProxyUrl string `json:"down_proxy_url"` // 用于中转下载服务的URL 两处 1. path请求中返回的链接 2. down下载时进行302
|
||||
APIProxyUrl string `json:"api_proxy_url"` // 用于中转api的地址
|
||||
@ -43,9 +48,13 @@ type Account struct {
|
||||
AccessKey string `json:"access_key"`
|
||||
AccessSecret string `json:"access_secret"`
|
||||
CustomHost string `json:"custom_host"`
|
||||
ExtractFolder string `json:"extract_folder"`
|
||||
Bool1 bool `json:"bool_1"`
|
||||
}
|
||||
|
||||
var accountsMap = map[string]Account{}
|
||||
var accountsMap = make(map[string]Account)
|
||||
|
||||
var balance = ".balance"
|
||||
|
||||
// SaveAccount save account to database
|
||||
func SaveAccount(account *Account) error {
|
||||
@ -64,19 +73,18 @@ func CreateAccount(account *Account) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteAccount(id uint) error {
|
||||
func DeleteAccount(id uint) (*Account, error) {
|
||||
var account Account
|
||||
account.ID = id
|
||||
if err := conf.DB.First(&account).Error; err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
name := account.Name
|
||||
conf.Cron.Remove(cron.EntryID(account.CronId))
|
||||
if err := conf.DB.Delete(&account).Error; err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
delete(accountsMap, name)
|
||||
return nil
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func DeleteAccountFromMap(name string) {
|
||||
@ -91,6 +99,7 @@ func RegisterAccount(account Account) {
|
||||
accountsMap[account.Name] = account
|
||||
}
|
||||
|
||||
// GetAccount 根据名称获取账号(不包含负载均衡账号) 用于定时任务更新账号
|
||||
func GetAccount(name string) (Account, bool) {
|
||||
if len(accountsMap) == 1 {
|
||||
for _, v := range accountsMap {
|
||||
@ -101,6 +110,51 @@ func GetAccount(name string) (Account, bool) {
|
||||
return account, ok
|
||||
}
|
||||
|
||||
// GetAccountsByName 根据名称获取账号(包含负载均衡账号)
|
||||
//func GetAccountsByName(name string) []Account {
|
||||
// accounts := make([]Account, 0)
|
||||
// if AccountsCount() == 1 {
|
||||
// for _, v := range accountsMap {
|
||||
// accounts = append(accounts, v)
|
||||
// }
|
||||
// return accounts
|
||||
// }
|
||||
// for _, v := range accountsMap {
|
||||
// if v.Name == name || strings.HasPrefix(v.Name, name+balance) {
|
||||
// accounts = append(accounts, v)
|
||||
// }
|
||||
// }
|
||||
// return accounts
|
||||
//}
|
||||
|
||||
var balanceMap sync.Map
|
||||
|
||||
// GetBalancedAccount 根据名称获取账号,负载均衡之后的
|
||||
func GetBalancedAccount(name string) (Account, bool) {
|
||||
accounts := GetAccountsByPath(name)
|
||||
log.Debugf("accounts: %+v", accounts)
|
||||
accountNum := len(accounts)
|
||||
switch accountNum {
|
||||
case 0:
|
||||
return Account{}, false
|
||||
case 1:
|
||||
return accounts[0], true
|
||||
default:
|
||||
cur, ok := balanceMap.Load(name)
|
||||
if ok {
|
||||
i := cur.(int)
|
||||
i = (i + 1) % accountNum
|
||||
balanceMap.Store(name, i)
|
||||
log.Debugln("use: ", i)
|
||||
return accounts[i], true
|
||||
} else {
|
||||
balanceMap.Store(name, 0)
|
||||
return accounts[0], true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetAccountById 根据id获取账号,用于更新账号
|
||||
func GetAccountById(id uint) (*Account, error) {
|
||||
var account Account
|
||||
account.ID = id
|
||||
@ -110,28 +164,117 @@ func GetAccountById(id uint) (*Account, error) {
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func GetAccountFiles() ([]File, error) {
|
||||
files := make([]File, 0)
|
||||
// GetAccountFiles 获取账号虚拟文件(去除负载均衡)
|
||||
//func GetAccountFiles() ([]File, error) {
|
||||
// files := make([]File, 0)
|
||||
// var accounts []Account
|
||||
// if err := conf.DB.Order(columnName("index")).Find(&accounts).Error; err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// for _, v := range accounts {
|
||||
// if strings.Contains(v.Name, balance) {
|
||||
// continue
|
||||
// }
|
||||
// files = append(files, File{
|
||||
// Name: v.Name,
|
||||
// Size: 0,
|
||||
// Driver: v.Type,
|
||||
// Type: conf.FOLDER,
|
||||
// UpdatedAt: v.UpdatedAt,
|
||||
// })
|
||||
// }
|
||||
// return files, nil
|
||||
//}
|
||||
|
||||
// GetAccounts 获取所有账号
|
||||
func GetAccounts() ([]Account, error) {
|
||||
var accounts []Account
|
||||
if err := conf.DB.Order("`index`").Find(&accounts).Error; err != nil {
|
||||
if err := conf.DB.Order(columnName("index")).Find(&accounts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
// GetAccountsByPath 根据路径获取账号,最长匹配,未负载均衡
|
||||
// 如有账号: /a/b,/a/c,/a/d/e,/a/d/e.balance
|
||||
// GetAccountsByPath(/a/d/e/f) => /a/d/e,/a/d/e.balance
|
||||
func GetAccountsByPath(path string) []Account {
|
||||
accounts := make([]Account, 0)
|
||||
curSlashCount := 0
|
||||
for _, v := range accountsMap {
|
||||
name := utils.ParsePath(v.Name)
|
||||
bIndex := strings.LastIndex(name, balance)
|
||||
if bIndex != -1 {
|
||||
name = name[:bIndex]
|
||||
}
|
||||
if name == "/" {
|
||||
name = ""
|
||||
}
|
||||
// 不是这个账号
|
||||
if path != name && !strings.HasPrefix(path, name+"/") {
|
||||
continue
|
||||
}
|
||||
slashCount := strings.Count(name, "/")
|
||||
// 不是最长匹配
|
||||
if slashCount < curSlashCount {
|
||||
continue
|
||||
}
|
||||
if slashCount > curSlashCount {
|
||||
accounts = accounts[:0]
|
||||
curSlashCount = slashCount
|
||||
}
|
||||
accounts = append(accounts, v)
|
||||
}
|
||||
sort.Slice(accounts, func(i, j int) bool {
|
||||
return accounts[i].Name < accounts[j].Name
|
||||
})
|
||||
return accounts
|
||||
}
|
||||
|
||||
// GetAccountFilesByPath 根据路径获取账号虚拟文件
|
||||
// 如有账号: /a/b,/a/c,/a/d/e,/a/b.balance1,/av
|
||||
// GetAccountFilesByPath(/a) => b,c,d
|
||||
func GetAccountFilesByPath(prefix string) []File {
|
||||
files := make([]File, 0)
|
||||
accounts := make([]Account, AccountsCount())
|
||||
i := 0
|
||||
for _, v := range accountsMap {
|
||||
accounts[i] = v
|
||||
i += 1
|
||||
}
|
||||
sort.Slice(accounts, func(i, j int) bool {
|
||||
if accounts[i].Index == accounts[j].Index {
|
||||
return accounts[i].Name < accounts[j].Name
|
||||
}
|
||||
return accounts[i].Index < accounts[j].Index
|
||||
})
|
||||
prefix = utils.ParsePath(prefix)
|
||||
set := make(map[string]interface{})
|
||||
for _, v := range accounts {
|
||||
// 负载均衡账号
|
||||
if strings.Contains(v.Name, balance) {
|
||||
continue
|
||||
}
|
||||
full := utils.ParsePath(v.Name)
|
||||
if len(full) <= len(prefix) {
|
||||
continue
|
||||
}
|
||||
// 不是以prefix为前缀
|
||||
if !strings.HasPrefix(full, prefix+"/") && prefix != "/" {
|
||||
continue
|
||||
}
|
||||
name := strings.Split(strings.TrimPrefix(strings.TrimPrefix(full, prefix), "/"), "/")[0]
|
||||
if _, ok := set[name]; ok {
|
||||
continue
|
||||
}
|
||||
files = append(files, File{
|
||||
Name: v.Name,
|
||||
Name: name,
|
||||
Size: 0,
|
||||
Driver: v.Type,
|
||||
Type: conf.FOLDER,
|
||||
UpdatedAt: v.UpdatedAt,
|
||||
})
|
||||
set[name] = nil
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func GetAccounts() ([]Account, error) {
|
||||
var accounts []Account
|
||||
if err := conf.DB.Order("`index`").Find(&accounts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return accounts, nil
|
||||
return files
|
||||
}
|
||||
|
@ -25,14 +25,6 @@ func SortFiles(files []File, account *Account) {
|
||||
return
|
||||
}
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
if files[i].IsDir() || files[j].IsDir() {
|
||||
if !files[i].IsDir() {
|
||||
return false
|
||||
}
|
||||
if !files[j].IsDir() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
switch account.OrderBy {
|
||||
case "name":
|
||||
{
|
||||
@ -59,6 +51,24 @@ func SortFiles(files []File, account *Account) {
|
||||
})
|
||||
}
|
||||
|
||||
func ExtractFolder(files []File, account *Account) {
|
||||
if account.ExtractFolder == "" {
|
||||
return
|
||||
}
|
||||
front := account.ExtractFolder == "front"
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
if files[i].IsDir() || files[j].IsDir() {
|
||||
if !files[i].IsDir() {
|
||||
return !front
|
||||
}
|
||||
if !files[j].IsDir() {
|
||||
return front
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func (f File) GetSize() uint64 {
|
||||
return uint64(f.Size)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ type Meta struct {
|
||||
Password string `json:"password"`
|
||||
Hide string `json:"hide"`
|
||||
Upload bool `json:"upload"`
|
||||
OnlyShows string `json:"only_shows"`
|
||||
}
|
||||
|
||||
func GetMetaByPath(path string) (*Meta, error) {
|
||||
|
@ -50,7 +50,7 @@ func SaveSetting(item SettingItem) error {
|
||||
|
||||
func GetSettingsPublic() ([]SettingItem, error) {
|
||||
var items []SettingItem
|
||||
if err := conf.DB.Where("`access` <> ?", 1).Find(&items).Error; err != nil {
|
||||
if err := conf.DB.Where(fmt.Sprintf("%s <> ?", columnName("access")), 1).Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
@ -58,7 +58,7 @@ func GetSettingsPublic() ([]SettingItem, error) {
|
||||
|
||||
func GetSettingsByGroup(group int) ([]SettingItem, error) {
|
||||
var items []SettingItem
|
||||
if err := conf.DB.Where("`group` = ?", group).Find(&items).Error; err != nil {
|
||||
if err := conf.DB.Where(fmt.Sprintf("%s = ?", columnName("group")), group).Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append([]SettingItem{Version}, items...)
|
||||
@ -82,7 +82,7 @@ func DeleteSetting(key string) error {
|
||||
|
||||
func GetSettingByKey(key string) (*SettingItem, error) {
|
||||
var items SettingItem
|
||||
if err := conf.DB.Where("`key` = ?", key).First(&items).Error; err != nil {
|
||||
if err := conf.DB.Where(fmt.Sprintf("%s = ?", columnName("key")), key).First(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &items, nil
|
||||
@ -93,11 +93,23 @@ func LoadSettings() {
|
||||
if err == nil {
|
||||
conf.TextTypes = strings.Split(textTypes.Value, ",")
|
||||
}
|
||||
audioTypes, err := GetSettingByKey("audio types")
|
||||
if err == nil {
|
||||
conf.AudioTypes = strings.Split(audioTypes.Value, ",")
|
||||
}
|
||||
videoTypes, err := GetSettingByKey("video types")
|
||||
if err == nil {
|
||||
conf.VideoTypes = strings.Split(videoTypes.Value, ",")
|
||||
}
|
||||
dProxyTypes, err := GetSettingByKey("d_proxy types")
|
||||
if err == nil {
|
||||
conf.DProxyTypes = strings.Split(dProxyTypes.Value, ",")
|
||||
}
|
||||
// html
|
||||
favicon, err := GetSettingByKey("favicon")
|
||||
if err == nil {
|
||||
//conf.Favicon = favicon.Value
|
||||
conf.ManageHtml = strings.Replace(conf.RawIndexHtml, "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png", favicon.Value, 1)
|
||||
conf.ManageHtml = strings.Replace(conf.RawIndexHtml, "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", favicon.Value, 1)
|
||||
}
|
||||
title, err := GetSettingByKey("title")
|
||||
if err == nil {
|
||||
@ -114,7 +126,11 @@ func LoadSettings() {
|
||||
// token
|
||||
adminPassword, err := GetSettingByKey("password")
|
||||
if err == nil {
|
||||
if adminPassword.Value != "" {
|
||||
conf.Token = utils.GetMD5Encode(fmt.Sprintf("https://github.com/Xhofe/alist-%s", adminPassword.Value))
|
||||
} else {
|
||||
conf.Token = ""
|
||||
}
|
||||
}
|
||||
// load settings
|
||||
for _, key := range conf.LoadSettings {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user