Compare commits
368 Commits
v2.2.0
...
refactor/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06f3618897 | ||
|
|
b399c924b7 | ||
|
|
e707d6b26e | ||
|
|
4ba04fa7db | ||
|
|
5166d73b4d | ||
|
|
826e4807dc | ||
|
|
4691142f80 | ||
|
|
9d92834ee3 | ||
|
|
4f3129ec28 | ||
|
|
fb65e98fa3 | ||
|
|
90a5c175ed | ||
|
|
872e7cf87b | ||
|
|
638db77ca1 | ||
|
|
fe94016289 | ||
|
|
184b9d1e6c | ||
|
|
e08810a12f | ||
|
|
303d245e0f | ||
|
|
a16da3b45e | ||
|
|
2bff656f00 | ||
|
|
fbc858b43c | ||
|
|
4ac312fd07 | ||
|
|
b1d563c874 | ||
|
|
6ebb36b2eb | ||
|
|
3691ee5861 | ||
|
|
dc38f21294 | ||
|
|
8971a924f1 | ||
|
|
18b218c6c9 | ||
|
|
a25d76ef6e | ||
|
|
69d1287254 | ||
|
|
f102b130db | ||
|
|
fc1204c914 | ||
|
|
efa20cc7bd | ||
|
|
e28c1e436d | ||
|
|
90283ef29c | ||
|
|
156da2b794 | ||
|
|
9ba7cf0835 | ||
|
|
fb23758d12 | ||
|
|
8125fee3f9 | ||
|
|
e3891246b9 | ||
|
|
a6e5edcf53 | ||
|
|
4340a48633 | ||
|
|
4d0ae6b1ef | ||
|
|
53416172e7 | ||
|
|
2b1726614b | ||
|
|
dd013ac0b2 | ||
|
|
3934d9029e | ||
|
|
fba96d024f | ||
|
|
35b04ffa9c | ||
|
|
e614faa99b | ||
|
|
fd55f2cbfa | ||
|
|
f54418bdae | ||
|
|
786e44d1d2 | ||
|
|
58d153e5ff | ||
|
|
0bf724f447 | ||
|
|
c88680b495 | ||
|
|
d24e51bc86 | ||
|
|
3c7a2f78cf | ||
|
|
8abee6504f | ||
|
|
a09a1b814b | ||
|
|
bf950ee6e1 | ||
|
|
40548926e6 | ||
|
|
f275f83de0 | ||
|
|
8a0915ffb1 | ||
|
|
505b126888 | ||
|
|
96380a50da | ||
|
|
d1efec4539 | ||
|
|
67bc66fedf | ||
|
|
d89ec89d51 | ||
|
|
5dbf5db4ff | ||
|
|
7903ed1f52 | ||
|
|
c8f10703b7 | ||
|
|
db6b5f8950 | ||
|
|
74973bc5b5 | ||
|
|
7c0b86a9cd | ||
|
|
c6007aa9e6 | ||
|
|
f01a81ee9c | ||
|
|
005ded41c3 | ||
|
|
1a148eee7c | ||
|
|
e4c3ef0262 | ||
|
|
6bb2b76e25 | ||
|
|
e71aff9d94 | ||
|
|
490df4f5fe | ||
|
|
087fae1b15 | ||
|
|
2aff218356 | ||
|
|
b98cd915a4 | ||
|
|
3349982312 | ||
|
|
5783aa99f1 | ||
|
|
cab498e376 | ||
|
|
6b9bca893b | ||
|
|
c67f128f15 | ||
|
|
4cef3adc90 | ||
|
|
acd4083399 | ||
|
|
7cbfe93a02 | ||
|
|
54ca68e4b3 | ||
|
|
b474eefd87 | ||
|
|
c5295f4d72 | ||
|
|
306b90399c | ||
|
|
7dadab95b2 | ||
|
|
ee2bc99e4c | ||
|
|
935416de45 | ||
|
|
3f49271db6 | ||
|
|
956a5ae906 | ||
|
|
40b7ecc845 | ||
|
|
92983aa185 | ||
|
|
6c61f1d261 | ||
|
|
aedcae840d | ||
|
|
ffdb198247 | ||
|
|
3a1fcbef1c | ||
|
|
ffa0bc294a | ||
|
|
a65dcb48b4 | ||
|
|
b971b13362 | ||
|
|
d77dea733f | ||
|
|
fd5c3e831d | ||
|
|
c3040fdfc3 | ||
|
|
2612cd7f1c | ||
|
|
3fe0a7bf6b | ||
|
|
a6df492fff | ||
|
|
72208e052a | ||
|
|
f6242d46b1 | ||
|
|
55c4a925ba | ||
|
|
9633af4e25 | ||
|
|
55d6434daa | ||
|
|
1b3387ca1a | ||
|
|
6c552a9d62 | ||
|
|
4db25605e7 | ||
|
|
a61bb6ab1f | ||
|
|
31ff31d3dd | ||
|
|
d665cce739 | ||
|
|
dd46e99e66 | ||
|
|
adf0178bb7 | ||
|
|
6ad2cf2003 | ||
|
|
68ca2abd0c | ||
|
|
d73a9e4734 | ||
|
|
73c0c0bf44 | ||
|
|
72a76599e4 | ||
|
|
b9f9e5853e | ||
|
|
fa6e918fc7 | ||
|
|
53e969e894 | ||
|
|
6d0e54d87e | ||
|
|
626e878861 | ||
|
|
52575f6ad6 | ||
|
|
ca13678105 | ||
|
|
355db3ab9b | ||
|
|
04f43cb684 | ||
|
|
52ab1310be | ||
|
|
56c95eadea | ||
|
|
1df5472855 | ||
|
|
9aa7074600 | ||
|
|
69647f73f0 | ||
|
|
09ef7c7106 | ||
|
|
d9eb188b7a | ||
|
|
083395ee53 | ||
|
|
2d60dab13c | ||
|
|
4fa7846f00 | ||
|
|
9fcdbec5c9 | ||
|
|
979f8383d8 | ||
|
|
2cddd3cf2b | ||
|
|
c65a9b3001 | ||
|
|
066ddd3e09 | ||
|
|
6cdd85283b | ||
|
|
5780d9d834 | ||
|
|
097b516dc5 | ||
|
|
b73dbee7e6 | ||
|
|
b8e4a2e7c0 | ||
|
|
0d4542a3f1 | ||
|
|
7c4d28d55a | ||
|
|
1143331b4d | ||
|
|
e4b956b091 | ||
|
|
e3d2e6dd64 | ||
|
|
6accc2eff6 | ||
|
|
c525406516 | ||
|
|
6056fdbddc | ||
|
|
2f52b5d354 | ||
|
|
e16ab876aa | ||
|
|
3e8f36e9f3 | ||
|
|
3135775250 | ||
|
|
77b0c69112 | ||
|
|
ec89bb70c7 | ||
|
|
cd7e9974df | ||
|
|
354dee67dc | ||
|
|
122b7baa73 | ||
|
|
c5e5666b64 | ||
|
|
7b6f11fa52 | ||
|
|
2481676c46 | ||
|
|
164dab49ac | ||
|
|
e1a2ed0436 | ||
|
|
5b73b68eb5 | ||
|
|
cd21f14106 | ||
|
|
65fba7936c | ||
|
|
ba648fa10c | ||
|
|
ae755db2d2 | ||
|
|
677047c80b | ||
|
|
0d93a6aa41 | ||
|
|
84eb978731 | ||
|
|
ac0f984136 | ||
|
|
79965ab4b3 | ||
|
|
492476dfe4 | ||
|
|
62ac168226 | ||
|
|
09616dbe25 | ||
|
|
fced60c2b5 | ||
|
|
b76060570e | ||
|
|
eb15bce24b | ||
|
|
52814266b8 | ||
|
|
f845ec05e0 | ||
|
|
29fb02c886 | ||
|
|
072e854a71 | ||
|
|
cae0a5f603 | ||
|
|
7c6d8ca222 | ||
|
|
f6be50f15a | ||
|
|
c35d54d092 | ||
|
|
323dad2a1c | ||
|
|
62aefc4f68 | ||
|
|
6a7eb8b3eb | ||
|
|
eb549f2631 | ||
|
|
9207eb69ee | ||
|
|
866df0540b | ||
|
|
04e04a1aa6 | ||
|
|
6a66e39d5b | ||
|
|
f2b2728be7 | ||
|
|
39b8f28fc4 | ||
|
|
e1ccc0b215 | ||
|
|
87e339850d | ||
|
|
79c9b6ac77 | ||
|
|
aeb2297f1f | ||
|
|
3b59bb5c09 | ||
|
|
bc4bac921f | ||
|
|
f917882a84 | ||
|
|
6a67d1cf69 | ||
|
|
041b3587bf | ||
|
|
0eef7a129c | ||
|
|
4b635f06e3 | ||
|
|
279111a8e2 | ||
|
|
67674835da | ||
|
|
732e9eb1c3 | ||
|
|
b6af9aa587 | ||
|
|
a9027c0f06 | ||
|
|
d780fa18a5 | ||
|
|
d5626d6e2f | ||
|
|
52dcbfe1a4 | ||
|
|
bf0ee3d315 | ||
|
|
0237e78c1e | ||
|
|
44b8c6abf7 | ||
|
|
33e1acd344 | ||
|
|
c54cb61f14 | ||
|
|
734b204709 | ||
|
|
b7d9c5e4ff | ||
|
|
e698b457b9 | ||
|
|
5258c21656 | ||
|
|
1ca9a3d14e | ||
|
|
f23bec9a35 | ||
|
|
62a1acd1f4 | ||
|
|
fa6e3fe567 | ||
|
|
b71b62ee35 | ||
|
|
410b4939a4 | ||
|
|
62c0071f29 | ||
|
|
f043a41005 | ||
|
|
2e9da57036 | ||
|
|
d83cd37984 | ||
|
|
bad8b0ebbb | ||
|
|
4535e65948 | ||
|
|
3b413c2ee2 | ||
|
|
427ae56333 | ||
|
|
658fd5ad6e | ||
|
|
11830bb51c | ||
|
|
75c98429bf | ||
|
|
f77ea1b3a5 | ||
|
|
0a8bd96d33 | ||
|
|
68f37fc11f | ||
|
|
d6775cda69 | ||
|
|
43c6e07bac | ||
|
|
4901e9080c | ||
|
|
48049a5ea3 | ||
|
|
bd7260f0ff | ||
|
|
6c0d54394f | ||
|
|
ce5dacbf3f | ||
|
|
08aaa5e2c0 | ||
|
|
42c0e438d5 | ||
|
|
e4df146043 | ||
|
|
27b7dae113 | ||
|
|
293d574ce7 | ||
|
|
56b3b35556 | ||
|
|
a7a0e85a46 | ||
|
|
95c0106fdd | ||
|
|
6612338fc1 | ||
|
|
c276a1541f | ||
|
|
cc96a5bbdb | ||
|
|
0810561a8a | ||
|
|
82a5c43b94 | ||
|
|
d38f36ef44 | ||
|
|
f9533440c7 | ||
|
|
41a186b051 | ||
|
|
4e6a44253c | ||
|
|
ebda77cd43 | ||
|
|
1a1e86521f | ||
|
|
1b4740dae3 | ||
|
|
91fc8df84e | ||
|
|
e6ecf1fa30 | ||
|
|
183a6f1b3a | ||
|
|
3c2d59e272 | ||
|
|
fd80e3eaf7 | ||
|
|
4928c331a8 | ||
|
|
3ad75e54cb | ||
|
|
a2cf3ab42e | ||
|
|
e24814ee2f | ||
|
|
37b42e6e17 | ||
|
|
30ebb0f4d4 | ||
|
|
8e059c64b5 | ||
|
|
395de069c2 | ||
|
|
4c22f37d54 | ||
|
|
a73a40133d | ||
|
|
6591af58ea | ||
|
|
58568d4ef6 | ||
|
|
5295593bf8 | ||
|
|
24d031d578 | ||
|
|
7141bf0358 | ||
|
|
c5d707cf0a | ||
|
|
dfcf66b43e | ||
|
|
fa6ee62cf0 | ||
|
|
1428d90361 | ||
|
|
c413c22201 | ||
|
|
9b6adecd62 | ||
|
|
b3540cf539 | ||
|
|
f8650c9c0b | ||
|
|
bf2e5768d6 | ||
|
|
18c82e79b5 | ||
|
|
d69d24a5b2 | ||
|
|
342729179d | ||
|
|
0537449335 | ||
|
|
df90311453 | ||
|
|
876579ea3b | ||
|
|
e83081380e | ||
|
|
9daeaf7562 | ||
|
|
e6be11c17f | ||
|
|
b52e1e8be3 | ||
|
|
948bbe9136 | ||
|
|
ced61da33a | ||
|
|
a0f4383d41 | ||
|
|
5a527dfa2c | ||
|
|
49fc475f9f | ||
|
|
83c377270e | ||
|
|
7ffaef0de6 | ||
|
|
dd151480a8 | ||
|
|
ad3121d367 | ||
|
|
c1525ebc69 | ||
|
|
30277cd81f | ||
|
|
466ec27ffe | ||
|
|
85c757b035 | ||
|
|
712687370a | ||
|
|
b68ba22df3 | ||
|
|
d9652e2a0b | ||
|
|
a5b757b251 | ||
|
|
0bc05a60b0 | ||
|
|
db275f885a | ||
|
|
9e483d902f | ||
|
|
801f843f8a | ||
|
|
77ffb93cbe | ||
|
|
bf73ea7f5d | ||
|
|
9b23d0ab29 | ||
|
|
908cdd2c78 | ||
|
|
f4f61a5787 | ||
|
|
6db09a2736 | ||
|
|
b21801d505 | ||
|
|
2dbedc245c | ||
|
|
58426613f6 | ||
|
|
ef19e851e3 | ||
|
|
5a1b16a601 | ||
|
|
4eef9cd9bc | ||
|
|
79b5c018ea |
@@ -51,11 +51,47 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Windman1320",
|
||||
"name": "Windman",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/9999486?v=4",
|
||||
"profile": "https://github.com/Windman1320",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ericarena",
|
||||
"name": "ericarena",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4518927?v=4",
|
||||
"profile": "https://github.com/ericarena",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "WntFlm",
|
||||
"name": "WntFlm",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/34620278?v=4",
|
||||
"profile": "https://github.com/WntFlm",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "XZB-1248",
|
||||
"name": "XZB-1248",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/28593573?v=4",
|
||||
"profile": "https://github.com/XZB-1248",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
"projectName": "alist",
|
||||
"projectOwner": "Xhofe",
|
||||
"projectOwner": "alist-org",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"skipCi": true
|
||||
|
||||
25
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
25
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,11 +1,20 @@
|
||||
name: "Bug report"
|
||||
description: Bug report
|
||||
labels: [pending triage]
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report, please confirm that your issue is not a duplicate issue
|
||||
Thanks for taking the time to fill out this bug report, please **confirm that your issue is not a duplicate issue and not because of your operation or version issues**
|
||||
感谢您花时间填写此错误报告,请**务必确认您的issue不是重复的且不是因为您的操作或版本问题**
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please make sure of the following things
|
||||
description: You may select more than one, even select all.
|
||||
options:
|
||||
- label: I have read the [documentation](https://alist-doc.nn.ci).
|
||||
- label: I'm sure there are no duplicate issues or discussions.
|
||||
- label: I'm sure it's due to `alist` and not something else(such as `Dependencies` or `Operational`).
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
@@ -14,6 +23,14 @@ body:
|
||||
placeholder: v2.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: driver
|
||||
attributes:
|
||||
label: Driver used / 使用的存储驱动
|
||||
description: What storage driver are you using?
|
||||
placeholder: "for example: Onedrive"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
@@ -28,11 +45,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.
|
||||
请复制粘贴错误日志,或者截图
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
11
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,7 +1,16 @@
|
||||
name: "Feature request"
|
||||
description: Feature request
|
||||
labels: ["enhancement: pending triage"]
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please make sure of the following things
|
||||
description: You may select more than one, even select all.
|
||||
options:
|
||||
- label: I have read the [documentation](https://alist-doc.nn.ci).
|
||||
- label: I'm sure there are no duplicate issues or discussions.
|
||||
- label: I'm sure this feature is not implemented.
|
||||
- label: I'm sure it's a reasonable and popular requirement.
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
|
||||
50
.github/workflows/build.yml
vendored
50
.github/workflows/build.yml
vendored
@@ -1,50 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '**' ]
|
||||
pull_request:
|
||||
branches: [ '**' ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
go-version: [1.17]
|
||||
name: Build
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: alist
|
||||
|
||||
- name: Install upx
|
||||
run: |
|
||||
docker pull techknowlogick/xgo:latest
|
||||
go install src.techknowlogick.com/xgo@latest
|
||||
sudo apt install upx
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mv alist/build.sh .
|
||||
bash build.sh web
|
||||
mv dist/* alist/public
|
||||
bash build.sh build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: artifact
|
||||
path: alist/build
|
||||
46
.github/workflows/build_docker.yml
vendored
46
.github/workflows/build_docker.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: build_docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ v2 ]
|
||||
pull_request:
|
||||
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
|
||||
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 }}
|
||||
2
.github/workflows/issue_question.yml
vendored
2
.github/workflows/issue_question.yml
vendored
@@ -17,4 +17,4 @@ jobs:
|
||||
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自动关闭。
|
||||
你好 @${{ github.event.issue.user.login }},请按照issue模板填写, 并详细说明问题/复现步骤/复现链接/实现思路或提供更多信息等, 7天内未回复issue自动关闭。
|
||||
46
.github/workflows/release.yml
vendored
46
.github/workflows/release.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
go-version: [1.17]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: v2
|
||||
path: alist
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install upx
|
||||
run: |
|
||||
docker pull techknowlogick/xgo:latest
|
||||
go install src.techknowlogick.com/xgo@latest
|
||||
sudo apt install upx
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mv alist/build.sh .
|
||||
bash build.sh cdn
|
||||
mv dist/* alist/public
|
||||
bash build.sh release
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: alist/build/compress/*
|
||||
45
.github/workflows/release_docker.yml
vendored
45
.github/workflows/release_docker.yml
vendored
@@ -1,45 +0,0 @@
|
||||
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
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -22,8 +22,10 @@ dist/
|
||||
# vendor/
|
||||
bin/*
|
||||
/alist
|
||||
/alist.exe
|
||||
*.json
|
||||
public/*.html
|
||||
public/assets/
|
||||
public/public/
|
||||
data/
|
||||
/data
|
||||
log/
|
||||
@@ -7,7 +7,7 @@
|
||||
Prerequisites:
|
||||
|
||||
- [git](https://nodejs.org/zh-cn/)
|
||||
- [Go 1.17+](https://golang.org/doc/install)
|
||||
- [Go 1.18+](https://golang.org/doc/install)
|
||||
- [gcc](https://gcc.gnu.org/)
|
||||
- [nodejs](https://nodejs.org/)
|
||||
|
||||
@@ -17,12 +17,12 @@ Clone `alist` and `alist-web` anywhere:
|
||||
$ 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.
|
||||
You should switch to the `main` branch for development.
|
||||
|
||||
## Preview your change
|
||||
### backend
|
||||
```shell
|
||||
$ go run alist.go
|
||||
$ go run cmd/alist.go
|
||||
```
|
||||
### frontend
|
||||
```shell
|
||||
@@ -102,4 +102,4 @@ 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.
|
||||
`main` branch.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
## Contributors ✨
|
||||
@@ -11,11 +11,17 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=foxxorcat" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=vg-land" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://c5y.moe"><img src="https://avatars.githubusercontent.com/u/18461360?v=4?s=100" width="100px;" alt=""/><br /><sub><b>凌莞~(=^▽^=)</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Clansty" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/alist-org/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=foxxorcat" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=vg-land" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://c5y.moe"><img src="https://avatars.githubusercontent.com/u/18461360?v=4?s=100" width="100px;" alt=""/><br /><sub><b>凌莞~(=^▽^=)</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=Clansty" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Windman1320"><img src="https://avatars.githubusercontent.com/u/9999486?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Windman</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=Windman1320" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ericarena"><img src="https://avatars.githubusercontent.com/u/4518927?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ericarena</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=ericarena" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/WntFlm"><img src="https://avatars.githubusercontent.com/u/34620278?v=4?s=100" width="100px;" alt=""/><br /><sub><b>WntFlm</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=WntFlm" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/XZB-1248"><img src="https://avatars.githubusercontent.com/u/28593573?v=4?s=100" width="100px;" alt=""/><br /><sub><b>XZB-1248</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=XZB-1248" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
14
Dockerfile
14
Dockerfile
@@ -1,14 +0,0 @@
|
||||
FROM alpine:edge as builder
|
||||
LABEL stage=go-builder
|
||||
WORKDIR /app/
|
||||
COPY ./ ./
|
||||
RUN apk add --no-cache bash git go gcc musl-dev; \
|
||||
bash build.sh docker
|
||||
|
||||
FROM alpine:edge
|
||||
LABEL MAINTAINER="i@nn.ci"
|
||||
VOLUME /opt/alist/data/
|
||||
WORKDIR /opt/alist/
|
||||
COPY --from=builder /app/bin/alist ./
|
||||
EXPOSE 5244
|
||||
CMD [ "./alist", "-docker" ]
|
||||
2
LICENSE
2
LICENSE
@@ -658,4 +658,4 @@ specific requirements.
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
66
README.md
66
README.md
@@ -1,6 +1,6 @@
|
||||
<div align="center">
|
||||
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||
<p><em>🗂️Another file list program that supports multiple storage, powered by Gin and React.</em></p>
|
||||
<p><em>🗂️A 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>
|
||||
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
|
||||
@@ -13,66 +13,30 @@
|
||||
|
||||
---
|
||||
|
||||
English | [中文](./README_cn.md) | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
||||
[Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
||||
|
||||
## Features
|
||||
|
||||
- [x] Multiple storage
|
||||
- [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] [GoogleDrive](https://drive.google.com/)
|
||||
- [x] [123pan](https://www.123pan.com/)
|
||||
- [x] [Lanzou](https://pc.woozooo.com/)
|
||||
- [x] [Alist](https://github.com/Xhofe/alist)
|
||||
- [x] FTP
|
||||
- [x] [PikPak](https://www.mypikpak.com/)
|
||||
- [x] [ShandianPan](https://shandianpan.com/)
|
||||
- [x] [S3](https://aws.amazon.com/s3/)
|
||||
- [x] WebDav
|
||||
- [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
|
||||
- [x] Video and audio preview (mp4, mp3, ...)
|
||||
- [x] Office documents preview (docx, pptx, xlsx, ...)
|
||||
- [x] `README.md` preview rendering
|
||||
- [x] File permalink copy and direct file download
|
||||
- [x] Dark mode
|
||||
- [x] I18n
|
||||
- [x] Protected routes (password protection and authentication)
|
||||
- [x] WebDav (see https://alist-doc.nn.ci/en/docs/webdav for details)
|
||||
- [x] [Docker Deploy](https://hub.docker.com/r/xhofe/alist)
|
||||
- [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), delete, mkdir, rename, move and copy
|
||||
> ### New version is under development. Checkout [v2](https://github.com/alist-org/alist/tree/v2) branch for latest version.
|
||||
|
||||
## Discussion
|
||||
|
||||
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
|
||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/)
|
||||
- [KinhDown 百度云盘不限速下载!永久免费!已稳定运行3年!非常可靠!](https://kinhdown.com/?Type=Tutorials)
|
||||
- [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) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
|
||||
> [@Blog](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=YJJj2Gwb)
|
||||
78
README_cn.md
78
README_cn.md
@@ -1,78 +0,0 @@
|
||||
<div align="center">
|
||||
<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>
|
||||
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
|
||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square&color=%239F7AEA" alt="Downloads"></a>
|
||||
<a href="https://github.com/Xhofe/alist/blob/v2/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a>
|
||||
<a href="https://pay.xhofe.top">
|
||||
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
[English](./README.md) | 中文 | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
||||
|
||||
## 支持
|
||||
|
||||
- [x] 多种存储
|
||||
- [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] [GoogleDrive](https://drive.google.com/)
|
||||
- [x] [123云盘](https://www.123pan.com/)
|
||||
- [x] [蓝奏云](https://pc.woozooo.com/)
|
||||
- [x] [Alist](https://github.com/Xhofe/alist)
|
||||
- [x] FTP
|
||||
- [x] [PikPak](https://www.mypikpak.com/)
|
||||
- [x] [闪电盘](https://shandianpan.com/)
|
||||
- [x] [S3](https://aws.amazon.com/cn/s3/)
|
||||
- [x] WebDav
|
||||
- [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] 画廊模式下的图像预览
|
||||
- [x] 视频和音频预览(mp4、mp3 等)
|
||||
- [x] Office 文档预览(docx、pptx、xlsx、...)
|
||||
- [x] `README.md` 预览渲染
|
||||
- [x] 文件永久链接复制和直接文件下载
|
||||
- [x] 黑暗模式
|
||||
- [x] 国际化
|
||||
- [x] 受保护的路由(密码保护和身份验证)
|
||||
- [x] WebDav(具体见https://alist-doc.nn.ci/docs/webdav )
|
||||
- [x] [Docker 部署](https://hub.docker.com/r/xhofe/alist)
|
||||
- [x] Cloudflare workers 中转
|
||||
- [x] 文件/文件夹打包下载
|
||||
- [x] 支持视频列表播放和字幕(ass,srt,vtt)
|
||||
- [x] 网页上传(可以允许访客上传),删除,新建文件夹,重命名,移动,复制
|
||||
|
||||
## 讨论
|
||||
|
||||
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) ,**issue仅针对错误报告和功能请求。**
|
||||
|
||||
## 演示
|
||||
|
||||
<https://alist.nn.ci>。
|
||||
|
||||

|
||||
|
||||
## 文档
|
||||
|
||||
<https://alist-doc.nn.ci/>
|
||||
|
||||
## 许可
|
||||
|
||||
`AList` 是在 AGPL-3.0 许可下许可的开源软件。
|
||||
|
||||
---
|
||||
|
||||
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
|
||||
60
alist.go
60
alist.go
@@ -1,60 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/bootstrap"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
_ "github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/server"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Init() bool {
|
||||
//bootstrap.InitLog()
|
||||
bootstrap.InitConf()
|
||||
bootstrap.InitCron()
|
||||
bootstrap.InitModel()
|
||||
if conf.Password {
|
||||
pass, err := model.GetSettingByKey("password")
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
return false
|
||||
}
|
||||
fmt.Printf("your password: %s\n", pass.Value)
|
||||
return false
|
||||
}
|
||||
server.InitIndex()
|
||||
bootstrap.InitSettings()
|
||||
bootstrap.InitAccounts()
|
||||
bootstrap.InitCache()
|
||||
return true
|
||||
}
|
||||
|
||||
func main() {
|
||||
if conf.Version {
|
||||
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() {
|
||||
return
|
||||
}
|
||||
if !conf.Debug {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
r := gin.Default()
|
||||
server.InitApiRouter(r)
|
||||
base := fmt.Sprintf("%s:%d", conf.Conf.Address, conf.Conf.Port)
|
||||
log.Infof("start server @ %s", base)
|
||||
var err error
|
||||
if conf.Conf.Scheme.Https {
|
||||
err = r.RunTLS(base, conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
|
||||
} else {
|
||||
err = r.Run(base)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("failed to start: %s", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUrl(t *testing.T) {
|
||||
s,_ := url.QueryUnescape("/ali/%E7%8C%AA%E5%A4%B4%E7%9A%84%E6%96%87%E4%BB%B6%5B%E5%98%BF%E5%98%BF%5D/%E9%82%B9%E9%82%B9%E7%9A%84%E6%96%87%E4%BB%B6/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8/v2-e8f266ba17ae387eefed1cb22b2b5e4e_r.jpg")
|
||||
fmt.Print(s)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func InitAccounts() {
|
||||
log.Infof("init accounts...")
|
||||
var accounts []model.Account
|
||||
if err := conf.DB.Find(&accounts).Error; err != nil {
|
||||
log.Fatalf("failed sync init accounts")
|
||||
}
|
||||
for i, account := range accounts {
|
||||
model.RegisterAccount(account)
|
||||
driver, ok := base.GetDriver(account.Type)
|
||||
if !ok {
|
||||
log.Errorf("no [%s] driver", account.Type)
|
||||
} else {
|
||||
log.Infof("start init account: [%s], type: [%s]", account.Name, account.Type)
|
||||
err := driver.Save(&accounts[i], nil)
|
||||
if err != nil {
|
||||
log.Errorf("init account [%s] error:[%s]", account.Name, err.Error())
|
||||
} else {
|
||||
log.Infof("success init account: %s, type: %s", account.Name, account.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/eko/gocache/v2/cache"
|
||||
"github.com/eko/gocache/v2/store"
|
||||
goCache "github.com/patrickmn/go-cache"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InitCache init cache
|
||||
func InitCache() {
|
||||
log.Infof("init cache...")
|
||||
c := conf.Conf.Cache
|
||||
if c.Expiration == 0 {
|
||||
c.Expiration, c.CleanupInterval = 60, 120
|
||||
}
|
||||
goCacheClient := goCache.New(time.Duration(c.Expiration)*time.Minute, time.Duration(c.CleanupInterval)*time.Minute)
|
||||
goCacheStore := store.NewGoCache(goCacheClient, nil)
|
||||
conf.Cache = cache.New(goCacheStore)
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
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
|
||||
func InitConf() {
|
||||
log.Infof("reading config file: %s", conf.ConfigFile)
|
||||
if !utils.Exists(conf.ConfigFile) {
|
||||
log.Infof("config file not exists, creating default config file")
|
||||
_, err := utils.CreatNestedFile(conf.ConfigFile)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create config file")
|
||||
}
|
||||
conf.Conf = conf.DefaultConfig()
|
||||
if !utils.WriteToJson(conf.ConfigFile, conf.Conf) {
|
||||
log.Fatalf("failed to create default config file")
|
||||
}
|
||||
} else {
|
||||
config, err := ioutil.ReadFile(conf.ConfigFile)
|
||||
if err != nil {
|
||||
log.Fatalf("reading config file error:%s", err.Error())
|
||||
}
|
||||
conf.Conf = conf.DefaultConfig()
|
||||
err = utils.Json.Unmarshal(config, conf.Conf)
|
||||
if err != nil {
|
||||
log.Fatalf("load config error: %s", err.Error())
|
||||
}
|
||||
log.Debugf("config:%+v", conf.Conf)
|
||||
// update config.json struct
|
||||
confBody, err := utils.Json.MarshalIndent(conf.Conf, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("marshal config error:%s", err.Error())
|
||||
}
|
||||
err = ioutil.WriteFile(conf.ConfigFile, confBody, 0777)
|
||||
if err != nil {
|
||||
log.Fatalf("update config struct error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/robfig/cron/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// InitCron init cron
|
||||
func InitCron() {
|
||||
log.Infof("init cron...")
|
||||
conf.Cron = cron.New()
|
||||
conf.Cron.Start()
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// InitLog init log
|
||||
func InitLog() {
|
||||
if conf.Debug {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.SetReportCaller(true)
|
||||
}
|
||||
if conf.Password || conf.Version {
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
//DisableColors: true,
|
||||
ForceColors: true,
|
||||
EnvironmentOverrideColors: true,
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
FullTimestamp: true,
|
||||
})
|
||||
log.Infof("init log...")
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&conf.ConfigFile, "conf", "data/config.json", "config file")
|
||||
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()
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/model"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
log2 "log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func InitModel() {
|
||||
log.Infof("init model...")
|
||||
var err error
|
||||
databaseConfig := conf.Conf.Database
|
||||
newLogger := logger.New(
|
||||
log2.New(os.Stdout, "\r\n", log2.LstdFlags),
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second,
|
||||
LogLevel: logger.Silent,
|
||||
IgnoreRecordNotFoundError: true,
|
||||
Colorful: true,
|
||||
},
|
||||
)
|
||||
gormConfig := &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: databaseConfig.TablePrefix,
|
||||
},
|
||||
Logger: newLogger,
|
||||
}
|
||||
switch databaseConfig.Type {
|
||||
case "sqlite3":
|
||||
{
|
||||
if !(strings.HasSuffix(databaseConfig.DBFile, ".db") && len(databaseConfig.DBFile) > 3) {
|
||||
log.Fatalf("db name error.")
|
||||
}
|
||||
db, err := gorm.Open(sqlite.Open(databaseConfig.DBFile), gormConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect database:%s", err.Error())
|
||||
}
|
||||
conf.DB = db
|
||||
}
|
||||
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)
|
||||
db, err := gorm.Open(mysql.Open(dsn), gormConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect database:%s", err.Error())
|
||||
}
|
||||
conf.DB = db
|
||||
}
|
||||
case "postgres":
|
||||
{
|
||||
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)
|
||||
}
|
||||
log.Infof("auto migrate model...")
|
||||
if databaseConfig.Type == "mysql" {
|
||||
err = conf.DB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").
|
||||
AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{})
|
||||
} else {
|
||||
err = conf.DB.AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{})
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("failed to auto migrate: %s", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
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"
|
||||
)
|
||||
|
||||
func InitSettings() {
|
||||
log.Infof("init settings...")
|
||||
|
||||
err := model.SaveSetting(model.Version)
|
||||
if err != nil {
|
||||
log.Fatalf("failed write setting: %s", err.Error())
|
||||
}
|
||||
|
||||
settings := []model.SettingItem{
|
||||
{
|
||||
Key: "title",
|
||||
Value: "Alist",
|
||||
Description: "title",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "password",
|
||||
Value: utils.RandomStr(8),
|
||||
Description: "password",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
Group: model.BACK,
|
||||
},
|
||||
{
|
||||
Key: "logo",
|
||||
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/can_circle.svg",
|
||||
Description: "logo",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "favicon",
|
||||
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg",
|
||||
Description: "favicon",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "icon color",
|
||||
Value: "#1890ff",
|
||||
Description: "icon's color",
|
||||
Type: "string",
|
||||
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, ","),
|
||||
Type: "string",
|
||||
Description: "text 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://cdn.jsdelivr.net/gh/alist-org/logo@main/circle_center.svg",
|
||||
Description: "music cover image",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "site beian",
|
||||
Description: "chinese beian info",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "home readme url",
|
||||
Description: "when have multiple, the readme file to show",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "autoplay video",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "autoplay audio",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "check parent folder",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Description: "check parent folder password",
|
||||
Access: model.PRIVATE,
|
||||
Group: model.BACK,
|
||||
},
|
||||
{
|
||||
Key: "customize head",
|
||||
Value: "",
|
||||
Type: "text",
|
||||
Description: "Customize head, placed at the beginning of the head",
|
||||
Access: model.PRIVATE,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "customize body",
|
||||
Value: "",
|
||||
Type: "text",
|
||||
Description: "Customize script, placed at the end of the body",
|
||||
Access: model.PRIVATE,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "home emoji",
|
||||
Value: "🏠",
|
||||
Type: "string",
|
||||
Description: "emoji in front of home in nav",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "animation",
|
||||
Value: "true",
|
||||
Type: "bool",
|
||||
Description: "when there are a lot of files, the animation will freeze when opening",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "check down link",
|
||||
Value: "false",
|
||||
Type: "bool",
|
||||
Description: "check down link password, your link will be 'https://alist.com/d/filename?pw=xxx'",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.BACK,
|
||||
},
|
||||
{
|
||||
Key: "WebDAV username",
|
||||
Value: "admin",
|
||||
Description: "WebDAV username",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
Group: model.BACK,
|
||||
},
|
||||
{
|
||||
Key: "WebDAV password",
|
||||
Value: utils.RandomStr(8),
|
||||
Description: "WebDAV password",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
Group: model.BACK,
|
||||
},
|
||||
{
|
||||
Key: "artplayer whitelist",
|
||||
Value: "*",
|
||||
Description: "refer to https://artplayer.org/document/options#whitelist",
|
||||
Type: "string",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "artplayer autoSize",
|
||||
Value: "true",
|
||||
Description: "refer to https://artplayer.org/document/options#autosize",
|
||||
Type: "bool",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "Visitor WebDAV username",
|
||||
Value: "guest",
|
||||
Description: "Visitor WebDAV username",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
Group: model.BACK,
|
||||
},
|
||||
{
|
||||
Key: "Visitor WebDAV password",
|
||||
Value: "guest",
|
||||
Description: "Visitor WebDAV password",
|
||||
Type: "string",
|
||||
Access: model.PRIVATE,
|
||||
Group: model.BACK,
|
||||
},
|
||||
{
|
||||
Key: "load type",
|
||||
Value: "all",
|
||||
Type: "select",
|
||||
Values: "all,load more,auto load more,pagination",
|
||||
Description: "Not recommended to choose to auto load more, it has bugs now",
|
||||
Access: model.PUBLIC,
|
||||
Group: model.FRONT,
|
||||
},
|
||||
{
|
||||
Key: "default page size",
|
||||
Value: "30",
|
||||
Type: "number",
|
||||
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]
|
||||
v.Version = conf.GitTag
|
||||
o, err := model.GetSettingByKey(v.Key)
|
||||
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())
|
||||
}
|
||||
} else {
|
||||
log.Fatalf("can't get setting: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
//o.Version = conf.GitTag
|
||||
//err = model.SaveSetting(*o)
|
||||
v.Value = o.Value
|
||||
err = model.SaveSetting(v)
|
||||
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()
|
||||
}
|
||||
156
build.sh
156
build.sh
@@ -1,156 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 构建前端,在当前目录产生一个dist文件夹
|
||||
BUILD_WEB() {
|
||||
git clone https://github.com/alist-org/alist-web.git
|
||||
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 .. || exit
|
||||
rm -rf alist-web
|
||||
}
|
||||
|
||||
CDN_WEB() {
|
||||
curl -L https://github.com/alist-org/alist-web/releases/latest/download/dist.tar.gz -o dist.tar.gz
|
||||
tar -zxvf dist.tar.gz
|
||||
rm -f dist.tar.gz
|
||||
}
|
||||
|
||||
# 在DOCKER中构建
|
||||
BUILD_DOCKER() {
|
||||
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' \
|
||||
"
|
||||
go build -o ./bin/alist -ldflags="$ldflags" -tags=jsoniter alist.go
|
||||
}
|
||||
|
||||
BUILD() {
|
||||
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')
|
||||
echo "build version: $gitTag"
|
||||
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' \
|
||||
"
|
||||
|
||||
if [ "$1" == "release" ]; then
|
||||
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||
else
|
||||
xgo -targets=linux/amd64,windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||
fi
|
||||
mkdir -p "build"
|
||||
mv alist-* build
|
||||
if [ "$1" != "release" ]; then
|
||||
cd build
|
||||
upx -9 ./alist-linux*
|
||||
upx -9 ./alist-windows*
|
||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||
cat md5.txt
|
||||
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
|
||||
tar -czvf compress/"$i".tar.gz "$i"
|
||||
done
|
||||
for i in $(find . -type f -name "$appName-darwin-*"); do
|
||||
tar -czvf compress/"$i".tar.gz "$i"
|
||||
done
|
||||
for i in $(find . -type f -name "$appName-windows-*"); do
|
||||
zip compress/$(echo $i | sed 's/\.[^.]*$//').zip "$i"
|
||||
done
|
||||
cd ../.. || exit
|
||||
}
|
||||
|
||||
if [ "$1" = "web" ]; then
|
||||
BUILD_WEB
|
||||
elif [ "$1" = "cdn" ]; then
|
||||
CDN_WEB
|
||||
elif [ "$1" = "docker" ]; then
|
||||
BUILD_DOCKER
|
||||
elif [ "$1" = "build" ]; then
|
||||
BUILD build
|
||||
elif [ "$1" = "release" ]; then
|
||||
BUILD release
|
||||
BUILD_MUSL
|
||||
RELEASE
|
||||
else
|
||||
echo -e "${RED_COLOR} Parameter error ${RES}"
|
||||
fi
|
||||
59
cmd/alist.go
Normal file
59
cmd/alist.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
bootstrap2 "github.com/alist-org/alist/v3/internal/bootstrap"
|
||||
"github.com/alist-org/alist/v3/internal/bootstrap/data"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/server"
|
||||
"os"
|
||||
|
||||
"github.com/alist-org/alist/v3/cmd/args"
|
||||
_ "github.com/alist-org/alist/v3/drivers"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&args.Config, "conf", "data/config.json", "config file")
|
||||
flag.BoolVar(&args.Debug, "debug", false, "start with debug mode")
|
||||
flag.BoolVar(&args.Version, "version", false, "print version info")
|
||||
flag.BoolVar(&args.Password, "password", false, "print current password")
|
||||
flag.BoolVar(&args.NoPrefix, "no-prefix", false, "disable env prefix")
|
||||
flag.BoolVar(&args.Dev, "dev", false, "start with dev mode")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func Init() {
|
||||
if args.Version {
|
||||
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.Version, conf.WebVersion)
|
||||
os.Exit(0)
|
||||
}
|
||||
bootstrap2.InitConfig()
|
||||
bootstrap2.Log()
|
||||
bootstrap2.InitDB()
|
||||
data.InitData()
|
||||
bootstrap2.InitAria2()
|
||||
}
|
||||
func main() {
|
||||
Init()
|
||||
if !args.Debug && !args.Dev {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
r := gin.New()
|
||||
r.Use(gin.LoggerWithWriter(log.StandardLogger().Out), gin.RecoveryWithWriter(log.StandardLogger().Out))
|
||||
server.Init(r)
|
||||
base := fmt.Sprintf("%s:%d", conf.Conf.Address, conf.Conf.Port)
|
||||
log.Infof("start server @ %s", base)
|
||||
var err error
|
||||
if conf.Conf.Scheme.Https {
|
||||
err = r.RunTLS(base, conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
|
||||
} else {
|
||||
err = r.Run(base)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("failed to start: %s", err.Error())
|
||||
}
|
||||
}
|
||||
10
cmd/args/config.go
Normal file
10
cmd/args/config.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package args
|
||||
|
||||
var (
|
||||
Config string // config file
|
||||
Debug bool
|
||||
Version bool
|
||||
Password bool
|
||||
NoPrefix bool
|
||||
Dev bool
|
||||
)
|
||||
@@ -1,55 +0,0 @@
|
||||
package conf
|
||||
|
||||
type Database struct {
|
||||
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" 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" env:"CACHE_EXPIRATION"`
|
||||
CleanupInterval int64 `json:"cleanup_interval" env:"CLEANUP_INTERVAL"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Force bool `json:"force"`
|
||||
Address string `json:"address" env:"ADDR"`
|
||||
Port int `json:"port" env:"PORT"`
|
||||
Assets string `json:"assets" env:"ASSETS"`
|
||||
Database Database `json:"database"`
|
||||
Scheme Scheme `json:"scheme"`
|
||||
Cache CacheConfig `json:"cache"`
|
||||
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Address: "0.0.0.0",
|
||||
Port: 5244,
|
||||
Assets: "https://npm.elemecdn.com/alist-web@$version/dist",
|
||||
TempDir: "data/temp",
|
||||
Database: Database{
|
||||
Type: "sqlite3",
|
||||
Port: 0,
|
||||
TablePrefix: "x_",
|
||||
DBFile: "data/data.db",
|
||||
SslMode: "disable",
|
||||
},
|
||||
Cache: CacheConfig{
|
||||
Expiration: 60,
|
||||
CleanupInterval: 120,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package conf
|
||||
|
||||
const (
|
||||
UNKNOWN = iota
|
||||
FOLDER
|
||||
OFFICE
|
||||
VIDEO
|
||||
AUDIO
|
||||
TEXT
|
||||
IMAGE
|
||||
)
|
||||
103
conf/var.go
103
conf/var.go
@@ -1,103 +0,0 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/eko/gocache/v2/cache"
|
||||
"github.com/fatih/color"
|
||||
"github.com/robfig/cron/v3"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
BuiltAt string
|
||||
GoVersion string
|
||||
GitAuthor string
|
||||
GitCommit string
|
||||
GitTag string = "dev"
|
||||
WebTag string
|
||||
)
|
||||
|
||||
var (
|
||||
ConfigFile string // config file
|
||||
Conf *Config
|
||||
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", "opus"}
|
||||
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
|
||||
)
|
||||
|
||||
var settingsMap = make(map[string]string)
|
||||
|
||||
func Set(key string, value string) {
|
||||
settingsMap[key] = value
|
||||
}
|
||||
|
||||
func GetStr(key string) string {
|
||||
value, ok := settingsMap[key]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func GetBool(key string) bool {
|
||||
value, ok := settingsMap[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return value == "true"
|
||||
}
|
||||
|
||||
func GetInt(key string, defaultV int) int {
|
||||
value, ok := settingsMap[key]
|
||||
if !ok {
|
||||
return defaultV
|
||||
}
|
||||
v, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return defaultV
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
var (
|
||||
LoadSettings = []string{
|
||||
"check parent folder", "check down link", "WebDAV username", "WebDAV password",
|
||||
"Visitor WebDAV username", "Visitor WebDAV password",
|
||||
"default page size", "load type",
|
||||
"ocr api", "favicon",
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
RawIndexHtml string
|
||||
ManageHtml string
|
||||
IndexHtml string
|
||||
Token string
|
||||
|
||||
//CheckParent bool
|
||||
//CheckDown bool
|
||||
//DavUsername string
|
||||
//DavPassword string
|
||||
//VisitorDavUsername string
|
||||
//VisitorDavPassword string
|
||||
)
|
||||
@@ -1,230 +0,0 @@
|
||||
package _23
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BaseResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type Pan123TokenResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
Token string `json:"token"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type Pan123File struct {
|
||||
FileName string `json:"FileName"`
|
||||
Size int64 `json:"Size"`
|
||||
UpdateAt *time.Time `json:"UpdateAt"`
|
||||
FileId int64 `json:"FileId"`
|
||||
Type int `json:"Type"`
|
||||
Etag string `json:"Etag"`
|
||||
S3KeyFlag string `json:"S3KeyFlag"`
|
||||
}
|
||||
|
||||
type Pan123Files struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
InfoList []Pan123File `json:"InfoList"`
|
||||
Next string `json:"Next"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type Pan123DownResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
DownloadUrl string `json:"DownloadUrl"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type UploadResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
AccessKeyId string `json:"AccessKeyId"`
|
||||
Bucket string `json:"Bucket"`
|
||||
Key string `json:"Key"`
|
||||
SecretAccessKey string `json:"SecretAccessKey"`
|
||||
SessionToken string `json:"SessionToken"`
|
||||
FileId int64 `json:"FileId"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func (driver Pan123) Login(account *model.Account) error {
|
||||
url := "https://www.123pan.com/api/user/sign_in"
|
||||
if account.APIProxyUrl != "" {
|
||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
||||
}
|
||||
var resp Pan123TokenResp
|
||||
_, err := base.RestyClient.R().
|
||||
SetResult(&resp).
|
||||
SetBody(base.Json{
|
||||
"passport": account.Username,
|
||||
"password": account.Password,
|
||||
}).Post(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Code != 200 {
|
||||
err = fmt.Errorf(resp.Message)
|
||||
account.Status = resp.Message
|
||||
} else {
|
||||
account.Status = "work"
|
||||
account.AccessToken = resp.Data.Token
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Pan123) FormatFile(file *Pan123File) *model.File {
|
||||
f := &model.File{
|
||||
Id: strconv.FormatInt(file.FileId, 10),
|
||||
Name: file.FileName,
|
||||
Size: file.Size,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: file.UpdateAt,
|
||||
}
|
||||
if file.Type == 1 {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.FileName))
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (driver Pan123) GetFiles(parentId string, account *model.Account) ([]Pan123File, error) {
|
||||
next := "0"
|
||||
res := make([]Pan123File, 0)
|
||||
for next != "-1" {
|
||||
var resp Pan123Files
|
||||
query := map[string]string{
|
||||
"driveId": "0",
|
||||
"limit": "100",
|
||||
"next": next,
|
||||
"orderBy": account.OrderBy,
|
||||
"orderDirection": account.OrderDirection,
|
||||
"parentFileId": parentId,
|
||||
"trashed": "false",
|
||||
}
|
||||
_, err := driver.Request("https://www.123pan.com/api/file/list",
|
||||
base.Get, nil, query, nil, &resp, false, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
next = resp.Data.Next
|
||||
res = append(res, resp.Data.InfoList...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (driver Pan123) Request(url string, method int, headers, query map[string]string, data *base.Json, resp interface{}, proxy bool, account *model.Account) ([]byte, error) {
|
||||
rawUrl := url
|
||||
if account.APIProxyUrl != "" && proxy {
|
||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
||||
}
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
|
||||
if headers != nil {
|
||||
req.SetHeaders(headers)
|
||||
}
|
||||
if query != nil {
|
||||
req.SetQueryParams(query)
|
||||
}
|
||||
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(url)
|
||||
case base.Post:
|
||||
res, err = req.Post(url)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
body := res.Body()
|
||||
code := jsoniter.Get(body, "code").ToInt()
|
||||
if code != 0 {
|
||||
if code == 401 {
|
||||
err := driver.Login(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver.Request(rawUrl, method, headers, query, data, resp, proxy, account)
|
||||
}
|
||||
return nil, errors.New(jsoniter.Get(body, "message").ToString())
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
//func (driver Pan123) Post(url string, data base.Json, account *model.Account) ([]byte, error) {
|
||||
// res, err := pan123Client.R().
|
||||
// SetHeader("authorization", "Bearer "+account.AccessToken).
|
||||
// SetBody(data).Post(url)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// body := res.Body()
|
||||
// if jsoniter.Get(body, "code").ToInt() != 0 {
|
||||
// return nil, errors.New(jsoniter.Get(body, "message").ToString())
|
||||
// }
|
||||
// return body, nil
|
||||
//}
|
||||
|
||||
func (driver Pan123) GetFile(path string, account *model.Account) (*Pan123File, error) {
|
||||
dir, name := filepath.Split(path)
|
||||
dir = utils.ParsePath(dir)
|
||||
_, err := driver.Files(dir, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentFiles_, _ := base.GetCache(dir, account)
|
||||
parentFiles, _ := parentFiles_.([]Pan123File)
|
||||
for _, file := range parentFiles {
|
||||
if file.FileName == name {
|
||||
if file.Type != conf.FOLDER {
|
||||
return &file, err
|
||||
} else {
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
//func HMAC(message string, secret string) string {
|
||||
// key := []byte(secret)
|
||||
// h := hmac.New(sha256.New, key)
|
||||
// h.Write([]byte(message))
|
||||
// // fmt.Println(h.Sum(nil))
|
||||
// //sha := hex.EncodeToString(h.Sum(nil))
|
||||
// // fmt.Println(sha)
|
||||
// //return sha
|
||||
// return string(h.Sum(nil))
|
||||
//}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Pan123{})
|
||||
}
|
||||
@@ -1,439 +0,0 @@
|
||||
package _23
|
||||
|
||||
import (
|
||||
"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"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Pan123 struct{}
|
||||
|
||||
func (driver Pan123) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "123Pan",
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Pan123) 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: false,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,fileId,updateAt,createAt",
|
||||
Required: true,
|
||||
Default: "name",
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
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"
|
||||
}
|
||||
err := driver.Login(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Pan123) 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 Pan123) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var rawFiles []Pan123File
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
rawFiles, _ = cache.([]Pan123File)
|
||||
} else {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rawFiles) > 0 {
|
||||
_ = base.SetCache(path, rawFiles, account)
|
||||
}
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
for _, file := range rawFiles {
|
||||
files = append(files, *driver.FormatFile(&file))
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Pan123) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
log.Debugf("%+v", args)
|
||||
file, err := driver.GetFile(utils.ParsePath(args.Path), account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp Pan123DownResp
|
||||
var headers map[string]string
|
||||
if args.IP != "" && args.IP != "::1" {
|
||||
headers = map[string]string{
|
||||
//"X-Real-IP": "1.1.1.1",
|
||||
"X-Forwarded-For": args.IP,
|
||||
}
|
||||
}
|
||||
data := base.Json{
|
||||
"driveId": 0,
|
||||
"etag": file.Etag,
|
||||
"fileId": file.FileId,
|
||||
"fileName": file.FileName,
|
||||
"s3keyFlag": file.S3KeyFlag,
|
||||
"size": file.Size,
|
||||
"type": file.Type,
|
||||
}
|
||||
_, err = driver.Request("https://www.123pan.com/api/file/download_info",
|
||||
base.Post, headers, nil, &data, &resp, false, account)
|
||||
//_, err = pan123Client.R().SetResult(&resp).SetHeader("authorization", "Bearer "+account.AccessToken).
|
||||
// SetBody().Post("https://www.123pan.com/api/file/download_info")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u, err := url.Parse(resp.Data.DownloadUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u_ := fmt.Sprintf("https://%s%s", u.Host, u.Path)
|
||||
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Get(u_)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
link := base.Link{
|
||||
Url: resp.Data.DownloadUrl,
|
||||
}
|
||||
if res.StatusCode() == 302 {
|
||||
link.Url = res.Header().Get("location")
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Pan123) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("pan123 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 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
|
||||
}
|
||||
|
||||
func (driver Pan123) 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
|
||||
}
|
||||
parentFileId, _ := strconv.Atoi(parentFile.Id)
|
||||
data := base.Json{
|
||||
"driveId": 0,
|
||||
"etag": "",
|
||||
"fileName": name,
|
||||
"parentFileId": parentFileId,
|
||||
"size": 0,
|
||||
"type": 1,
|
||||
}
|
||||
_, err = driver.Request("https://www.123pan.com/api/file/upload_request",
|
||||
base.Post, nil, nil, &data, nil, false, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Pan123) Move(src string, dst string, account *model.Account) error {
|
||||
dstDir, _ := filepath.Split(dst)
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileId, _ := strconv.Atoi(srcFile.Id)
|
||||
|
||||
dstDirFile, err := driver.File(dstDir, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parentFileId, _ := strconv.Atoi(dstDirFile.Id)
|
||||
data := base.Json{
|
||||
"fileIdList": []base.Json{{"FileId": fileId}},
|
||||
"parentFileId": parentFileId,
|
||||
}
|
||||
_, err = driver.Request("https://www.123pan.com/api/file/mod_pid",
|
||||
base.Post, nil, nil, &data, nil, false, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Pan123) Rename(src string, dst string, account *model.Account) error {
|
||||
_, dstName := filepath.Split(dst)
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileId, _ := strconv.Atoi(srcFile.Id)
|
||||
|
||||
data := base.Json{
|
||||
"driveId": 0,
|
||||
"fileId": fileId,
|
||||
"fileName": dstName,
|
||||
}
|
||||
_, err = driver.Request("https://www.123pan.com/api/file/rename",
|
||||
base.Post, nil, nil, &data, nil, false, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Pan123) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Pan123) Delete(path string, account *model.Account) error {
|
||||
file, err := driver.GetFile(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"driveId": 0,
|
||||
"operation": true,
|
||||
"fileTrashInfoList": file,
|
||||
}
|
||||
_, err = driver.Request("https://www.123pan.com/api/file/trash",
|
||||
base.Post, nil, nil, &data, nil, false, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Pan123) 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
|
||||
}
|
||||
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": etag,
|
||||
"fileName": file.GetFileName(),
|
||||
"parentFileId": parentFileId,
|
||||
"size": file.GetSize(),
|
||||
"type": 0,
|
||||
}
|
||||
var resp UploadResp
|
||||
_, err = driver.Request("https://www.123pan.com/api/file/upload_request",
|
||||
base.Post, nil, nil, &data, &resp, false, account)
|
||||
//res, err := driver.Post("https://www.123pan.com/api/file/upload_request", data, account)
|
||||
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"),
|
||||
Endpoint: aws.String("file.123pan.com"),
|
||||
S3ForcePathStyle: aws.Bool(true),
|
||||
}
|
||||
s, err := session.NewSession(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploader := s3manager.NewUploader(s)
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &resp.Data.Bucket,
|
||||
Key: &resp.Data.Key,
|
||||
Body: tempFile,
|
||||
}
|
||||
_, err = uploader.Upload(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = driver.Request("https://www.123pan.com/api/file/upload_complete", base.Post, nil, nil, &base.Json{
|
||||
"fileId": resp.Data.FileId,
|
||||
}, nil, false, account)
|
||||
return err
|
||||
}
|
||||
|
||||
//type UploadResp struct {
|
||||
// XMLName xml.Name `xml:"InitiateMultipartUploadResult"`
|
||||
// Bucket string `xml:"Bucket"`
|
||||
// Key string `xml:"Key"`
|
||||
// UploadId string `xml:"UploadId"`
|
||||
//}
|
||||
|
||||
// TODO unfinished
|
||||
//func (driver Pan123) Upload(file *model.FileStream, account *model.Account) error {
|
||||
// return base.ErrNotImplement
|
||||
// parentFile, err := driver.File(file.ParentPath, account)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if !parentFile.IsDir() {
|
||||
// return base.ErrNotFolder
|
||||
// }
|
||||
// parentFileId, _ := strconv.Atoi(parentFile.Id)
|
||||
// data := base.Json{
|
||||
// "driveId": 0,
|
||||
// "duplicate": true,
|
||||
// "etag": RandStr(32), //maybe file's md5
|
||||
// "fileName": file.GetFileName(),
|
||||
// "parentFileId": parentFileId,
|
||||
// "size": file.GetSize(),
|
||||
// "type": 0,
|
||||
// }
|
||||
// res, err := driver.Request("https://www.123pan.com/api/file/upload_request",
|
||||
// base.Post, nil, nil, &data, nil, false, account)
|
||||
// //res, err := driver.Post("https://www.123pan.com/api/file/upload_request", data, account)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// baseUrl := fmt.Sprintf("https://file.123pan.com/%s/%s", jsoniter.Get(res, "data.Bucket").ToString(), jsoniter.Get(res, "data.Key").ToString())
|
||||
// var resp UploadResp
|
||||
// kSecret := jsoniter.Get(res, "data.SecretAccessKey").ToString()
|
||||
// nowTimeStr := time.Now().String()
|
||||
// Date := strings.ReplaceAll(strings.Split(nowTimeStr, "T")[0], "-", "")
|
||||
//
|
||||
// StringToSign := fmt.Sprintf("%s\n%s\n%s\n%s",
|
||||
// "AWS4-HMAC-SHA256",
|
||||
// nowTimeStr,
|
||||
// fmt.Sprintf("%s/us-east-1/s3/aws4_request", Date),
|
||||
// )
|
||||
//
|
||||
// kDate := HMAC("AWS4"+kSecret, Date)
|
||||
// kRegion := HMAC(kDate, "us-east-1")
|
||||
// kService := HMAC(kRegion, "s3")
|
||||
// kSigning := HMAC(kService, "aws4_request")
|
||||
// _, err = base.RestyClient.R().SetResult(&resp).SetHeaders(map[string]string{
|
||||
// "Authorization": fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-user-agent, Signature=%s",
|
||||
// jsoniter.Get(res, "data.AccessKeyId"),
|
||||
// Date,
|
||||
// hex.EncodeToString([]byte(HMAC(StringToSign, kSigning)))),
|
||||
// "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD",
|
||||
// "X-Amz-Date": nowTimeStr,
|
||||
// "x-amz-security-token": jsoniter.Get(res, "data.SessionToken").ToString(),
|
||||
// }).Post(fmt.Sprintf("%s?uploads", baseUrl))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return base.ErrNotImplement
|
||||
//}
|
||||
|
||||
var _ base.Driver = (*Pan123)(nil)
|
||||
@@ -1,175 +0,0 @@
|
||||
package _39
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 := utils.RandomStr(16)
|
||||
ts := time.Now().Format("2006-01-02 15:04:05")
|
||||
log.Debugf("%+v", data)
|
||||
body, err := utils.Json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sign := calSign(string(body), ts, randStr)
|
||||
svcType := "1"
|
||||
if isFamily(account) {
|
||||
svcType = "2"
|
||||
}
|
||||
req.SetHeaders(map[string]string{
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"CMS-DEVICE": "default",
|
||||
"Cookie": account.AccessToken,
|
||||
"mcloud-channel": "1000101",
|
||||
"mcloud-client": "10701",
|
||||
//"mcloud-route": "001",
|
||||
"mcloud-sign": fmt.Sprintf("%s,%s,%s", ts, randStr, sign),
|
||||
//"mcloud-skey":"",
|
||||
"mcloud-version": "6.6.0",
|
||||
"Origin": "https://yun.139.com",
|
||||
"Referer": "https://yun.139.com/w/",
|
||||
"x-DeviceInfo": "||9|6.6.0|chrome|95.0.4638.69|uwIy75obnsRPIwlJSd7D9GhUvFwG96ce||macos 10.15.2||zh-CN|||",
|
||||
"x-huawei-channelSrc": "10000034",
|
||||
"x-inner-ntwk": "2",
|
||||
"x-m4c-caller": "PC",
|
||||
"x-m4c-src": "10002",
|
||||
"x-SvcType": svcType,
|
||||
})
|
||||
if headers != nil {
|
||||
req.SetHeaders(headers)
|
||||
}
|
||||
if query != nil {
|
||||
req.SetQueryParams(query)
|
||||
}
|
||||
if form != nil {
|
||||
req.SetFormData(form)
|
||||
}
|
||||
if data != nil {
|
||||
req.SetBody(data)
|
||||
}
|
||||
var e BaseResp
|
||||
//var err error
|
||||
var res *resty.Response
|
||||
req.SetResult(&e)
|
||||
switch method {
|
||||
case base.Get:
|
||||
res, err = req.Get(url)
|
||||
case base.Post:
|
||||
res, err = req.Post(url)
|
||||
case base.Delete:
|
||||
res, err = req.Delete(url)
|
||||
case base.Patch:
|
||||
res, err = req.Patch(url)
|
||||
case base.Put:
|
||||
res, err = req.Put(url)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugln(res.String())
|
||||
if !e.Success {
|
||||
return nil, errors.New(e.Message)
|
||||
}
|
||||
if resp != nil {
|
||||
err = utils.Json.Unmarshal(res.Body(), resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (driver Cloud139) 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 Cloud139) GetFiles(catalogID string, account *model.Account) ([]model.File, error) {
|
||||
start := 0
|
||||
limit := 100
|
||||
files := make([]model.File, 0)
|
||||
for {
|
||||
data := base.Json{
|
||||
"catalogID": catalogID,
|
||||
"sortDirection": 1,
|
||||
"startNumber": start + 1,
|
||||
"endNumber": start + limit,
|
||||
"filterType": 0,
|
||||
"catalogSortType": 0,
|
||||
"contentSortType": 0,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
var resp GetDiskResp
|
||||
_, err := driver.Post("/orchestration/personalCloud/catalog/v1.0/getDisk", data, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, catalog := range resp.Data.GetDiskResult.CatalogList {
|
||||
f := model.File{
|
||||
Id: catalog.CatalogID,
|
||||
Name: catalog.CatalogName,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: getTime(catalog.UpdateTime),
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
for _, content := range resp.Data.GetDiskResult.ContentList {
|
||||
f := model.File{
|
||||
Id: content.ContentID,
|
||||
Name: content.ContentName,
|
||||
Size: content.ContentSize,
|
||||
Type: utils.GetFileType(path.Ext(content.ContentName)),
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: getTime(content.UpdateTime),
|
||||
Thumbnail: content.ThumbnailURL,
|
||||
//Thumbnail: content.BigthumbnailURL,
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
if start+limit >= resp.Data.GetDiskResult.NodeCount {
|
||||
break
|
||||
}
|
||||
start += limit
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Cloud139) GetLink(contentId string, account *model.Account) (string, error) {
|
||||
data := base.Json{
|
||||
"appName": "",
|
||||
"contentID": contentId,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
res, err := driver.Post("/orchestration/personalCloud/uploadAndDownload/v1.0/downloadRequest",
|
||||
data, nil, account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Cloud139{})
|
||||
}
|
||||
@@ -1,456 +0,0 @@
|
||||
package _39
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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"
|
||||
"math"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Cloud139 struct{}
|
||||
|
||||
func (driver Cloud139) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "139Yun",
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Cloud139) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "username",
|
||||
Label: "phone",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Description: "phone number",
|
||||
},
|
||||
{
|
||||
Name: "access_token",
|
||||
Label: "Cookie",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Description: "Unknown expiration time",
|
||||
},
|
||||
{
|
||||
Name: "internal_type",
|
||||
Label: "139yun type",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "Personal,Family",
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "site_id",
|
||||
Label: "cloud_id",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
}, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud139) 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 Cloud139) 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
|
||||
}
|
||||
if isFamily(account) {
|
||||
files, err = driver.familyGetFiles(file.Id, account)
|
||||
} else {
|
||||
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 Cloud139) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(args.Path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var u string
|
||||
//if isFamily(account) {
|
||||
// u, err = driver.familyLink(file.Id, account)
|
||||
//} else {
|
||||
u, err = driver.GetLink(file.Id, account)
|
||||
//}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &base.Link{Url: u}, nil
|
||||
}
|
||||
|
||||
func (driver Cloud139) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("139 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 Cloud139) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver Cloud139) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Cloud139) MakeDir(path string, account *model.Account) error {
|
||||
parentFile, err := driver.File(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"createCatalogExtReq": base.Json{
|
||||
"parentCatalogID": parentFile.Id,
|
||||
"newCatalogName": utils.Base(path),
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/catalog/v1.0/createCatalogExt"
|
||||
if isFamily(account) {
|
||||
data = base.Json{
|
||||
"cloudID": account.SiteId,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
"docLibName": utils.Base(path),
|
||||
}
|
||||
pathname = "/orchestration/familyCloud/cloudCatalog/v1.0/createCloudDoc"
|
||||
}
|
||||
_, err = driver.Post(pathname,
|
||||
data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud139) Move(src string, dst string, account *model.Account) error {
|
||||
if isFamily(account) {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var contentInfoList []string
|
||||
var catalogInfoList []string
|
||||
if srcFile.IsDir() {
|
||||
catalogInfoList = append(catalogInfoList, srcFile.Id)
|
||||
} else {
|
||||
contentInfoList = append(contentInfoList, srcFile.Id)
|
||||
}
|
||||
data := base.Json{
|
||||
"createBatchOprTaskReq": base.Json{
|
||||
"taskType": 3,
|
||||
"actionType": "304",
|
||||
"taskInfo": base.Json{
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
"newCatalogID": dstParentFile.Id,
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
_, err = driver.Post(pathname, data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud139) Rename(src string, dst string, account *model.Account) error {
|
||||
if isFamily(account) {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var data base.Json
|
||||
var pathname string
|
||||
if srcFile.IsDir() {
|
||||
data = base.Json{
|
||||
"catalogID": srcFile.Id,
|
||||
"catalogName": utils.Base(dst),
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
pathname = "/orchestration/personalCloud/catalog/v1.0/updateCatalogInfo"
|
||||
} else {
|
||||
data = base.Json{
|
||||
"contentID": srcFile.Id,
|
||||
"contentName": utils.Base(dst),
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
pathname = "/orchestration/personalCloud/catalog/v1.0/updateContentInfo"
|
||||
}
|
||||
_, err = driver.Post(pathname, data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud139) Copy(src string, dst string, account *model.Account) error {
|
||||
if isFamily(account) {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var contentInfoList []string
|
||||
var catalogInfoList []string
|
||||
if srcFile.IsDir() {
|
||||
catalogInfoList = append(catalogInfoList, srcFile.Id)
|
||||
} else {
|
||||
contentInfoList = append(contentInfoList, srcFile.Id)
|
||||
}
|
||||
data := base.Json{
|
||||
"createBatchOprTaskReq": base.Json{
|
||||
"taskType": 3,
|
||||
"actionType": 309,
|
||||
"taskInfo": base.Json{
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
"newCatalogID": dstParentFile.Id,
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
_, err = driver.Post(pathname, data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud139) Delete(path string, account *model.Account) error {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var contentInfoList []string
|
||||
var catalogInfoList []string
|
||||
if file.IsDir() {
|
||||
catalogInfoList = append(catalogInfoList, file.Id)
|
||||
} else {
|
||||
contentInfoList = append(contentInfoList, file.Id)
|
||||
}
|
||||
data := base.Json{
|
||||
"createBatchOprTaskReq": base.Json{
|
||||
"taskType": 2,
|
||||
"actionType": 201,
|
||||
"taskInfo": base.Json{
|
||||
"newCatalogID": "",
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
if isFamily(account) {
|
||||
data = base.Json{
|
||||
"catalogList": catalogInfoList,
|
||||
"contentList": contentInfoList,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
"sourceCatalogType": 1002,
|
||||
"taskType": 2,
|
||||
}
|
||||
pathname = "/orchestration/familyCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
}
|
||||
_, err = driver.Post(pathname, data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud139) 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
|
||||
}
|
||||
data := base.Json{
|
||||
"manualRename": 2,
|
||||
"operation": 0,
|
||||
"fileCount": 1,
|
||||
"totalSize": file.Size,
|
||||
"uploadContentList": []base.Json{{
|
||||
"contentName": file.Name,
|
||||
"contentSize": file.Size,
|
||||
// "digest": "5a3231986ce7a6b46e408612d385bafa"
|
||||
}},
|
||||
"parentCatalogID": parentFile.Id,
|
||||
"newCatalogName": "",
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/uploadAndDownload/v1.0/pcUploadFileRequest"
|
||||
if isFamily(account) {
|
||||
data = newJson(base.Json{
|
||||
"fileCount": 1,
|
||||
"manualRename": 2,
|
||||
"operation": 0,
|
||||
"path": "",
|
||||
"seqNo": "",
|
||||
"totalSize": file.Size,
|
||||
"uploadContentList": []base.Json{{
|
||||
"contentName": file.Name,
|
||||
"contentSize": file.Size,
|
||||
// "digest": "5a3231986ce7a6b46e408612d385bafa"
|
||||
}},
|
||||
}, account)
|
||||
pathname = "/orchestration/familyCloud/content/v1.0/getFileUploadURL"
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
var resp UploadResp
|
||||
_, err = driver.Post(pathname, data, &resp, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var Default uint64 = 10485760
|
||||
part := int(math.Ceil(float64(file.Size) / float64(Default)))
|
||||
var start uint64 = 0
|
||||
for i := 0; i < part; i++ {
|
||||
byteSize := file.Size - start
|
||||
if byteSize > Default {
|
||||
byteSize = Default
|
||||
}
|
||||
byteData := make([]byte, byteSize)
|
||||
_, err = io.ReadFull(file, byteData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("POST", resp.Data.UploadResult.RedirectionURL, bytes.NewBuffer(byteData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headers := map[string]string{
|
||||
"Accept": "*/*",
|
||||
"Content-Type": "text/plain;name=" + unicode(file.Name),
|
||||
"contentSize": strconv.FormatUint(file.Size, 10),
|
||||
"range": fmt.Sprintf("bytes=%d-%d", start, start+byteSize-1),
|
||||
"content-length": strconv.FormatUint(byteSize, 10),
|
||||
"uploadtaskID": resp.Data.UploadResult.UploadTaskID,
|
||||
"rangeType": "0",
|
||||
"Referer": "https://yun.139.com/",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.44",
|
||||
"x-SvcType": "1",
|
||||
}
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("%+v", res)
|
||||
start += byteSize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Cloud139)(nil)
|
||||
@@ -1,74 +0,0 @@
|
||||
package _39
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"path"
|
||||
)
|
||||
|
||||
func (driver Cloud139) familyGetFiles(catalogID string, account *model.Account) ([]model.File, error) {
|
||||
pageNum := 1
|
||||
files := make([]model.File, 0)
|
||||
for {
|
||||
data := newJson(base.Json{
|
||||
"catalogID": catalogID,
|
||||
"contentSortType": 0,
|
||||
"pageInfo": base.Json{
|
||||
"pageNum": pageNum,
|
||||
"pageSize": 100,
|
||||
},
|
||||
"sortDirection": 1,
|
||||
}, account)
|
||||
|
||||
var resp QueryContentListResp
|
||||
_, err := driver.Post("/orchestration/familyCloud/content/v1.0/queryContentList", data, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, catalog := range resp.Data.CloudCatalogList {
|
||||
f := model.File{
|
||||
Id: catalog.CatalogID,
|
||||
Name: catalog.CatalogName,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: getTime(catalog.LastUpdateTime),
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
for _, content := range resp.Data.CloudContentList {
|
||||
f := model.File{
|
||||
Id: content.ContentID,
|
||||
Name: content.ContentName,
|
||||
Size: content.ContentSize,
|
||||
Type: utils.GetFileType(path.Ext(content.ContentName)),
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: getTime(content.LastUpdateTime),
|
||||
Thumbnail: content.ThumbnailURL,
|
||||
//Thumbnail: content.BigthumbnailURL,
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
if 100*pageNum > resp.Data.TotalCount {
|
||||
break
|
||||
}
|
||||
pageNum++
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Cloud139) familyLink(contentId string, account *model.Account) (string, error) {
|
||||
data := newJson(base.Json{
|
||||
"contentID": contentId,
|
||||
//"path":"",
|
||||
}, account)
|
||||
res, err := driver.Post("/orchestration/familyCloud/content/v1.0/getFileDownLoadURL",
|
||||
data, nil, account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
package _39
|
||||
|
||||
type BaseResp struct {
|
||||
Success bool `json:"success"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type Catalog struct {
|
||||
CatalogID string `json:"catalogID"`
|
||||
CatalogName string `json:"catalogName"`
|
||||
//CatalogType int `json:"catalogType"`
|
||||
//CreateTime string `json:"createTime"`
|
||||
UpdateTime string `json:"updateTime"`
|
||||
//IsShared bool `json:"isShared"`
|
||||
//CatalogLevel int `json:"catalogLevel"`
|
||||
//ShareDoneeCount int `json:"shareDoneeCount"`
|
||||
//OpenType int `json:"openType"`
|
||||
//ParentCatalogID string `json:"parentCatalogId"`
|
||||
//DirEtag int `json:"dirEtag"`
|
||||
//Tombstoned int `json:"tombstoned"`
|
||||
//ProxyID interface{} `json:"proxyID"`
|
||||
//Moved int `json:"moved"`
|
||||
//IsFixedDir int `json:"isFixedDir"`
|
||||
//IsSynced interface{} `json:"isSynced"`
|
||||
//Owner string `json:"owner"`
|
||||
//Modifier interface{} `json:"modifier"`
|
||||
//Path string `json:"path"`
|
||||
//ShareType int `json:"shareType"`
|
||||
//SoftLink interface{} `json:"softLink"`
|
||||
//ExtProp1 interface{} `json:"extProp1"`
|
||||
//ExtProp2 interface{} `json:"extProp2"`
|
||||
//ExtProp3 interface{} `json:"extProp3"`
|
||||
//ExtProp4 interface{} `json:"extProp4"`
|
||||
//ExtProp5 interface{} `json:"extProp5"`
|
||||
//ETagOprType int `json:"ETagOprType"`
|
||||
}
|
||||
|
||||
type Content struct {
|
||||
ContentID string `json:"contentID"`
|
||||
ContentName string `json:"contentName"`
|
||||
//ContentSuffix string `json:"contentSuffix"`
|
||||
ContentSize int64 `json:"contentSize"`
|
||||
//ContentDesc string `json:"contentDesc"`
|
||||
//ContentType int `json:"contentType"`
|
||||
//ContentOrigin int `json:"contentOrigin"`
|
||||
UpdateTime string `json:"updateTime"`
|
||||
//CommentCount int `json:"commentCount"`
|
||||
ThumbnailURL string `json:"thumbnailURL"`
|
||||
//BigthumbnailURL string `json:"bigthumbnailURL"`
|
||||
//PresentURL string `json:"presentURL"`
|
||||
//PresentLURL string `json:"presentLURL"`
|
||||
//PresentHURL string `json:"presentHURL"`
|
||||
//ContentTAGList interface{} `json:"contentTAGList"`
|
||||
//ShareDoneeCount int `json:"shareDoneeCount"`
|
||||
//Safestate int `json:"safestate"`
|
||||
//Transferstate int `json:"transferstate"`
|
||||
//IsFocusContent int `json:"isFocusContent"`
|
||||
//UpdateShareTime interface{} `json:"updateShareTime"`
|
||||
//UploadTime string `json:"uploadTime"`
|
||||
//OpenType int `json:"openType"`
|
||||
//AuditResult int `json:"auditResult"`
|
||||
//ParentCatalogID string `json:"parentCatalogId"`
|
||||
//Channel string `json:"channel"`
|
||||
//GeoLocFlag string `json:"geoLocFlag"`
|
||||
//Digest string `json:"digest"`
|
||||
//Version string `json:"version"`
|
||||
//FileEtag string `json:"fileEtag"`
|
||||
//FileVersion string `json:"fileVersion"`
|
||||
//Tombstoned int `json:"tombstoned"`
|
||||
//ProxyID string `json:"proxyID"`
|
||||
//Moved int `json:"moved"`
|
||||
//MidthumbnailURL string `json:"midthumbnailURL"`
|
||||
//Owner string `json:"owner"`
|
||||
//Modifier string `json:"modifier"`
|
||||
//ShareType int `json:"shareType"`
|
||||
//ExtInfo struct {
|
||||
// Uploader string `json:"uploader"`
|
||||
// Address string `json:"address"`
|
||||
//} `json:"extInfo"`
|
||||
//Exif struct {
|
||||
// CreateTime string `json:"createTime"`
|
||||
// Longitude interface{} `json:"longitude"`
|
||||
// Latitude interface{} `json:"latitude"`
|
||||
// LocalSaveTime interface{} `json:"localSaveTime"`
|
||||
//} `json:"exif"`
|
||||
//CollectionFlag interface{} `json:"collectionFlag"`
|
||||
//TreeInfo interface{} `json:"treeInfo"`
|
||||
//IsShared bool `json:"isShared"`
|
||||
//ETagOprType int `json:"ETagOprType"`
|
||||
}
|
||||
|
||||
type GetDiskResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
Result struct {
|
||||
ResultCode string `json:"resultCode"`
|
||||
ResultDesc interface{} `json:"resultDesc"`
|
||||
} `json:"result"`
|
||||
GetDiskResult struct {
|
||||
ParentCatalogID string `json:"parentCatalogID"`
|
||||
NodeCount int `json:"nodeCount"`
|
||||
CatalogList []Catalog `json:"catalogList"`
|
||||
ContentList []Content `json:"contentList"`
|
||||
IsCompleted int `json:"isCompleted"`
|
||||
} `json:"getDiskResult"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type UploadResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
Result struct {
|
||||
ResultCode string `json:"resultCode"`
|
||||
ResultDesc interface{} `json:"resultDesc"`
|
||||
} `json:"result"`
|
||||
UploadResult struct {
|
||||
UploadTaskID string `json:"uploadTaskID"`
|
||||
RedirectionURL string `json:"redirectionUrl"`
|
||||
NewContentIDList []struct {
|
||||
ContentID string `json:"contentID"`
|
||||
ContentName string `json:"contentName"`
|
||||
IsNeedUpload string `json:"isNeedUpload"`
|
||||
FileEtag int64 `json:"fileEtag"`
|
||||
FileVersion int64 `json:"fileVersion"`
|
||||
OverridenFlag int `json:"overridenFlag"`
|
||||
} `json:"newContentIDList"`
|
||||
CatalogIDList interface{} `json:"catalogIDList"`
|
||||
IsSlice interface{} `json:"isSlice"`
|
||||
} `json:"uploadResult"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type CloudContent struct {
|
||||
ContentID string `json:"contentID"`
|
||||
//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 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"`
|
||||
}
|
||||
|
||||
type CloudCatalog struct {
|
||||
CatalogID string `json:"catalogID"`
|
||||
CatalogName string `json:"catalogName"`
|
||||
//CloudID string `json:"cloudID"`
|
||||
//CreateTime string `json:"createTime"`
|
||||
LastUpdateTime string `json:"lastUpdateTime"`
|
||||
//Creator string `json:"creator"`
|
||||
//CreatorNickname string `json:"creatorNickname"`
|
||||
}
|
||||
|
||||
type QueryContentListResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
Result struct {
|
||||
ResultCode string `json:"resultCode"`
|
||||
ResultDesc string `json:"resultDesc"`
|
||||
} `json:"result"`
|
||||
Path string `json:"path"`
|
||||
CloudContentList []CloudContent `json:"cloudContentList"`
|
||||
CloudCatalogList []CloudCatalog `json:"cloudCatalogList"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
RecallContent interface{} `json:"recallContent"`
|
||||
} `json:"data"`
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package _39
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func encodeURIComponent(str string) string {
|
||||
r := url.QueryEscape(str)
|
||||
r = strings.Replace(r, "+", "%20", -1)
|
||||
return r
|
||||
}
|
||||
|
||||
func calSign(body, ts, randStr string) string {
|
||||
body = strings.ReplaceAll(body, "\n", "")
|
||||
body = strings.ReplaceAll(body, " ", "")
|
||||
body = encodeURIComponent(body)
|
||||
strs := strings.Split(body, "")
|
||||
sort.Strings(strs)
|
||||
body = strings.Join(strs, "")
|
||||
body = base64.StdEncoding.EncodeToString([]byte(body))
|
||||
res := utils.GetMD5Encode(body) + utils.GetMD5Encode(ts+":"+randStr)
|
||||
res = strings.ToUpper(utils.GetMD5Encode(res))
|
||||
return res
|
||||
}
|
||||
|
||||
func getTime(t string) *time.Time {
|
||||
stamp, _ := time.ParseInLocation("20060102150405", t, time.Local)
|
||||
return &stamp
|
||||
}
|
||||
|
||||
func isFamily(account *model.Account) bool {
|
||||
return account.InternalType == "Family"
|
||||
}
|
||||
|
||||
func unicode(str string) string {
|
||||
textQuoted := strconv.QuoteToASCII(str)
|
||||
textUnquoted := textQuoted[1 : len(textQuoted)-1]
|
||||
return textUnquoted
|
||||
}
|
||||
|
||||
func MergeMap(mObj ...map[string]interface{}) map[string]interface{} {
|
||||
newObj := map[string]interface{}{}
|
||||
for _, m := range mObj {
|
||||
for k, v := range m {
|
||||
newObj[k] = v
|
||||
}
|
||||
}
|
||||
return newObj
|
||||
}
|
||||
|
||||
func newJson(data map[string]interface{}, account *model.Account) map[string]interface{} {
|
||||
common := map[string]interface{}{
|
||||
"catalogType": 3,
|
||||
"cloudID": account.SiteId,
|
||||
"cloudType": 1,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": account.Username,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
return MergeMap(data, common)
|
||||
}
|
||||
@@ -1,621 +0,0 @@
|
||||
package _89
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
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{
|
||||
Id: strconv.FormatInt(file.Id, 10),
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: nil,
|
||||
Thumbnail: file.Icon.SmallUrl,
|
||||
Url: file.Url,
|
||||
}
|
||||
loc, _ := time.LoadLocation("Local")
|
||||
lastOpTime, err := time.ParseInLocation("2006-01-02 15:04:05", file.LastOpTime, loc)
|
||||
if err == nil {
|
||||
f.UpdatedAt = &lastOpTime
|
||||
}
|
||||
if file.Size == -1 {
|
||||
f.Type = conf.FOLDER
|
||||
f.Size = 0
|
||||
} else {
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
//func (c Cloud189) GetFile(path string, account *model.Account) (*Cloud189File, error) {
|
||||
// dir, name := filepath.Split(path)
|
||||
// dir = utils.ParsePath(dir)
|
||||
// _, _, err := c.ParentPath(dir, account)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
|
||||
// parentFiles, _ := parentFiles_.([]Cloud189File)
|
||||
// for _, file := range parentFiles {
|
||||
// if file.Name == name {
|
||||
// if file.Size != -1 {
|
||||
// return &file, err
|
||||
// } else {
|
||||
// return nil, ErrNotFile
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return nil, ErrPathNotFound
|
||||
//}
|
||||
|
||||
type LoginResp struct {
|
||||
Msg string `json:"msg"`
|
||||
Result int `json:"result"`
|
||||
ToUrl string `json:"toUrl"`
|
||||
}
|
||||
|
||||
// Login refer to PanIndex
|
||||
func (driver Cloud189) Login(account *model.Account) error {
|
||||
client, ok := client189Map[account.Name]
|
||||
if !ok {
|
||||
//cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
||||
client = resty.New()
|
||||
//client.SetCookieJar(cookieJar)
|
||||
client.SetTimeout(base.DefaultTimeout)
|
||||
client.SetRetryCount(3)
|
||||
client.SetHeader("Referer", "https://cloud.189.cn/")
|
||||
}
|
||||
url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
|
||||
b := ""
|
||||
lt := ""
|
||||
ltText := regexp.MustCompile(`lt = "(.+?)"`)
|
||||
for i := 0; i < 3; i++ {
|
||||
res, err := client.R().Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 已经登陆
|
||||
if res.RawResponse.Request.URL.String() == "https://cloud.189.cn/web/main" {
|
||||
return nil
|
||||
}
|
||||
b = res.String()
|
||||
ltTextArr := ltText.FindStringSubmatch(b)
|
||||
if len(ltTextArr) > 0 {
|
||||
lt = ltTextArr[1]
|
||||
break
|
||||
} else {
|
||||
<-time.After(time.Second)
|
||||
}
|
||||
}
|
||||
if lt == "" {
|
||||
return fmt.Errorf("get empty login page")
|
||||
}
|
||||
captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
|
||||
returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
|
||||
paramId := regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(b)[1]
|
||||
//reqId := regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(b)[1]
|
||||
jRsakey := regexp.MustCompile(`j_rsaKey" value="(\S+)"`).FindStringSubmatch(b)[1]
|
||||
vCodeID := regexp.MustCompile(`picCaptcha\.do\?token\=([A-Za-z0-9\&\=]+)`).FindStringSubmatch(b)[1]
|
||||
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
|
||||
}
|
||||
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().
|
||||
SetHeaders(map[string]string{
|
||||
"lt": lt,
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
||||
"Referer": "https://open.e.189.cn/",
|
||||
"accept": "application/json;charset=UTF-8",
|
||||
}).SetFormData(map[string]string{
|
||||
"appKey": "cloud",
|
||||
"accountType": "01",
|
||||
"userName": "{RSA}" + userRsa,
|
||||
"password": "{RSA}" + passwordRsa,
|
||||
"validateCode": vCodeRS,
|
||||
"captchaToken": captchaToken,
|
||||
"returnUrl": returnUrl,
|
||||
"mailSuffix": "@pan.cn",
|
||||
"paramId": paramId,
|
||||
"clientType": "10010",
|
||||
"dynamicCheck": "FALSE",
|
||||
"cb_SaveName": "1",
|
||||
"isOauth2": "false",
|
||||
}).Post(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = utils.Json.Unmarshal(res.Body(), &loginResp)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
if loginResp.Result != 0 {
|
||||
return fmt.Errorf(loginResp.Msg)
|
||||
}
|
||||
_, err = client.R().Get(loginResp.ToUrl)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
return err
|
||||
}
|
||||
client189Map[account.Name] = client
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) isFamily(account *model.Account) bool {
|
||||
return account.InternalType == "Family"
|
||||
}
|
||||
|
||||
func (driver Cloud189) GetFiles(fileId string, account *model.Account) ([]Cloud189File, error) {
|
||||
res := make([]Cloud189File, 0)
|
||||
pageNum := 1
|
||||
|
||||
for {
|
||||
var resp Cloud189Files
|
||||
body, err := driver.Request("https://cloud.189.cn/api/open/file/listFiles.action", base.Get, map[string]string{
|
||||
//"noCache": random(),
|
||||
"pageSize": "60",
|
||||
"pageNum": strconv.Itoa(pageNum),
|
||||
"mediaType": "0",
|
||||
"folderId": fileId,
|
||||
"iconOption": "5",
|
||||
"orderBy": "lastOpTime", //account.OrderBy
|
||||
"descending": "true", //account.OrderDirection
|
||||
}, nil, nil, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = utils.Json.Unmarshal(body, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.ResCode != 0 {
|
||||
return nil, fmt.Errorf(resp.ResMessage)
|
||||
}
|
||||
if resp.FileListAO.Count == 0 {
|
||||
break
|
||||
}
|
||||
for _, folder := range resp.FileListAO.FolderList {
|
||||
res = append(res, Cloud189File{
|
||||
Id: folder.Id,
|
||||
LastOpTime: folder.LastOpTime,
|
||||
Name: folder.Name,
|
||||
Size: -1,
|
||||
})
|
||||
}
|
||||
res = append(res, resp.FileListAO.FileList...)
|
||||
pageNum++
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) Request(url string, method int, query, form map[string]string, headers map[string]string, account *model.Account) ([]byte, error) {
|
||||
client, err := driver.getClient(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//var resp base.Json
|
||||
if driver.isFamily(account) {
|
||||
url = strings.Replace(url, "/api/open", "/api/open/family", 1)
|
||||
if query != nil {
|
||||
query["familyId"] = account.SiteId
|
||||
}
|
||||
if form != nil {
|
||||
form["familyId"] = account.SiteId
|
||||
}
|
||||
}
|
||||
var e Cloud189Error
|
||||
req := client.R().SetError(&e).
|
||||
SetHeader("Accept", "application/json;charset=UTF-8").
|
||||
SetQueryParams(map[string]string{
|
||||
"noCache": random(),
|
||||
})
|
||||
if query != nil {
|
||||
req = req.SetQueryParams(query)
|
||||
}
|
||||
if form != nil {
|
||||
req = req.SetFormData(form)
|
||||
}
|
||||
if headers != nil {
|
||||
req = req.SetHeaders(headers)
|
||||
}
|
||||
var res *resty.Response
|
||||
switch method {
|
||||
case base.Get:
|
||||
res, err = req.Get(url)
|
||||
case base.Post:
|
||||
res, err = req.Post(url)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//log.Debug(res.String())
|
||||
if e.ErrorCode != "" {
|
||||
if e.ErrorCode == "InvalidSessionKey" {
|
||||
err = driver.Login(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver.Request(url, method, query, form, nil, account)
|
||||
}
|
||||
}
|
||||
if jsoniter.Get(res.Body(), "res_code").ToInt() != 0 {
|
||||
err = errors.New(jsoniter.Get(res.Body(), "res_message").ToString())
|
||||
}
|
||||
return res.Body(), err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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) 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
|
||||
}
|
||||
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", 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(uri + "---" + jsoniter.Get(data, "msg").ToString())
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
parentFile, err := driver.File(file.ParentPath, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !parentFile.IsDir() {
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
res, err := driver.UploadRequest("/person/initMultiUpload", map[string]string{
|
||||
"parentFolderId": parentFile.Id,
|
||||
"fileName": encode(file.Name),
|
||||
"fileSize": strconv.FormatInt(int64(file.Size), 10),
|
||||
"sliceSize": strconv.FormatInt(int64(DEFAULT), 10),
|
||||
"lazyCheck": "1",
|
||||
}, account, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
md5Sum := md5.New()
|
||||
for i = 1; 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)
|
||||
//log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
finish += uint64(n)
|
||||
md5Bytes := getMd5(byteData)
|
||||
md5Hex := hex.EncodeToString(md5Bytes)
|
||||
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
|
||||
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, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
fileMd5 := hex.EncodeToString(md5Sum.Sum(nil))
|
||||
sliceMd5 := fileMd5
|
||||
if file.GetSize() > DEFAULT {
|
||||
sliceMd5 = utils.GetMD5Encode(strings.Join(md5s, "\n"))
|
||||
}
|
||||
res, err = driver.UploadRequest("/person/commitMultiUploadFile", map[string]string{
|
||||
"uploadFileId": uploadFileId,
|
||||
"fileMd5": fileMd5,
|
||||
"sliceMd5": sliceMd5,
|
||||
"lazyCheck": "1",
|
||||
}, account, nil)
|
||||
account.DriveId, _ = driver.GetSessionKey(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) OldUpload(file *model.FileStream, account *model.Account) error {
|
||||
//return base.ErrNotImplement
|
||||
client, err := driver.getClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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())
|
||||
}
|
||||
@@ -1,375 +0,0 @@
|
||||
package _89
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Cloud189 struct{}
|
||||
|
||||
func (driver Cloud189) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "189Cloud",
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
//},
|
||||
//{
|
||||
// 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 {
|
||||
delete(client189Map, old.Name)
|
||||
}
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if err := driver.Login(account); err != nil {
|
||||
account.Status = err.Error()
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
sessionKey, err := driver.GetSessionKey(account)
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
} else {
|
||||
account.Status = "work"
|
||||
account.DriveId = sessionKey
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
var rawFiles []Cloud189File
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
rawFiles, _ = cache.([]Cloud189File)
|
||||
} else {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rawFiles) > 0 {
|
||||
_ = base.SetCache(path, rawFiles, account)
|
||||
}
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
for _, file := range rawFiles {
|
||||
files = append(files, *driver.FormatFile(&file))
|
||||
}
|
||||
return 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
|
||||
}
|
||||
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
|
||||
}
|
||||
if resp.ResCode != 0 {
|
||||
return nil, fmt.Errorf(resp.ResMessage)
|
||||
}
|
||||
client, err := driver.getClient(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client = resty.NewWithClient(client.GetClient()).SetRedirectPolicy(
|
||||
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}))
|
||||
res, err := client.R().Get("https:" + resp.FileDownloadUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugln(res.Status())
|
||||
link := base.Link{
|
||||
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
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("189 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) 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
|
||||
}
|
||||
|
||||
func (driver Cloud189) MakeDir(path string, account *model.Account) error {
|
||||
dir, name := filepath.Split(path)
|
||||
parent, err := driver.File(dir, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !parent.IsDir() {
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
form := map[string]string{
|
||||
"parentFolderId": parent.Id,
|
||||
"folderName": name,
|
||||
}
|
||||
_, err = driver.Request("https://cloud.189.cn/api/open/file/createFolder.action", base.Post, nil, form, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
|
||||
dstDir, dstName := 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
|
||||
}
|
||||
isFolder := 0
|
||||
if srcFile.IsDir() {
|
||||
isFolder = 1
|
||||
}
|
||||
taskInfos := []base.Json{
|
||||
{
|
||||
"fileId": srcFile.Id,
|
||||
"fileName": dstName,
|
||||
"isFolder": isFolder,
|
||||
},
|
||||
}
|
||||
taskInfosBytes, err := utils.Json.Marshal(taskInfos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
form := map[string]string{
|
||||
"type": "MOVE",
|
||||
"targetFolderId": dstDirFile.Id,
|
||||
"taskInfos": string(taskInfosBytes),
|
||||
}
|
||||
_, err = driver.Request("https://cloud.189.cn/api/open/batch/createBatchTask.action", base.Post, nil, form, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) Rename(src string, dst string, account *model.Account) error {
|
||||
_, dstName := filepath.Split(dst)
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url := "https://cloud.189.cn/api/open/file/renameFile.action"
|
||||
idKey := "fileId"
|
||||
nameKey := "destFileName"
|
||||
if srcFile.IsDir() {
|
||||
url = "https://cloud.189.cn/api/open/file/renameFolder.action"
|
||||
idKey = "folderId"
|
||||
nameKey = "destFolderName"
|
||||
}
|
||||
form := map[string]string{
|
||||
idKey: srcFile.Id,
|
||||
nameKey: dstName,
|
||||
}
|
||||
_, err = driver.Request(url, base.Post, nil, form, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) Copy(src string, dst string, account *model.Account) error {
|
||||
dstDir, dstName := 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
|
||||
}
|
||||
isFolder := 0
|
||||
if srcFile.IsDir() {
|
||||
isFolder = 1
|
||||
}
|
||||
taskInfos := []base.Json{
|
||||
{
|
||||
"fileId": srcFile.Id,
|
||||
"fileName": dstName,
|
||||
"isFolder": isFolder,
|
||||
},
|
||||
}
|
||||
taskInfosBytes, err := utils.Json.Marshal(taskInfos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
form := map[string]string{
|
||||
"type": "COPY",
|
||||
"targetFolderId": dstDirFile.Id,
|
||||
"taskInfos": string(taskInfosBytes),
|
||||
}
|
||||
_, err = driver.Request("https://cloud.189.cn/api/open/batch/createBatchTask.action", base.Post, nil, form, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) Delete(path string, account *model.Account) error {
|
||||
path = utils.ParsePath(path)
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isFolder := 0
|
||||
if file.IsDir() {
|
||||
isFolder = 1
|
||||
}
|
||||
taskInfos := []base.Json{
|
||||
{
|
||||
"fileId": file.Id,
|
||||
"fileName": file.Name,
|
||||
"isFolder": isFolder,
|
||||
},
|
||||
}
|
||||
taskInfosBytes, err := utils.Json.Marshal(taskInfos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
form := map[string]string{
|
||||
"type": "DELETE",
|
||||
"targetFolderId": "",
|
||||
"taskInfos": string(taskInfosBytes),
|
||||
}
|
||||
_, err = driver.Request("https://cloud.189.cn/api/open/batch/createBatchTask.action", base.Post, nil, form, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
|
||||
//return base.ErrNotImplement
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
return driver.NewUpload(file, account)
|
||||
//return driver.OldUpload(file, account)
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Cloud189)(nil)
|
||||
@@ -1,67 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
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)
|
||||
//}
|
||||
@@ -1,331 +0,0 @@
|
||||
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"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
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 := jsoniter.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 jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
|
||||
return nil, errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
|
||||
}
|
||||
param.vCodeRS = jsoniter.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 jsoniter.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 jsoniter.Get(res.Body(), "res_code").ToInt64() != 0 {
|
||||
return res, fmt.Errorf(jsoniter.Get(res.Body(), "res_message").ToString())
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -1,804 +0,0 @@
|
||||
package _189
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"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,
|
||||
},
|
||||
{
|
||||
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() {
|
||||
return state.Login(account)
|
||||
}
|
||||
account.Status = "work"
|
||||
model.SaveAccount(account)
|
||||
return 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(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(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(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 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 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": "1", //5 覆盖
|
||||
"ResumePolicy": "1",
|
||||
"isLog": "0",
|
||||
})
|
||||
}, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Cloud189) uploadFileData(file *model.FileStream, tempFile *os.File, createUpload CreateUploadFileResult, account *model.Account) error {
|
||||
var uploadFileState *UploadFileStatusResult
|
||||
var err error
|
||||
for i := 0; i < 10; i++ {
|
||||
if uploadFileState, err = driver.getUploadFileState(createUpload.UploadFileId, account); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if uploadFileState.FileDataExists == 1 || uploadFileState.DataSize == int64(file.Size) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err = tempFile.Seek(uploadFileState.DataSize, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = GetState(account).Request("PUT", uploadFileState.FileUploadUrl, nil, func(r *resty.Request) {
|
||||
r.SetQueryParams(clientSuffix())
|
||||
r.SetHeaders(map[string]string{
|
||||
"ResumePolicy": "1",
|
||||
"Edrive-UploadFileId": fmt.Sprint(createUpload.UploadFileId),
|
||||
"Edrive-UploadFileRange": fmt.Sprintf("bytes=%d-%d", uploadFileState.DataSize, file.Size),
|
||||
"Expect": "100-continue",
|
||||
})
|
||||
if isFamily(account) {
|
||||
r.SetHeader("FamilyId", account.SiteId)
|
||||
}
|
||||
r.SetBody(tempFile)
|
||||
}, account)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return 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)
|
||||
@@ -1,167 +0,0 @@
|
||||
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 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"`
|
||||
}
|
||||
*/
|
||||
@@ -1,145 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
package alidrive
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
var aliClient = resty.New()
|
||||
|
||||
type AliRespError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type AliFiles struct {
|
||||
Items []AliFile `json:"items"`
|
||||
NextMarker string `json:"next_marker"`
|
||||
}
|
||||
|
||||
type AliFile struct {
|
||||
DriveId string `json:"drive_id"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
FileExtension string `json:"file_extension"`
|
||||
FileId string `json:"file_id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
ParentFileId string `json:"parent_file_id"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
Size int64 `json:"size"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
func (driver AliDrive) FormatFile(file *AliFile) *model.File {
|
||||
f := &model.File{
|
||||
Id: file.FileId,
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
UpdatedAt: file.UpdatedAt,
|
||||
Thumbnail: file.Thumbnail,
|
||||
Driver: driver.Config().Name,
|
||||
Url: file.Url,
|
||||
}
|
||||
if file.Type == "folder" {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(file.FileExtension)
|
||||
}
|
||||
if file.Category == "video" {
|
||||
f.Type = conf.VIDEO
|
||||
}
|
||||
if file.Category == "image" {
|
||||
f.Type = conf.IMAGE
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (driver AliDrive) GetFiles(fileId string, account *model.Account) ([]AliFile, error) {
|
||||
marker := "first"
|
||||
res := make([]AliFile, 0)
|
||||
for marker != "" {
|
||||
if marker == "first" {
|
||||
marker = ""
|
||||
}
|
||||
var resp AliFiles
|
||||
var e AliRespError
|
||||
_, err := aliClient.R().
|
||||
SetResult(&resp).
|
||||
SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"fields": "*",
|
||||
"image_thumbnail_process": "image/resize,w_400/format,jpeg",
|
||||
"image_url_process": "image/resize,w_1920/format,jpeg",
|
||||
"limit": account.Limit,
|
||||
"marker": marker,
|
||||
"order_by": account.OrderBy,
|
||||
"order_direction": account.OrderDirection,
|
||||
"parent_file_id": fileId,
|
||||
"video_thumbnail_process": "video/snapshot,t_0,f_jpg,ar_auto,w_300",
|
||||
"url_expire_sec": 14400,
|
||||
}).Post("https://api.aliyundrive.com/v2/file/list")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.GetFiles(fileId, account)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
marker = resp.NextMarker
|
||||
res = append(res, resp.Items...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (driver AliDrive) GetFile(path string, account *model.Account) (*AliFile, error) {
|
||||
dir, name := filepath.Split(path)
|
||||
dir = utils.ParsePath(dir)
|
||||
_, err := driver.Files(dir, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentFiles_, _ := base.GetCache(dir, account)
|
||||
parentFiles, _ := parentFiles_.([]AliFile)
|
||||
for _, file := range parentFiles {
|
||||
if file.Name == name {
|
||||
if file.Type == "file" {
|
||||
return &file, err
|
||||
} else {
|
||||
return nil, fmt.Errorf("not file")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver AliDrive) RefreshToken(account *model.Account) error {
|
||||
url := "https://auth.aliyundrive.com/v2/account/token"
|
||||
var resp base.TokenResp
|
||||
var e AliRespError
|
||||
_, err := aliClient.R().
|
||||
//ForceContentType("application/json").
|
||||
SetBody(base.Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}).
|
||||
SetResult(&resp).
|
||||
SetError(&e).
|
||||
Post(url)
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
return err
|
||||
}
|
||||
log.Debugf("%+v,%+v", resp, e)
|
||||
if e.Code != "" {
|
||||
account.Status = e.Message
|
||||
return fmt.Errorf("failed to refresh token: %s", e.Message)
|
||||
} else {
|
||||
account.Status = "work"
|
||||
}
|
||||
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver AliDrive) rename(fileId, name string, account *model.Account) error {
|
||||
var resp base.Json
|
||||
var e AliRespError
|
||||
_, err := aliClient.R().SetResult(&resp).SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"check_name_mode": "refuse",
|
||||
"drive_id": account.DriveId,
|
||||
"file_id": fileId,
|
||||
"name": name,
|
||||
}).Post("https://api.aliyundrive.com/v3/file/update")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.rename(fileId, name, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if resp["name"] == name {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%+v", resp)
|
||||
}
|
||||
|
||||
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).
|
||||
SetBody(base.Json{
|
||||
"requests": []base.Json{
|
||||
{
|
||||
"headers": base.Json{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
"method": "POST",
|
||||
"id": srcId,
|
||||
"body": base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"file_id": srcId,
|
||||
"to_drive_id": account.DriveId,
|
||||
"to_parent_file_id": dstId,
|
||||
},
|
||||
"url": url,
|
||||
},
|
||||
},
|
||||
"resource": "file",
|
||||
}).Post("https://api.aliyundrive.com/v3/batch")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.batch(srcId, dstId, url, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
status := jsoniter.Get(res.Body(), "status").ToInt()
|
||||
if status < 400 && status >= 100 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(res.String())
|
||||
}
|
||||
|
||||
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").
|
||||
SetHeader("origin", "https://www.aliyundrive.com")
|
||||
}
|
||||
@@ -1,544 +0,0 @@
|
||||
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/robfig/cron/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type AliDrive struct{}
|
||||
|
||||
func (driver AliDrive) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "AliDrive",
|
||||
}
|
||||
}
|
||||
|
||||
func (driver AliDrive) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "refresh_token",
|
||||
Label: "refresh token",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,size,updated_at,created_at",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: base.TypeSelect,
|
||||
Values: "ASC,DESC",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "limit",
|
||||
Label: "limit",
|
||||
Type: base.TypeNumber,
|
||||
Required: false,
|
||||
Description: ">0 and <=200",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
if account.Limit == 0 {
|
||||
account.Limit = 200
|
||||
}
|
||||
err := driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var resp base.Json
|
||||
_, _ = aliClient.R().SetResult(&resp).
|
||||
SetBody("{}").
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
Post("https://api.aliyundrive.com/v2/user/get")
|
||||
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)
|
||||
log.Debugf("ali 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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver AliDrive) 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 AliDrive) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var rawFiles []AliFile
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
rawFiles, _ = cache.([]AliFile)
|
||||
} else {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rawFiles) > 0 {
|
||||
_ = base.SetCache(path, rawFiles, account)
|
||||
}
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
for _, file := range rawFiles {
|
||||
files = append(files, *driver.FormatFile(&file))
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver AliDrive) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(args.Path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp base.Json
|
||||
var e AliRespError
|
||||
_, err = aliClient.R().SetResult(&resp).
|
||||
SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"file_id": file.Id,
|
||||
"expire_sec": 14400,
|
||||
}).Post("https://api.aliyundrive.com/v2/file/get_download_url")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.Link(args, account)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (driver AliDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("ali 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 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// office
|
||||
var resp base.Json
|
||||
var e AliRespError
|
||||
var url string
|
||||
req := base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"file_id": file.FileId,
|
||||
}
|
||||
switch file.Category {
|
||||
case "doc":
|
||||
{
|
||||
url = "https://api.aliyundrive.com/v2/file/get_office_preview_url"
|
||||
req["access_token"] = account.AccessToken
|
||||
}
|
||||
case "video":
|
||||
{
|
||||
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
|
||||
req["category"] = "live_transcoding"
|
||||
}
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(req).Post(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Code != "" {
|
||||
return nil, fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (driver AliDrive) 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
|
||||
}
|
||||
var resp base.Json
|
||||
var e AliRespError
|
||||
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"check_name_mode": "refuse",
|
||||
"drive_id": account.DriveId,
|
||||
"name": name,
|
||||
"parent_file_id": parentFile.Id,
|
||||
"type": "folder",
|
||||
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.MakeDir(path, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if resp["file_name"] == name {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%+v", resp)
|
||||
}
|
||||
|
||||
func (driver AliDrive) Move(src string, dst string, account *model.Account) error {
|
||||
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/move", account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver AliDrive) 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 = driver.rename(srcFile.Id, dstName, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver AliDrive) Copy(src string, dst string, account *model.Account) error {
|
||||
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 {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var e AliRespError
|
||||
res, err := aliClient.R().SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"file_id": file.Id,
|
||||
}).Post("https://api.aliyundrive.com/v2/recyclebin/trash")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.Delete(path, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if res.StatusCode() == 204 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(res.String())
|
||||
}
|
||||
|
||||
type UploadResp struct {
|
||||
FileId string `json:"file_id"`
|
||||
UploadId string `json:"upload_id"`
|
||||
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 int64 = 10485760
|
||||
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||
parentFile, err := driver.File(file.ParentPath, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !parentFile.IsDir() {
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
|
||||
partInfoList := make([]base.Json, 0, count)
|
||||
var i int64
|
||||
for i = 0; i < count; i++ {
|
||||
partInfoList = append(partInfoList, base.Json{
|
||||
"part_number": i + 1,
|
||||
})
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := file.Read(buf[:])
|
||||
reqBody := base.Json{
|
||||
"check_name_mode": "auto_rename",
|
||||
"drive_id": account.DriveId,
|
||||
"name": file.GetFileName(),
|
||||
"parent_file_id": parentFile.Id,
|
||||
"part_info_list": partInfoList,
|
||||
"size": file.GetSize(),
|
||||
"type": "file",
|
||||
"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 {
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.Upload(file, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
|
||||
if e.Code == "PreHashMatched" {
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
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
|
||||
}
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("%+v", res)
|
||||
//res, err := base.BaseClient.R().
|
||||
// SetHeader("Content-Type","").
|
||||
// SetBody(byteData).Put(resp.PartInfoList[i].UploadUrl)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//log.Debugf("put to %s : %d,%s", resp.PartInfoList[i].UploadUrl, res.StatusCode(),res.String())
|
||||
}
|
||||
var resp2 base.Json
|
||||
_, err = aliClient.R().SetResult(&resp2).SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"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)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// } else {
|
||||
// _ = model.SaveAccount(account)
|
||||
// return driver.Upload(file, account)
|
||||
// }
|
||||
//}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if resp2["file_id"] == resp.FileId {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%+v", resp2)
|
||||
}
|
||||
|
||||
var _ base.Driver = (*AliDrive)(nil)
|
||||
@@ -1,44 +0,0 @@
|
||||
package alist
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
)
|
||||
|
||||
type BaseResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type PathResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
Type string `json:"type"`
|
||||
//Meta Meta `json:"meta"`
|
||||
Files []model.File `json:"files"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type PreviewResp struct {
|
||||
BaseResp
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func (driver *Alist) Login(account *model.Account) error {
|
||||
var resp BaseResp
|
||||
_, err := base.RestyClient.R().SetResult(&resp).
|
||||
SetHeader("Authorization", account.AccessToken).
|
||||
Get(account.SiteUrl + "/api/admin/login")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Code != 200 {
|
||||
return errors.New(resp.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Alist{})
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
package alist
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Alist struct{}
|
||||
|
||||
func (driver Alist) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Alist",
|
||||
NoNeedSetLink: true,
|
||||
NoCors: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Alist) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "site_url",
|
||||
Label: "alist site url",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "access_token",
|
||||
Label: "token",
|
||||
Type: base.TypeString,
|
||||
Description: "admin token",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder path",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 = "/"
|
||||
}
|
||||
err := driver.Login(account)
|
||||
if err == nil {
|
||||
account.Status = "work"
|
||||
} else {
|
||||
account.Status = err.Error()
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Alist) File(path string, account *model.Account) (*model.File, error) {
|
||||
now := time.Now()
|
||||
if path == "/" {
|
||||
return &model.File{
|
||||
Id: "root",
|
||||
Name: "root",
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: &now,
|
||||
}, nil
|
||||
}
|
||||
_, files, err := driver.Path(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if files == nil {
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
name := utils.Base(path)
|
||||
for _, file := range files {
|
||||
if file.Name == name {
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver Alist) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
//return nil, base.ErrNotImplement
|
||||
_, files, err := driver.Path(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if files == nil {
|
||||
return nil, base.ErrNotFolder
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Alist) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
path := args.Path
|
||||
path = utils.ParsePath(path)
|
||||
name := utils.Base(path)
|
||||
flag := "d"
|
||||
if utils.GetFileType(filepath.Ext(path)) == conf.TEXT {
|
||||
flag = "p"
|
||||
}
|
||||
link := base.Link{}
|
||||
link.Url = fmt.Sprintf("%s/%s%s?sign=%s", account.SiteUrl, flag, path, utils.SignWithToken(name, conf.Token))
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Alist) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
path = filepath.Join(account.RootFolder, path)
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files := cache.([]model.File)
|
||||
return nil, files, nil
|
||||
}
|
||||
var resp PathResp
|
||||
_, err = base.RestyClient.R().SetResult(&resp).
|
||||
SetHeader("Authorization", account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"path": path,
|
||||
}).Post(account.SiteUrl + "/api/public/path")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.Code != 200 {
|
||||
return nil, nil, errors.New(resp.Message)
|
||||
}
|
||||
if resp.Data.Type == "file" {
|
||||
return &resp.Data.Files[0], nil, nil
|
||||
}
|
||||
if len(resp.Data.Files) > 0 {
|
||||
_ = base.SetCache(path, resp.Data.Files, account)
|
||||
}
|
||||
return nil, resp.Data.Files, nil
|
||||
}
|
||||
|
||||
//func (driver Alist) Proxy(r *http.Request, account *model.Account) {}
|
||||
|
||||
func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
var resp PathResp
|
||||
_, err := base.RestyClient.R().SetResult(&resp).
|
||||
SetHeader("Authorization", account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"path": path,
|
||||
}).Post(account.SiteUrl + "/api/public/preview")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Code != 200 {
|
||||
return nil, errors.New(resp.Message)
|
||||
}
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
func (driver Alist) MakeDir(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Alist) Move(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Alist) Rename(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Alist) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Alist) Delete(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Alist) Upload(file *model.FileStream, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Alist)(nil)
|
||||
@@ -1,50 +1,6 @@
|
||||
package drivers
|
||||
|
||||
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"
|
||||
_ "github.com/Xhofe/alist/drivers/lanzou"
|
||||
_ "github.com/Xhofe/alist/drivers/mediatrack"
|
||||
_ "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"
|
||||
_ "github.com/alist-org/alist/v3/drivers/local"
|
||||
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
||||
)
|
||||
|
||||
var NoCors string
|
||||
var NoUpload string
|
||||
|
||||
func GetConfig() {
|
||||
for k, v := range base.GetDriversMap() {
|
||||
if v.Config().NoCors {
|
||||
NoCors += k + ","
|
||||
}
|
||||
if v.Upload(nil, nil) != base.ErrEmptyFile {
|
||||
NoUpload += k + ","
|
||||
}
|
||||
}
|
||||
NoCors = strings.Trim(NoCors, ",")
|
||||
NoUpload += "root"
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.Debug("all init")
|
||||
GetConfig()
|
||||
}
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
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{})
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
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 {
|
||||
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)
|
||||
@@ -1,84 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package baidu
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getTime(t int64) *time.Time {
|
||||
tm := time.Unix(t, 0)
|
||||
return &tm
|
||||
}
|
||||
|
||||
func encodeURIComponent(str string) string {
|
||||
r := url.QueryEscape(str)
|
||||
r = strings.ReplaceAll(r, "+", "%20")
|
||||
return r
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func KeyCache(path string, account *model.Account) string {
|
||||
path = utils.ParsePath(path)
|
||||
return fmt.Sprintf("%s%s", account.Name, path)
|
||||
}
|
||||
|
||||
func SetCache(path string, obj interface{}, account *model.Account) error {
|
||||
return conf.Cache.Set(conf.Ctx, KeyCache(path, account), obj, nil)
|
||||
}
|
||||
|
||||
func GetCache(path string, account *model.Account) (interface{}, error) {
|
||||
return conf.Cache.Get(conf.Ctx, KeyCache(path, account))
|
||||
}
|
||||
|
||||
func DeleteCache(path string, account *model.Account) error {
|
||||
err := conf.Cache.Delete(conf.Ctx, KeyCache(path, account))
|
||||
log.Debugf("delete cache %s: %+v", path, err)
|
||||
return err
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DriverConfig struct {
|
||||
Name string
|
||||
OnlyProxy bool // 必须使用代理(本机或者其他机器)
|
||||
OnlyLocal bool // 必须本机返回的
|
||||
ApiProxy bool // 使用API中转的
|
||||
NoNeedSetLink bool // 不需要设置链接的
|
||||
NoCors bool // 不可以跨域
|
||||
LocalSort bool // 本地排序
|
||||
}
|
||||
|
||||
type Args struct {
|
||||
Path string
|
||||
IP string
|
||||
}
|
||||
|
||||
type Driver interface {
|
||||
// Config 配置
|
||||
Config() DriverConfig
|
||||
// Items 账号所需参数
|
||||
Items() []Item
|
||||
// Save 保存时处理
|
||||
Save(account *model.Account, old *model.Account) error
|
||||
// File 取文件
|
||||
File(path string, account *model.Account) (*model.File, error)
|
||||
// Files 取文件夹
|
||||
Files(path string, account *model.Account) ([]model.File, error)
|
||||
// Link 取链接
|
||||
Link(args Args, account *model.Account) (*Link, error)
|
||||
// Path 取路径(文件或文件夹)
|
||||
Path(path string, account *model.Account) (*model.File, []model.File, error)
|
||||
// Deprecated Proxy 代理处理
|
||||
//Proxy(r *http.Request, account *model.Account)
|
||||
// Preview 预览
|
||||
Preview(path string, account *model.Account) (interface{}, error)
|
||||
// MakeDir 创建文件夹
|
||||
MakeDir(path string, account *model.Account) error
|
||||
// Move 移动/改名
|
||||
Move(src string, dst string, account *model.Account) error
|
||||
// Rename 改名
|
||||
Rename(src string, dst string, account *model.Account) error
|
||||
// Copy 拷贝
|
||||
Copy(src string, dst string, account *model.Account) error
|
||||
// Delete 删除
|
||||
Delete(path string, account *model.Account) error
|
||||
// Upload 上传
|
||||
Upload(file *model.FileStream, account *model.Account) error
|
||||
// TODO
|
||||
//Search(path string, keyword string, account *model.Account) ([]*model.File, error)
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
var driversMap = map[string]Driver{}
|
||||
|
||||
func RegisterDriver(driver Driver) {
|
||||
log.Infof("register driver: [%s]", driver.Config().Name)
|
||||
driversMap[driver.Config().Name] = driver
|
||||
}
|
||||
|
||||
func GetDriver(name string) (driver Driver, ok bool) {
|
||||
driver, ok = driversMap[name]
|
||||
return
|
||||
}
|
||||
|
||||
func GetDriversMap() map[string]Driver {
|
||||
return driversMap
|
||||
}
|
||||
|
||||
func GetDrivers() map[string][]Item {
|
||||
res := make(map[string][]Item)
|
||||
for k, v := range driversMap {
|
||||
webdavDirect := Item{
|
||||
Name: "webdav_direct",
|
||||
Label: "webdav direct",
|
||||
Type: TypeBool,
|
||||
Required: true,
|
||||
Description: "Transfer the WebDAV of this account through the native",
|
||||
}
|
||||
if v.Config().OnlyProxy {
|
||||
res[k] = append([]Item{
|
||||
webdavDirect,
|
||||
}, v.Items()...)
|
||||
} else {
|
||||
res[k] = append([]Item{
|
||||
{
|
||||
Name: "proxy",
|
||||
Label: "proxy",
|
||||
Type: TypeBool,
|
||||
Required: true,
|
||||
Description: "web proxy",
|
||||
},
|
||||
{
|
||||
Name: "webdav_proxy",
|
||||
Label: "webdav proxy",
|
||||
Type: TypeBool,
|
||||
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,
|
||||
},
|
||||
{
|
||||
Name: "extract_folder",
|
||||
Label: "extract_folder",
|
||||
Values: "front,back",
|
||||
Type: TypeSelect,
|
||||
},
|
||||
}, res[k]...)
|
||||
if v.Config().ApiProxy {
|
||||
res[k] = append([]Item{
|
||||
{
|
||||
Name: "api_proxy_url",
|
||||
Label: "api_proxy_url",
|
||||
Type: TypeString,
|
||||
},
|
||||
}, res[k]...)
|
||||
}
|
||||
if v.Config().LocalSort {
|
||||
res[k] = append(res[k], []Item{
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: TypeSelect,
|
||||
Values: "name,size,updated_at",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: TypeSelect,
|
||||
Values: "ASC,DESC",
|
||||
Required: false,
|
||||
},
|
||||
}...)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
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(
|
||||
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}),
|
||||
)
|
||||
NoRedirectClient.SetHeader("user-agent", UserAgent)
|
||||
RestyClient.SetHeader("user-agent", UserAgent)
|
||||
RestyClient.SetRetryCount(3)
|
||||
RestyClient.SetTimeout(DefaultTimeout)
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPathNotFound = errors.New("path not found")
|
||||
ErrNotFile = errors.New("not file")
|
||||
ErrNotImplement = errors.New("not implement")
|
||||
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 (
|
||||
TypeString = "string"
|
||||
TypeSelect = "select"
|
||||
TypeBool = "bool"
|
||||
TypeNumber = "number"
|
||||
)
|
||||
|
||||
const (
|
||||
Get = iota
|
||||
Post
|
||||
Put
|
||||
Delete
|
||||
Patch
|
||||
)
|
||||
|
||||
type Json map[string]interface{}
|
||||
|
||||
type TokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
Url string `json:"url"`
|
||||
Headers []Header `json:"headers"`
|
||||
Data io.ReadCloser
|
||||
FilePath string `json:"path"` // for native
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/jlaffaye/ftp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type FTP struct{}
|
||||
|
||||
func (driver FTP) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "FTP",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: true,
|
||||
NoNeedSetLink: true,
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver FTP) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "site_url",
|
||||
Label: "ftp host url",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
Label: "username",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Label: "password",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder path",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver FTP) Save(account *model.Account, old *model.Account) error {
|
||||
if old != nil {
|
||||
conn, ok := connMap[old.Name]
|
||||
if ok {
|
||||
err := conn.Quit()
|
||||
log.Error("ftp:", err)
|
||||
delete(connMap, old.Name)
|
||||
}
|
||||
}
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "/"
|
||||
}
|
||||
_, err := driver.Login(account)
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
} else {
|
||||
account.Status = "work"
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver FTP) File(path string, account *model.Account) (*model.File, error) {
|
||||
log.Debugf("file: %s", path)
|
||||
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 FTP) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
log.Debugf("files: %s", path)
|
||||
path = utils.ParsePath(path)
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files, _ := cache.([]model.File)
|
||||
return files, nil
|
||||
}
|
||||
realPath := utils.Join(account.RootFolder, path)
|
||||
conn, err := driver.Login(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//defer func() { _ = conn.Quit() }()
|
||||
entries, err := conn.List(realPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]model.File, 0)
|
||||
for i, _ := range entries {
|
||||
entry := entries[i]
|
||||
if entry.Name == "." || entry.Name == ".." {
|
||||
continue
|
||||
}
|
||||
f := model.File{
|
||||
Name: entry.Name,
|
||||
Size: int64(entry.Size),
|
||||
UpdatedAt: &entry.Time,
|
||||
Driver: driver.Config().Name,
|
||||
}
|
||||
if entry.Type == ftp.EntryTypeFolder {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(filepath.Ext(entry.Name))
|
||||
}
|
||||
res = append(res, f)
|
||||
}
|
||||
if len(res) > 0 {
|
||||
_ = base.SetCache(path, res, account)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (driver FTP) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
path := args.Path
|
||||
path = utils.ParsePath(path)
|
||||
realPath := utils.Join(account.RootFolder, path)
|
||||
conn, err := driver.Login(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//defer func() { _ = conn.Quit() }()
|
||||
resp, err := conn.Retr(realPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//defer func() { _ = resp.Close() }()
|
||||
//data, err := ioutil.ReadAll(resp)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
return &base.Link{
|
||||
Data: resp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (driver FTP) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
log.Debugf("ftp path: %s", path)
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !file.IsDir() {
|
||||
//file.Url, _ = driver.Link(path, account)
|
||||
return file, nil, nil
|
||||
}
|
||||
files, err := driver.Files(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
//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
|
||||
}
|
||||
|
||||
func (driver FTP) MakeDir(path string, account *model.Account) error {
|
||||
path = utils.ParsePath(path)
|
||||
realPath := utils.Join(account.RootFolder, path)
|
||||
conn, err := driver.Login(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//defer func() { _ = conn.Quit() }()
|
||||
err = conn.MakeDir(realPath)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver FTP) Move(src string, dst string, account *model.Account) error {
|
||||
realSrc := utils.Join(account.RootFolder, src)
|
||||
realDst := utils.Join(account.RootFolder, dst)
|
||||
conn, err := driver.Login(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//defer func() { _ = conn.Quit() }()
|
||||
err = conn.Rename(realSrc, realDst)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver FTP) Rename(src string, dst string, account *model.Account) error {
|
||||
return driver.Move(src, dst, account)
|
||||
}
|
||||
|
||||
func (driver FTP) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver FTP) Delete(path string, account *model.Account) error {
|
||||
path = utils.ParsePath(path)
|
||||
realPath := utils.Join(account.RootFolder, path)
|
||||
conn, err := driver.Login(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//defer func() { _ = conn.Quit() }()
|
||||
err = conn.Delete(realPath)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver FTP) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
realPath := utils.Join(account.RootFolder, file.ParentPath, file.Name)
|
||||
conn, err := driver.Login(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//defer func() { _ = conn.Quit() }()
|
||||
err = conn.Stor(realPath, file)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*FTP)(nil)
|
||||
@@ -1,36 +0,0 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/jlaffaye/ftp"
|
||||
)
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
err = conn.Login(account.Username, account.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connMap[account.Name] = conn
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&FTP{})
|
||||
connMap = make(map[string]*ftp.ServerConn)
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type GoogleDrive struct{}
|
||||
|
||||
func (driver GoogleDrive) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "GoogleDrive",
|
||||
OnlyProxy: true,
|
||||
ApiProxy: true,
|
||||
NoNeedSetLink: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "client_id",
|
||||
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",
|
||||
Label: "refresh token",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
Description: "such as: folder,name,modifiedTime",
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: base.TypeSelect,
|
||||
Values: "asc,desc",
|
||||
Required: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
account.Status = err.Error()
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "root"
|
||||
}
|
||||
account.Status = "work"
|
||||
_ = model.SaveAccount(account)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) 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 GoogleDrive) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var rawFiles []File
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
rawFiles, _ = cache.([]File)
|
||||
} else {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rawFiles) > 0 {
|
||||
_ = base.SetCache(path, rawFiles, account)
|
||||
}
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
for _, file := range rawFiles {
|
||||
files = append(files, *driver.FormatFile(&file, account))
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) 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.Type == conf.FOLDER {
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
url := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.Id)
|
||||
_, err = driver.Request(url, base.Get, nil, nil, nil, nil, nil, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link := base.Link{
|
||||
Url: url + "&alt=media",
|
||||
Headers: []base.Header{
|
||||
{
|
||||
Name: "Authorization",
|
||||
Value: "Bearer " + account.AccessToken,
|
||||
},
|
||||
},
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("google 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 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
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) MakeDir(path string, account *model.Account) error {
|
||||
parentFile, err := driver.File(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"name": utils.Base(path),
|
||||
"parents": []string{parentFile.Id},
|
||||
"mimeType": "application/vnd.google-apps.folder",
|
||||
}
|
||||
_, err = driver.Request("https://www.googleapis.com/drive/v3/files", base.Post, nil, nil, nil, &data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Move(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
url := "https://www.googleapis.com/drive/v3/files/" + srcFile.Id
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query := map[string]string{
|
||||
"addParents": dstParentFile.Id,
|
||||
"removeParents": "root",
|
||||
}
|
||||
_, err = driver.Request(url, base.Patch, nil, query, nil, nil, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Rename(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
url := "https://www.googleapis.com/drive/v3/files/" + srcFile.Id
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"name": utils.Base(dst),
|
||||
}
|
||||
_, err = driver.Request(url, base.Patch, nil, nil, nil, &data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Delete(path string, account *model.Account) error {
|
||||
file, err := driver.File(path, account)
|
||||
url := "https://www.googleapis.com/drive/v3/files/" + file.Id
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = driver.Request(url, base.Delete, nil, nil, nil, nil, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) 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
|
||||
}
|
||||
data := base.Json{
|
||||
"name": file.Name,
|
||||
"parents": []string{parentFile.Id},
|
||||
}
|
||||
var e Error
|
||||
url := "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&supportsAllDrives=true"
|
||||
if account.APIProxyUrl != "" {
|
||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
||||
}
|
||||
res, err := base.NoRedirectClient.R().SetHeader("Authorization", "Bearer "+account.AccessToken).
|
||||
SetError(&e).SetBody(data).
|
||||
Post(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Error.Code != 0 {
|
||||
if e.Error.Code == 401 {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
return driver.Upload(file, account)
|
||||
}
|
||||
return fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
||||
}
|
||||
putUrl := res.Header().Get("location")
|
||||
byteData, _ := ioutil.ReadAll(file)
|
||||
_, err = driver.Request(putUrl, base.Put, nil, nil, nil, byteData, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*GoogleDrive)(nil)
|
||||
@@ -1,198 +0,0 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TokenError struct {
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) RefreshToken(account *model.Account) error {
|
||||
url := "https://www.googleapis.com/oauth2/v4/token"
|
||||
if account.APIProxyUrl != "" {
|
||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
||||
}
|
||||
var resp base.TokenResp
|
||||
var e TokenError
|
||||
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
|
||||
SetFormData(map[string]string{
|
||||
"client_id": account.ClientId,
|
||||
"client_secret": account.ClientSecret,
|
||||
"refresh_token": account.RefreshToken,
|
||||
"grant_type": "refresh_token",
|
||||
}).Post(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if e.Error != "" {
|
||||
return fmt.Errorf(e.Error)
|
||||
}
|
||||
account.AccessToken = resp.AccessToken
|
||||
account.Status = "work"
|
||||
return nil
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MimeType string `json:"mimeType"`
|
||||
ModifiedTime *time.Time `json:"modifiedTime"`
|
||||
Size string `json:"size"`
|
||||
ThumbnailLink string `json:"thumbnailLink"`
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) IsDir(mimeType string) bool {
|
||||
return mimeType == "application/vnd.google-apps.folder" || mimeType == "application/vnd.google-apps.shortcut"
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) FormatFile(file *File, account *model.Account) *model.File {
|
||||
f := &model.File{
|
||||
Id: file.Id,
|
||||
Name: file.Name,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: file.ModifiedTime,
|
||||
Url: "",
|
||||
}
|
||||
if driver.IsDir(file.MimeType) {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
size, _ := strconv.ParseInt(file.Size, 10, 64)
|
||||
f.Size = size
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
||||
}
|
||||
if file.ThumbnailLink != "" {
|
||||
if account.DownProxyUrl != "" {
|
||||
f.Thumbnail = fmt.Sprintf("%s/%s", account.DownProxyUrl, file.ThumbnailLink)
|
||||
} else {
|
||||
f.Thumbnail = file.ThumbnailLink
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
type Files struct {
|
||||
NextPageToken string `json:"nextPageToken"`
|
||||
Files []File `json:"files"`
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Error struct {
|
||||
Errors []struct {
|
||||
Domain string `json:"domain"`
|
||||
Reason string `json:"reason"`
|
||||
Message string `json:"message"`
|
||||
LocationType string `json:"location_type"`
|
||||
Location string `json:"location"`
|
||||
}
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]File, error) {
|
||||
pageToken := "first"
|
||||
res := make([]File, 0)
|
||||
for pageToken != "" {
|
||||
if pageToken == "first" {
|
||||
pageToken = ""
|
||||
}
|
||||
var resp Files
|
||||
orderBy := "folder,name,modifiedTime desc"
|
||||
if account.OrderBy != "" {
|
||||
orderBy = account.OrderBy + " " + account.OrderDirection
|
||||
}
|
||||
query := map[string]string{
|
||||
"orderBy": orderBy,
|
||||
"fields": "files(id,name,mimeType,size,modifiedTime,thumbnailLink),nextPageToken",
|
||||
"pageSize": "1000",
|
||||
"q": fmt.Sprintf("'%s' in parents and trashed = false", id),
|
||||
//"includeItemsFromAllDrives": "true",
|
||||
//"supportsAllDrives": "true",
|
||||
"pageToken": pageToken,
|
||||
}
|
||||
_, err := driver.Request("https://www.googleapis.com/drive/v3/files",
|
||||
base.Get, nil, query, nil, nil, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pageToken = resp.NextPageToken
|
||||
res = append(res, resp.Files...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Request(url string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
rawUrl := url
|
||||
if account.APIProxyUrl != "" {
|
||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
||||
}
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
|
||||
req.SetQueryParam("includeItemsFromAllDrives", "true")
|
||||
req.SetQueryParam("supportsAllDrives", "true")
|
||||
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 Error
|
||||
req.SetError(&e)
|
||||
switch method {
|
||||
case base.Get:
|
||||
res, err = req.Get(url)
|
||||
case base.Post:
|
||||
res, err = req.Post(url)
|
||||
case base.Delete:
|
||||
res, err = req.Delete(url)
|
||||
case base.Patch:
|
||||
res, err = req.Patch(url)
|
||||
case base.Put:
|
||||
res, err = req.Put(url)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if e.Error.Code != 0 {
|
||||
if e.Error.Code == 401 {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
_ = model.SaveAccount(account)
|
||||
return nil, err
|
||||
}
|
||||
return driver.Request(rawUrl, method, headers, query, form, data, resp, account)
|
||||
}
|
||||
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&GoogleDrive{})
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
package lanzou
|
||||
|
||||
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"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Lanzou struct{}
|
||||
|
||||
func (driver Lanzou) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Lanzou",
|
||||
NoCors: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Lanzou) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "internal_type",
|
||||
Label: "lanzou type",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "cookie,url",
|
||||
},
|
||||
{
|
||||
Name: "access_token",
|
||||
Label: "cookie",
|
||||
Type: base.TypeString,
|
||||
Description: "about 15 days valid",
|
||||
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",
|
||||
Label: "share password",
|
||||
Type: base.TypeString,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
account.Status = "work"
|
||||
_ = model.SaveAccount(account)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver Lanzou) 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 Lanzou) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var rawFiles []LanZouFile
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
rawFiles, _ = cache.([]LanZouFile)
|
||||
} else {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rawFiles) > 0 {
|
||||
_ = base.SetCache(path, rawFiles, account)
|
||||
}
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
for _, file := range rawFiles {
|
||||
files = append(files, *driver.FormatFile(&file))
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Lanzou) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(args.Path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("down file: %+v", file)
|
||||
downId := file.Id
|
||||
pwd := ""
|
||||
if account.InternalType == "cookie" {
|
||||
downId, pwd, err = driver.GetDownPageId(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("lanzou 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 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
|
||||
}
|
||||
|
||||
func (driver *Lanzou) MakeDir(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver *Lanzou) Move(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver *Lanzou) Rename(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver *Lanzou) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver *Lanzou) Delete(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver *Lanzou) Upload(file *model.FileStream, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Lanzou)(nil)
|
||||
@@ -1,285 +0,0 @@
|
||||
package lanzou
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LanZouFile struct {
|
||||
Name string `json:"name"`
|
||||
NameAll string `json:"name_all"`
|
||||
Id string `json:"id"`
|
||||
FolId string `json:"fol_id"`
|
||||
Size string `json:"size"`
|
||||
Time string `json:"time"`
|
||||
Folder bool
|
||||
}
|
||||
|
||||
func (driver *Lanzou) FormatFile(file *LanZouFile) *model.File {
|
||||
now := time.Now()
|
||||
f := &model.File{
|
||||
Id: file.Id,
|
||||
Name: file.Name,
|
||||
Driver: driver.Config().Name,
|
||||
SizeStr: file.Size,
|
||||
TimeStr: file.Time,
|
||||
UpdatedAt: &now,
|
||||
}
|
||||
if file.Folder {
|
||||
f.Type = conf.FOLDER
|
||||
f.Id = file.FolId
|
||||
} else {
|
||||
f.Name = file.NameAll
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.NameAll))
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
type LanZouFilesResp struct {
|
||||
Zt int `json:"zt"`
|
||||
Info interface{} `json:"info"`
|
||||
Text []LanZouFile `json:"text"`
|
||||
}
|
||||
|
||||
func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZouFile, error) {
|
||||
if account.InternalType == "cookie" {
|
||||
files := make([]LanZouFile, 0)
|
||||
var resp LanZouFilesResp
|
||||
// folders
|
||||
res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
SetFormData(map[string]string{
|
||||
"task": "47",
|
||||
"folder_id": folderId,
|
||||
}).Post("https://pc.woozooo.com/doupload.php")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if resp.Zt != 1 && resp.Zt != 2 {
|
||||
return nil, fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
for _, file := range resp.Text {
|
||||
file.Folder = true
|
||||
files = append(files, file)
|
||||
}
|
||||
// files
|
||||
pg := 1
|
||||
for {
|
||||
_, err = base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
SetFormData(map[string]string{
|
||||
"task": "5",
|
||||
"folder_id": folderId,
|
||||
"pg": strconv.Itoa(pg),
|
||||
}).Post("https://pc.woozooo.com/doupload.php")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Zt != 1 {
|
||||
return nil, fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
if len(resp.Text) == 0 {
|
||||
break
|
||||
}
|
||||
files = append(files, resp.Text...)
|
||||
pg++
|
||||
}
|
||||
return files, nil
|
||||
} else {
|
||||
return driver.GetFilesByUrl(account)
|
||||
}
|
||||
}
|
||||
|
||||
func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error) {
|
||||
files := make([]LanZouFile, 0)
|
||||
shareUrl := account.SiteUrl
|
||||
u, err := url.Parse(shareUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := base.RestyClient.R().Get(shareUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lxArr := regexp.MustCompile(`'lx':(.+?),`).FindStringSubmatch(res.String())
|
||||
if len(lxArr) == 0 {
|
||||
return nil, fmt.Errorf("get empty page")
|
||||
}
|
||||
lx := lxArr[1]
|
||||
fid := regexp.MustCompile(`'fid':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
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 := ""
|
||||
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]
|
||||
k := regexp.MustCompile(`var ` + kName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
||||
pg := 1
|
||||
for {
|
||||
var resp LanZouFilesResp
|
||||
res, err = base.RestyClient.R().SetResult(&resp).SetFormData(map[string]string{
|
||||
"lx": lx,
|
||||
"fid": fid,
|
||||
"uid": uid,
|
||||
"pg": strconv.Itoa(pg),
|
||||
"rep": rep,
|
||||
"t": t,
|
||||
"k": k,
|
||||
"up": up,
|
||||
"ls": ls,
|
||||
"pwd": account.Password,
|
||||
}).Post(fmt.Sprintf("https://%s/filemoreajax.php", u.Host))
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
break
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if resp.Zt != 1 {
|
||||
return nil, fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
if len(resp.Text) == 0 {
|
||||
break
|
||||
}
|
||||
pg++
|
||||
time.Sleep(time.Second)
|
||||
files = append(files, resp.Text...)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
//type LanzouDownInfo struct {
|
||||
// FId string `json:"f_id"`
|
||||
// IsNewd string `json:"is_newd"`
|
||||
//}
|
||||
|
||||
// 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
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if resp.Zt != 1 {
|
||||
return "", "", fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
return resp.Info.FId, resp.Info.Pwd, nil
|
||||
}
|
||||
|
||||
type LanzouLinkResp struct {
|
||||
Dom string `json:"dom"`
|
||||
Url string `json:"url"`
|
||||
Zt int `json:"zt"`
|
||||
}
|
||||
|
||||
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 driver.GetLinkWithPassword(downId, pwd, res.String(), account)
|
||||
}
|
||||
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]
|
||||
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,
|
||||
"websignkey": websignkey,
|
||||
}
|
||||
log.Debugf("form: %+v", form)
|
||||
res, err = base.RestyClient.R().SetResult(&resp).
|
||||
SetHeader("origin", "https://"+u.Host).
|
||||
SetHeader("referer", iframeUrl).
|
||||
SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
|
||||
log.Debug(res.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.Zt == 1 {
|
||||
return resp.Dom + "/file/" + resp.Url, nil
|
||||
}
|
||||
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{})
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
182
drivers/local/driver.go
Normal file
182
drivers/local/driver.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/operations"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Local struct {
|
||||
model.Storage
|
||||
Addition
|
||||
}
|
||||
|
||||
func (d *Local) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *Local) Init(ctx context.Context, storage model.Storage) error {
|
||||
d.Storage = storage
|
||||
err := utils.Json.UnmarshalFromString(d.Storage.Addition, &d.Addition)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error while unmarshal addition")
|
||||
}
|
||||
if !utils.Exists(d.RootFolder) {
|
||||
err = errors.Errorf("root folder %s not exists", d.RootFolder)
|
||||
d.SetStatus(err.Error())
|
||||
} else {
|
||||
if !filepath.IsAbs(d.RootFolder) {
|
||||
d.RootFolder, err = filepath.Abs(d.RootFolder)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error while get abs path")
|
||||
}
|
||||
}
|
||||
d.SetStatus("OK")
|
||||
}
|
||||
operations.MustSaveDriverStorage(d)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Local) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Local) GetAddition() driver.Additional {
|
||||
return d.Addition
|
||||
}
|
||||
|
||||
func (d *Local) List(ctx context.Context, dir model.Obj) ([]model.Obj, error) {
|
||||
fullPath := dir.GetID()
|
||||
rawFiles, err := ioutil.ReadDir(fullPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error while read dir %s", fullPath)
|
||||
}
|
||||
var files []model.Obj
|
||||
for _, f := range rawFiles {
|
||||
if strings.HasPrefix(f.Name(), ".") {
|
||||
continue
|
||||
}
|
||||
file := model.Object{
|
||||
Name: f.Name(),
|
||||
Modified: f.ModTime(),
|
||||
Size: f.Size(),
|
||||
IsFolder: f.IsDir(),
|
||||
}
|
||||
files = append(files, &file)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (d *Local) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||
f, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error while stat %s", path)
|
||||
}
|
||||
file := model.Object{
|
||||
ID: path,
|
||||
Name: f.Name(),
|
||||
Modified: f.ModTime(),
|
||||
Size: f.Size(),
|
||||
IsFolder: f.IsDir(),
|
||||
}
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
fullPath := file.GetID()
|
||||
link := model.Link{
|
||||
FilePath: &fullPath,
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (d *Local) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
fullPath := filepath.Join(parentDir.GetID(), dirName)
|
||||
err := os.MkdirAll(fullPath, 0700)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error while make dir %s", fullPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Local) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
srcPath := srcObj.GetID()
|
||||
dstPath := filepath.Join(dstDir.GetID(), srcObj.GetName())
|
||||
err := os.Rename(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error while move %s to %s", srcPath, dstPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Local) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
srcPath := srcObj.GetID()
|
||||
dstPath := filepath.Join(filepath.Dir(srcPath), newName)
|
||||
err := os.Rename(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error while rename %s to %s", srcPath, dstPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Local) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
srcPath := srcObj.GetID()
|
||||
dstPath := filepath.Join(dstDir.GetID(), srcObj.GetName())
|
||||
var err error
|
||||
if srcObj.IsDir() {
|
||||
err = copyDir(srcPath, dstPath)
|
||||
} else {
|
||||
err = copyFile(srcPath, dstPath)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error while copy %s to %s", srcPath, dstPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Local) Remove(ctx context.Context, obj model.Obj) error {
|
||||
var err error
|
||||
if obj.IsDir() {
|
||||
err = os.RemoveAll(obj.GetID())
|
||||
} else {
|
||||
err = os.Remove(obj.GetID())
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error while remove %s", obj.GetID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Local) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
fullPath := filepath.Join(dstDir.GetID(), stream.GetName())
|
||||
out, err := os.Create(fullPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error while create file %s", fullPath)
|
||||
}
|
||||
defer func() {
|
||||
_ = out.Close()
|
||||
if errors.Is(err, context.Canceled) {
|
||||
_ = os.Remove(fullPath)
|
||||
}
|
||||
}()
|
||||
err = utils.CopyWithCtx(ctx, out, stream)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error while copy file %s", fullPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Local) Other(ctx context.Context, data interface{}) (interface{}, error) {
|
||||
return nil, errs.NotSupport
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*Local)(nil)
|
||||
26
drivers/local/meta.go
Normal file
26
drivers/local/meta.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/operations"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootFolderPath
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Local",
|
||||
OnlyLocal: true,
|
||||
LocalSort: true,
|
||||
NoCache: true,
|
||||
DefaultRoot: "/",
|
||||
}
|
||||
|
||||
func New() driver.Driver {
|
||||
return &Local{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
operations.RegisterDriver(config, New)
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
package native
|
||||
package local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// File copies a single file from src to dst
|
||||
func (driver *Native) CopyFile(src, dst string) error {
|
||||
// copyFile File copies a single file from src to dst
|
||||
func copyFile(src, dst string) error {
|
||||
var err error
|
||||
var srcfd *os.File
|
||||
var dstfd *os.File
|
||||
@@ -35,8 +34,8 @@ func (driver *Native) CopyFile(src, dst string) error {
|
||||
return os.Chmod(dst, srcinfo.Mode())
|
||||
}
|
||||
|
||||
// Dir copies a whole directory recursively
|
||||
func (driver *Native) CopyDir(src string, dst string) error {
|
||||
// copyDir Dir copies a whole directory recursively
|
||||
func copyDir(src string, dst string) error {
|
||||
var err error
|
||||
var fds []os.FileInfo
|
||||
var srcinfo os.FileInfo
|
||||
@@ -44,11 +43,9 @@ func (driver *Native) CopyDir(src string, dst string) error {
|
||||
if srcinfo, err = os.Stat(src); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fds, err = ioutil.ReadDir(src); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -57,18 +54,14 @@ func (driver *Native) CopyDir(src string, dst string) error {
|
||||
dstfp := path.Join(dst, fd.Name())
|
||||
|
||||
if fd.IsDir() {
|
||||
if err = driver.CopyDir(srcfp, dstfp); err != nil {
|
||||
if err = copyDir(srcfp, dstfp); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
} else {
|
||||
if err = driver.CopyFile(srcfp, dstfp); err != nil {
|
||||
if err = copyFile(srcfp, dstfp); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Native{})
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
package mediatrack
|
||||
|
||||
import (
|
||||
"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"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/google/uuid"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type MediaTrack struct{}
|
||||
|
||||
func (driver MediaTrack) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "MediaTrack",
|
||||
}
|
||||
}
|
||||
|
||||
func (driver MediaTrack) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "access_token",
|
||||
Label: "Token",
|
||||
Type: base.TypeString,
|
||||
Description: "Unknown expiration time",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "updated_at,title,size",
|
||||
Required: true,
|
||||
Description: "title",
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "desc",
|
||||
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
|
||||
}
|
||||
|
||||
func (driver MediaTrack) 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 MediaTrack) 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 MediaTrack) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(args.Path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pre := "https://jayce.api.mediatrack.cn/v3/assets/" + file.Id
|
||||
body, err := driver.Request(pre+"/token", base.Get, nil, nil, nil, nil, nil, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
url := pre + "/raw"
|
||||
res, err := base.NoRedirectClient.R().SetQueryParam("token", jsoniter.Get(body, "data").ToString()).Get(url)
|
||||
return &base.Link{Url: res.Header().Get("location")}, nil
|
||||
}
|
||||
|
||||
func (driver MediaTrack) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("MediaTrack 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 MediaTrack) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver MediaTrack) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver MediaTrack) MakeDir(path string, account *model.Account) error {
|
||||
parentFile, err := driver.File(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id)
|
||||
_, err = driver.Request(url, base.Post, nil, nil, nil, base.Json{
|
||||
"type": 1,
|
||||
"title": utils.Base(path),
|
||||
}, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver MediaTrack) 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{
|
||||
"parent_id": dstParentFile.Id,
|
||||
"ids": []string{srcFile.Id},
|
||||
}
|
||||
url := "https://jayce.api.mediatrack.cn/v4/assets/batch/move"
|
||||
_, err = driver.Request(url, base.Post, nil, nil, nil, data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver MediaTrack) Rename(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url := "https://jayce.api.mediatrack.cn/v3/assets/" + srcFile.Id
|
||||
data := base.Json{
|
||||
"title": utils.Base(dst),
|
||||
}
|
||||
_, err = driver.Request(url, base.Put, nil, nil, nil, data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver MediaTrack) Copy(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{
|
||||
"parent_id": dstParentFile.Id,
|
||||
"ids": []string{srcFile.Id},
|
||||
}
|
||||
url := "https://jayce.api.mediatrack.cn/v4/assets/batch/clone"
|
||||
_, err = driver.Request(url, base.Post, nil, nil, nil, data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver MediaTrack) Delete(path string, account *model.Account) error {
|
||||
parentFile, err := driver.File(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"origin_id": parentFile.Id,
|
||||
"ids": []string{file.Id},
|
||||
}
|
||||
url := "https://jayce.api.mediatrack.cn/v4/assets/batch/delete"
|
||||
_, err = driver.Request(url, base.Delete, nil, nil, nil, data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver MediaTrack) 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
|
||||
}
|
||||
src := "assets/" + uuid.New().String()
|
||||
var resp UploadResp
|
||||
_, err = driver.Request("https://jayce.api.mediatrack.cn/v3/storage/tokens/asset", base.Get, nil, map[string]string{
|
||||
"src": src,
|
||||
}, nil, nil, &resp, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
credential := resp.Data.Credentials
|
||||
cfg := &aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(credential.TmpSecretID, credential.TmpSecretKey, credential.Token),
|
||||
Region: &resp.Data.Region,
|
||||
Endpoint: aws.String("cos.accelerate.myqcloud.com"),
|
||||
}
|
||||
s, err := session.NewSession(cfg)
|
||||
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
|
||||
}
|
||||
uploader := s3manager.NewUploader(s)
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &resp.Data.Bucket,
|
||||
Key: &resp.Data.Object,
|
||||
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, tempFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash := hex.EncodeToString(h.Sum(nil))
|
||||
data := base.Json{
|
||||
"category": 0,
|
||||
"description": file.GetFileName(),
|
||||
"hash": hash,
|
||||
"mime": file.MIMEType,
|
||||
"size": file.GetSize(),
|
||||
"src": src,
|
||||
"title": file.GetFileName(),
|
||||
"type": 0,
|
||||
}
|
||||
_, err = driver.Request(url, base.Post, nil, nil, nil, data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*MediaTrack)(nil)
|
||||
@@ -1,183 +0,0 @@
|
||||
package mediatrack
|
||||
|
||||
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"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BaseResp struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type T struct {
|
||||
BaseResp
|
||||
}
|
||||
|
||||
func (driver MediaTrack) Request(url string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Authorization", "Bearer "+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)
|
||||
}
|
||||
var e BaseResp
|
||||
req.SetResult(&e)
|
||||
var err error
|
||||
var res *resty.Response
|
||||
switch method {
|
||||
case base.Get:
|
||||
res, err = req.Get(url)
|
||||
case base.Post:
|
||||
res, err = req.Post(url)
|
||||
case base.Delete:
|
||||
res, err = req.Delete(url)
|
||||
case base.Patch:
|
||||
res, err = req.Patch(url)
|
||||
case base.Put:
|
||||
res, err = req.Put(url)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugln(res.String())
|
||||
if e.Status != "SUCCESS" {
|
||||
return nil, errors.New(e.Message)
|
||||
}
|
||||
if resp != nil {
|
||||
err = utils.Json.Unmarshal(res.Body(), resp)
|
||||
}
|
||||
return res.Body(), err
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Category int `json:"category"`
|
||||
ChildAssets []interface{} `json:"childAssets"`
|
||||
CommentCount int `json:"comment_count"`
|
||||
CoverAsset interface{} `json:"cover_asset"`
|
||||
CoverAssetID string `json:"cover_asset_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
DeletedAt string `json:"deleted_at"`
|
||||
Description string `json:"description"`
|
||||
File *struct {
|
||||
Cover string `json:"cover"`
|
||||
Src string `json:"src"`
|
||||
} `json:"file"`
|
||||
//FileID string `json:"file_id"`
|
||||
ID string `json:"id"`
|
||||
|
||||
Size string `json:"size"`
|
||||
Thumbnails []interface{} `json:"thumbnails"`
|
||||
Title string `json:"title"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (driver MediaTrack) formatFile(file *File) *model.File {
|
||||
f := model.File{
|
||||
Id: file.ID,
|
||||
Name: file.Title,
|
||||
Size: 0,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: &file.UpdatedAt,
|
||||
}
|
||||
if file.File == nil {
|
||||
// folder
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
size, _ := strconv.ParseInt(file.Size, 10, 64)
|
||||
f.Size = size
|
||||
f.Type = utils.GetFileType(path.Ext(file.Title))
|
||||
if file.File.Cover != "" {
|
||||
f.Thumbnail = "https://nano.mtres.cn/" + file.File.Cover
|
||||
}
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
type ChildrenResp struct {
|
||||
Status string `json:"status"`
|
||||
Data struct {
|
||||
Total int `json:"total"`
|
||||
Assets []File `json:"assets"`
|
||||
} `json:"data"`
|
||||
Path string `json:"path"`
|
||||
TraceID string `json:"trace_id"`
|
||||
RequestID string `json:"requestId"`
|
||||
}
|
||||
|
||||
func (driver MediaTrack) GetFiles(parentId string, account *model.Account) ([]model.File, error) {
|
||||
files := make([]model.File, 0)
|
||||
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v4/assets/%s/children", parentId)
|
||||
sort := ""
|
||||
if account.OrderBy != "" {
|
||||
if account.OrderDirection == "true" {
|
||||
sort = "-"
|
||||
}
|
||||
sort += account.OrderBy
|
||||
}
|
||||
page := 1
|
||||
for {
|
||||
var resp ChildrenResp
|
||||
_, err := driver.Request(url, base.Get, nil, map[string]string{
|
||||
"page": strconv.Itoa(page),
|
||||
"size": "50",
|
||||
"sort": sort,
|
||||
}, nil, nil, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.Data.Assets) == 0 {
|
||||
break
|
||||
}
|
||||
page++
|
||||
for _, file := range resp.Data.Assets {
|
||||
files = append(files, *driver.formatFile(&file))
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
type UploadResp struct {
|
||||
Status string `json:"status"`
|
||||
Data struct {
|
||||
Credentials struct {
|
||||
TmpSecretID string `json:"TmpSecretId"`
|
||||
TmpSecretKey string `json:"TmpSecretKey"`
|
||||
Token string `json:"Token"`
|
||||
ExpiredTime int `json:"ExpiredTime"`
|
||||
Expiration time.Time `json:"Expiration"`
|
||||
StartTime int `json:"StartTime"`
|
||||
} `json:"credentials"`
|
||||
Object string `json:"object"`
|
||||
Bucket string `json:"bucket"`
|
||||
Region string `json:"region"`
|
||||
URL string `json:"url"`
|
||||
Size string `json:"size"`
|
||||
} `json:"data"`
|
||||
Path string `json:"path"`
|
||||
TraceID string `json:"trace_id"`
|
||||
RequestID string `json:"requestId"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&MediaTrack{})
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
package native
|
||||
|
||||
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"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Native struct{}
|
||||
|
||||
func (driver Native) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Native",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: true,
|
||||
NoNeedSetLink: true,
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Native) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder path",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
_ = model.SaveAccount(account)
|
||||
return fmt.Errorf("[%s] not exist", account.RootFolder)
|
||||
}
|
||||
account.Status = "work"
|
||||
account.Proxy = true
|
||||
err := model.SaveAccount(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
f, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
time := f.ModTime()
|
||||
file := &model.File{
|
||||
Name: f.Name(),
|
||||
Size: f.Size(),
|
||||
UpdatedAt: &time,
|
||||
Driver: driver.Config().Name,
|
||||
}
|
||||
if f.IsDir() {
|
||||
file.Type = conf.FOLDER
|
||||
} else {
|
||||
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
rawFiles, err := ioutil.ReadDir(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range rawFiles {
|
||||
if strings.HasPrefix(f.Name(), ".") {
|
||||
continue
|
||||
}
|
||||
time := f.ModTime()
|
||||
file := model.File{
|
||||
Name: f.Name(),
|
||||
Size: f.Size(),
|
||||
Type: 0,
|
||||
UpdatedAt: &time,
|
||||
Driver: driver.Config().Name,
|
||||
}
|
||||
if f.IsDir() {
|
||||
file.Type = conf.FOLDER
|
||||
} else {
|
||||
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
if s.IsDir() {
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
link := base.Link{
|
||||
FilePath: fullPath,
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Native) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
log.Debugf("native path: %s", path)
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !file.IsDir() {
|
||||
//file.Url, _ = driver.Link(path, account)
|
||||
return file, nil, nil
|
||||
}
|
||||
files, err := driver.Files(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
//model.SortFiles(files, account)
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
//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)
|
||||
}
|
||||
|
||||
func (driver Native) Rename(src string, dst string, account *model.Account) error {
|
||||
return driver.Move(src, dst, account)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstFile, err := driver.File(dst, account)
|
||||
if err == nil {
|
||||
if !dstFile.IsDir() {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
}
|
||||
if srcFile.IsDir() {
|
||||
return driver.CopyDir(fullSrc, fullDst)
|
||||
}
|
||||
return driver.CopyFile(fullSrc, fullDst)
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
if file.IsDir() {
|
||||
return os.RemoveAll(fullPath)
|
||||
}
|
||||
return os.Remove(fullPath)
|
||||
}
|
||||
|
||||
func (driver Native) Upload(file *model.FileStream, account *model.Account) error {
|
||||
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 {
|
||||
// TODO overwrite?
|
||||
}
|
||||
basePath := filepath.Dir(fullPath)
|
||||
if !utils.Exists(basePath) {
|
||||
err := os.MkdirAll(basePath, 0744)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
out, err := os.Create(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Native)(nil)
|
||||
@@ -1,278 +0,0 @@
|
||||
package onedrive
|
||||
|
||||
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"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Onedrive struct{}
|
||||
|
||||
func (driver Onedrive) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Onedrive",
|
||||
NoNeedSetLink: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Onedrive) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "zone",
|
||||
Label: "zone",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "global,cn,us,de",
|
||||
Description: "",
|
||||
},
|
||||
{
|
||||
Name: "internal_type",
|
||||
Label: "onedrive type",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "onedrive,sharepoint",
|
||||
},
|
||||
{
|
||||
Name: "client_id",
|
||||
Label: "client id",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "client_secret",
|
||||
Label: "client secret",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "redirect_uri",
|
||||
Label: "redirect uri",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "refresh_token",
|
||||
Label: "refresh token",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "site_id",
|
||||
Label: "site id",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder path",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,size,lastModifiedDateTime",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: base.TypeSelect,
|
||||
Values: "asc,desc",
|
||||
Required: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver Onedrive) 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 Onedrive) 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
|
||||
}
|
||||
rawFiles, err := driver.GetFiles(account, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
for _, file := range rawFiles {
|
||||
files = append(files, *driver.FormatFile(&file))
|
||||
}
|
||||
if len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Onedrive) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.GetFile(account, args.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.File == nil {
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
link := base.Link{
|
||||
Url: file.Url,
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Onedrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
log.Debugf("onedrive 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 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
|
||||
}
|
||||
|
||||
func (driver Onedrive) MakeDir(path string, account *model.Account) error {
|
||||
url := driver.GetMetaUrl(account, false, utils.Dir(path)) + "/children"
|
||||
data := base.Json{
|
||||
"name": utils.Base(path),
|
||||
"folder": base.Json{},
|
||||
"@microsoft.graph.conflictBehavior": "rename",
|
||||
}
|
||||
_, err := driver.Request(url, base.Post, nil, nil, nil, &data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Onedrive) Move(src string, dst string, account *model.Account) error {
|
||||
dstParentFile, err := driver.GetFile(account, utils.Dir(dst))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"parentReference": base.Json{
|
||||
"id": dstParentFile.Id,
|
||||
},
|
||||
"name": utils.Base(dst),
|
||||
}
|
||||
url := driver.GetMetaUrl(account, false, src)
|
||||
_, err = driver.Request(url, base.Patch, nil, nil, nil, &data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Onedrive) Rename(src string, dst string, account *model.Account) error {
|
||||
return driver.Move(src, dst, account)
|
||||
}
|
||||
|
||||
func (driver Onedrive) Copy(src string, dst string, account *model.Account) error {
|
||||
dstParentFile, err := driver.GetFile(account, utils.Dir(dst))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"parentReference": base.Json{
|
||||
"driveId": dstParentFile.ParentReference.DriveId,
|
||||
"id": dstParentFile.Id,
|
||||
},
|
||||
"name": utils.Base(dst),
|
||||
}
|
||||
url := driver.GetMetaUrl(account, false, src) + "/copy"
|
||||
_, err = driver.Request(url, base.Post, nil, nil, nil, &data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Onedrive) Delete(path string, account *model.Account) error {
|
||||
url := driver.GetMetaUrl(account, false, path)
|
||||
_, err := driver.Request(url, base.Delete, nil, nil, nil, nil, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Onedrive) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
var err error
|
||||
if file.GetSize() <= 4*1024*1024 {
|
||||
err = driver.UploadSmall(file, account)
|
||||
} else {
|
||||
err = driver.UploadBig(file, account)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Onedrive)(nil)
|
||||
@@ -1,318 +0,0 @@
|
||||
package onedrive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Host struct {
|
||||
Oauth string
|
||||
Api string
|
||||
}
|
||||
|
||||
var onedriveHostMap = map[string]Host{
|
||||
"global": {
|
||||
Oauth: "https://login.microsoftonline.com",
|
||||
Api: "https://graph.microsoft.com",
|
||||
},
|
||||
"cn": {
|
||||
Oauth: "https://login.chinacloudapi.cn",
|
||||
Api: "https://microsoftgraph.chinacloudapi.cn",
|
||||
},
|
||||
"us": {
|
||||
Oauth: "https://login.microsoftonline.us",
|
||||
Api: "https://graph.microsoft.us",
|
||||
},
|
||||
"de": {
|
||||
Oauth: "https://login.microsoftonline.de",
|
||||
Api: "https://graph.microsoft.de",
|
||||
},
|
||||
}
|
||||
|
||||
func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string {
|
||||
path = filepath.Join(account.RootFolder, path)
|
||||
//log.Debugf(path)
|
||||
host, _ := onedriveHostMap[account.Zone]
|
||||
if auth {
|
||||
return host.Oauth
|
||||
}
|
||||
switch account.InternalType {
|
||||
case "onedrive":
|
||||
{
|
||||
if path == "/" || path == "\\" {
|
||||
return fmt.Sprintf("%s/v1.0/me/drive/root", host.Api)
|
||||
} else {
|
||||
return fmt.Sprintf("%s/v1.0/me/drive/root:%s:", host.Api, path)
|
||||
}
|
||||
}
|
||||
case "sharepoint":
|
||||
{
|
||||
if path == "/" || path == "\\" {
|
||||
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root", host.Api, account.SiteId)
|
||||
} else {
|
||||
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root:%s:", host.Api, account.SiteId, path)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type OneTokenErr struct {
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
}
|
||||
|
||||
func (driver Onedrive) RefreshToken(account *model.Account) error {
|
||||
err := driver.refreshToken(account)
|
||||
if err != nil && err == base.ErrEmptyToken {
|
||||
return driver.refreshToken(account)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
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 := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": account.ClientId,
|
||||
"client_secret": account.ClientSecret,
|
||||
"redirect_uri": account.RedirectUri,
|
||||
"refresh_token": account.RefreshToken,
|
||||
}).Post(url)
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
return err
|
||||
}
|
||||
if e.Error != "" {
|
||||
account.Status = e.ErrorDescription
|
||||
return fmt.Errorf("%s", e.ErrorDescription)
|
||||
} else {
|
||||
account.Status = "work"
|
||||
}
|
||||
if resp.RefreshToken == "" {
|
||||
account.Status = base.ErrEmptyToken.Error()
|
||||
return base.ErrEmptyToken
|
||||
}
|
||||
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
|
||||
return nil
|
||||
}
|
||||
|
||||
type OneFile struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
LastModifiedDateTime *time.Time `json:"lastModifiedDateTime"`
|
||||
Url string `json:"@microsoft.graph.downloadUrl"`
|
||||
File *struct {
|
||||
MimeType string `json:"mimeType"`
|
||||
} `json:"file"`
|
||||
Thumbnails []struct {
|
||||
Medium struct {
|
||||
Url string `json:"url"`
|
||||
} `json:"medium"`
|
||||
} `json:"thumbnails"`
|
||||
ParentReference struct {
|
||||
DriveId string `json:"driveId"`
|
||||
} `json:"parentReference"`
|
||||
}
|
||||
|
||||
type OneFiles struct {
|
||||
Value []OneFile `json:"value"`
|
||||
NextLink string `json:"@odata.nextLink"`
|
||||
}
|
||||
|
||||
type OneRespErr struct {
|
||||
Error struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func (driver Onedrive) FormatFile(file *OneFile) *model.File {
|
||||
f := &model.File{
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
UpdatedAt: file.LastModifiedDateTime,
|
||||
Driver: driver.Config().Name,
|
||||
Url: file.Url,
|
||||
Id: file.Id,
|
||||
}
|
||||
if len(file.Thumbnails) > 0 {
|
||||
f.Thumbnail = file.Thumbnails[0].Medium.Url
|
||||
}
|
||||
if file.File == nil {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile, error) {
|
||||
var res []OneFile
|
||||
nextLink := driver.GetMetaUrl(account, false, path) + "/children?$expand=thumbnails"
|
||||
if account.OrderBy != "" {
|
||||
nextLink += fmt.Sprintf("&orderby=%s", account.OrderBy)
|
||||
if account.OrderDirection != "" {
|
||||
nextLink += fmt.Sprintf("%%20%s", account.OrderDirection)
|
||||
}
|
||||
}
|
||||
for nextLink != "" {
|
||||
var files OneFiles
|
||||
_, 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)
|
||||
//}
|
||||
res = append(res, files.Value...)
|
||||
nextLink = files.NextLink
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) {
|
||||
var file OneFile
|
||||
//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)
|
||||
//}
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
func (driver Onedrive) Request(url string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
rawUrl := url
|
||||
if account.APIProxyUrl != "" {
|
||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
||||
}
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Authorization", "Bearer "+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 OneRespErr
|
||||
req.SetError(&e)
|
||||
switch method {
|
||||
case base.Get:
|
||||
res, err = req.Get(url)
|
||||
case base.Post:
|
||||
res, err = req.Post(url)
|
||||
case base.Patch:
|
||||
res, err = req.Patch(url)
|
||||
case base.Delete:
|
||||
res, err = req.Delete(url)
|
||||
case base.Put:
|
||||
res, err = req.Put(url)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//log.Debug(res.String())
|
||||
if e.Error.Code != "" {
|
||||
if e.Error.Code == "InvalidAuthenticationToken" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
_ = model.SaveAccount(account)
|
||||
return nil, err
|
||||
}
|
||||
return driver.Request(rawUrl, method, headers, query, form, data, resp, account)
|
||||
}
|
||||
return nil, errors.New(e.Error.Message)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (driver Onedrive) UploadSmall(file *model.FileStream, account *model.Account) error {
|
||||
url := driver.GetMetaUrl(account, false, utils.Join(file.ParentPath, file.Name)) + "/content"
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = driver.Request(url, base.Put, nil, nil, nil, data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Onedrive) UploadBig(file *model.FileStream, account *model.Account) error {
|
||||
url := driver.GetMetaUrl(account, false, utils.Join(file.ParentPath, file.Name)) + "/createUploadSession"
|
||||
res, err := driver.Request(url, base.Post, nil, nil, nil, nil, nil, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
|
||||
var finish uint64 = 0
|
||||
const DEFAULT = 4 * 1024 * 1024
|
||||
for finish < file.GetSize() {
|
||||
log.Debugf("upload: %d", finish)
|
||||
var byteSize uint64 = DEFAULT
|
||||
left := file.GetSize() - finish
|
||||
if left < DEFAULT {
|
||||
byteSize = left
|
||||
}
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(file, byteData)
|
||||
log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("PUT", uploadUrl, bytes.NewBuffer(byteData))
|
||||
req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, file.Size))
|
||||
finish += byteSize
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if res.StatusCode != 201 && res.StatusCode != 202 {
|
||||
data, _ := ioutil.ReadAll(res.Body)
|
||||
return errors.New(string(data))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Onedrive{})
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package operate
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("mkdir error: %s", err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
var err error
|
||||
if rename {
|
||||
err = driver.Rename(src, dst, account)
|
||||
} else {
|
||||
err = driver.Move(src, dst, account)
|
||||
}
|
||||
if err == nil && clearCache {
|
||||
_ = base.DeleteCache(utils.Dir(src), account)
|
||||
if !rename {
|
||||
_ = base.DeleteCache(utils.Dir(dst), account)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("move error: %s", err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("copy error: %s", err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("delete error: %s", err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func Upload(driver base.Driver, account *model.Account, file *model.FileStream, clearCache bool) error {
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
err := driver.Upload(file, account)
|
||||
if err == nil && clearCache {
|
||||
_ = base.DeleteCache(file.ParentPath, account)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("upload error: %s", err.Error())
|
||||
}
|
||||
debug.FreeOSMemory()
|
||||
return err
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
package pikpak
|
||||
|
||||
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/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PikPak struct{}
|
||||
|
||||
func (driver PikPak) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "PikPak",
|
||||
ApiProxy: true,
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver PikPak) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "username",
|
||||
Label: "username",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Label: "password",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder id",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver PikPak) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
err := driver.Login(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver PikPak) 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 PikPak) 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
|
||||
}
|
||||
rawFiles, err := driver.GetFiles(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = make([]model.File, 0)
|
||||
for _, file := range rawFiles {
|
||||
files = append(files, *driver.FormatFile(&file))
|
||||
}
|
||||
if len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver PikPak) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(args.Path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp File
|
||||
_, err = driver.Request(fmt.Sprintf("https://api-drive.mypikpak.com/drive/v1/files/%s?_magic=2021&thumbnail_size=SIZE_LARGE", file.Id),
|
||||
base.Get, nil, nil, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link := base.Link{
|
||||
Url: resp.WebContentLink,
|
||||
}
|
||||
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) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("pikpak 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 PikPak) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver PikPak) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver PikPak) MakeDir(path string, account *model.Account) error {
|
||||
path = utils.ParsePath(path)
|
||||
dir, name := filepath.Split(path)
|
||||
parentFile, err := driver.File(dir, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !parentFile.IsDir() {
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Post, nil, &base.Json{
|
||||
"kind": "drive#folder",
|
||||
"parent_id": parentFile.Id,
|
||||
"name": name,
|
||||
}, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver PikPak) Move(src string, dst string, account *model.Account) error {
|
||||
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.Request("https://api-drive.mypikpak.com/drive/v1/files:batchMove", base.Post, nil, &base.Json{
|
||||
"ids": []string{srcFile.Id},
|
||||
"to": base.Json{
|
||||
"parent_id": dstDirFile.Id,
|
||||
},
|
||||
}, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver PikPak) 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 = driver.Request("https://api-drive.mypikpak.com/drive/v1/files/"+srcFile.Id, base.Patch, nil, &base.Json{
|
||||
"name": dstName,
|
||||
}, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver PikPak) Copy(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstDirFile, err := driver.File(utils.Dir(dst), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchCopy", base.Post, nil, &base.Json{
|
||||
"ids": []string{srcFile.Id},
|
||||
"to": base.Json{
|
||||
"parent_id": dstDirFile.Id,
|
||||
},
|
||||
}, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver PikPak) Delete(path string, account *model.Account) error {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchTrash", base.Post, nil, &base.Json{
|
||||
"ids": []string{file.Id},
|
||||
}, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver PikPak) 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
|
||||
}
|
||||
data := base.Json{
|
||||
"kind": "drive#file",
|
||||
"name": file.GetFileName(),
|
||||
"size": file.GetSize(),
|
||||
"hash": "1CF254FBC456E1B012CD45C546636AA62CF8350E",
|
||||
"upload_type": "UPLOAD_TYPE_RESUMABLE",
|
||||
"objProvider": base.Json{"provider": "UPLOAD_TYPE_UNKNOWN"},
|
||||
"parent_id": parentFile.Id,
|
||||
}
|
||||
res, err := driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Post, nil, &data, nil, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params := jsoniter.Get(res, "resumable").Get("params")
|
||||
endpoint := params.Get("endpoint").ToString()
|
||||
endpointS := strings.Split(endpoint, ".")
|
||||
endpoint = strings.Join(endpointS[1:], ".")
|
||||
accessKeyId := params.Get("access_key_id").ToString()
|
||||
accessKeySecret := params.Get("access_key_secret").ToString()
|
||||
securityToken := params.Get("security_token").ToString()
|
||||
key := params.Get("key").ToString()
|
||||
bucket := params.Get("bucket").ToString()
|
||||
cfg := &aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(accessKeyId, accessKeySecret, securityToken),
|
||||
Region: aws.String("pikpak"),
|
||||
Endpoint: &endpoint,
|
||||
}
|
||||
s, err := session.NewSession(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploader := s3manager.NewUploader(s)
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
Body: file,
|
||||
}
|
||||
_, err = uploader.Upload(input)
|
||||
return err
|
||||
}
|
||||
|
||||
// use aliyun-oss-sdk
|
||||
//func (driver PikPak) 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
|
||||
// }
|
||||
// data := base.Json{
|
||||
// "kind": "drive#file",
|
||||
// "name": file.GetFileName(),
|
||||
// "size": file.GetSize(),
|
||||
// "hash": "1CF254FBC456E1B012CD45C546636AA62CF8350E",
|
||||
// "upload_type": "UPLOAD_TYPE_RESUMABLE",
|
||||
// "objProvider": base.Json{"provider": "UPLOAD_TYPE_UNKNOWN"},
|
||||
// "parent_id": parentFile.Id,
|
||||
// }
|
||||
// res, err := driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Post, nil, &data, nil, account)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// params := jsoniter.Get(res, "resumable").Get("params")
|
||||
// endpoint := params.Get("endpoint").ToString()
|
||||
// endpointS := strings.Split(endpoint, ".")
|
||||
// endpoint = strings.Join(endpointS[1:], ".")
|
||||
// accessKeyId := params.Get("access_key_id").ToString()
|
||||
// accessKeySecret := params.Get("access_key_secret").ToString()
|
||||
// securityToken := params.Get("security_token").ToString()
|
||||
// client, err := oss.New("https://"+endpoint, accessKeyId,
|
||||
// accessKeySecret, oss.SecurityToken(securityToken))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// bucket, err := client.Bucket(params.Get("bucket").ToString())
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// signedURL, err := bucket.SignURL(params.Get("key").ToString(), oss.HTTPPut, 60)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// err = bucket.PutObjectWithURL(signedURL, file)
|
||||
// return err
|
||||
//}
|
||||
|
||||
var _ base.Driver = (*PikPak)(nil)
|
||||
@@ -1,232 +0,0 @@
|
||||
package pikpak
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RespErr struct {
|
||||
ErrorCode int `json:"error_code"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func (driver PikPak) Login(account *model.Account) error {
|
||||
url := "https://user.mypikpak.com/v1/auth/signin"
|
||||
if account.APIProxyUrl != "" {
|
||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
||||
}
|
||||
var e RespErr
|
||||
res, err := base.RestyClient.R().SetError(&e).SetBody(base.Json{
|
||||
"captcha_token": "",
|
||||
"client_id": "YNxT9w7GMdWvEOKa",
|
||||
"client_secret": "dbw2OtmVEeuUvIptb1Coyg",
|
||||
"username": account.Username,
|
||||
"password": account.Password,
|
||||
}).Post(url)
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if e.ErrorCode != 0 {
|
||||
account.Status = e.Error
|
||||
err = errors.New(e.Error)
|
||||
} else {
|
||||
data := res.Body()
|
||||
account.Status = "work"
|
||||
account.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
|
||||
account.AccessToken = jsoniter.Get(data, "access_token").ToString()
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver PikPak) RefreshToken(account *model.Account) error {
|
||||
url := "https://user.mypikpak.com/v1/auth/token"
|
||||
if account.APIProxyUrl != "" {
|
||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
||||
}
|
||||
var e RespErr
|
||||
res, err := base.RestyClient.R().SetError(&e).
|
||||
SetHeader("user-agent", "").SetBody(base.Json{
|
||||
"client_id": "YNxT9w7GMdWvEOKa",
|
||||
"client_secret": "dbw2OtmVEeuUvIptb1Coyg",
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": account.RefreshToken,
|
||||
}).Post(url)
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
return err
|
||||
}
|
||||
if e.ErrorCode != 0 {
|
||||
if e.ErrorCode == 4126 {
|
||||
// refresh_token 失效,重新登陆
|
||||
return driver.Login(account)
|
||||
}
|
||||
account.Status = e.Error
|
||||
_ = model.SaveAccount(account)
|
||||
return errors.New(e.Error)
|
||||
}
|
||||
data := res.Body()
|
||||
account.Status = "work"
|
||||
account.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
|
||||
account.AccessToken = jsoniter.Get(data, "access_token").ToString()
|
||||
log.Debugf("%s\n %+v", res.String(), account)
|
||||
_ = model.SaveAccount(account)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver PikPak) Request(url string, method int, query map[string]string, data *base.Json, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
rawUrl := url
|
||||
if account.APIProxyUrl != "" {
|
||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
||||
}
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
|
||||
if query != nil {
|
||||
req.SetQueryParams(query)
|
||||
}
|
||||
if data != nil {
|
||||
req.SetBody(data)
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
var e RespErr
|
||||
req.SetError(&e)
|
||||
var res *resty.Response
|
||||
var err error
|
||||
switch method {
|
||||
case base.Get:
|
||||
res, err = req.Get(url)
|
||||
case base.Post:
|
||||
res, err = req.Post(url)
|
||||
case base.Patch:
|
||||
res, err = req.Patch(url)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if e.ErrorCode != 0 {
|
||||
if e.ErrorCode == 16 {
|
||||
// login / refresh token
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver.Request(rawUrl, method, query, data, resp, account)
|
||||
} else {
|
||||
return nil, errors.New(e.Error)
|
||||
}
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Id string `json:"id"`
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
ModifiedTime *time.Time `json:"modified_time"`
|
||||
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 {
|
||||
size, _ := strconv.ParseInt(file.Size, 10, 64)
|
||||
f := &model.File{
|
||||
Id: file.Id,
|
||||
Name: file.Name,
|
||||
Size: size,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: file.ModifiedTime,
|
||||
Thumbnail: file.ThumbnailLink,
|
||||
}
|
||||
if file.Kind == "drive#folder" {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
type Files struct {
|
||||
Files []File `json:"files"`
|
||||
NextPageToken string `json:"next_page_token"`
|
||||
}
|
||||
|
||||
func (driver PikPak) GetFiles(id string, account *model.Account) ([]File, error) {
|
||||
res := make([]File, 0)
|
||||
pageToken := "first"
|
||||
for pageToken != "" {
|
||||
if pageToken == "first" {
|
||||
pageToken = ""
|
||||
}
|
||||
query := map[string]string{
|
||||
"parent_id": id,
|
||||
"thumbnail_size": "SIZE_LARGE",
|
||||
"with_audit": "true",
|
||||
"limit": "100",
|
||||
"filters": `{"phase":{"eq":"PHASE_TYPE_COMPLETE"},"trashed":{"eq":false}}`,
|
||||
"page_token": pageToken,
|
||||
}
|
||||
var resp Files
|
||||
_, err := driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Get, query, nil, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("%+v", resp)
|
||||
pageToken = resp.NextPageToken
|
||||
res = append(res, resp.Files...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&PikPak{})
|
||||
}
|
||||
@@ -1,320 +0,0 @@
|
||||
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)
|
||||
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)
|
||||
@@ -1,266 +0,0 @@
|
||||
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/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
|
||||
}
|
||||
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{})
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
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,289 +0,0 @@
|
||||
package s3
|
||||
|
||||
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/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type S3 struct {
|
||||
}
|
||||
|
||||
func (driver S3) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "S3",
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver S3) 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: "region",
|
||||
Label: "Region",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "access_key",
|
||||
Label: "Access Key",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "access_secret",
|
||||
Label: "Access Secret",
|
||||
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,
|
||||
Description: "default 4 hours",
|
||||
},
|
||||
{
|
||||
Name: "zone",
|
||||
Label: "placeholder filename",
|
||||
Type: base.TypeString,
|
||||
Description: "default empty string",
|
||||
},
|
||||
{
|
||||
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
|
||||
}
|
||||
client, err := driver.NewSession(account)
|
||||
if err != nil {
|
||||
account.Status = err.Error()
|
||||
} else {
|
||||
sessionsMap[account.Name] = client
|
||||
account.Status = "work"
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver S3) 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 S3) 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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
return files, err
|
||||
}
|
||||
|
||||
func (driver S3) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
client, err := driver.GetClient(account, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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,
|
||||
}
|
||||
if account.CustomHost == "" {
|
||||
input.ResponseContentDisposition = &disposition
|
||||
}
|
||||
req, _ := client.GetObjectRequest(input)
|
||||
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
|
||||
}
|
||||
return &base.Link{
|
||||
Url: link,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (driver S3) 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 S3) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver S3) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver S3) MakeDir(path string, account *model.Account) error {
|
||||
// not support, default as success
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver S3) Move(src string, dst string, account *model.Account) error {
|
||||
err := driver.Copy(src, dst, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return driver.Delete(src, account)
|
||||
}
|
||||
|
||||
func (driver S3) Rename(src string, dst string, account *model.Account) error {
|
||||
return driver.Move(src, dst, account)
|
||||
}
|
||||
|
||||
func (driver S3) Copy(src string, dst string, account *model.Account) error {
|
||||
client, err := driver.GetClient(account, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcKey := driver.GetKey(src, account, srcFile.IsDir())
|
||||
dstKey := driver.GetKey(dst, account, srcFile.IsDir())
|
||||
input := &s3.CopyObjectInput{
|
||||
Bucket: &account.Bucket,
|
||||
CopySource: &srcKey,
|
||||
Key: &dstKey,
|
||||
}
|
||||
_, err = client.CopyObject(input)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver S3) Delete(path string, account *model.Account) error {
|
||||
client, err := driver.GetClient(account, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := driver.GetKey(path, account, file.IsDir())
|
||||
input := &s3.DeleteObjectInput{
|
||||
Bucket: &account.Bucket,
|
||||
Key: &key,
|
||||
}
|
||||
_, err = client.DeleteObject(input)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver S3) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
s, ok := sessionsMap[account.Name]
|
||||
if !ok {
|
||||
return fmt.Errorf("can't find [%s] session", account.Name)
|
||||
}
|
||||
uploader := s3manager.NewUploader(s)
|
||||
key := driver.GetKey(utils.Join(file.ParentPath, file.GetFileName()), account, false)
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &account.Bucket,
|
||||
Key: &key,
|
||||
Body: file,
|
||||
}
|
||||
_, err := uploader.Upload(input)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*S3)(nil)
|
||||
195
drivers/s3/s3.go
195
drivers/s3/s3.go
@@ -1,195 +0,0 @@
|
||||
package s3
|
||||
|
||||
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/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var sessionsMap map[string]*session.Session
|
||||
|
||||
func (driver S3) NewSession(account *model.Account) (*session.Session, error) {
|
||||
cfg := &aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(account.AccessKey, account.AccessSecret, ""),
|
||||
Region: &account.Region,
|
||||
Endpoint: &account.Endpoint,
|
||||
S3ForcePathStyle: aws.Bool(account.Bool1),
|
||||
}
|
||||
return session.NewSession(cfg)
|
||||
}
|
||||
|
||||
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 link && account.CustomHost != "" {
|
||||
cURL, err := url.Parse(account.CustomHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.Handlers.Build.PushBack(func(r *request.Request) {
|
||||
if r.HTTPRequest.Method != http.MethodGet {
|
||||
return
|
||||
}
|
||||
r.HTTPRequest.URL.Scheme = cURL.Scheme
|
||||
r.HTTPRequest.URL.Host = cURL.Host
|
||||
})
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
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, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
marker := ""
|
||||
for {
|
||||
input := &s3.ListObjectsInput{
|
||||
Bucket: &account.Bucket,
|
||||
Marker: &marker,
|
||||
Prefix: &prefix,
|
||||
Delimiter: aws.String("/"),
|
||||
}
|
||||
listObjectsResult, err := client.ListObjects(input)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 == 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 listObjectsResult.IsTruncated == nil {
|
||||
return nil, errors.New("IsTruncated nil")
|
||||
}
|
||||
if *listObjectsResult.IsTruncated {
|
||||
marker = *listObjectsResult.NextMarker
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
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 == 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, "/")
|
||||
if path != "" && dir {
|
||||
path += "/"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func init() {
|
||||
sessionsMap = make(map[string]*session.Session)
|
||||
base.RegisterDriver(&S3{})
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
package shandian
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Shandian struct{}
|
||||
|
||||
func (driver Shandian) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "ShandianPan",
|
||||
NoNeedSetLink: true,
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Shandian) 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: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Shandian) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "0"
|
||||
}
|
||||
return driver.Login(account)
|
||||
}
|
||||
|
||||
func (driver Shandian) 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 Shandian) 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
|
||||
}
|
||||
rawFiles, err := driver.GetFiles(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = make([]model.File, 0)
|
||||
for _, file := range rawFiles {
|
||||
files = append(files, *driver.FormatFile(&file))
|
||||
}
|
||||
if len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Shandian) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
log.Debugf("shandian link")
|
||||
file, err := driver.File(args.Path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var e Resp
|
||||
res, err := base.NoRedirectClient.R().SetError(&e).SetHeader("Accept", "application/json").SetQueryParams(map[string]string{
|
||||
"id": file.Id,
|
||||
"token": account.AccessToken,
|
||||
}).Get("https://shandianpan.com/api/pan/file-download")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Code != 0 {
|
||||
if e.Code == 10 {
|
||||
err = driver.Login(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver.Link(args, account)
|
||||
}
|
||||
return nil, errors.New(e.Msg)
|
||||
}
|
||||
return &base.Link{
|
||||
Url: res.Header().Get("location"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (driver Shandian) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("shandian 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 Shandian) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver Shandian) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Shandian) MakeDir(path string, account *model.Account) error {
|
||||
parentFile, err := driver.File(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"id": parentFile.Id,
|
||||
"name": utils.Base(path),
|
||||
}
|
||||
_, err = driver.Post("https://shandianpan.com/api/pan/mkdir", data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Shandian) 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 := map[string]interface{}{
|
||||
"id": srcFile.Id,
|
||||
"to_id": dstParentFile.Id,
|
||||
}
|
||||
_, err = driver.Post("https://shandianpan.com/api/pan/move", data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Shandian) Rename(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"id": srcFile.Id,
|
||||
"name": utils.Base(dst),
|
||||
}
|
||||
_, err = driver.Post("https://shandianpan.com/api/pan/change", data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Shandian) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Shandian) Delete(path string, account *model.Account) error {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"id": file.Id,
|
||||
}
|
||||
_, err = driver.Post("https://shandianpan.com/api/pan/recycle-in", data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Shandian) 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
|
||||
}
|
||||
var resp UploadResp
|
||||
parentId, err := strconv.Atoi(parentFile.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"id": parentId,
|
||||
"name": file.GetFileName(),
|
||||
}
|
||||
res, err := driver.Post("https://shandianpan.com/api/pan/upload", data, nil, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = utils.Json.Unmarshal(res, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Code != 0 {
|
||||
if resp.Code == 10 {
|
||||
err = driver.Login(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return driver.Upload(file, account)
|
||||
}
|
||||
return errors.New(resp.Msg)
|
||||
}
|
||||
var r Resp
|
||||
_, err = base.RestyClient.R().SetMultipartFormData(map[string]string{
|
||||
"token": account.AccessToken,
|
||||
"id": "0",
|
||||
"key": resp.Data.Key,
|
||||
"ossAccessKeyId": resp.Data.Accessid,
|
||||
"policy": resp.Data.Policy,
|
||||
"signature": resp.Data.Signature,
|
||||
"callback": resp.Data.Callback,
|
||||
}).SetMultipartField("file", file.GetFileName(), file.GetMIMEType(), file).
|
||||
SetResult(&r).SetError(&r).Post("https:" + resp.Data.Host + "/")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.Code == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(r.Msg)
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Shandian)(nil)
|
||||
@@ -1,150 +0,0 @@
|
||||
package shandian
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Resp struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
type LoginResp struct {
|
||||
Resp
|
||||
Data struct {
|
||||
Token string `json:"token"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func (driver Shandian) Login(account *model.Account) error {
|
||||
var resp LoginResp
|
||||
_, err := base.RestyClient.R().SetResult(&resp).SetHeader("Accept", "application/json").SetBody(base.Json{
|
||||
"mobile": account.Username,
|
||||
"password": account.Password,
|
||||
"smscode": "",
|
||||
}).Post("https://shandianpan.com/api/login")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Code != 0 {
|
||||
account.Status = resp.Msg
|
||||
err = errors.New(resp.Msg)
|
||||
} else {
|
||||
account.Status = "work"
|
||||
account.AccessToken = resp.Data.Token
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Id int64 `json:"id"`
|
||||
Type int `json:"type"`
|
||||
Name string `json:"name"`
|
||||
UpdateTime int64 `json:"update_time"`
|
||||
Size int64 `json:"size"`
|
||||
Ext string `json:"ext"`
|
||||
}
|
||||
|
||||
func (driver Shandian) FormatFile(file *File) *model.File {
|
||||
t := time.Unix(file.UpdateTime, 0)
|
||||
f := &model.File{
|
||||
Id: strconv.FormatInt(file.Id, 10),
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: &t,
|
||||
}
|
||||
if file.Type == 1 {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
f.Type = utils.GetFileType(file.Ext)
|
||||
if file.Ext != "" {
|
||||
f.Name += "." + file.Ext
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (driver Shandian) Post(url string, data map[string]interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Accept", "application/json")
|
||||
data["token"] = account.AccessToken
|
||||
req.SetBody(data)
|
||||
var e Resp
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
} else {
|
||||
req.SetResult(&e)
|
||||
}
|
||||
req.SetError(&e)
|
||||
res, err := req.Post(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if e.Code != 0 {
|
||||
if e.Code == 10 {
|
||||
err = driver.Login(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver.Post(url, data, resp, account)
|
||||
}
|
||||
return nil, errors.New(e.Msg)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
type FilesResp struct {
|
||||
Resp
|
||||
Data []File `json:"data"`
|
||||
}
|
||||
|
||||
func (driver Shandian) GetFiles(id string, account *model.Account) ([]File, error) {
|
||||
// TODO page not wok
|
||||
//res := make([]File, 0)
|
||||
page := 1
|
||||
//for {
|
||||
data := map[string]interface{}{
|
||||
"id": id,
|
||||
"page": page,
|
||||
"page_size": 100,
|
||||
}
|
||||
var resp FilesResp
|
||||
_, err := driver.Post("https://shandianpan.com/api/pan", data, &resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//res = append(res, resp.Data...)
|
||||
// if len(resp.Data) == 0 {
|
||||
// break
|
||||
// }
|
||||
//}
|
||||
//return res, nil
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
type UploadResp struct {
|
||||
Resp
|
||||
Data struct {
|
||||
Accessid string `json:"accessid"`
|
||||
Policy string `json:"policy"`
|
||||
Expire int `json:"expire"`
|
||||
Callback string `json:"callback"`
|
||||
Key string `json:"key"`
|
||||
Host string `json:"host"`
|
||||
Signature string `json:"signature"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Shandian{})
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
package teambition
|
||||
|
||||
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"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Teambition struct{}
|
||||
|
||||
func (driver Teambition) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Teambition",
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Teambition) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "internal_type",
|
||||
Label: "Teambition type",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "China,International",
|
||||
},
|
||||
{
|
||||
Name: "access_token",
|
||||
Label: "Cookie",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Description: "Unknown expiration time",
|
||||
},
|
||||
{
|
||||
Name: "zone",
|
||||
Label: "Project id",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "fileName,fileSize,updated,created",
|
||||
Required: true,
|
||||
Default: "fileName",
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
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
|
||||
}
|
||||
|
||||
func (driver Teambition) 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 Teambition) 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 Teambition) 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
|
||||
}
|
||||
url := file.Url
|
||||
res, err := base.NoRedirectClient.R().Get(url)
|
||||
if res.StatusCode() == 302 {
|
||||
url = res.Header().Get("location")
|
||||
}
|
||||
return &base.Link{Url: url}, nil
|
||||
}
|
||||
|
||||
func (driver Teambition) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("teambition 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 Teambition) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver Teambition) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Teambition) MakeDir(path string, account *model.Account) error {
|
||||
parentFile, err := driver.File(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := base.Json{
|
||||
"objectType": "collection",
|
||||
"_projectId": account.Zone,
|
||||
"_creatorId": "",
|
||||
"created": "",
|
||||
"updated": "",
|
||||
"title": utils.Base(path),
|
||||
"color": "blue",
|
||||
"description": "",
|
||||
"workCount": 0,
|
||||
"collectionType": "",
|
||||
"recentWorks": []interface{}{},
|
||||
"_parentId": parentFile.Id,
|
||||
"subCount": nil,
|
||||
}
|
||||
_, err = driver.Request("/api/collections", base.Post, nil, nil, nil, &data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Teambition) 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
|
||||
}
|
||||
pre := "/api/works/"
|
||||
if srcFile.IsDir() {
|
||||
pre = "/api/collections/"
|
||||
}
|
||||
_, err = driver.Request(pre+srcFile.Id+"/move", base.Put, nil, nil, nil, &base.Json{
|
||||
"_parentId": dstParentFile.Id,
|
||||
}, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Teambition) Rename(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pre := "/api/works/"
|
||||
data := base.Json{
|
||||
"fileName": utils.Base(dst),
|
||||
}
|
||||
if srcFile.IsDir() {
|
||||
pre = "/api/collections/"
|
||||
data = base.Json{
|
||||
"title": utils.Base(dst),
|
||||
}
|
||||
}
|
||||
_, err = driver.Request(pre+srcFile.Id, base.Put, nil, nil, nil, &data, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Teambition) Copy(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
|
||||
}
|
||||
pre := "/api/works/"
|
||||
if srcFile.IsDir() {
|
||||
pre = "/api/collections/"
|
||||
}
|
||||
_, err = driver.Request(pre+srcFile.Id+"/fork", base.Put, nil, nil, nil, &base.Json{
|
||||
"_parentId": dstParentFile.Id,
|
||||
}, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Teambition) Delete(path string, account *model.Account) error {
|
||||
srcFile, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pre := "/api/works/"
|
||||
if srcFile.IsDir() {
|
||||
pre = "/api/collections/"
|
||||
}
|
||||
_, err = driver.Request(pre+srcFile.Id+"/archive", base.Post, nil, nil, nil, nil, nil, account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Teambition) Upload(file *model.FileStream, account *model.Account) error {
|
||||
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)
|
||||
@@ -1,237 +0,0 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type ErrResp struct {
|
||||
Name string `json:"name"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (driver Teambition) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||
url := "https://www.teambition.com" + pathname
|
||||
if account.InternalType == "International" {
|
||||
url = "https://us.teambition.com" + pathname
|
||||
}
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Cookie", 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 e ErrResp
|
||||
var err error
|
||||
var res *resty.Response
|
||||
req.SetError(&e)
|
||||
switch method {
|
||||
case base.Get:
|
||||
res, err = req.Get(url)
|
||||
case base.Post:
|
||||
res, err = req.Post(url)
|
||||
case base.Delete:
|
||||
res, err = req.Delete(url)
|
||||
case base.Patch:
|
||||
res, err = req.Patch(url)
|
||||
case base.Put:
|
||||
res, err = req.Put(url)
|
||||
default:
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Name != "" {
|
||||
return nil, errors.New(e.Message)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]model.File, error) {
|
||||
files := make([]model.File, 0)
|
||||
page := 1
|
||||
for {
|
||||
var collections []Collection
|
||||
_, err := driver.Request("/api/collections", base.Get, nil, map[string]string{
|
||||
"_parentId": parentId,
|
||||
"_projectId": account.Zone,
|
||||
"order": account.OrderBy + account.OrderDirection,
|
||||
"count": "50",
|
||||
"page": strconv.Itoa(page),
|
||||
}, nil, nil, &collections, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(collections) == 0 {
|
||||
break
|
||||
}
|
||||
page++
|
||||
for _, collection := range collections {
|
||||
if collection.Title == "" {
|
||||
continue
|
||||
}
|
||||
files = append(files, model.File{
|
||||
Id: collection.ID,
|
||||
Name: collection.Title,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: collection.Updated,
|
||||
})
|
||||
}
|
||||
}
|
||||
page = 1
|
||||
for {
|
||||
var works []Work
|
||||
_, err := driver.Request("/api/works", base.Get, nil, map[string]string{
|
||||
"_parentId": parentId,
|
||||
"_projectId": account.Zone,
|
||||
"order": account.OrderBy + account.OrderDirection,
|
||||
"count": "50",
|
||||
"page": strconv.Itoa(page),
|
||||
}, nil, nil, &works, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(works) == 0 {
|
||||
break
|
||||
}
|
||||
page++
|
||||
for _, work := range works {
|
||||
files = append(files, model.File{
|
||||
Id: work.ID,
|
||||
Name: work.FileName,
|
||||
Size: work.FileSize,
|
||||
Type: utils.GetFileType(path.Ext(work.FileName)),
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: work.Updated,
|
||||
Thumbnail: work.Thumbnail,
|
||||
Url: work.DownloadURL,
|
||||
})
|
||||
}
|
||||
}
|
||||
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{})
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
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)
|
||||
@@ -1,90 +0,0 @@
|
||||
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{})
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package template
|
||||
|
||||
// write util func here, such as cal sign
|
||||
@@ -1,237 +0,0 @@
|
||||
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)
|
||||
@@ -1,82 +0,0 @@
|
||||
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{})
|
||||
}
|
||||
100
drivers/virtual/driver.go
Normal file
100
drivers/virtual/driver.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package virtual
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/pkg/utils/random"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Virtual struct {
|
||||
model.Storage
|
||||
Addition
|
||||
}
|
||||
|
||||
func (d *Virtual) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *Virtual) Init(ctx context.Context, storage model.Storage) error {
|
||||
d.Storage = storage
|
||||
err := utils.Json.UnmarshalFromString(storage.Addition, &d.Addition)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error while unmarshal addition")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Virtual) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Virtual) GetStorage() model.Storage {
|
||||
return d.Storage
|
||||
}
|
||||
|
||||
func (d *Virtual) GetAddition() driver.Additional {
|
||||
return d.Addition
|
||||
}
|
||||
|
||||
func (d *Virtual) List(ctx context.Context, dir model.Obj) ([]model.Obj, error) {
|
||||
var res []model.Obj
|
||||
for i := 0; i < d.NumFile; i++ {
|
||||
res = append(res, model.Object{
|
||||
Name: random.String(10),
|
||||
Size: random.RangeInt64(d.MinFileSize, d.MaxFileSize),
|
||||
IsFolder: false,
|
||||
Modified: time.Now(),
|
||||
})
|
||||
}
|
||||
for i := 0; i < d.NumFolder; i++ {
|
||||
res = append(res, model.Object{
|
||||
Name: random.String(10),
|
||||
Size: 0,
|
||||
IsFolder: true,
|
||||
Modified: time.Now(),
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Virtual) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
return &model.Link{
|
||||
Data: io.NopCloser(io.LimitReader(random.Rand, file.GetSize())),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Virtual) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Virtual) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Virtual) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Virtual) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Virtual) Remove(ctx context.Context, obj model.Obj) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Virtual) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Virtual) Other(ctx context.Context, data interface{}) (interface{}, error) {
|
||||
return nil, errs.NotSupport
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*Virtual)(nil)
|
||||
29
drivers/virtual/meta.go
Normal file
29
drivers/virtual/meta.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package virtual
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/operations"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootFolderPath
|
||||
NumFile int `json:"num_file" type:"number" default:"30" required:"true"`
|
||||
NumFolder int `json:"num_folder" type:"number" default:"30" required:"true"`
|
||||
MaxFileSize int64 `json:"max_file_size" type:"number" default:"1073741824" required:"true"`
|
||||
MinFileSize int64 `json:"min_file_size" type:"number" default:"1048576" required:"true"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Virtual",
|
||||
OnlyLocal: true,
|
||||
LocalSort: true,
|
||||
//NoCache: true,
|
||||
}
|
||||
|
||||
func New() driver.Driver {
|
||||
return &Virtual{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
operations.RegisterDriver(config, New)
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
package webdav
|
||||
|
||||
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 WebDav struct{}
|
||||
|
||||
func (driver WebDav) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "WebDav",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: true,
|
||||
NoNeedSetLink: true,
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver WebDav) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "site_url",
|
||||
Label: "webdav root url",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
Label: "username",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Label: "password",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver WebDav) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
account.Status = "work"
|
||||
_ = model.SaveAccount(account)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver WebDav) File(path string, account *model.Account) (*model.File, error) {
|
||||
if path == "/" {
|
||||
return &model.File{
|
||||
Id: "/",
|
||||
Name: account.Name,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
dir, name := filepath.Split(path)
|
||||
files, err := driver.Files(dir, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
if file.Name == name {
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver WebDav) 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
|
||||
}
|
||||
c := driver.NewClient(account)
|
||||
rawFiles, err := c.ReadDir(driver.WebDavPath(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
if len(rawFiles) == 0 {
|
||||
return files, nil
|
||||
}
|
||||
for _, f := range rawFiles {
|
||||
t := f.ModTime()
|
||||
file := model.File{
|
||||
Name: f.Name(),
|
||||
Size: f.Size(),
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: &t,
|
||||
}
|
||||
if f.IsDir() {
|
||||
file.Type = conf.FOLDER
|
||||
} else {
|
||||
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
_ = base.SetCache(path, files, account)
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver WebDav) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||
path := args.Path
|
||||
c := driver.NewClient(account)
|
||||
reader, err := c.ReadStream(driver.WebDavPath(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &base.Link{Data: reader}, nil
|
||||
}
|
||||
|
||||
func (driver WebDav) 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 WebDav) Proxy(r *http.Request, account *model.Account) {
|
||||
//
|
||||
//}
|
||||
|
||||
func (driver WebDav) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver WebDav) MakeDir(path string, account *model.Account) error {
|
||||
c := driver.NewClient(account)
|
||||
err := c.MkdirAll(driver.WebDavPath(path), 0644)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver WebDav) Move(src string, dst string, account *model.Account) error {
|
||||
c := driver.NewClient(account)
|
||||
err := c.Rename(driver.WebDavPath(src), driver.WebDavPath(dst), true)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver WebDav) Rename(src string, dst string, account *model.Account) error {
|
||||
return driver.Move(src, dst, account)
|
||||
}
|
||||
|
||||
func (driver WebDav) Copy(src string, dst string, account *model.Account) error {
|
||||
c := driver.NewClient(account)
|
||||
err := c.Copy(driver.WebDavPath(src), driver.WebDavPath(dst), true)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver WebDav) Delete(path string, account *model.Account) error {
|
||||
c := driver.NewClient(account)
|
||||
err := c.RemoveAll(driver.WebDavPath(path))
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver WebDav) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
c := driver.NewClient(account)
|
||||
path := utils.Join(file.ParentPath, file.Name)
|
||||
err := c.WriteStream(driver.WebDavPath(path), file, 0644)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*WebDav)(nil)
|
||||
@@ -1,23 +0,0 @@
|
||||
package webdav
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/studio-b12/gowebdav"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (driver WebDav) NewClient(account *model.Account) *gowebdav.Client {
|
||||
return gowebdav.NewClient(account.SiteUrl, account.Username, account.Password)
|
||||
}
|
||||
|
||||
func (driver WebDav) WebDavPath(path string) string {
|
||||
path = utils.ParsePath(path)
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
return path
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&WebDav{})
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
package xunlei
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type XunLeiCloud struct{}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(new(XunLeiCloud))
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "XunLeiCloud",
|
||||
LocalSort: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "username",
|
||||
Label: "username",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Description: "account username/phone number",
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Label: "password",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Description: "account password",
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Save(account *model.Account, old *model.Account) error {
|
||||
if account == nil {
|
||||
return nil
|
||||
}
|
||||
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) {
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files, _ := cache.([]model.File)
|
||||
return files, nil
|
||||
}
|
||||
file, err := driver.File(utils.ParsePath(path), account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make([]model.File, 0)
|
||||
for {
|
||||
var fileList FileList
|
||||
u := fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files?parent_id=%s&page_token=%s&with_audit=true&filters=%s", file.Id, fileList.NextPageToken, url.QueryEscape(`{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`))
|
||||
if err = GetState(account).Request("GET", u, nil, &fileList, account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
if err = GetState(account).Request("GET", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s?&with_audit=true", file.Id), nil, &lFile, account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &base.Link{
|
||||
Headers: []base.Header{
|
||||
{Name: "User-Agent", Value: base.UserAgent},
|
||||
},
|
||||
Url: lFile.WebContentLink,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("xunlei path: %s", path)
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !file.IsDir() {
|
||||
return file, nil, nil
|
||||
}
|
||||
files, err := driver.Files(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) MakeDir(path string, account *model.Account) error {
|
||||
dir, name := filepath.Split(path)
|
||||
parentFile, err := driver.File(dir, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !parentFile.IsDir() {
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files", &base.Json{"kind": FOLDER, "name": name, "parent_id": parentFile.Id}, nil, account)
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Move(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files:batchMove", &base.Json{"to": base.Json{"parent_id": dstDirFile.Id}, "ids": []string{srcFile.Id}}, nil, account)
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Copy(src string, dst string, account *model.Account) error {
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files:batchCopy", &base.Json{"to": base.Json{"parent_id": dstDirFile.Id}, "ids": []string{srcFile.Id}}, nil, account)
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
|
||||
srcFile, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return GetState(account).Request("PATCH", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s/trash", srcFile.Id), &base.Json{}, nil, account)
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account) error {
|
||||
if file == nil {
|
||||
return base.ErrEmptyFile
|
||||
}
|
||||
|
||||
parentFile, err := driver.File(file.ParentPath, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var rep UploadTaskResponse
|
||||
err = GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files", &base.Json{
|
||||
"kind": FILE,
|
||||
"parent_id": parentFile.Id,
|
||||
"name": file.Name,
|
||||
"size": fmt.Sprint(file.Size),
|
||||
"hash": gcid,
|
||||
"upload_type": UPLOAD_TYPE_RESUMABLE,
|
||||
}, &rep, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
param := rep.Resumable.Params
|
||||
client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true), oss.HTTPClient(xunleiClient.GetClient()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bucket, err := client.Bucket(param.Bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.UploadFile(param.Key, tempFile.Name(), 4*1024*1024, oss.Routines(3), oss.Checkpoint(true, ""), oss.Expires(param.Expiration))
|
||||
}
|
||||
|
||||
func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account) error {
|
||||
_, dstName := filepath.Split(dst)
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return GetState(account).Request("PATCH", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s", srcFile.Id), &base.Json{"name": dstName}, nil, account)
|
||||
}
|
||||
|
||||
var _ base.Driver = (*XunLeiCloud)(nil)
|
||||
@@ -1,154 +0,0 @@
|
||||
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"`
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user