mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-11-25 03:15:19 +08:00
Compare commits
286 Commits
v2.0.0-bet
...
v2.0.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a22903533e | ||
|
|
86cda58b22 | ||
|
|
7804cf9d5c | ||
|
|
2bb7036110 | ||
|
|
ba545555cf | ||
|
|
be55ca690c | ||
|
|
9013add749 | ||
|
|
3201b6da76 | ||
|
|
feb42f1f4b | ||
|
|
6f14d0eb5c | ||
|
|
7530d8f5b2 | ||
|
|
e25fe05a53 | ||
|
|
8e0ab8f780 | ||
|
|
cb2a3c2b42 | ||
|
|
1b6ec94f33 | ||
|
|
cb23edc1fe | ||
|
|
6fd05d7d72 | ||
|
|
f26ac57569 | ||
|
|
2434ac54d0 | ||
|
|
f25b557327 | ||
|
|
81a0706d01 | ||
|
|
5f6b576cbf | ||
|
|
549877f71e | ||
|
|
c6a5ba9b91 | ||
|
|
1a69d80489 | ||
|
|
b797f4302c | ||
|
|
bf9aa5c3d3 | ||
|
|
7390e19a7a | ||
|
|
b31a12a0cc | ||
|
|
26ce001782 | ||
|
|
a2c7ff3262 | ||
|
|
8fc7c716c0 | ||
|
|
c70fc3fc4b | ||
|
|
df513b7dc0 | ||
|
|
2a9598f4c6 | ||
|
|
224c20779c | ||
|
|
5d722298cb | ||
|
|
4bcc6359e3 | ||
|
|
4144afcc92 | ||
|
|
2ad27046fb | ||
|
|
9516ac6718 | ||
|
|
de638c7c36 | ||
|
|
c6b34a033b | ||
|
|
31de3399d2 | ||
|
|
0dc2ca019f | ||
|
|
04724f7f0f | ||
|
|
75a983a965 | ||
|
|
e12d8bb8ca | ||
|
|
68f1ccfed4 | ||
|
|
54272db59c | ||
|
|
6d34e88360 | ||
|
|
0a901a2eb0 | ||
|
|
e1671a0511 | ||
|
|
dcb4ec695f | ||
|
|
4a21b6fe1d | ||
|
|
96a237902b | ||
|
|
cfb51e9f80 | ||
|
|
e952f1c243 | ||
|
|
07d6ca27db | ||
|
|
8245da485a | ||
|
|
5c759217cf | ||
|
|
0648fdebc2 | ||
|
|
ed670e528f | ||
|
|
2473309a51 | ||
|
|
21ca2f11b7 | ||
|
|
ccaa28a323 | ||
|
|
fea8b376f8 | ||
|
|
55d244b726 | ||
|
|
1640a52789 | ||
|
|
424ec10692 | ||
|
|
b472c2ee18 | ||
|
|
65a01251e9 | ||
|
|
96be6bbbd1 | ||
|
|
6f7465aab7 | ||
|
|
c9bc8227bb | ||
|
|
4ea9371c00 | ||
|
|
326c74cdc6 | ||
|
|
f11c2efa8c | ||
|
|
6dd8102a82 | ||
|
|
ddf6a4955f | ||
|
|
602994e213 | ||
|
|
7c7306bf96 | ||
|
|
c24894b5de | ||
|
|
e10412c530 | ||
|
|
5c2491b6c3 | ||
|
|
036373032c | ||
|
|
db58dabd31 | ||
|
|
7ef98c05fa | ||
|
|
6351f43b9b | ||
|
|
6613c8a6c1 | ||
|
|
2b97882b42 | ||
|
|
925f386bed | ||
|
|
0fbbd54b0c | ||
|
|
12ba1fed00 | ||
|
|
2826bac53c | ||
|
|
3e7e9f354f | ||
|
|
86ff80885d | ||
|
|
be03e34406 | ||
|
|
83231becba | ||
|
|
876ee49fb0 | ||
|
|
10bec31033 | ||
|
|
fc48f29575 | ||
|
|
f0d9a452bb | ||
|
|
c03a4f83d1 | ||
|
|
10f06fde5c | ||
|
|
36b533cb16 | ||
|
|
6ff2cdab98 | ||
|
|
ef5cad1bf0 | ||
|
|
b60c7ecd9e | ||
|
|
68aaa8fee2 | ||
|
|
67bf14d428 | ||
|
|
c22ff77c89 | ||
|
|
84fc0ab1bd | ||
|
|
beb06f2f7f | ||
|
|
7cf30836bf | ||
|
|
e7ba289d06 | ||
|
|
d255ff4fd0 | ||
|
|
bcf19f4f3e | ||
|
|
efeee0e276 | ||
|
|
e789873eca | ||
|
|
14b9b76e87 | ||
|
|
38323fd24f | ||
|
|
366148a450 | ||
|
|
bc364cee0d | ||
|
|
f433277227 | ||
|
|
35fc1c87d2 | ||
|
|
9da2af8c49 | ||
|
|
103e049f22 | ||
|
|
cc217df924 | ||
|
|
939c9cd5ac | ||
|
|
6f0959a98e | ||
|
|
d71ed4d775 | ||
|
|
0af3e95f1f | ||
|
|
582f7bbfee | ||
|
|
cf2506901f | ||
|
|
db06b627cc | ||
|
|
5f2621eca9 | ||
|
|
3331462229 | ||
|
|
0f079827e5 | ||
|
|
ba66e33913 | ||
|
|
6a54ed87f3 | ||
|
|
88a9edb90a | ||
|
|
606134f39c | ||
|
|
3c03344ef1 | ||
|
|
6f5914ae6f | ||
|
|
4c00866249 | ||
|
|
04752f7473 | ||
|
|
26b4766da7 | ||
|
|
12af9cb89f | ||
|
|
958d793725 | ||
|
|
36f07ee194 | ||
|
|
91c2c21522 | ||
|
|
d6d2f52922 | ||
|
|
9162e782a0 | ||
|
|
dc41ceb99b | ||
|
|
c5e274f52a | ||
|
|
3781043c78 | ||
|
|
1485ab2677 | ||
|
|
337bf08cd3 | ||
|
|
22665aa19a | ||
|
|
1ab6b4e201 | ||
|
|
d97afb691b | ||
|
|
b63e65880f | ||
|
|
44cbe0522c | ||
|
|
fedab86c30 | ||
|
|
731dbf6c3a | ||
|
|
d00f75c814 | ||
|
|
f5b8815a84 | ||
|
|
99d06c7449 | ||
|
|
8e7b2c5837 | ||
|
|
3d3a97288a | ||
|
|
c2142cc03a | ||
|
|
3ce94de823 | ||
|
|
c64c003257 | ||
|
|
1c65588b4a | ||
|
|
d49f92b542 | ||
|
|
92a0453e00 | ||
|
|
bd7d27efc7 | ||
|
|
b2055777e0 | ||
|
|
fe79f9518b | ||
|
|
a7e9bb9e9a | ||
|
|
73d85d96f1 | ||
|
|
ff91d7a37d | ||
|
|
78f81ddc3b | ||
|
|
2f8258053f | ||
|
|
511efce624 | ||
|
|
14ff3450ab | ||
|
|
bbba161d55 | ||
|
|
6b61f8e9cc | ||
|
|
a295e7024a | ||
|
|
b36eaf08f0 | ||
|
|
bb6e520ab5 | ||
|
|
9b64e2e045 | ||
|
|
ee7c12c30f | ||
|
|
96d6d58910 | ||
|
|
2bf235a5ac | ||
|
|
236f9969c0 | ||
|
|
09e63027d9 | ||
|
|
a15dae291e | ||
|
|
efaaeedfb8 | ||
|
|
190c8001a5 | ||
|
|
b8698700ef | ||
|
|
985b81826f | ||
|
|
74d8fa3919 | ||
|
|
43e4928bb9 | ||
|
|
03580fd76c | ||
|
|
6e8d551420 | ||
|
|
28998d6f8c | ||
|
|
1779617cb9 | ||
|
|
7dfe48339c | ||
|
|
9c5627a382 | ||
|
|
809850321a | ||
|
|
bdc1f68746 | ||
|
|
9aaef6c3a3 | ||
|
|
bb50c52d0e | ||
|
|
6041e5a0fa | ||
|
|
308a86c36e | ||
|
|
ba7c4fc230 | ||
|
|
d81ec0637d | ||
|
|
1d7d37e642 | ||
|
|
b62a716267 | ||
|
|
944941db10 | ||
|
|
bd91acc5d0 | ||
|
|
cd50227835 | ||
|
|
50226f66e3 | ||
|
|
9dcaa9b07a | ||
|
|
fa6c0f78bc | ||
|
|
7f35dc6ade | ||
|
|
5d6463b75a | ||
|
|
733b38b435 | ||
|
|
50a02a7af7 | ||
|
|
71b1517de7 | ||
|
|
ffdd88ec66 | ||
|
|
4ff2756572 | ||
|
|
d955038ebc | ||
|
|
72d5e4e691 | ||
|
|
b1e662cd34 | ||
|
|
0f0e1104a4 | ||
|
|
3041da35ab | ||
|
|
9eab54a7c8 | ||
|
|
0b8d3a0a2c | ||
|
|
f9945a14a8 | ||
|
|
c39752ceb4 | ||
|
|
53b383d2cf | ||
|
|
e76fc3e616 | ||
|
|
eb21b87020 | ||
|
|
f577d82242 | ||
|
|
98691b2aa8 | ||
|
|
4fe6ed6c3e | ||
|
|
fe73ece57d | ||
|
|
59b8f1084a | ||
|
|
2f669ac45c | ||
|
|
d03d91d518 | ||
|
|
fe981f67ec | ||
|
|
8cfabfd0f5 | ||
|
|
163ee1159e | ||
|
|
e31402e94f | ||
|
|
5500980d63 | ||
|
|
b1695445e0 | ||
|
|
5db1ad4adf | ||
|
|
725f5b0c55 | ||
|
|
87a74394b3 | ||
|
|
a41c820525 | ||
|
|
cd53dc6d24 | ||
|
|
cfe16b5ed2 | ||
|
|
3d9746485d | ||
|
|
0b7f2fee7d | ||
|
|
36d52e0b75 | ||
|
|
e4d254e4b0 | ||
|
|
e8d27a30b4 | ||
|
|
69514668cc | ||
|
|
e5f8f59c87 | ||
|
|
f87ee1ed9e | ||
|
|
07155cfd01 | ||
|
|
5e982980dc | ||
|
|
8987958e26 | ||
|
|
4466cb19a5 | ||
|
|
da74e29b26 | ||
|
|
82272fcbf5 | ||
|
|
74d86f8cc4 | ||
|
|
c03646dedf | ||
|
|
c0d1888e25 | ||
|
|
f4942e89bd | ||
|
|
27e61c9eb8 | ||
|
|
aeb72320ca | ||
|
|
caddba05e9 |
39
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
39
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: "Bug report"
|
||||||
|
description: Bug report
|
||||||
|
labels: [pending triage]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Alist Version / Alist 版本
|
||||||
|
description: What version of our software are you running?
|
||||||
|
placeholder: v2.0.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: bug-description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug / 问题描述
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: Reproduction / 复现链接
|
||||||
|
description: |
|
||||||
|
Please provide a link to a repo that can reproduce the problem you ran into.
|
||||||
|
请提供能复现此问题的链接
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: 日志 / Logs
|
||||||
|
description: |
|
||||||
|
Please copy and paste any relevant log output.
|
||||||
|
请复制粘贴错误日志,或者截图
|
||||||
|
render: shell
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Questions & Discussions & Feature request
|
||||||
|
url: https://github.com/Xhofe/alist/discussions
|
||||||
|
about: Use GitHub discussions for message-board style questions and discussions or feature request.
|
||||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -25,22 +25,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '16'
|
||||||
|
|
||||||
# - name: Setup docker
|
|
||||||
# uses: docker-practice/actions-setup-docker@master
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
ref: v2
|
ref: v2
|
||||||
path: alist
|
path: alist
|
||||||
|
|
||||||
- name: Checkout web repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: Xhofe/alist-web
|
|
||||||
ref: v2
|
|
||||||
path: alist-web
|
|
||||||
|
|
||||||
- name: Set up xgo
|
- name: Set up xgo
|
||||||
run: |
|
run: |
|
||||||
docker pull techknowlogick/xgo:latest
|
docker pull techknowlogick/xgo:latest
|
||||||
@@ -50,10 +40,12 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
mv alist/build.sh .
|
mv alist/build.sh .
|
||||||
bash build.sh
|
bash build.sh web
|
||||||
|
mv dist/* alist/public
|
||||||
|
bash build.sh build
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: artifact
|
name: artifact
|
||||||
path: alist/build
|
path: alist/build
|
||||||
@@ -1,17 +1,13 @@
|
|||||||
name: docker
|
name: build_docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches: [ v2 ]
|
||||||
- 'v2'
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches: [ v2 ]
|
||||||
- 'v2'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
build_docker:
|
||||||
name: Docker
|
name: Docker
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -27,7 +23,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '16'
|
||||||
- name: Build web
|
- name: Build web
|
||||||
run: bash build.sh web
|
run: |
|
||||||
|
bash build.sh web
|
||||||
|
mv dist/* public
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -45,4 +43,4 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/ppc64le,linux/s390x
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
||||||
27
.github/workflows/release.yml
vendored
27
.github/workflows/release.yml
vendored
@@ -19,14 +19,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
# - name: Setup docker
|
|
||||||
# uses: docker-practice/actions-setup-docker@master
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: '16'
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
@@ -35,15 +27,6 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Checkout web repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: Xhofe/alist-web
|
|
||||||
ref: v2
|
|
||||||
path: alist-web
|
|
||||||
persist-credentials: false
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up xgo
|
- name: Set up xgo
|
||||||
run: |
|
run: |
|
||||||
docker pull techknowlogick/xgo:latest
|
docker pull techknowlogick/xgo:latest
|
||||||
@@ -53,16 +36,10 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
mv alist/build.sh .
|
mv alist/build.sh .
|
||||||
|
bash build.sh cdn
|
||||||
|
mv dist/* alist/public
|
||||||
bash build.sh release
|
bash build.sh release
|
||||||
|
|
||||||
- name: Upload asserts files
|
|
||||||
uses: ad-m/github-push-action@master
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.MY_TOKEN }}
|
|
||||||
branch: cdn
|
|
||||||
directory: alist-web
|
|
||||||
repository: Xhofe/alist-web
|
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
45
.github/workflows/release_docker.yml
vendored
Normal file
45
.github/workflows/release_docker.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: release_docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker_release:
|
||||||
|
name: Docker
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
images: xhofe/alist
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
- name: Build web
|
||||||
|
run: |
|
||||||
|
bash build.sh cdn
|
||||||
|
mv dist/* public
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: xhofe
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -21,8 +21,9 @@ dist/
|
|||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
bin/*
|
bin/*
|
||||||
alist
|
/alist
|
||||||
*.json
|
*.json
|
||||||
public/index.html
|
public/*.html
|
||||||
public/assets/
|
public/assets/
|
||||||
|
public/public/
|
||||||
data/
|
data/
|
||||||
@@ -3,7 +3,7 @@ LABEL stage=go-builder
|
|||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
COPY ./ ./
|
COPY ./ ./
|
||||||
RUN apk add --no-cache bash git go gcc musl-dev; \
|
RUN apk add --no-cache bash git go gcc musl-dev; \
|
||||||
sh build.sh docker
|
bash build.sh docker
|
||||||
|
|
||||||
FROM alpine:edge
|
FROM alpine:edge
|
||||||
LABEL MAINTAINER="i@nn.ci"
|
LABEL MAINTAINER="i@nn.ci"
|
||||||
|
|||||||
661
LICENSE
Normal file
661
LICENSE
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
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/>.
|
||||||
81
README.md
Normal file → Executable file
81
README.md
Normal file → Executable file
@@ -1,43 +1,78 @@
|
|||||||
<h2 align="center">Alist</h2>
|
<div align="center">
|
||||||
<p align="center">
|
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/assets@main/logo.svg"/></a>
|
||||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="Release version"></a>
|
<p><em>🗂️Another file list program that supports multiple storage, powered by Gin and React.</em></p>
|
||||||
|
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
|
||||||
|
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
|
||||||
<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/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" alt="Downloads"></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/main/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></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">
|
<a href="https://pay.xhofe.top">
|
||||||
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate">
|
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 这是什么?
|
English | [中文](./README_cn.md)
|
||||||
|
|
||||||
一款支持多种存储的目录文件列表程序,后端基于`gin`,前端使用`react`。
|
## 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] 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
|
||||||
|
|
||||||
- https://github.com/Xhofe/alist-web
|
## Discussion
|
||||||
|
|
||||||
### 演示地址
|
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports only.**
|
||||||
|
|
||||||
- https://alist.nn.ci
|
## Demo
|
||||||
|
|
||||||
### 预览
|
Available at: <https://alist.nn.ci>.
|
||||||
|
|
||||||
<a href="https://alist.nn.ci/"><img src="https://store.heytapimage.com/cdo-portal/feedback/202111/03/695ef77854a144e928518efde38db97a.png"></a>
|

|
||||||
|
|
||||||
### 支持的存储
|
## Document
|
||||||
|
|
||||||
- 本地存储
|
<https://alist-doc.nn.ci/en/>
|
||||||
- 阿里云盘
|
|
||||||
- Onedrive/世纪互联
|
|
||||||
- ...
|
|
||||||
|
|
||||||
### 如何使用
|
## License
|
||||||
|
|
||||||
- https://www.nn.ci/archives/alist.html
|
The `AList` is open-source software licensed under the AGPL-3.0 license.
|
||||||
|
|
||||||
### License
|
---
|
||||||
|
|
||||||
The `AList` is open-source software licensed under the MIT license.
|
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe)
|
||||||
77
README_cn.md
Normal file
77
README_cn.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<div align="center">
|
||||||
|
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/assets@main/logo.svg"/></a>
|
||||||
|
<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) | 中文
|
||||||
|
|
||||||
|
## 支持
|
||||||
|
|
||||||
|
- [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] 部署方便,开箱即用
|
||||||
|
- [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)
|
||||||
42
alist.go
42
alist.go
@@ -1,28 +1,35 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Xhofe/alist/bootstrap"
|
"github.com/Xhofe/alist/bootstrap"
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
|
_ "github.com/Xhofe/alist/drivers"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
"github.com/Xhofe/alist/server"
|
"github.com/Xhofe/alist/server"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func Init() bool {
|
||||||
flag.StringVar(&conf.ConfigFile, "conf", "data/config.json", "config file")
|
//bootstrap.InitLog()
|
||||||
flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
|
|
||||||
flag.BoolVar(&conf.Version, "version", false, "print version info")
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init() {
|
|
||||||
bootstrap.InitLog()
|
|
||||||
bootstrap.InitConf()
|
bootstrap.InitConf()
|
||||||
bootstrap.InitCron()
|
bootstrap.InitCron()
|
||||||
bootstrap.InitModel()
|
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()
|
bootstrap.InitCache()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -30,15 +37,22 @@ func main() {
|
|||||||
fmt.Printf("Built At: %s\nGo Version: %s\nAuthor: %s\nCommit ID: %s\nVersion: %s\n", conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.GitTag)
|
fmt.Printf("Built At: %s\nGo Version: %s\nAuthor: %s\nCommit ID: %s\nVersion: %s\n", conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.GitTag)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Init()
|
if !Init() {
|
||||||
|
return
|
||||||
|
}
|
||||||
if !conf.Debug {
|
if !conf.Debug {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
}
|
}
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
server.InitApiRouter(r)
|
server.InitApiRouter(r)
|
||||||
|
base := fmt.Sprintf("%s:%d", conf.Conf.Address, conf.Conf.Port)
|
||||||
log.Info("starting server")
|
log.Infof("start server @ %s", base)
|
||||||
err := r.Run(fmt.Sprintf("%s:%d", conf.Conf.Address, conf.Conf.Port))
|
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 {
|
if err != nil {
|
||||||
log.Errorf("failed to start: %s", err.Error())
|
log.Errorf("failed to start: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
31
bootstrap/account.go
Normal file
31
bootstrap/account.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,11 @@ import (
|
|||||||
// InitCache init cache
|
// InitCache init cache
|
||||||
func InitCache() {
|
func InitCache() {
|
||||||
log.Infof("init cache...")
|
log.Infof("init cache...")
|
||||||
goCacheClient := goCache.New(60*time.Minute, 120*time.Minute)
|
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)
|
goCacheStore := store.NewGoCache(goCacheClient, nil)
|
||||||
conf.Cache = cache.New(goCacheStore)
|
conf.Cache = cache.New(goCacheStore)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
"github.com/Xhofe/alist/utils"
|
"github.com/Xhofe/alist/utils"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitConf init config
|
// InitConf init config
|
||||||
@@ -28,9 +28,22 @@ func InitConf() {
|
|||||||
log.Fatalf("reading config file error:%s", err.Error())
|
log.Fatalf("reading config file error:%s", err.Error())
|
||||||
}
|
}
|
||||||
conf.Conf = new(conf.Config)
|
conf.Conf = new(conf.Config)
|
||||||
err = json.Unmarshal(config, conf.Conf)
|
err = utils.Json.Unmarshal(config, conf.Conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load config error: %s", err.Error())
|
log.Fatalf("load config error: %s", err.Error())
|
||||||
}
|
}
|
||||||
log.Debugf("config:%+v", conf.Conf)
|
log.Debugf("config:%+v", conf.Conf)
|
||||||
|
// update config.json struct
|
||||||
|
confBody, err := utils.Json.MarshalIndent(conf.Conf, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("marshal config error:%s", err.Error())
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(conf.ConfigFile, confBody, 0777)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("update config struct error: %s", err.Error())
|
||||||
|
}
|
||||||
|
err = os.MkdirAll("data/temp", 0700)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("create temp dir error: %s", err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// initLog init log
|
// InitLog init log
|
||||||
func InitLog() {
|
func InitLog() {
|
||||||
if conf.Debug {
|
if conf.Debug {
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
log.SetReportCaller(true)
|
log.SetReportCaller(true)
|
||||||
}
|
}
|
||||||
|
if conf.Password {
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
}
|
||||||
log.SetFormatter(&log.TextFormatter{
|
log.SetFormatter(&log.TextFormatter{
|
||||||
//DisableColors: true,
|
//DisableColors: true,
|
||||||
ForceColors: true,
|
ForceColors: true,
|
||||||
@@ -18,4 +22,14 @@ func InitLog() {
|
|||||||
TimestampFormat: "2006-01-02 15:04:05",
|
TimestampFormat: "2006-01-02 15:04:05",
|
||||||
FullTimestamp: true,
|
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.Parse()
|
||||||
|
InitLog()
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package bootstrap
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
"github.com/Xhofe/alist/drivers"
|
|
||||||
"github.com/Xhofe/alist/model"
|
"github.com/Xhofe/alist/model"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
@@ -20,6 +19,7 @@ import (
|
|||||||
|
|
||||||
func InitModel() {
|
func InitModel() {
|
||||||
log.Infof("init model...")
|
log.Infof("init model...")
|
||||||
|
var err error
|
||||||
databaseConfig := conf.Conf.Database
|
databaseConfig := conf.Conf.Database
|
||||||
newLogger := logger.New(
|
newLogger := logger.New(
|
||||||
log2.New(os.Stdout, "\r\n", log2.LstdFlags),
|
log2.New(os.Stdout, "\r\n", log2.LstdFlags),
|
||||||
@@ -60,173 +60,25 @@ func InitModel() {
|
|||||||
}
|
}
|
||||||
case "postgres":
|
case "postgres":
|
||||||
{
|
{
|
||||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai",
|
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.Host, databaseConfig.User, databaseConfig.Password, databaseConfig.Name, databaseConfig.Port, databaseConfig.SslMode)
|
||||||
db, err := gorm.Open(postgres.Open(dsn), gormConfig)
|
db, err := gorm.Open(postgres.Open(dsn), gormConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to connect database:%s", err.Error())
|
log.Errorf("failed to connect database:%s", err.Error())
|
||||||
}
|
}
|
||||||
conf.DB = db
|
conf.DB = db
|
||||||
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Fatalf("not supported database type: %s", databaseConfig.Type)
|
log.Fatalf("not supported database type: %s", databaseConfig.Type)
|
||||||
}
|
}
|
||||||
log.Infof("auto migrate model")
|
log.Infof("auto migrate model...")
|
||||||
err := conf.DB.AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{})
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("failed to auto migrate")
|
log.Fatalf("failed to auto migrate")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO init filetype
|
|
||||||
initAccounts()
|
|
||||||
initSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
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 _, account := range accounts {
|
|
||||||
model.RegisterAccount(account)
|
|
||||||
driver, ok := drivers.GetDriver(account.Type)
|
|
||||||
if !ok {
|
|
||||||
log.Errorf("no [%s] driver", driver)
|
|
||||||
} else {
|
|
||||||
err := driver.Save(&account, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("init account [%s] error:[%s]", account.Name, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func initSettings() {
|
|
||||||
log.Infof("init settings...")
|
|
||||||
version := model.SettingItem{
|
|
||||||
Key: "version",
|
|
||||||
Value: conf.GitTag,
|
|
||||||
Description: "version",
|
|
||||||
Type: "string",
|
|
||||||
Group: model.CONST,
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = model.SaveSetting(version)
|
|
||||||
|
|
||||||
settings := []model.SettingItem{
|
|
||||||
{
|
|
||||||
Key: "title",
|
|
||||||
Value: "Alist",
|
|
||||||
Description: "title",
|
|
||||||
Type: "string",
|
|
||||||
Group: model.PUBLIC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "password",
|
|
||||||
Value: "alist",
|
|
||||||
Description: "password",
|
|
||||||
Type: "string",
|
|
||||||
Group: model.PRIVATE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "logo",
|
|
||||||
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
|
|
||||||
Description: "logo",
|
|
||||||
Type: "string",
|
|
||||||
Group: model.PUBLIC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "favicon",
|
|
||||||
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
|
|
||||||
Description: "favicon",
|
|
||||||
Type: "string",
|
|
||||||
Group: model.PUBLIC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "icon color",
|
|
||||||
Value: "teal.300",
|
|
||||||
Description: "icon's color",
|
|
||||||
Type: "string",
|
|
||||||
Group: model.PUBLIC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "text types",
|
|
||||||
Value: "txt,htm,html,xml,java,properties,sql,js,md,json,conf,ini,vue,php,py,bat,gitignore,yml,go,sh,c,cpp,h,hpp",
|
|
||||||
Type: "string",
|
|
||||||
Description: "text type extensions",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "hide readme file",
|
|
||||||
Value: "true",
|
|
||||||
Type: "bool",
|
|
||||||
Description: "hide readme file? ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "music cover",
|
|
||||||
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
|
|
||||||
Description: "music cover image",
|
|
||||||
Type: "string",
|
|
||||||
Group: model.PUBLIC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "site beian",
|
|
||||||
Description: "chinese beian info",
|
|
||||||
Type: "string",
|
|
||||||
Group: model.PUBLIC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "home readme url",
|
|
||||||
Description: "when have multiple, the readme file to show",
|
|
||||||
Type: "string",
|
|
||||||
Group: model.PUBLIC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "markdown theme",
|
|
||||||
Value: "vuepress",
|
|
||||||
Description: "default | github | vuepress",
|
|
||||||
Group: model.PUBLIC,
|
|
||||||
Type: "select",
|
|
||||||
Values: "default,github,vuepress",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "autoplay video",
|
|
||||||
Value: "false",
|
|
||||||
Type: "bool",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "autoplay audio",
|
|
||||||
Value: "false",
|
|
||||||
Type: "bool",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "check parent folder",
|
|
||||||
Value: "false",
|
|
||||||
Type: "bool",
|
|
||||||
Description: "check parent folder password",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "customize style",
|
|
||||||
Value: "",
|
|
||||||
Type: "text",
|
|
||||||
Description: "customize style, don't need add <style></style>",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "customize script",
|
|
||||||
Value: "",
|
|
||||||
Type: "text",
|
|
||||||
Description: "customize script, don't need add <script></script>",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, v := range settings {
|
|
||||||
_, err := model.GetSettingByKey(v.Key)
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
err = model.SaveSetting(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed write setting: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
model.LoadSettings()
|
|
||||||
}
|
}
|
||||||
|
|||||||
259
bootstrap/setting.go
Normal file
259
bootstrap/setting.go
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
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: "text types",
|
||||||
|
Value: strings.Join(conf.TextTypes, ","),
|
||||||
|
Type: "string",
|
||||||
|
Description: "text type extensions",
|
||||||
|
Group: model.FRONT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "hide readme file",
|
||||||
|
Value: "true",
|
||||||
|
Type: "bool",
|
||||||
|
Description: "hide readme file? ",
|
||||||
|
Group: model.FRONT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "music cover",
|
||||||
|
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
|
||||||
|
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", 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model.LoadSettings()
|
||||||
|
}
|
||||||
193
build.sh
193
build.sh
@@ -1,17 +1,24 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
if [ "$1" == "web" ]; then
|
|
||||||
git clone https://github.com/Xhofe/alist-web.git
|
# 构建前端,在当前目录产生一个dist文件夹
|
||||||
cd alist-web || exit
|
BUILD_WEB() {
|
||||||
|
git clone https://github.com/alist-org/alist-web.git
|
||||||
|
cd alist-web
|
||||||
yarn
|
yarn
|
||||||
yarn build
|
yarn build
|
||||||
mv dist/* ../public
|
mv dist ..
|
||||||
cd ..
|
cd ..
|
||||||
exit 0
|
rm -rf alist-web
|
||||||
fi
|
}
|
||||||
|
|
||||||
go env -w GOPROXY=https://goproxy.cn,https://mirrors.aliyun.com/goproxy/,https://goproxy.io,direct
|
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
|
||||||
|
}
|
||||||
|
|
||||||
if [ "$1" == "docker" ]; then
|
# 在DOCKER中构建
|
||||||
|
BUILD_DOCKER() {
|
||||||
appName="alist"
|
appName="alist"
|
||||||
builtAt="$(date +'%F %T %z')"
|
builtAt="$(date +'%F %T %z')"
|
||||||
goVersion=$(go version | sed 's/go version //')
|
goVersion=$(go version | sed 's/go version //')
|
||||||
@@ -25,34 +32,20 @@ if [ "$1" == "docker" ]; then
|
|||||||
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
|
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
|
||||||
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
|
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
|
||||||
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
|
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
|
||||||
"
|
"
|
||||||
go build -o ./bin/alist -ldflags="$ldflags" alist.go
|
go build -o ./bin/alist -ldflags="$ldflags" -tags=jsoniter alist.go
|
||||||
exit 0
|
}
|
||||||
fi
|
|
||||||
|
|
||||||
cd alist-web || exit
|
BUILD() {
|
||||||
webCommit=$(git log --pretty=format:"%h" -1)
|
cd alist
|
||||||
echo "web commit id: $webCommit"
|
appName="alist"
|
||||||
yarn
|
builtAt="$(date +'%F %T %z')"
|
||||||
if [ "$1" == "release" ]; then
|
goVersion=$(go version | sed 's/go version //')
|
||||||
yarn build --base="https://cdn.jsdelivr.net/gh/Xhofe/alist-web@cdn/v2/$webCommit"
|
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
|
||||||
mv dist/assets ..
|
gitCommit=$(git log --pretty=format:"%h" -1)
|
||||||
else
|
gitTag=$(git describe --long --tags --dirty --always)
|
||||||
yarn build
|
echo "build version: $gitTag"
|
||||||
fi
|
ldflags="\
|
||||||
cd ..
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
echo "build version: $gitTag"
|
|
||||||
|
|
||||||
ldflags="\
|
|
||||||
-w -s \
|
-w -s \
|
||||||
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
|
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
|
||||||
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
|
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
|
||||||
@@ -61,46 +54,92 @@ ldflags="\
|
|||||||
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
|
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
|
||||||
"
|
"
|
||||||
|
|
||||||
cp -R ../alist-web/dist/* public
|
if [ "$1" == "release" ]; then
|
||||||
|
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||||
if [ "$1" == "release" ]; then
|
else
|
||||||
xgo -out alist -ldflags="$ldflags" .
|
xgo -targets=linux/amd64,windows/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||||
else
|
fi
|
||||||
xgo -targets=linux/amd64,windows/amd64 -out alist -ldflags="$ldflags" .
|
mkdir -p "build"
|
||||||
fi
|
mv alist-* build
|
||||||
mkdir "build"
|
if [ "$1" != "release" ]; then
|
||||||
mv alist-* build
|
cd build
|
||||||
cd build || exit
|
upx -9 ./*
|
||||||
upx -9 ./*
|
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||||
find . -type f -print0 | xargs -0 md5sum > md5.txt
|
cat md5.txt
|
||||||
cat md5.txt
|
cd ..
|
||||||
# compress file (release)
|
fi
|
||||||
if [ "$1" == "release" ]; then
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
cd ../..
|
|
||||||
|
|
||||||
if [ "$1" == "release" ]; then
|
|
||||||
cd alist-web
|
|
||||||
git checkout cdn
|
|
||||||
mkdir "v2/$webCommit"
|
|
||||||
mv ../assets/ v2/$webCommit
|
|
||||||
git add .
|
|
||||||
git config --local user.email "i@nn.ci"
|
|
||||||
git config --local user.name "Xhofe"
|
|
||||||
git commit --allow-empty -m "upload $webCommit assets files" -a
|
|
||||||
cd ..
|
cd ..
|
||||||
fi
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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' \
|
||||||
|
"
|
||||||
|
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 ..
|
||||||
|
}
|
||||||
|
|
||||||
|
RELEASE() {
|
||||||
|
cd alist/build
|
||||||
|
upx -9 ./*
|
||||||
|
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 ../..
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
@@ -9,26 +9,44 @@ type Database struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
TablePrefix string `json:"table_prefix"`
|
TablePrefix string `json:"table_prefix"`
|
||||||
DBFile string `json:"db_file"`
|
DBFile string `json:"db_file"`
|
||||||
|
SslMode string `json:"ssl_mode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Scheme struct {
|
||||||
|
Https bool `json:"https"`
|
||||||
|
CertFile string `json:"cert_file"`
|
||||||
|
KeyFile string `json:"key_file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CacheConfig struct {
|
||||||
|
Expiration int64 `json:"expiration"`
|
||||||
|
CleanupInterval int64 `json:"cleanup_interval"`
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Database Database `json:"database"`
|
Assets string `json:"assets"`
|
||||||
|
Database Database `json:"database"`
|
||||||
|
Scheme Scheme `json:"scheme"`
|
||||||
|
Cache CacheConfig `json:"cache"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Address: "0.0.0.0",
|
Address: "0.0.0.0",
|
||||||
Port: 5244,
|
Port: 5244,
|
||||||
|
Assets: "jsdelivr",
|
||||||
Database: Database{
|
Database: Database{
|
||||||
Type: "sqlite3",
|
Type: "sqlite3",
|
||||||
User: "",
|
|
||||||
Password: "",
|
|
||||||
Host: "",
|
|
||||||
Port: 0,
|
Port: 0,
|
||||||
Name: "",
|
|
||||||
TablePrefix: "x_",
|
TablePrefix: "x_",
|
||||||
DBFile: "data/data.db",
|
DBFile: "data/data.db",
|
||||||
|
SslMode: "disable",
|
||||||
|
},
|
||||||
|
Cache: CacheConfig{
|
||||||
|
Expiration: 60,
|
||||||
|
CleanupInterval: 120,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
75
conf/var.go
75
conf/var.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/eko/gocache/v2/cache"
|
"github.com/eko/gocache/v2/cache"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -12,7 +13,7 @@ var (
|
|||||||
GoVersion string
|
GoVersion string
|
||||||
GitAuthor string
|
GitAuthor string
|
||||||
GitCommit string
|
GitCommit string
|
||||||
GitTag string
|
GitTag string = "dev"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -20,6 +21,7 @@ var (
|
|||||||
Conf *Config
|
Conf *Config
|
||||||
Debug bool
|
Debug bool
|
||||||
Version bool
|
Version bool
|
||||||
|
Password bool
|
||||||
|
|
||||||
DB *gorm.DB
|
DB *gorm.DB
|
||||||
Cache *cache.Cache
|
Cache *cache.Cache
|
||||||
@@ -28,19 +30,68 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
TextTypes = []string{"txt", "go", "md"}
|
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"}
|
||||||
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
|
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
|
||||||
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb"}
|
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"}
|
||||||
AudioTypes = []string{"mp3", "flac", "ogg", "m4a"}
|
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav"}
|
||||||
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg"}
|
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
|
||||||
)
|
)
|
||||||
|
|
||||||
// settings
|
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 (
|
var (
|
||||||
RawIndexHtml string
|
LoadSettings = []string{
|
||||||
IndexHtml string
|
"check parent folder", "check down link", "WebDAV username", "WebDAV password",
|
||||||
CheckParent bool
|
"Visitor WebDAV username", "Visitor WebDAV password",
|
||||||
//CustomizeStyle string
|
"default page size", "load type",
|
||||||
//CustomizeScript string
|
"ocr api",
|
||||||
//Favicon string
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
RawIndexHtml string
|
||||||
|
ManageHtml string
|
||||||
|
IndexHtml string
|
||||||
|
Token string
|
||||||
|
|
||||||
|
//CheckParent bool
|
||||||
|
//CheckDown bool
|
||||||
|
//DavUsername string
|
||||||
|
//DavPassword string
|
||||||
|
//VisitorDavUsername string
|
||||||
|
//VisitorDavPassword string
|
||||||
)
|
)
|
||||||
|
|||||||
230
drivers/123/123.go
Normal file
230
drivers/123/123.go
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
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{})
|
||||||
|
}
|
||||||
437
drivers/123/driver.go
Normal file
437
drivers/123/driver.go
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "order_direction",
|
||||||
|
Label: "order_direction",
|
||||||
|
Type: base.TypeSelect,
|
||||||
|
Values: "asc,desc",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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("data/temp", "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)
|
||||||
175
drivers/139/139.go
Normal file
175
drivers/139/139.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
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{})
|
||||||
|
}
|
||||||
456
drivers/139/driver.go
Normal file
456
drivers/139/driver.go
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
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": contentInfoList,
|
||||||
|
},
|
||||||
|
"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)
|
||||||
74
drivers/139/family.go
Normal file
74
drivers/139/family.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
187
drivers/139/types.go
Normal file
187
drivers/139/types.go
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
70
drivers/139/util.go
Normal file
70
drivers/139/util.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
626
drivers/189/189.go
Normal file
626
drivers/189/189.go
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
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 Cloud189Down struct {
|
||||||
|
ResCode int `json:"res_code"`
|
||||||
|
ResMessage string `json:"res_message"`
|
||||||
|
FileDownloadUrl string `json:"fileDownloadUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginResp struct {
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Result int `json:"result"`
|
||||||
|
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.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": 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())
|
||||||
|
}
|
||||||
354
drivers/189/driver.go
Normal file
354
drivers/189/driver.go
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
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"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"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 Cloud189Down
|
||||||
|
u := "https://cloud.189.cn/api/open/file/getFileDownloadUrl.action"
|
||||||
|
body, err := driver.Request(u, base.Get, map[string]string{
|
||||||
|
"fileId": file.Id,
|
||||||
|
}, 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)
|
||||||
|
}
|
||||||
|
res, err := base.NoRedirectClient.R().Get(resp.FileDownloadUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
link := base.Link{}
|
||||||
|
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)
|
||||||
55
drivers/189/types.go
Normal file
55
drivers/189/types.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
187
drivers/189/util.go
Normal file
187
drivers/189/util.go
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
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('=')
|
||||||
|
buf.WriteString(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
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,388 +0,0 @@
|
|||||||
package drivers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var aliClient = resty.New()
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterDriver("AliDrive", &AliDrive{})
|
|
||||||
aliClient.
|
|
||||||
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://aliyundrive.com")
|
|
||||||
}
|
|
||||||
|
|
||||||
type AliDrive struct{}
|
|
||||||
|
|
||||||
func (a AliDrive) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
file, err := a.GetFile(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// office
|
|
||||||
var resp Json
|
|
||||||
var e AliRespError
|
|
||||||
var url string
|
|
||||||
req := 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, fmt.Errorf("don't support")
|
|
||||||
}
|
|
||||||
_, 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 (a AliDrive) Items() []Item {
|
|
||||||
return []Item{
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: "select",
|
|
||||||
Values: "name,size,updated_at,created_at",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "order_direction",
|
|
||||||
Type: "select",
|
|
||||||
Values: "ASC,DESC",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "refresh_token",
|
|
||||||
Label: "refresh token",
|
|
||||||
Type: "string",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: "string",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "limit",
|
|
||||||
Label: "limit",
|
|
||||||
Type: "number",
|
|
||||||
Required: false,
|
|
||||||
Description: ">0 and <=200",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AliDrive) Proxy(c *gin.Context) {
|
|
||||||
c.Request.Header.Del("Origin")
|
|
||||||
c.Request.Header.Set("Referer", "https://www.aliyundrive.com/")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (a AliDrive) FormatFile(file *AliFile) *model.File {
|
|
||||||
f := &model.File{
|
|
||||||
Name: file.Name,
|
|
||||||
Size: file.Size,
|
|
||||||
UpdatedAt: file.UpdatedAt,
|
|
||||||
Thumbnail: file.Thumbnail,
|
|
||||||
Driver: "AliDrive",
|
|
||||||
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 (a 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(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 = a.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return a.GetFiles(fileId, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
marker = resp.NextMarker
|
|
||||||
res = append(res, resp.Items...)
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AliDrive) GetFile(path string, account *model.Account) (*AliFile, error) {
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
dir = utils.ParsePath(dir)
|
|
||||||
_, _, err := a.Path(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
|
|
||||||
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, fmt.Errorf("path not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// path: /aaa/bbb
|
|
||||||
func (a AliDrive) Path(path string, account *model.Account) (*model.File, []*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("ali path: %s", path)
|
|
||||||
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
|
|
||||||
if err == nil {
|
|
||||||
file, ok := cache.(AliFile)
|
|
||||||
if ok {
|
|
||||||
return a.FormatFile(&file), nil, nil
|
|
||||||
} else {
|
|
||||||
files, _ := cache.([]AliFile)
|
|
||||||
if len(files) != 0 {
|
|
||||||
res := make([]*model.File, 0)
|
|
||||||
for _, file = range files {
|
|
||||||
res = append(res, a.FormatFile(&file))
|
|
||||||
}
|
|
||||||
return nil, res, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// no cache or len(files) == 0
|
|
||||||
fileId := account.RootFolder
|
|
||||||
if path != "/" {
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
dir = utils.ParsePath(dir)
|
|
||||||
_, _, err = a.Path(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
|
|
||||||
parentFiles, _ := parentFiles_.([]AliFile)
|
|
||||||
found := false
|
|
||||||
for _, file := range parentFiles {
|
|
||||||
if file.Name == name {
|
|
||||||
found = true
|
|
||||||
if file.Type == "file" {
|
|
||||||
url, err := a.Link(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
file.Url = url
|
|
||||||
return a.FormatFile(&file), nil, nil
|
|
||||||
} else {
|
|
||||||
fileId = file.FileId
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return nil, nil, fmt.Errorf("path not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files, err := a.GetFiles(fileId, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), files, nil)
|
|
||||||
res := make([]*model.File, 0)
|
|
||||||
for _, file := range files {
|
|
||||||
res = append(res, a.FormatFile(&file))
|
|
||||||
}
|
|
||||||
return nil, res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AliDrive) Link(path string, account *model.Account) (string, error) {
|
|
||||||
file, err := a.GetFile(utils.ParsePath(path), account)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
var resp Json
|
|
||||||
var e AliRespError
|
|
||||||
_, err = aliClient.R().SetResult(&resp).
|
|
||||||
SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
|
||||||
SetBody(Json{
|
|
||||||
"drive_id": account.DriveId,
|
|
||||||
"file_id": file.FileId,
|
|
||||||
"expire_sec": 14400,
|
|
||||||
}).Post("https://api.aliyundrive.com/v2/file/get_download_url")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if e.Code != "" {
|
|
||||||
if e.Code == "AccessTokenInvalid" {
|
|
||||||
err = a.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return a.Link(path, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
return resp["url"].(string), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AliDrive) RefreshToken(account *model.Account) error {
|
|
||||||
url := "https://auth.aliyundrive.com/v2/account/token"
|
|
||||||
var resp TokenResp
|
|
||||||
var e AliRespError
|
|
||||||
_, err := aliClient.R().
|
|
||||||
//ForceContentType("application/json").
|
|
||||||
SetBody(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)
|
|
||||||
}
|
|
||||||
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AliDrive) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if old != nil {
|
|
||||||
conf.Cron.Remove(cron.EntryID(old.CronId))
|
|
||||||
}
|
|
||||||
if account.RootFolder == "" {
|
|
||||||
account.RootFolder = "root"
|
|
||||||
}
|
|
||||||
if account.Limit == 0 {
|
|
||||||
account.Limit = 200
|
|
||||||
}
|
|
||||||
err := a.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var resp 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
|
|
||||||
newAccount, ok := model.GetAccount(name)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = a.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
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Driver = (*AliDrive)(nil)
|
|
||||||
243
drivers/alidrive/alidrive.go
Normal file
243
drivers/alidrive/alidrive.go
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
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"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var aliClient = resty.New()
|
||||||
|
|
||||||
|
type AliRespError struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AliFiles struct {
|
||||||
|
Items []AliFile `json:"items"`
|
||||||
|
NextMarker string `json:"next_marker"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AliFile struct {
|
||||||
|
DriveId string `json:"drive_id"`
|
||||||
|
CreatedAt *time.Time `json:"created_at"`
|
||||||
|
FileExtension string `json:"file_extension"`
|
||||||
|
FileId string `json:"file_id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
ParentFileId string `json:"parent_file_id"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver AliDrive) FormatFile(file *AliFile) *model.File {
|
||||||
|
f := &model.File{
|
||||||
|
Id: file.FileId,
|
||||||
|
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, 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"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, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s", e.Message)
|
||||||
|
}
|
||||||
|
if strings.Contains(res.String(), `"status":200`) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New(res.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RegisterDriver(&AliDrive{})
|
||||||
|
aliClient.
|
||||||
|
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")
|
||||||
|
}
|
||||||
484
drivers/alidrive/driver.go
Normal file
484
drivers/alidrive/driver.go
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
package alidrive
|
||||||
|
|
||||||
|
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/robfig/cron/v3"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
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, 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 {
|
||||||
|
return base.ErrNotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
|
||||||
|
if file == nil {
|
||||||
|
return base.ErrEmptyFile
|
||||||
|
}
|
||||||
|
const DEFAULT uint64 = 10485760
|
||||||
|
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||||
|
var finish uint64 = 0
|
||||||
|
parentFile, err := driver.File(file.ParentPath, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !parentFile.IsDir() {
|
||||||
|
return base.ErrNotFolder
|
||||||
|
}
|
||||||
|
var resp UploadResp
|
||||||
|
var e AliRespError
|
||||||
|
partInfoList := make([]base.Json, 0)
|
||||||
|
var i int64
|
||||||
|
for i = 0; i < count; i++ {
|
||||||
|
partInfoList = append(partInfoList, base.Json{
|
||||||
|
"part_number": i + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
||||||
|
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||||
|
SetBody(base.Json{
|
||||||
|
"check_name_mode": "auto_rename",
|
||||||
|
// content_hash
|
||||||
|
"content_hash_name": "none",
|
||||||
|
"drive_id": account.DriveId,
|
||||||
|
"name": file.GetFileName(),
|
||||||
|
"parent_file_id": parentFile.Id,
|
||||||
|
"part_info_list": partInfoList,
|
||||||
|
//proof_code
|
||||||
|
"proof_version": "v1",
|
||||||
|
"size": file.GetSize(),
|
||||||
|
"type": "file",
|
||||||
|
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders") // /v2/file/create_with_proof
|
||||||
|
//log.Debugf("%+v\n%+v", resp, e)
|
||||||
|
if e.Code != "" {
|
||||||
|
if e.Code == "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)
|
||||||
|
}
|
||||||
|
var byteSize uint64
|
||||||
|
for i = 0; i < count; i++ {
|
||||||
|
byteSize = file.GetSize() - finish
|
||||||
|
if DEFAULT < byteSize {
|
||||||
|
byteSize = DEFAULT
|
||||||
|
}
|
||||||
|
log.Debugf("%d,%d", byteSize, finish)
|
||||||
|
byteData := make([]byte, byteSize)
|
||||||
|
n, err := io.ReadFull(file, byteData)
|
||||||
|
//n, err := file.Read(byteData)
|
||||||
|
//byteData, err := io.ReadAll(file)
|
||||||
|
//n := len(byteData)
|
||||||
|
log.Debug(err, n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
finish += uint64(n)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, bytes.NewBuffer(byteData))
|
||||||
|
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 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)
|
||||||
44
drivers/alist/alist.go
Normal file
44
drivers/alist/alist.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
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{})
|
||||||
|
}
|
||||||
192
drivers/alist/driver.go
Normal file
192
drivers/alist/driver.go
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
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)
|
||||||
46
drivers/all.go
Normal file
46
drivers/all.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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/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/s3"
|
||||||
|
_ "github.com/Xhofe/alist/drivers/shandian"
|
||||||
|
_ "github.com/Xhofe/alist/drivers/teambition"
|
||||||
|
_ "github.com/Xhofe/alist/drivers/webdav"
|
||||||
|
_ "github.com/Xhofe/alist/drivers/yandex"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
186
drivers/baidu/baidu.go
Normal file
186
drivers/baidu/baidu.go
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package baidu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (driver Baidu) RefreshToken(account *model.Account) error {
|
||||||
|
err := driver.refreshToken(account)
|
||||||
|
if err != nil && err == base.ErrEmptyToken {
|
||||||
|
err = driver.refreshToken(account)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
account.Status = err.Error()
|
||||||
|
}
|
||||||
|
_ = model.SaveAccount(account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Baidu) refreshToken(account *model.Account) error {
|
||||||
|
u := "https://openapi.baidu.com/oauth/2.0/token"
|
||||||
|
var resp base.TokenResp
|
||||||
|
var e TokenErrResp
|
||||||
|
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
"refresh_token": account.RefreshToken,
|
||||||
|
"client_id": account.ClientId,
|
||||||
|
"client_secret": account.ClientSecret,
|
||||||
|
}).Get(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Error != "" {
|
||||||
|
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||||
|
}
|
||||||
|
if resp.RefreshToken == "" {
|
||||||
|
return base.ErrEmptyToken
|
||||||
|
}
|
||||||
|
account.Status = "work"
|
||||||
|
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Baidu) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||||
|
u := "https://pan.baidu.com/rest/2.0" + pathname
|
||||||
|
req := base.RestyClient.R()
|
||||||
|
req.SetQueryParam("access_token", account.AccessToken)
|
||||||
|
if headers != nil {
|
||||||
|
req.SetHeaders(headers)
|
||||||
|
}
|
||||||
|
if query != nil {
|
||||||
|
req.SetQueryParams(query)
|
||||||
|
}
|
||||||
|
if form != nil {
|
||||||
|
req.SetFormData(form)
|
||||||
|
}
|
||||||
|
if data != nil {
|
||||||
|
req.SetBody(data)
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
req.SetResult(resp)
|
||||||
|
}
|
||||||
|
var res *resty.Response
|
||||||
|
var err error
|
||||||
|
switch method {
|
||||||
|
case base.Get:
|
||||||
|
res, err = req.Get(u)
|
||||||
|
case base.Post:
|
||||||
|
res, err = req.Post(u)
|
||||||
|
case base.Patch:
|
||||||
|
res, err = req.Patch(u)
|
||||||
|
case base.Delete:
|
||||||
|
res, err = req.Delete(u)
|
||||||
|
case base.Put:
|
||||||
|
res, err = req.Put(u)
|
||||||
|
default:
|
||||||
|
return nil, base.ErrNotSupport
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//log.Debug(res.String())
|
||||||
|
errno := jsoniter.Get(res.Body(), "errno").ToInt()
|
||||||
|
if errno != 0 {
|
||||||
|
if errno == -6 {
|
||||||
|
err = driver.RefreshToken(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return driver.Request(pathname, method, headers, query, form, data, resp, account)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("errno: %d, refer to https://pan.baidu.com/union/doc/", errno)
|
||||||
|
}
|
||||||
|
return res.Body(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Baidu) Get(pathname string, params map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
|
||||||
|
return driver.Request(pathname, base.Get, nil, params, nil, nil, resp, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Baidu) Post(pathname string, params map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||||
|
return driver.Request(pathname, base.Post, nil, params, nil, data, resp, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Baidu) manage(opera string, filelist interface{}, account *model.Account) ([]byte, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"method": "filemanager",
|
||||||
|
"opera": opera,
|
||||||
|
}
|
||||||
|
marshal, err := utils.Json.Marshal(filelist)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := fmt.Sprintf("async=0&filelist=%s&ondup=newcopy", string(marshal))
|
||||||
|
return driver.Post("/xpan/file", params, data, nil, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Baidu) GetFiles(dir string, account *model.Account) ([]model.File, error) {
|
||||||
|
dir = utils.Join(account.RootFolder, dir)
|
||||||
|
start := 0
|
||||||
|
limit := 200
|
||||||
|
params := map[string]string{
|
||||||
|
"method": "list",
|
||||||
|
"dir": dir,
|
||||||
|
"web": "web",
|
||||||
|
}
|
||||||
|
if account.OrderBy != "" {
|
||||||
|
params["order"] = account.OrderBy
|
||||||
|
if account.OrderDirection == "desc" {
|
||||||
|
params["desc"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res := make([]model.File, 0)
|
||||||
|
for {
|
||||||
|
params["start"] = strconv.Itoa(start)
|
||||||
|
params["limit"] = strconv.Itoa(limit)
|
||||||
|
start += limit
|
||||||
|
var resp ListResp
|
||||||
|
_, err := driver.Get("/xpan/file", params, &resp, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(resp.List) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, f := range resp.List {
|
||||||
|
file := model.File{
|
||||||
|
Id: strconv.FormatInt(f.FsId, 10),
|
||||||
|
Name: f.ServerFilename,
|
||||||
|
Size: f.Size,
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: getTime(f.ServerMtime),
|
||||||
|
Thumbnail: f.Thumbs.Url3,
|
||||||
|
}
|
||||||
|
if f.Isdir == 1 {
|
||||||
|
file.Type = conf.FOLDER
|
||||||
|
} else {
|
||||||
|
file.Type = utils.GetFileType(path.Ext(f.ServerFilename))
|
||||||
|
}
|
||||||
|
res = append(res, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Baidu) create(path string, size uint64, isdir int, uploadid, block_list string, account *model.Account) ([]byte, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"method": "create",
|
||||||
|
}
|
||||||
|
data := fmt.Sprintf("path=%s&size=%d&isdir=%d", path, size, isdir)
|
||||||
|
if uploadid != "" {
|
||||||
|
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
|
||||||
|
}
|
||||||
|
return driver.Post("/xpan/file", params, data, nil, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RegisterDriver(&Baidu{})
|
||||||
|
}
|
||||||
355
drivers/baidu/driver.go
Normal file
355
drivers/baidu/driver.go
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
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().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("data/temp", "file-*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = tempFile.Close()
|
||||||
|
_ = os.Remove(tempFile.Name())
|
||||||
|
}()
|
||||||
|
_, err = io.Copy(tempFile, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = tempFile.Seek(0, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var Default uint64 = 4 * 1024 * 1024
|
||||||
|
defaultByteData := make([]byte, Default)
|
||||||
|
count := int(math.Ceil(float64(file.GetSize()) / float64(Default)))
|
||||||
|
var SliceSize uint64 = 256 * 1024
|
||||||
|
// cal md5
|
||||||
|
h1 := md5.New()
|
||||||
|
h2 := md5.New()
|
||||||
|
block_list := make([]string, 0)
|
||||||
|
content_md5 := ""
|
||||||
|
slice_md5 := ""
|
||||||
|
left := file.GetSize()
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
byteSize := Default
|
||||||
|
var byteData []byte
|
||||||
|
if left < Default {
|
||||||
|
byteSize = left
|
||||||
|
byteData = make([]byte, byteSize)
|
||||||
|
} else {
|
||||||
|
byteData = defaultByteData
|
||||||
|
}
|
||||||
|
left -= byteSize
|
||||||
|
_, err = io.ReadFull(tempFile, byteData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h1.Write(byteData)
|
||||||
|
h2.Write(byteData)
|
||||||
|
block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil))))
|
||||||
|
h2.Reset()
|
||||||
|
}
|
||||||
|
content_md5 = hex.EncodeToString(h1.Sum(nil))
|
||||||
|
_, err = tempFile.Seek(0, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if file.GetSize() <= SliceSize {
|
||||||
|
slice_md5 = content_md5
|
||||||
|
} else {
|
||||||
|
sliceData := make([]byte, SliceSize)
|
||||||
|
_, err = io.ReadFull(tempFile, sliceData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h2.Write(sliceData)
|
||||||
|
slice_md5 = hex.EncodeToString(h2.Sum(nil))
|
||||||
|
_, err = tempFile.Seek(0, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path := encodeURIComponent(utils.Join(account.RootFolder, file.ParentPath, file.Name))
|
||||||
|
block_list_str := fmt.Sprintf("[%s]", strings.Join(block_list, ","))
|
||||||
|
data := fmt.Sprintf("path=%s&size=%d&isdir=0&autoinit=1&block_list=%s&content-md5=%s&slice-md5=%s",
|
||||||
|
path, file.GetSize(),
|
||||||
|
block_list_str,
|
||||||
|
content_md5, slice_md5)
|
||||||
|
params := map[string]string{
|
||||||
|
"method": "precreate",
|
||||||
|
}
|
||||||
|
var precreateResp PrecreateResp
|
||||||
|
_, err = driver.Post("/xpan/file", params, data, &precreateResp, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("%+v", precreateResp)
|
||||||
|
if precreateResp.ReturnType == 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
params = map[string]string{
|
||||||
|
"method": "upload",
|
||||||
|
"access_token": account.AccessToken,
|
||||||
|
"type": "tmpfile",
|
||||||
|
"path": path,
|
||||||
|
"uploadid": precreateResp.Uploadid,
|
||||||
|
}
|
||||||
|
left = file.GetSize()
|
||||||
|
for _, partseq := range precreateResp.BlockList {
|
||||||
|
byteSize := Default
|
||||||
|
var byteData []byte
|
||||||
|
if left < Default {
|
||||||
|
byteSize = left
|
||||||
|
byteData = make([]byte, byteSize)
|
||||||
|
} else {
|
||||||
|
byteData = defaultByteData
|
||||||
|
}
|
||||||
|
left -= byteSize
|
||||||
|
_, err = io.ReadFull(tempFile, byteData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u := "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2"
|
||||||
|
params["partseq"] = strconv.Itoa(partseq)
|
||||||
|
res, err := base.RestyClient.R().SetQueryParams(params).SetFileReader("file", file.Name, bytes.NewReader(byteData)).Post(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugln(res.String())
|
||||||
|
}
|
||||||
|
_, err = driver.create(path, file.GetSize(), 0, precreateResp.Uploadid, block_list_str, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ base.Driver = (*Baidu)(nil)
|
||||||
84
drivers/baidu/types.go
Normal file
84
drivers/baidu/types.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package baidu
|
||||||
|
|
||||||
|
type TokenErrResp struct {
|
||||||
|
ErrorDescription string `json:"error_description"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
//TkbindId int `json:"tkbind_id"`
|
||||||
|
//OwnerType int `json:"owner_type"`
|
||||||
|
//Category int `json:"category"`
|
||||||
|
//RealCategory string `json:"real_category"`
|
||||||
|
FsId int64 `json:"fs_id"`
|
||||||
|
ServerMtime int64 `json:"server_mtime"`
|
||||||
|
//OperId int `json:"oper_id"`
|
||||||
|
//ServerCtime int `json:"server_ctime"`
|
||||||
|
Thumbs struct {
|
||||||
|
//Icon string `json:"icon"`
|
||||||
|
Url3 string `json:"url3"`
|
||||||
|
//Url2 string `json:"url2"`
|
||||||
|
//Url1 string `json:"url1"`
|
||||||
|
} `json:"thumbs"`
|
||||||
|
//Wpfile int `json:"wpfile"`
|
||||||
|
//LocalMtime int `json:"local_mtime"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
//ExtentTinyint7 int `json:"extent_tinyint7"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
//Share int `json:"share"`
|
||||||
|
//ServerAtime int `json:"server_atime"`
|
||||||
|
//Pl int `json:"pl"`
|
||||||
|
//LocalCtime int `json:"local_ctime"`
|
||||||
|
ServerFilename string `json:"server_filename"`
|
||||||
|
//Md5 string `json:"md5"`
|
||||||
|
//OwnerId int `json:"owner_id"`
|
||||||
|
//Unlist int `json:"unlist"`
|
||||||
|
Isdir int `json:"isdir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListResp struct {
|
||||||
|
Errno int `json:"errno"`
|
||||||
|
GuidInfo string `json:"guid_info"`
|
||||||
|
List []File `json:"list"`
|
||||||
|
RequestId int64 `json:"request_id"`
|
||||||
|
Guid int `json:"guid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownloadResp struct {
|
||||||
|
Errmsg string `json:"errmsg"`
|
||||||
|
Errno int `json:"errno"`
|
||||||
|
List []struct {
|
||||||
|
//Category int `json:"category"`
|
||||||
|
//DateTaken int `json:"date_taken,omitempty"`
|
||||||
|
Dlink string `json:"dlink"`
|
||||||
|
//Filename string `json:"filename"`
|
||||||
|
//FsId int64 `json:"fs_id"`
|
||||||
|
//Height int `json:"height,omitempty"`
|
||||||
|
//Isdir int `json:"isdir"`
|
||||||
|
//Md5 string `json:"md5"`
|
||||||
|
//OperId int `json:"oper_id"`
|
||||||
|
//Path string `json:"path"`
|
||||||
|
//ServerCtime int `json:"server_ctime"`
|
||||||
|
//ServerMtime int `json:"server_mtime"`
|
||||||
|
//Size int `json:"size"`
|
||||||
|
//Thumbs struct {
|
||||||
|
// Icon string `json:"icon,omitempty"`
|
||||||
|
// Url1 string `json:"url1,omitempty"`
|
||||||
|
// Url2 string `json:"url2,omitempty"`
|
||||||
|
// Url3 string `json:"url3,omitempty"`
|
||||||
|
//} `json:"thumbs"`
|
||||||
|
//Width int `json:"width,omitempty"`
|
||||||
|
} `json:"list"`
|
||||||
|
//Names struct {
|
||||||
|
//} `json:"names"`
|
||||||
|
RequestId string `json:"request_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrecreateResp struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Uploadid string `json:"uploadid"`
|
||||||
|
ReturnType int `json:"return_type"`
|
||||||
|
BlockList []int `json:"block_list"`
|
||||||
|
Errno int `json:"errno"`
|
||||||
|
RequestId int64 `json:"request_id"`
|
||||||
|
}
|
||||||
18
drivers/baidu/util.go
Normal file
18
drivers/baidu/util.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package baidu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getTime(t int64) *time.Time {
|
||||||
|
tm := time.Unix(t, 0)
|
||||||
|
return &tm
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeURIComponent(str string) string {
|
||||||
|
r := url.QueryEscape(str)
|
||||||
|
r = strings.ReplaceAll(r, "+", "%20")
|
||||||
|
return r
|
||||||
|
}
|
||||||
28
drivers/base/cache.go
Normal file
28
drivers/base/cache.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
174
drivers/base/driver.go
Normal file
174
drivers/base/driver.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if v.Config().OnlyProxy {
|
||||||
|
res[k] = v.Items()
|
||||||
|
} else {
|
||||||
|
res[k] = append([]Item{
|
||||||
|
//{
|
||||||
|
// Name: "allow_proxy",
|
||||||
|
// Label: "allow_proxy",
|
||||||
|
// Type: TypeBool,
|
||||||
|
// Required: true,
|
||||||
|
// Description: "allow proxy",
|
||||||
|
//},
|
||||||
|
{
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
}, 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{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
NoRedirectClient = resty.New().SetRedirectPolicy(
|
||||||
|
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
userAgent := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||||
|
NoRedirectClient.SetHeader("user-agent", userAgent)
|
||||||
|
RestyClient.SetHeader("user-agent", userAgent)
|
||||||
|
RestyClient.SetRetryCount(3)
|
||||||
|
}
|
||||||
50
drivers/base/types.go
Normal file
50
drivers/base/types.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package drivers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Driver interface {
|
|
||||||
Items() []Item
|
|
||||||
Path(path string, account *model.Account) (*model.File, []*model.File, error)
|
|
||||||
Link(path string, account *model.Account) (string, error)
|
|
||||||
Save(account *model.Account, old *model.Account) error
|
|
||||||
Proxy(c *gin.Context)
|
|
||||||
Preview(path string, account *model.Account) (interface{}, error)
|
|
||||||
// TODO
|
|
||||||
//MakeDir(path string, account *model.Account) error
|
|
||||||
//Move(src string, des string, account *model.Account) error
|
|
||||||
//Delete(path string) error
|
|
||||||
//Upload(file *fs.File, path string, account *model.Account) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Item struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Label string `json:"label"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Values string `json:"values"`
|
|
||||||
Required bool `json:"required"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenResp struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var driversMap = map[string]Driver{}
|
|
||||||
|
|
||||||
func RegisterDriver(name string, driver Driver) {
|
|
||||||
driversMap[name] = driver
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDriver(name string) (driver Driver, ok bool) {
|
|
||||||
driver, ok = driversMap[name]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDrivers() map[string][]Item {
|
|
||||||
res := make(map[string][]Item, 0)
|
|
||||||
for k, v := range driversMap {
|
|
||||||
res[k] = v.Items()
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
type Json map[string]interface{}
|
|
||||||
254
drivers/ftp/driver.go
Normal file
254
drivers/ftp/driver.go
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
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)
|
||||||
36
drivers/ftp/ftp.go
Normal file
36
drivers/ftp/ftp.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
287
drivers/google/driver.go
Normal file
287
drivers/google/driver.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "client_secret",
|
||||||
|
Label: "client secret",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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)
|
||||||
198
drivers/google/googledrive.go
Normal file
198
drivers/google/googledrive.go
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
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{})
|
||||||
|
}
|
||||||
193
drivers/lanzou/driver.go
Normal file
193
drivers/lanzou/driver.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
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
|
||||||
|
if account.InternalType == "cookie" {
|
||||||
|
downId, err = driver.GetDownPageId(file.Id, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url, err := driver.GetLink(downId, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
link := base.Link{
|
||||||
|
Url: url,
|
||||||
|
}
|
||||||
|
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)
|
||||||
259
drivers/lanzou/lanzou.go
Normal file
259
drivers/lanzou/lanzou.go
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
package lanzou
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var lanzouClient = resty.New()
|
||||||
|
|
||||||
|
type LanZouFile struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
NameAll string `json:"name_all"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
FolId string `json:"fol_id"`
|
||||||
|
Size string `json:"size"`
|
||||||
|
Time string `json:"time"`
|
||||||
|
Folder bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *Lanzou) FormatFile(file *LanZouFile) *model.File {
|
||||||
|
now := time.Now()
|
||||||
|
f := &model.File{
|
||||||
|
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 := lanzouClient.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 = lanzouClient.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 := lanzouClient.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 := 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 = lanzouClient.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++
|
||||||
|
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, error) {
|
||||||
|
var resp LanZouFilesResp
|
||||||
|
res, err := lanzouClient.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)
|
||||||
|
}
|
||||||
|
info, ok := resp.Info.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("%v", resp.Info)
|
||||||
|
}
|
||||||
|
fid, ok := info["f_id"].(string)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("%v", info["f_id"])
|
||||||
|
}
|
||||||
|
return fid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LanzouLinkResp struct {
|
||||||
|
Dom string `json:"dom"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Zt int `json:"zt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, error) {
|
||||||
|
shareUrl := account.SiteUrl
|
||||||
|
u, err := url.Parse(shareUrl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
res, err := lanzouClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
iframe := regexp.MustCompile(`<iframe class="ifr2" name=".{2,20}" src="(.+?)"`).FindStringSubmatch(res.String())
|
||||||
|
if len(iframe) == 0 {
|
||||||
|
return "", fmt.Errorf("get down empty page")
|
||||||
|
}
|
||||||
|
iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1])
|
||||||
|
res, err = lanzouClient.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 := regexp.MustCompile(`var websign = '(.+?)'`).FindStringSubmatch(res.String())[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 = lanzouClient.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("can't get link")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RegisterDriver(&Lanzou{})
|
||||||
|
lanzouClient.
|
||||||
|
SetRetryCount(3).
|
||||||
|
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
|
||||||
|
}
|
||||||
319
drivers/mediatrack/driver.go
Normal file
319
drivers/mediatrack/driver.go
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "order_direction",
|
||||||
|
Label: "desc",
|
||||||
|
Type: base.TypeSelect,
|
||||||
|
Values: "true,false",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
_, err := driver.File(path, account)
|
||||||
|
if err != base.ErrPathNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
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("data/temp", "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)
|
||||||
183
drivers/mediatrack/mediatrack.go
Normal file
183
drivers/mediatrack/mediatrack.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
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,111 +0,0 @@
|
|||||||
package drivers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Native struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Native) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil,fmt.Errorf("no need")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Native) Items() []Item {
|
|
||||||
return []Item{
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: "string",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Native) Proxy(c *gin.Context) {
|
|
||||||
// unnecessary
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Native) Save(account *model.Account, old *model.Account) error {
|
|
||||||
log.Debugf("save a account: [%s]", account.Name)
|
|
||||||
if !utils.Exists(account.RootFolder) {
|
|
||||||
return fmt.Errorf("[%s] not exist", account.RootFolder)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO sort files
|
|
||||||
func (n Native) Path(path string, account *model.Account) (*model.File, []*model.File, error) {
|
|
||||||
fullPath := filepath.Join(account.RootFolder, path)
|
|
||||||
log.Debugf("%s-%s-%s", account.RootFolder, path, fullPath)
|
|
||||||
if !utils.Exists(fullPath) {
|
|
||||||
return nil, nil, fmt.Errorf("path not found")
|
|
||||||
}
|
|
||||||
if utils.IsDir(fullPath) {
|
|
||||||
result := make([]*model.File, 0)
|
|
||||||
files, err := ioutil.ReadDir(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
|
||||||
if strings.HasPrefix(f.Name(), ".") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
time := f.ModTime()
|
|
||||||
file := &model.File{
|
|
||||||
Name: f.Name(),
|
|
||||||
Size: f.Size(),
|
|
||||||
Type: 0,
|
|
||||||
UpdatedAt: &time,
|
|
||||||
Driver: "Native",
|
|
||||||
}
|
|
||||||
if f.IsDir() {
|
|
||||||
file.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
|
|
||||||
}
|
|
||||||
result = append(result, file)
|
|
||||||
}
|
|
||||||
return nil, result, nil
|
|
||||||
}
|
|
||||||
f, err := os.Stat(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
time := f.ModTime()
|
|
||||||
file := &model.File{
|
|
||||||
Name: f.Name(),
|
|
||||||
Size: f.Size(),
|
|
||||||
Type: utils.GetFileType(filepath.Ext(f.Name())),
|
|
||||||
UpdatedAt: &time,
|
|
||||||
Driver: "Native",
|
|
||||||
}
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Native) Link(path string, account *model.Account) (string, error) {
|
|
||||||
fullPath := filepath.Join(account.RootFolder, path)
|
|
||||||
s, err := os.Stat(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if s.IsDir() {
|
|
||||||
return "", fmt.Errorf("can't down folder")
|
|
||||||
}
|
|
||||||
return fullPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Driver = (*Native)(nil)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterDriver("Native", &Native{})
|
|
||||||
}
|
|
||||||
265
drivers/native/driver.go
Normal file
265
drivers/native/driver.go
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
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{
|
||||||
|
Url: 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)
|
||||||
74
drivers/native/native.go
Normal file
74
drivers/native/native.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package native
|
||||||
|
|
||||||
|
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 {
|
||||||
|
var err error
|
||||||
|
var srcfd *os.File
|
||||||
|
var dstfd *os.File
|
||||||
|
var srcinfo os.FileInfo
|
||||||
|
|
||||||
|
if srcfd, err = os.Open(src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcfd.Close()
|
||||||
|
|
||||||
|
if dstfd, err = os.Create(dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstfd.Close()
|
||||||
|
|
||||||
|
if _, err = io.Copy(dstfd, srcfd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if srcinfo, err = os.Stat(src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Chmod(dst, srcinfo.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dir copies a whole directory recursively
|
||||||
|
func (driver *Native) CopyDir(src string, dst string) error {
|
||||||
|
var err error
|
||||||
|
var fds []os.FileInfo
|
||||||
|
var srcinfo os.FileInfo
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
for _, fd := range fds {
|
||||||
|
srcfp := path.Join(src, fd.Name())
|
||||||
|
dstfp := path.Join(dst, fd.Name())
|
||||||
|
|
||||||
|
if fd.IsDir() {
|
||||||
|
if err = driver.CopyDir(srcfp, dstfp); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = driver.CopyFile(srcfp, dstfp); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RegisterDriver(&Native{})
|
||||||
|
}
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
package drivers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Onedrive struct{}
|
|
||||||
|
|
||||||
var oneClient = resty.New()
|
|
||||||
|
|
||||||
type OnedriveHost struct {
|
|
||||||
Oauth string
|
|
||||||
Api string
|
|
||||||
}
|
|
||||||
|
|
||||||
var onedriveHostMap = map[string]OnedriveHost{
|
|
||||||
"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 init() {
|
|
||||||
RegisterDriver("Onedrive", &Onedrive{})
|
|
||||||
oneClient.SetRetryCount(3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string {
|
|
||||||
path = filepath.Join(account.RootFolder, path)
|
|
||||||
host, _ := onedriveHostMap[account.Zone]
|
|
||||||
if auth {
|
|
||||||
return host.Oauth
|
|
||||||
}
|
|
||||||
switch account.OnedriveType {
|
|
||||||
case "onedrive":
|
|
||||||
{
|
|
||||||
if 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 == "/" {
|
|
||||||
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 ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Onedrive) Items() []Item {
|
|
||||||
return []Item{
|
|
||||||
{
|
|
||||||
Name: "zone",
|
|
||||||
Label: "zone",
|
|
||||||
Type: "select",
|
|
||||||
Required: true,
|
|
||||||
Values: "global,cn,us,de",
|
|
||||||
Description: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "onedrive_type",
|
|
||||||
Label: "onedrive type",
|
|
||||||
Type: "select",
|
|
||||||
Required: true,
|
|
||||||
Values: "onedrive,sharepoint",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "client_id",
|
|
||||||
Label: "client id",
|
|
||||||
Type: "string",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "client_secret",
|
|
||||||
Label: "client secret",
|
|
||||||
Type: "string",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "redirect_uri",
|
|
||||||
Label: "redirect uri",
|
|
||||||
Type: "string",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "refresh_token",
|
|
||||||
Label: "refresh token",
|
|
||||||
Type: "string",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "site_id",
|
|
||||||
Label: "site id",
|
|
||||||
Type: "string",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: "string",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneTokenErr struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
ErrorDescription string `json:"error_description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Onedrive) RefreshToken(account *model.Account) error {
|
|
||||||
url := o.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
|
|
||||||
var resp TokenResp
|
|
||||||
var e OneTokenErr
|
|
||||||
_, err := oneClient.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)
|
|
||||||
}
|
|
||||||
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneFile struct {
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneFiles struct {
|
|
||||||
Value []OneFile `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneRespErr struct {
|
|
||||||
Error struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
} `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Onedrive) FormatFile(file *OneFile) *model.File {
|
|
||||||
f := &model.File{
|
|
||||||
Name: file.Name,
|
|
||||||
Size: file.Size,
|
|
||||||
UpdatedAt: file.LastModifiedDateTime,
|
|
||||||
Driver: "OneDrive",
|
|
||||||
Url: file.Url,
|
|
||||||
}
|
|
||||||
if file.File.MimeType == "" {
|
|
||||||
f.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Onedrive) GetFiles(account *model.Account, path string) ([]OneFile, error) {
|
|
||||||
var files OneFiles
|
|
||||||
var e OneRespErr
|
|
||||||
_, err := oneClient.R().SetResult(&files).SetError(&e).
|
|
||||||
SetHeader("Authorization", "Bearer "+account.AccessToken).
|
|
||||||
Get(o.GetMetaUrl(account, false, path) + "/children")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if e.Error.Code != "" {
|
|
||||||
return nil, fmt.Errorf("%s", e.Error.Message)
|
|
||||||
}
|
|
||||||
return files.Value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) {
|
|
||||||
var file OneFile
|
|
||||||
var e OneRespErr
|
|
||||||
_, err := oneClient.R().SetResult(&file).SetError(&e).
|
|
||||||
SetHeader("Authorization", "Bearer "+account.AccessToken).
|
|
||||||
Get(o.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 (o Onedrive) Path(path string, account *model.Account) (*model.File, []*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
|
|
||||||
if err == nil {
|
|
||||||
files, _ := cache.([]*model.File)
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
file, err := o.GetFile(account, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if file.File.MimeType != "" {
|
|
||||||
return o.FormatFile(file), nil, nil
|
|
||||||
} else {
|
|
||||||
files, err := o.GetFiles(account, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
res := make([]*model.File, 0)
|
|
||||||
for _, file := range files {
|
|
||||||
res = append(res, o.FormatFile(&file))
|
|
||||||
}
|
|
||||||
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), res, nil)
|
|
||||||
return nil, res, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Onedrive) Link(path string, account *model.Account) (string, error) {
|
|
||||||
file, err := o.GetFile(account, path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if file.File.MimeType == "" {
|
|
||||||
return "", fmt.Errorf("can't down folder")
|
|
||||||
}
|
|
||||||
return file.Url, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Onedrive) Save(account *model.Account, old *model.Account) error {
|
|
||||||
_, ok := onedriveHostMap[account.Zone]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("no [%s] zone", account.Zone)
|
|
||||||
}
|
|
||||||
if old != nil {
|
|
||||||
conf.Cron.Remove(cron.EntryID(old.CronId))
|
|
||||||
}
|
|
||||||
account.RootFolder = utils.ParsePath(account.RootFolder)
|
|
||||||
err := o.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cronId, err := conf.Cron.AddFunc("@every 1h", func() {
|
|
||||||
name := account.Name
|
|
||||||
newAccount, ok := model.GetAccount(name)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = o.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 (o Onedrive) Proxy(c *gin.Context) {
|
|
||||||
c.Request.Header.Del("Origin")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Driver = (*Onedrive)(nil)
|
|
||||||
278
drivers/onedrive/driver.go
Normal file
278
drivers/onedrive/driver.go
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
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.MimeType == "" {
|
||||||
|
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)
|
||||||
318
drivers/onedrive/onedrive.go
Normal file
318
drivers/onedrive/onedrive.go
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
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.MimeType == "" {
|
||||||
|
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{})
|
||||||
|
}
|
||||||
103
drivers/operate/operate.go
Normal file
103
drivers/operate/operate.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
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 := 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
|
||||||
|
}
|
||||||
325
drivers/pikpak/driver.go
Normal file
325
drivers/pikpak/driver.go
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
return &base.Link{
|
||||||
|
Url: resp.WebContentLink,
|
||||||
|
}, 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)
|
||||||
201
drivers/pikpak/pikpak.go
Normal file
201
drivers/pikpak/pikpak.go
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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{})
|
||||||
|
}
|
||||||
273
drivers/s3/driver.go
Normal file
273
drivers/s3/driver.go
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
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",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
123
drivers/s3/s3.go
Normal file
123
drivers/s3/s3.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
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/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,
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
marker = *listObjectsResult.NextMarker
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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{})
|
||||||
|
}
|
||||||
278
drivers/shandian/driver.go
Normal file
278
drivers/shandian/driver.go
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
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)
|
||||||
150
drivers/shandian/shandian.go
Normal file
150
drivers/shandian/shandian.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
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{})
|
||||||
|
}
|
||||||
284
drivers/teambition/driver.go
Normal file
284
drivers/teambition/driver.go
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "order_direction",
|
||||||
|
Label: "order_direction",
|
||||||
|
Type: base.TypeSelect,
|
||||||
|
Values: "Asc,Desc",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
235
drivers/teambition/teambition.go
Normal file
235
drivers/teambition/teambition.go
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
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"
|
||||||
|
if account.InternalType == "International" {
|
||||||
|
prefix = "us-tcs"
|
||||||
|
}
|
||||||
|
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": "https://www.teambition.com/",
|
||||||
|
}).SetBody(chunkData).Post(u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debug(res.Status(), res.String())
|
||||||
|
//req, err := http.NewRequest("POST",
|
||||||
|
// u,
|
||||||
|
// bytes.NewBuffer(chunkData))
|
||||||
|
//if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
//}
|
||||||
|
//req.Header.Set("Authorization", token)
|
||||||
|
//req.Header.Set("Content-Type", "application/octet-stream")
|
||||||
|
//req.Header.Set("Referer", "https://www.teambition.com/")
|
||||||
|
//resp, err := base.HttpClient.Do(req)
|
||||||
|
//res, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
//log.Debugf("chunk upload status: %s, res: %s", resp.Status, string(res))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res, err := base.RestyClient.R().SetHeader("Authorization", token).Post(
|
||||||
|
fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s",
|
||||||
|
prefix, newChunk.FileKey))
|
||||||
|
log.Debug(res.Status(), res.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &newChunk.FileUpload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Teambition) finishUpload(file *FileUpload, parentId string, account *model.Account) error {
|
||||||
|
file.InvolveMembers = []interface{}{}
|
||||||
|
file.Visible = "members"
|
||||||
|
file.ParentId = parentId
|
||||||
|
_, err := driver.Request("/api/works", base.Post, nil, nil, nil, base.Json{
|
||||||
|
"works": []FileUpload{*file},
|
||||||
|
"_parentId": parentId,
|
||||||
|
}, nil, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RegisterDriver(&Teambition{})
|
||||||
|
}
|
||||||
63
drivers/teambition/types.go
Normal file
63
drivers/teambition/types.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package teambition
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Collection struct {
|
||||||
|
ID string `json:"_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Updated time.Time `json:"updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Work struct {
|
||||||
|
ID string `json:"_id"`
|
||||||
|
FileName string `json:"fileName"`
|
||||||
|
FileSize int64 `json:"fileSize"`
|
||||||
|
FileKey string `json:"fileKey"`
|
||||||
|
FileCategory string `json:"fileCategory"`
|
||||||
|
DownloadURL string `json:"downloadUrl"`
|
||||||
|
ThumbnailURL string `json:"thumbnailUrl"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
Updated time.Time `json:"updated"`
|
||||||
|
PreviewURL string `json:"previewUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileUpload struct {
|
||||||
|
FileKey string `json:"fileKey"`
|
||||||
|
FileName string `json:"fileName"`
|
||||||
|
FileType string `json:"fileType"`
|
||||||
|
FileSize int `json:"fileSize"`
|
||||||
|
FileCategory string `json:"fileCategory"`
|
||||||
|
ImageWidth int `json:"imageWidth"`
|
||||||
|
ImageHeight int `json:"imageHeight"`
|
||||||
|
InvolveMembers []interface{} `json:"involveMembers"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
Visible string `json:"visible"`
|
||||||
|
ParentId string `json:"_parentId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChunkUpload struct {
|
||||||
|
FileUpload
|
||||||
|
Storage string `json:"storage"`
|
||||||
|
MimeType string `json:"mimeType"`
|
||||||
|
Chunks int `json:"chunks"`
|
||||||
|
ChunkSize int `json:"chunkSize"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
FileMD5 string `json:"fileMD5"`
|
||||||
|
LastUpdated time.Time `json:"lastUpdated"`
|
||||||
|
UploadedChunks []interface{} `json:"uploadedChunks"`
|
||||||
|
Token struct {
|
||||||
|
AppID string `json:"AppID"`
|
||||||
|
OrganizationID string `json:"OrganizationID"`
|
||||||
|
UserID string `json:"UserID"`
|
||||||
|
Exp time.Time `json:"Exp"`
|
||||||
|
Storage string `json:"Storage"`
|
||||||
|
Resource string `json:"Resource"`
|
||||||
|
Speed int `json:"Speed"`
|
||||||
|
} `json:"token"`
|
||||||
|
DownloadUrl string `json:"downloadUrl"`
|
||||||
|
ThumbnailUrl string `json:"thumbnailUrl"`
|
||||||
|
PreviewUrl string `json:"previewUrl"`
|
||||||
|
ImmPreviewUrl string `json:"immPreviewUrl"`
|
||||||
|
PreviewExt string `json:"previewExt"`
|
||||||
|
LastUploadTime interface{} `json:"lastUploadTime"`
|
||||||
|
}
|
||||||
18
drivers/teambition/util.go
Normal file
18
drivers/teambition/util.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package teambition
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func GetBetweenStr(str, start, end string) string {
|
||||||
|
n := strings.Index(str, start)
|
||||||
|
if n == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
n = n + len(start)
|
||||||
|
str = string([]byte(str)[n:])
|
||||||
|
m := strings.Index(str, end)
|
||||||
|
if m == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
str = string([]byte(str)[:m])
|
||||||
|
return str
|
||||||
|
}
|
||||||
185
drivers/webdav/driver.go
Normal file
185
drivers/webdav/driver.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
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)
|
||||||
23
drivers/webdav/webdav.go
Normal file
23
drivers/webdav/webdav.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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{})
|
||||||
|
}
|
||||||
224
drivers/yandex/driver.go
Normal file
224
drivers/yandex/driver.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
package yandex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Yandex struct{}
|
||||||
|
|
||||||
|
func (driver Yandex) Config() base.DriverConfig {
|
||||||
|
return base.DriverConfig{
|
||||||
|
Name: "Yandex.Disk",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Items() []base.Item {
|
||||||
|
return []base.Item{
|
||||||
|
{
|
||||||
|
Name: "refresh_token",
|
||||||
|
Label: "refresh token",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "root_folder",
|
||||||
|
Label: "root folder path",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Default: "/",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "order_by",
|
||||||
|
Label: "order_by",
|
||||||
|
Type: base.TypeSelect,
|
||||||
|
Default: "name",
|
||||||
|
Values: "name,path,created,modified,size",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "order_direction",
|
||||||
|
Label: "order_direction",
|
||||||
|
Type: base.TypeSelect,
|
||||||
|
Values: "asc,desc",
|
||||||
|
Default: "asc",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "client_id",
|
||||||
|
Label: "client id",
|
||||||
|
Default: "a78d5a69054042fa936f6c77f9a0ae8b",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "client_secret",
|
||||||
|
Label: "client secret",
|
||||||
|
Default: "9c119bbb04b346d2a52aa64401936b2b",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Save(account *model.Account, old *model.Account) error {
|
||||||
|
return driver.RefreshToken(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) File(path string, account *model.Account) (*model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
if path == "/" {
|
||||||
|
return &model.File{
|
||||||
|
Id: account.RootFolder,
|
||||||
|
Name: account.Name,
|
||||||
|
Size: 0,
|
||||||
|
Type: conf.FOLDER,
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: account.UpdatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
dir, name := filepath.Split(path)
|
||||||
|
files, err := driver.Files(dir, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if file.Name == name {
|
||||||
|
return &file, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, base.ErrPathNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Files(path string, account *model.Account) ([]model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
cache, err := base.GetCache(path, account)
|
||||||
|
if err == nil {
|
||||||
|
files, _ := cache.([]model.File)
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
files, err := driver.GetFiles(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(files) > 0 {
|
||||||
|
_ = base.SetCache(path, files, account)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||||
|
path := utils.Join(account.RootFolder, args.Path)
|
||||||
|
log.Debugln("down path:", path)
|
||||||
|
var resp DownResp
|
||||||
|
_, err := driver.Request("/download", base.Get, nil, map[string]string{
|
||||||
|
"path": path,
|
||||||
|
}, nil, nil, &resp, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
link := base.Link{
|
||||||
|
Url: resp.Href,
|
||||||
|
}
|
||||||
|
return &link, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||||
|
file, err := driver.File(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !file.IsDir() {
|
||||||
|
return file, nil, nil
|
||||||
|
}
|
||||||
|
files, err := driver.Files(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return nil, files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (driver Yandex) Proxy(r *http.Request, account *model.Account) {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (driver Yandex) Preview(path string, account *model.Account) (interface{}, error) {
|
||||||
|
return nil, base.ErrNotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) MakeDir(path string, account *model.Account) error {
|
||||||
|
path = utils.Join(account.RootFolder, path)
|
||||||
|
_, err := driver.Request("", base.Put, nil, map[string]string{
|
||||||
|
"path": path,
|
||||||
|
}, nil, nil, nil, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Move(src string, dst string, account *model.Account) error {
|
||||||
|
from := utils.Join(account.RootFolder, src)
|
||||||
|
path := utils.Join(account.RootFolder, dst)
|
||||||
|
_, err := driver.Request("/move", base.Post, nil, map[string]string{
|
||||||
|
"from": from,
|
||||||
|
"path": path,
|
||||||
|
"overwrite": "true",
|
||||||
|
}, nil, nil, nil, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Rename(src string, dst string, account *model.Account) error {
|
||||||
|
return driver.Move(src, dst, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Copy(src string, dst string, account *model.Account) error {
|
||||||
|
from := utils.Join(account.RootFolder, src)
|
||||||
|
path := utils.Join(account.RootFolder, dst)
|
||||||
|
_, err := driver.Request("/copy", base.Post, nil, map[string]string{
|
||||||
|
"from": from,
|
||||||
|
"path": path,
|
||||||
|
"overwrite": "true",
|
||||||
|
}, nil, nil, nil, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Delete(path string, account *model.Account) error {
|
||||||
|
path = utils.Join(account.RootFolder, path)
|
||||||
|
_, err := driver.Request("", base.Delete, nil, map[string]string{
|
||||||
|
"path": path,
|
||||||
|
}, nil, nil, nil, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Upload(file *model.FileStream, account *model.Account) error {
|
||||||
|
if file == nil {
|
||||||
|
return base.ErrEmptyFile
|
||||||
|
}
|
||||||
|
path := utils.Join(account.RootFolder, file.ParentPath, file.Name)
|
||||||
|
var resp UploadResp
|
||||||
|
_, err := driver.Request("/upload", base.Get, nil, map[string]string{
|
||||||
|
"path": path,
|
||||||
|
"overwrite": "true",
|
||||||
|
}, nil, nil, &resp, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(resp.Method, resp.Href, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Length", strconv.FormatUint(file.Size, 10))
|
||||||
|
req.Header.Set("Content-Type", "application/octet-stream")
|
||||||
|
_, err = base.HttpClient.Do(req)
|
||||||
|
//res, err := base.RestyClient.R().
|
||||||
|
// SetHeader("Content-Length", strconv.FormatUint(file.Size, 10)).
|
||||||
|
// SetBody(file).Put(resp.Href)
|
||||||
|
//log.Debugln(res.Status(), res.String())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ base.Driver = (*Yandex)(nil)
|
||||||
74
drivers/yandex/types.go
Normal file
74
drivers/yandex/types.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package yandex
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type TokenErrResp struct {
|
||||||
|
ErrorDescription string `json:"error_description"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrResp struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
//AntivirusStatus string `json:"antivirus_status"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
//CommentIds struct {
|
||||||
|
// PrivateResource string `json:"private_resource"`
|
||||||
|
// PublicResource string `json:"public_resource"`
|
||||||
|
//} `json:"comment_ids"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
//Exif struct {
|
||||||
|
// DateTime time.Time `json:"date_time"`
|
||||||
|
//} `json:"exif"`
|
||||||
|
//Created time.Time `json:"created"`
|
||||||
|
//ResourceId string `json:"resource_id"`
|
||||||
|
Modified time.Time `json:"modified"`
|
||||||
|
//MimeType string `json:"mime_type"`
|
||||||
|
File string `json:"file"`
|
||||||
|
//MediaType string `json:"media_type"`
|
||||||
|
Preview string `json:"preview"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
//Sha256 string `json:"sha256"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
//Md5 string `json:"md5"`
|
||||||
|
//Revision int64 `json:"revision"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilesResp struct {
|
||||||
|
Embedded struct {
|
||||||
|
Sort string `json:"sort"`
|
||||||
|
Items []File `json:"items"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
} `json:"_embedded"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Exif struct {
|
||||||
|
} `json:"exif"`
|
||||||
|
ResourceId string `json:"resource_id"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Modified time.Time `json:"modified"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
CommentIds struct {
|
||||||
|
} `json:"comment_ids"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Revision int64 `json:"revision"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownResp struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Templated bool `json:"templated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadResp struct {
|
||||||
|
OperationId string `json:"operation_id"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Templated bool `json:"templated"`
|
||||||
|
}
|
||||||
1
drivers/yandex/util.go
Normal file
1
drivers/yandex/util.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package yandex
|
||||||
154
drivers/yandex/yandex.go
Normal file
154
drivers/yandex/yandex.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package yandex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (driver Yandex) RefreshToken(account *model.Account) error {
|
||||||
|
err := driver.refreshToken(account)
|
||||||
|
if err != nil && err == base.ErrEmptyToken {
|
||||||
|
err = driver.refreshToken(account)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
account.Status = err.Error()
|
||||||
|
}
|
||||||
|
_ = model.SaveAccount(account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) refreshToken(account *model.Account) error {
|
||||||
|
u := "https://oauth.yandex.com/token"
|
||||||
|
var resp base.TokenResp
|
||||||
|
var e TokenErrResp
|
||||||
|
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
"refresh_token": account.RefreshToken,
|
||||||
|
"client_id": account.ClientId,
|
||||||
|
"client_secret": account.ClientSecret,
|
||||||
|
}).Post(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Error != "" {
|
||||||
|
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
||||||
|
}
|
||||||
|
if resp.RefreshToken == "" {
|
||||||
|
return base.ErrEmptyToken
|
||||||
|
}
|
||||||
|
account.Status = "work"
|
||||||
|
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||||
|
u := "https://cloud-api.yandex.net/v1/disk/resources" + pathname
|
||||||
|
req := base.RestyClient.R()
|
||||||
|
req.SetHeader("Authorization", "OAuth "+account.AccessToken)
|
||||||
|
if headers != nil {
|
||||||
|
req.SetHeaders(headers)
|
||||||
|
}
|
||||||
|
if query != nil {
|
||||||
|
req.SetQueryParams(query)
|
||||||
|
}
|
||||||
|
if form != nil {
|
||||||
|
req.SetFormData(form)
|
||||||
|
}
|
||||||
|
if data != nil {
|
||||||
|
req.SetBody(data)
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
req.SetResult(resp)
|
||||||
|
}
|
||||||
|
var res *resty.Response
|
||||||
|
var err error
|
||||||
|
var e ErrResp
|
||||||
|
req.SetError(&e)
|
||||||
|
switch method {
|
||||||
|
case base.Get:
|
||||||
|
res, err = req.Get(u)
|
||||||
|
case base.Post:
|
||||||
|
res, err = req.Post(u)
|
||||||
|
case base.Patch:
|
||||||
|
res, err = req.Patch(u)
|
||||||
|
case base.Delete:
|
||||||
|
res, err = req.Delete(u)
|
||||||
|
case base.Put:
|
||||||
|
res, err = req.Put(u)
|
||||||
|
default:
|
||||||
|
return nil, base.ErrNotSupport
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//log.Debug(res.String())
|
||||||
|
if e.Error != "" {
|
||||||
|
if e.Error == "UnauthorizedError" {
|
||||||
|
err = driver.RefreshToken(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return driver.Request(pathname, method, headers, query, form, data, resp, account)
|
||||||
|
}
|
||||||
|
return nil, errors.New(e.Description)
|
||||||
|
}
|
||||||
|
return res.Body(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Yandex) GetFiles(rawPath string, account *model.Account) ([]model.File, error) {
|
||||||
|
path_ := utils.Join(account.RootFolder, rawPath)
|
||||||
|
limit := 100
|
||||||
|
page := 1
|
||||||
|
res := make([]model.File, 0)
|
||||||
|
for {
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
query := map[string]string{
|
||||||
|
"path": path_,
|
||||||
|
"limit": strconv.Itoa(limit),
|
||||||
|
"offset": strconv.Itoa(offset),
|
||||||
|
}
|
||||||
|
if account.OrderBy != "" {
|
||||||
|
if account.OrderDirection == "desc" {
|
||||||
|
query["sort"] = "-" + account.OrderBy
|
||||||
|
} else {
|
||||||
|
query["sort"] = account.OrderBy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var resp FilesResp
|
||||||
|
_, err := driver.Request("", base.Get, nil, query, nil, nil, &resp, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, file := range resp.Embedded.Items {
|
||||||
|
f := model.File{
|
||||||
|
Name: file.Name,
|
||||||
|
Size: file.Size,
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: &file.Modified,
|
||||||
|
Thumbnail: file.Preview,
|
||||||
|
Url: file.File,
|
||||||
|
}
|
||||||
|
if file.Type == "dir" {
|
||||||
|
f.Type = conf.FOLDER
|
||||||
|
} else {
|
||||||
|
f.Type = utils.GetFileType(path.Ext(file.Name))
|
||||||
|
}
|
||||||
|
res = append(res, f)
|
||||||
|
}
|
||||||
|
if resp.Embedded.Total <= offset+limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RegisterDriver(&Yandex{})
|
||||||
|
}
|
||||||
37
go.mod
37
go.mod
@@ -3,17 +3,22 @@ module github.com/Xhofe/alist
|
|||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/aws/aws-sdk-go v1.27.0
|
||||||
github.com/eko/gocache/v2 v2.1.0
|
github.com/eko/gocache/v2 v2.1.0
|
||||||
|
github.com/gin-contrib/cors v1.3.1
|
||||||
github.com/gin-gonic/gin v1.7.4
|
github.com/gin-gonic/gin v1.7.4
|
||||||
github.com/go-playground/validator/v10 v10.9.0
|
|
||||||
github.com/go-resty/resty/v2 v2.6.0
|
github.com/go-resty/resty/v2 v2.6.0
|
||||||
|
github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b
|
||||||
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/robfig/cron/v3 v3.0.0
|
github.com/robfig/cron/v3 v3.0.0
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
gorm.io/driver/mysql v1.1.2
|
github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f
|
||||||
gorm.io/driver/postgres v1.1.2
|
golang.org/x/text v0.3.7
|
||||||
gorm.io/driver/sqlite v1.1.6
|
gorm.io/driver/mysql v1.2.3
|
||||||
gorm.io/gorm v1.21.16
|
gorm.io/driver/postgres v1.2.3
|
||||||
|
gorm.io/driver/sqlite v1.2.6
|
||||||
|
gorm.io/gorm v1.22.5
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -23,27 +28,28 @@ require (
|
|||||||
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/gin-contrib/cors v1.3.1 // indirect
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.9.0 // indirect
|
||||||
github.com/go-redis/redis/v8 v8.9.0 // indirect
|
github.com/go-redis/redis/v8 v8.9.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
github.com/jackc/pgconn v1.10.0 // indirect
|
github.com/jackc/pgconn v1.10.1 // indirect
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgproto3/v2 v2.1.1 // indirect
|
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||||
github.com/jackc/pgtype v1.8.1 // indirect
|
github.com/jackc/pgtype v1.9.1 // indirect
|
||||||
github.com/jackc/pgx/v4 v4.13.0 // indirect
|
github.com/jackc/pgx/v4 v4.14.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.2 // indirect
|
github.com/jinzhu/now v1.1.4 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.9 // indirect
|
github.com/mattn/go-sqlite3 v1.14.10 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
@@ -57,10 +63,9 @@ require (
|
|||||||
go.opentelemetry.io/otel v0.20.0 // indirect
|
go.opentelemetry.io/otel v0.20.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v0.20.0 // indirect
|
go.opentelemetry.io/otel/metric v0.20.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
|
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
|
||||||
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 // indirect
|
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
||||||
|
|||||||
40
go.sum
40
go.sum
@@ -29,6 +29,7 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
|
|||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||||
|
github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
|
||||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
@@ -181,6 +182,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
|||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
@@ -228,6 +231,8 @@ github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8
|
|||||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||||
github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU=
|
github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU=
|
||||||
github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||||
|
github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8=
|
||||||
|
github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||||
@@ -245,6 +250,8 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:
|
|||||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
|
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
|
||||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||||
@@ -253,19 +260,32 @@ github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrU
|
|||||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||||
github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
|
github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
|
||||||
github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||||
|
github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||||
|
github.com/jackc/pgtype v1.9.1 h1:MJc2s0MFS8C3ok1wQTdQxWuXQcB6+HwAm5x1CzW7mf0=
|
||||||
|
github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||||
github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
|
github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
|
||||||
github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
|
github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
|
||||||
|
github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8=
|
||||||
|
github.com/jackc/pgx/v4 v4.14.1 h1:71oo1KAGI6mXhLiTMn6iDFcp3e7+zon/capWjl2OEFU=
|
||||||
|
github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
|
||||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
||||||
|
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b h1:Ur6QAxsHCK99Quj9PaWafoV4unb0DO/HWiKExD+TN5g=
|
||||||
|
github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
@@ -325,6 +345,8 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
|
|||||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
@@ -482,6 +504,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f h1:L2NE7BXnSlSLoNYZ0lCwZDjdnYjCNYC71k9ClZUTFTs=
|
||||||
|
github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
|
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
|
||||||
@@ -533,6 +557,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
|
||||||
|
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
@@ -567,8 +593,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
|
golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY=
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -723,14 +749,24 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M=
|
gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M=
|
||||||
gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM=
|
gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM=
|
||||||
|
gorm.io/driver/mysql v1.2.3 h1:cZqzlOfg5Kf1VIdLC1D9hT6Cy9BgxhExLj/2tIgUe7Y=
|
||||||
|
gorm.io/driver/mysql v1.2.3/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
|
||||||
gorm.io/driver/postgres v1.1.2 h1:Amy3hCvLqM+/ICzjCnQr8wKFLVJTeOTdlMT7kCP+J1Q=
|
gorm.io/driver/postgres v1.1.2 h1:Amy3hCvLqM+/ICzjCnQr8wKFLVJTeOTdlMT7kCP+J1Q=
|
||||||
gorm.io/driver/postgres v1.1.2/go.mod h1:/AGV0zvqF3mt9ZtzLzQmXWQ/5vr+1V1TyHZGZVjzmwI=
|
gorm.io/driver/postgres v1.1.2/go.mod h1:/AGV0zvqF3mt9ZtzLzQmXWQ/5vr+1V1TyHZGZVjzmwI=
|
||||||
|
gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To=
|
||||||
|
gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs=
|
||||||
gorm.io/driver/sqlite v1.1.6 h1:p3U8WXkVFTOLPED4JjrZExfndjOtya3db8w9/vEMNyI=
|
gorm.io/driver/sqlite v1.1.6 h1:p3U8WXkVFTOLPED4JjrZExfndjOtya3db8w9/vEMNyI=
|
||||||
gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8=
|
gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8=
|
||||||
|
gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4=
|
||||||
|
gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY=
|
||||||
gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||||
gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||||
gorm.io/gorm v1.21.16 h1:YBIQLtP5PLfZQz59qfrq7xbrK7KWQ+JsXXCH/THlMqs=
|
gorm.io/gorm v1.21.16 h1:YBIQLtP5PLfZQz59qfrq7xbrK7KWQ+JsXXCH/THlMqs=
|
||||||
gorm.io/gorm v1.21.16/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
gorm.io/gorm v1.21.16/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||||
|
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
||||||
|
gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
|
||||||
|
gorm.io/gorm v1.22.5 h1:lYREBgc02Be/5lSCTuysZZDb6ffL2qrat6fg9CFbvXU=
|
||||||
|
gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -2,27 +2,25 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
ID uint `json:"id" gorm:"primaryKey"` // 唯一ID
|
||||||
Name string `json:"name" gorm:"unique" binding:"required"`
|
Name string `json:"name" gorm:"unique" binding:"required"` // 唯一名称
|
||||||
Index int `json:"index"`
|
Index int `json:"index"` // 序号 用于排序
|
||||||
Type string `json:"type"`
|
Type string `json:"type"` // 类型,即driver
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
RefreshToken string `json:"refresh_token"`
|
RefreshToken string `json:"refresh_token"`
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
RootFolder string `json:"root_folder"`
|
RootFolder string `json:"root_folder"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"` // 状态
|
||||||
CronId int
|
CronId int
|
||||||
DriveId string
|
DriveId string
|
||||||
Limit int `json:"limit"`
|
Limit int `json:"limit"`
|
||||||
OrderBy string `json:"order_by"`
|
OrderBy string `json:"order_by"`
|
||||||
OrderDirection string `json:"order_direction"`
|
OrderDirection string `json:"order_direction"`
|
||||||
Proxy bool `json:"proxy"`
|
|
||||||
UpdatedAt *time.Time `json:"updated_at"`
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
Search bool `json:"search"`
|
Search bool `json:"search"`
|
||||||
ClientId string `json:"client_id"`
|
ClientId string `json:"client_id"`
|
||||||
@@ -31,7 +29,20 @@ type Account struct {
|
|||||||
RedirectUri string `json:"redirect_uri"`
|
RedirectUri string `json:"redirect_uri"`
|
||||||
SiteUrl string `json:"site_url"`
|
SiteUrl string `json:"site_url"`
|
||||||
SiteId string `json:"site_id"`
|
SiteId string `json:"site_id"`
|
||||||
OnedriveType string `json:"onedrive_type"`
|
InternalType string `json:"internal_type"`
|
||||||
|
WebdavProxy bool `json:"webdav_proxy"` // 开启之后只会webdav走中转
|
||||||
|
Proxy bool `json:"proxy"` // 是否中转,开启之后web和webdav都会走中转
|
||||||
|
//AllowProxy bool `json:"allow_proxy"` // 是否允许中转下载
|
||||||
|
DownProxyUrl string `json:"down_proxy_url"` // 用于中转下载服务的URL 两处 1. path请求中返回的链接 2. down下载时进行302
|
||||||
|
APIProxyUrl string `json:"api_proxy_url"` // 用于中转api的地址
|
||||||
|
// for s3
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
AccessKey string `json:"access_key"`
|
||||||
|
AccessSecret string `json:"access_secret"`
|
||||||
|
CustomHost string `json:"custom_host"`
|
||||||
|
ExtractFolder string `json:"extract_folder"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var accountsMap = map[string]Account{}
|
var accountsMap = map[string]Account{}
|
||||||
@@ -53,19 +64,22 @@ func CreateAccount(account *Account) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteAccount(id uint) error {
|
func DeleteAccount(id uint) (*Account, error) {
|
||||||
var account Account
|
var account Account
|
||||||
account.ID = id
|
account.ID = id
|
||||||
if err := conf.DB.First(&account).Error; err != nil {
|
if err := conf.DB.First(&account).Error; err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
name := account.Name
|
name := account.Name
|
||||||
conf.Cron.Remove(cron.EntryID(account.CronId))
|
|
||||||
if err := conf.DB.Delete(&account).Error; err != nil {
|
if err := conf.DB.Delete(&account).Error; err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
delete(accountsMap, name)
|
delete(accountsMap, name)
|
||||||
return nil
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteAccountFromMap(name string) {
|
||||||
|
delete(accountsMap, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AccountsCount() int {
|
func AccountsCount() int {
|
||||||
@@ -86,16 +100,26 @@ func GetAccount(name string) (Account, bool) {
|
|||||||
return account, ok
|
return account, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAccountFiles() ([]*File, error) {
|
func GetAccountById(id uint) (*Account, error) {
|
||||||
files := make([]*File, 0)
|
var account Account
|
||||||
|
account.ID = id
|
||||||
|
if err := conf.DB.First(&account).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAccountFiles() ([]File, error) {
|
||||||
|
files := make([]File, 0)
|
||||||
var accounts []Account
|
var accounts []Account
|
||||||
if err := conf.DB.Order("`index`").Find(&accounts).Error; err != nil {
|
if err := conf.DB.Order("`index`").Find(&accounts).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, v := range accounts {
|
for _, v := range accounts {
|
||||||
files = append(files, &File{
|
files = append(files, File{
|
||||||
Name: v.Name,
|
Name: v.Name,
|
||||||
Size: 0,
|
Size: 0,
|
||||||
|
Driver: v.Type,
|
||||||
Type: conf.FOLDER,
|
Type: conf.FOLDER,
|
||||||
UpdatedAt: v.UpdatedAt,
|
UpdatedAt: v.UpdatedAt,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
|
Id string `json:"-"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
Type int `json:"type"`
|
Type int `json:"type"`
|
||||||
@@ -10,4 +16,71 @@ type File struct {
|
|||||||
UpdatedAt *time.Time `json:"updated_at"`
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
Thumbnail string `json:"thumbnail"`
|
Thumbnail string `json:"thumbnail"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
}
|
SizeStr string `json:"size_str"`
|
||||||
|
TimeStr string `json:"time_str"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortFiles(files []File, account *Account) {
|
||||||
|
if account.OrderBy == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Slice(files, func(i, j int) bool {
|
||||||
|
switch account.OrderBy {
|
||||||
|
case "name":
|
||||||
|
{
|
||||||
|
c := strings.Compare(files[i].Name, files[j].Name)
|
||||||
|
if account.OrderDirection == "DESC" {
|
||||||
|
return c >= 0
|
||||||
|
}
|
||||||
|
return c <= 0
|
||||||
|
}
|
||||||
|
case "size":
|
||||||
|
{
|
||||||
|
if account.OrderDirection == "DESC" {
|
||||||
|
return files[i].Size >= files[j].Size
|
||||||
|
}
|
||||||
|
return files[i].Size <= files[j].Size
|
||||||
|
}
|
||||||
|
case "updated_at":
|
||||||
|
if account.OrderDirection == "DESC" {
|
||||||
|
return files[i].UpdatedAt.After(*files[j].UpdatedAt)
|
||||||
|
}
|
||||||
|
return files[i].UpdatedAt.Before(*files[j].UpdatedAt)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractFolder(files []File, account *Account) {
|
||||||
|
if account.ExtractFolder == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
front := account.ExtractFolder == "front"
|
||||||
|
sort.Slice(files, func(i, j int) bool {
|
||||||
|
if files[i].IsDir() || files[j].IsDir() {
|
||||||
|
if !files[i].IsDir() {
|
||||||
|
return !front
|
||||||
|
}
|
||||||
|
if !files[j].IsDir() {
|
||||||
|
return front
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f File) GetSize() uint64 {
|
||||||
|
return uint64(f.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f File) GetName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f File) ModTime() time.Time {
|
||||||
|
return *f.UpdatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f File) IsDir() bool {
|
||||||
|
return f.Type == conf.FOLDER
|
||||||
|
}
|
||||||
|
|||||||
35
model/file_stream.go
Normal file
35
model/file_stream.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type FileStream struct {
|
||||||
|
File io.ReadCloser
|
||||||
|
Size uint64
|
||||||
|
ParentPath string
|
||||||
|
Name string
|
||||||
|
MIMEType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (file FileStream) Read(p []byte) (n int, err error) {
|
||||||
|
return file.File.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (file FileStream) GetMIMEType() string {
|
||||||
|
return file.MIMEType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (file FileStream) GetSize() uint64 {
|
||||||
|
return file.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (file FileStream) Close() error {
|
||||||
|
return file.File.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (file FileStream) GetFileName() string {
|
||||||
|
return file.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (file FileStream) GetParentPath() string {
|
||||||
|
return file.ParentPath
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ type Meta struct {
|
|||||||
Path string `json:"path" gorm:"unique" binding:"required"`
|
Path string `json:"path" gorm:"unique" binding:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Hide string `json:"hide"`
|
Hide string `json:"hide"`
|
||||||
|
Upload bool `json:"upload"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMetaByPath(path string) (*Meta, error) {
|
func GetMetaByPath(path string) (*Meta, error) {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,13 +13,31 @@ const (
|
|||||||
CONST
|
CONST
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FRONT = iota
|
||||||
|
BACK
|
||||||
|
OTHER
|
||||||
|
)
|
||||||
|
|
||||||
type SettingItem struct {
|
type SettingItem struct {
|
||||||
Key string `json:"key" gorm:"primaryKey" binding:"required"`
|
Key string `json:"key" gorm:"primaryKey" binding:"required"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Group int `json:"group"`
|
Group int `json:"group"`
|
||||||
|
Access int `json:"access"`
|
||||||
Values string `json:"values"`
|
Values string `json:"values"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var Version = SettingItem{
|
||||||
|
Key: "version",
|
||||||
|
Value: conf.GitTag,
|
||||||
|
Description: "version",
|
||||||
|
Type: "string",
|
||||||
|
Access: CONST,
|
||||||
|
Version: conf.GitTag,
|
||||||
|
Group: OTHER,
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveSettings(items []SettingItem) error {
|
func SaveSettings(items []SettingItem) error {
|
||||||
@@ -28,20 +48,36 @@ func SaveSetting(item SettingItem) error {
|
|||||||
return conf.DB.Save(item).Error
|
return conf.DB.Save(item).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSettingsPublic() (*[]SettingItem, error) {
|
func GetSettingsPublic() ([]SettingItem, error) {
|
||||||
var items []SettingItem
|
var items []SettingItem
|
||||||
if err := conf.DB.Where("`group` <> ?", 1).Find(&items).Error; err != nil {
|
if err := conf.DB.Where("`access` <> ?", 1).Find(&items).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSettings() (*[]SettingItem, error) {
|
func GetSettingsByGroup(group int) ([]SettingItem, error) {
|
||||||
|
var items []SettingItem
|
||||||
|
if err := conf.DB.Where("`group` = ?", group).Find(&items).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append([]SettingItem{Version}, items...)
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSettings() ([]SettingItem, error) {
|
||||||
var items []SettingItem
|
var items []SettingItem
|
||||||
if err := conf.DB.Find(&items).Error; err != nil {
|
if err := conf.DB.Find(&items).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &items, nil
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSetting(key string) error {
|
||||||
|
setting := SettingItem{
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
return conf.DB.Delete(&setting).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSettingByKey(key string) (*SettingItem, error) {
|
func GetSettingByKey(key string) (*SettingItem, error) {
|
||||||
@@ -57,23 +93,34 @@ func LoadSettings() {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
conf.TextTypes = strings.Split(textTypes.Value, ",")
|
conf.TextTypes = strings.Split(textTypes.Value, ",")
|
||||||
}
|
}
|
||||||
checkParent, err := GetSettingByKey("check parent folder")
|
// html
|
||||||
if err == nil {
|
|
||||||
conf.CheckParent = checkParent.Value == "true"
|
|
||||||
}
|
|
||||||
favicon, err := GetSettingByKey("favicon")
|
favicon, err := GetSettingByKey("favicon")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
//conf.Favicon = favicon.Value
|
//conf.Favicon = favicon.Value
|
||||||
conf.IndexHtml = strings.Replace(conf.RawIndexHtml, "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png", favicon.Value, 1)
|
conf.ManageHtml = strings.Replace(conf.RawIndexHtml, "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png", favicon.Value, 1)
|
||||||
}
|
}
|
||||||
customizeStyle, err := GetSettingByKey("customize style")
|
title, err := GetSettingByKey("title")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
//conf.CustomizeStyle = customizeStyle.Value
|
conf.ManageHtml = strings.Replace(conf.ManageHtml, "Loading...", title.Value, 1)
|
||||||
conf.IndexHtml = strings.Replace(conf.IndexHtml, "/* customize-style */", customizeStyle.Value, 1)
|
|
||||||
}
|
}
|
||||||
customizeScript, err := GetSettingByKey("customize script")
|
customizeHead, err := GetSettingByKey("customize head")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
//conf.CustomizeStyle = customizeScript.Value
|
conf.IndexHtml = strings.Replace(conf.ManageHtml, "<!-- customize head -->", customizeHead.Value, 1)
|
||||||
conf.IndexHtml = strings.Replace(conf.IndexHtml, "// customize-js", customizeScript.Value, 1)
|
}
|
||||||
|
customizeBody, err := GetSettingByKey("customize body")
|
||||||
|
if err == nil {
|
||||||
|
conf.IndexHtml = strings.Replace(conf.IndexHtml, "<!-- customize body -->", customizeBody.Value, 1)
|
||||||
|
}
|
||||||
|
// token
|
||||||
|
adminPassword, err := GetSettingByKey("password")
|
||||||
|
if err == nil {
|
||||||
|
conf.Token = utils.GetMD5Encode(fmt.Sprintf("https://github.com/Xhofe/alist-%s", adminPassword.Value))
|
||||||
|
}
|
||||||
|
// load settings
|
||||||
|
for _, key := range conf.LoadSettings {
|
||||||
|
vm, err := GetSettingByKey(key)
|
||||||
|
if err == nil {
|
||||||
|
conf.Set(key, vm.Value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/drivers"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetAccounts(c *gin.Context) {
|
|
||||||
accounts, err := model.GetAccounts()
|
|
||||||
if err != nil {
|
|
||||||
ErrorResp(c, err, 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
SuccessResp(c, accounts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateAccount(c *gin.Context) {
|
|
||||||
var req model.Account
|
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
|
||||||
ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
driver, ok := drivers.GetDriver(req.Type)
|
|
||||||
if !ok {
|
|
||||||
ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
req.UpdatedAt = &now
|
|
||||||
if err := model.CreateAccount(&req); err != nil {
|
|
||||||
ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
log.Debugf("new account: %+v", req)
|
|
||||||
err = driver.Save(&req, nil)
|
|
||||||
if err != nil {
|
|
||||||
ErrorResp(c, err, 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveAccount(c *gin.Context) {
|
|
||||||
var req model.Account
|
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
|
||||||
ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
driver, ok := drivers.GetDriver(req.Type)
|
|
||||||
if !ok {
|
|
||||||
ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
old, ok := model.GetAccount(req.Name)
|
|
||||||
now := time.Now()
|
|
||||||
req.UpdatedAt = &now
|
|
||||||
if err := model.SaveAccount(&req); err != nil {
|
|
||||||
ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
log.Debugf("save account: %+v", req)
|
|
||||||
if ok {
|
|
||||||
err = driver.Save(&req, &old)
|
|
||||||
} else {
|
|
||||||
err = driver.Save(&req, nil)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
ErrorResp(c, err, 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteAccount(c *gin.Context) {
|
|
||||||
idStr := c.Query("id")
|
|
||||||
id, err := strconv.Atoi(idStr)
|
|
||||||
if err != nil {
|
|
||||||
ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := model.DeleteAccount(uint(id)); err != nil {
|
|
||||||
ErrorResp(c, err, 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
SuccessResp(c)
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Auth(c *gin.Context) {
|
|
||||||
token := c.GetHeader("Authorization")
|
|
||||||
password, err := model.GetSettingByKey("password")
|
|
||||||
if err != nil {
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
ErrorResp(c, fmt.Errorf("password not set"), 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ErrorResp(c, err, 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if token != utils.GetMD5Encode(password.Value) {
|
|
||||||
ErrorResp(c, fmt.Errorf("wrong password"), 401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Login(c *gin.Context) {
|
|
||||||
SuccessResp(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckAccount(c *gin.Context) {
|
|
||||||
if model.AccountsCount() == 0 {
|
|
||||||
ErrorResp(c, fmt.Errorf("no accounts,please add one first"), 1001)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckParent(path string, password string) bool {
|
|
||||||
meta, err := model.GetMetaByPath(path)
|
|
||||||
if err == nil {
|
|
||||||
if meta.Password != "" && meta.Password != password {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
if path == "/" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return CheckParent(filepath.Dir(path), password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
51
server/common/check.go
Normal file
51
server/common/check.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Login(c *gin.Context) {
|
||||||
|
SuccessResp(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckParent(path string, password string) bool {
|
||||||
|
meta, err := model.GetMetaByPath(path)
|
||||||
|
if err == nil {
|
||||||
|
if meta.Password != "" && meta.Password != password {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
if path == "/" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return CheckParent(utils.Dir(path), password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckDownLink(path string, passwordMd5 string, name string) bool {
|
||||||
|
if !conf.GetBool("check down link") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
meta, err := model.GetMetaByPath(path)
|
||||||
|
log.Debugf("check down path: %s", path)
|
||||||
|
if err == nil {
|
||||||
|
log.Debugf("check down link: %s,%s", meta.Password, passwordMd5)
|
||||||
|
if meta.Password != "" && utils.SignWithPassword(name, meta.Password) != passwordMd5 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
if !conf.GetBool("check parent folder") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if path == "/" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return CheckDownLink(utils.Dir(path), passwordMd5, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
package server
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Xhofe/alist/drivers"
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
"github.com/Xhofe/alist/model"
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,7 +17,14 @@ type Resp struct {
|
|||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
|
type PathReq struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
PageNum int `json:"page_num"`
|
||||||
|
PageSize int `json:"page_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) {
|
||||||
var path, name string
|
var path, name string
|
||||||
switch model.AccountsCount() {
|
switch model.AccountsCount() {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -23,6 +33,9 @@ func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
|
|||||||
path = rawPath
|
path = rawPath
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
|
if path == "/" {
|
||||||
|
return nil, "", nil, errors.New("can't operate root of multiple accounts")
|
||||||
|
}
|
||||||
paths := strings.Split(rawPath, "/")
|
paths := strings.Split(rawPath, "/")
|
||||||
path = "/" + strings.Join(paths[2:], "/")
|
path = "/" + strings.Join(paths[2:], "/")
|
||||||
name = paths[1]
|
name = paths[1]
|
||||||
@@ -31,7 +44,7 @@ func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, "", nil, fmt.Errorf("no [%s] account", name)
|
return nil, "", nil, fmt.Errorf("no [%s] account", name)
|
||||||
}
|
}
|
||||||
driver, ok := drivers.GetDriver(account.Type)
|
driver, ok := base.GetDriver(account.Type)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
|
return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
|
||||||
}
|
}
|
||||||
@@ -39,6 +52,7 @@ func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ErrorResp(c *gin.Context, err error, code int) {
|
func ErrorResp(c *gin.Context, err error, code int) {
|
||||||
|
log.Error(err.Error())
|
||||||
c.JSON(200, Resp{
|
c.JSON(200, Resp{
|
||||||
Code: code,
|
Code: code,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
@@ -47,6 +61,16 @@ func ErrorResp(c *gin.Context, err error, code int) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ErrorStrResp(c *gin.Context, str string, code int) {
|
||||||
|
log.Error(str)
|
||||||
|
c.JSON(200, Resp{
|
||||||
|
Code: code,
|
||||||
|
Message: str,
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
func SuccessResp(c *gin.Context, data ...interface{}) {
|
func SuccessResp(c *gin.Context, data ...interface{}) {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
c.JSON(200, Resp{
|
c.JSON(200, Resp{
|
||||||
@@ -62,3 +86,18 @@ func SuccessResp(c *gin.Context, data ...interface{}) {
|
|||||||
Data: data[0],
|
Data: data[0],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Hide(meta *model.Meta, files []model.File) []model.File {
|
||||||
|
//meta, _ := model.GetMetaByPath(path)
|
||||||
|
if meta != nil && meta.Hide != "" {
|
||||||
|
tmpFiles := make([]model.File, 0)
|
||||||
|
hideFiles := strings.Split(meta.Hide, ",")
|
||||||
|
for _, item := range files {
|
||||||
|
if !utils.IsContain(hideFiles, item.Name) {
|
||||||
|
tmpFiles = append(tmpFiles, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
files = tmpFiles
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
102
server/controllers/account.go
Normal file
102
server/controllers/account.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAccounts(c *gin.Context) {
|
||||||
|
accounts, err := model.GetAccounts()
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateAccount(c *gin.Context) {
|
||||||
|
var req model.Account
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
driver, ok := base.GetDriver(req.Type)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorStrResp(c, fmt.Sprintf("No [%s] driver", req.Type), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
req.UpdatedAt = &now
|
||||||
|
if err := model.CreateAccount(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
log.Debugf("new account: %+v", req)
|
||||||
|
err = driver.Save(&req, nil)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveAccount(c *gin.Context) {
|
||||||
|
var req model.Account
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
driver, ok := base.GetDriver(req.Type)
|
||||||
|
if !ok {
|
||||||
|
common.ErrorStrResp(c, fmt.Sprintf("No [%s] driver", req.Type), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
old, err := model.GetAccountById(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
req.UpdatedAt = &now
|
||||||
|
if old.Name != req.Name {
|
||||||
|
model.DeleteAccountFromMap(old.Name)
|
||||||
|
}
|
||||||
|
if err := model.SaveAccount(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
log.Debugf("save account: %+v", req)
|
||||||
|
err = driver.Save(&req, old)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteAccount(c *gin.Context) {
|
||||||
|
idStr := c.Query("id")
|
||||||
|
id, err := strconv.Atoi(idStr)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if account, err := model.DeleteAccount(uint(id)); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
driver, ok := base.GetDriver(account.Type)
|
||||||
|
if ok {
|
||||||
|
_ = driver.Save(nil, account)
|
||||||
|
} else {
|
||||||
|
log.Errorf("no driver: %s", account.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
package server
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ClearCache(c *gin.Context) {
|
func ClearCache(c *gin.Context) {
|
||||||
err := conf.Cache.Clear(conf.Ctx)
|
err := conf.Cache.Clear(conf.Ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
} else {
|
} else {
|
||||||
SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
31
server/controllers/down.go
Normal file
31
server/controllers/down.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Down(c *gin.Context) {
|
||||||
|
rawPath := c.Param("path")
|
||||||
|
rawPath = utils.ParsePath(rawPath)
|
||||||
|
log.Debugf("down: %s", rawPath)
|
||||||
|
account, path, driver, err := common.ParsePath(rawPath)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if driver.Config().OnlyProxy || account.Proxy {
|
||||||
|
Proxy(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
link, err := driver.Link(base.Args{Path: path, IP: c.ClientIP()}, account)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Redirect(302, link.Url)
|
||||||
|
return
|
||||||
|
}
|
||||||
11
server/controllers/driver.go
Normal file
11
server/controllers/driver.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDrivers(c *gin.Context) {
|
||||||
|
common.SuccessResp(c, base.GetDrivers())
|
||||||
|
}
|
||||||
61
server/controllers/file/copy.go
Normal file
61
server/controllers/file/copy.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/drivers/operate"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Copy(c *gin.Context) {
|
||||||
|
var req MoveCopyReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.Names) == 0 {
|
||||||
|
common.ErrorStrResp(c, "Empty file names", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if model.AccountsCount() > 1 && (req.SrcDir == "/" || req.DstDir == "/") {
|
||||||
|
common.ErrorStrResp(c, "Can't operate root folder", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srcAccount, srcPath, srcDriver, err := common.ParsePath(utils.Join(req.SrcDir, req.Names[0]))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dstAccount, dstPath, _, err := common.ParsePath(utils.Join(req.DstDir, req.Names[0]))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if srcAccount.Name != dstAccount.Name {
|
||||||
|
common.ErrorStrResp(c, "Can't copy files between two accounts", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if srcPath == "/" || dstPath == "/" {
|
||||||
|
common.ErrorStrResp(c, "Can't copy root folder", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srcDir, dstDir := utils.Dir(srcPath), utils.Dir(dstPath)
|
||||||
|
for i, name := range req.Names {
|
||||||
|
clearCache := false
|
||||||
|
if i == len(req.Names)-1 {
|
||||||
|
clearCache = true
|
||||||
|
}
|
||||||
|
err := operate.Copy(srcDriver, srcAccount, utils.Join(srcDir, name), utils.Join(dstDir, name), clearCache)
|
||||||
|
if err != nil {
|
||||||
|
if i == 0 {
|
||||||
|
_ = base.DeleteCache(srcDir, srcAccount)
|
||||||
|
_ = base.DeleteCache(dstDir, dstAccount)
|
||||||
|
}
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
50
server/controllers/file/delete.go
Normal file
50
server/controllers/file/delete.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/drivers/operate"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeleteFilesReq struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Names []string `json:"names"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteFiles(c *gin.Context) {
|
||||||
|
var req DeleteFilesReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.Names) == 0 {
|
||||||
|
common.ErrorStrResp(c, "Empty file names", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, name := range req.Names {
|
||||||
|
account, path_, driver, err := common.ParsePath(utils.Join(req.Path, name))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if path_ == "/" {
|
||||||
|
common.ErrorStrResp(c, "Delete root folder is not allowed", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clearCache := false
|
||||||
|
if i == len(req.Names)-1 {
|
||||||
|
clearCache = true
|
||||||
|
}
|
||||||
|
err = operate.Delete(driver, account, path_, clearCache)
|
||||||
|
if err != nil {
|
||||||
|
if i == 0 {
|
||||||
|
_ = base.DeleteCache(utils.Dir(path_), account)
|
||||||
|
}
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
53
server/controllers/file/folder.go
Normal file
53
server/controllers/file/folder.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/drivers/operate"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FolderReq struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Folder(c *gin.Context) {
|
||||||
|
var req FolderReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var files = make([]model.File, 0)
|
||||||
|
var err error
|
||||||
|
if model.AccountsCount() > 1 && (req.Path == "/" || req.Path == "") {
|
||||||
|
files, err = model.GetAccountFiles()
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
account, path, driver, err := common.ParsePath(req.Path)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, rawFiles, err := operate.Path(driver, account, path)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if file != nil {
|
||||||
|
common.ErrorStrResp(c, "Not folder", 400)
|
||||||
|
}
|
||||||
|
for _, file := range rawFiles {
|
||||||
|
if file.IsDir() {
|
||||||
|
files = append(files, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.JSON(200, common.Resp{
|
||||||
|
Code: 200,
|
||||||
|
Message: "success",
|
||||||
|
Data: files,
|
||||||
|
})
|
||||||
|
}
|
||||||
34
server/controllers/file/mkdir.go
Normal file
34
server/controllers/file/mkdir.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/drivers/operate"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MkdirReq struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Mkdir(c *gin.Context) {
|
||||||
|
var req MkdirReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account, path_, driver, err := common.ParsePath(req.Path)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if path_ == "/" {
|
||||||
|
common.ErrorStrResp(c, "Folder name can't be empty", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = operate.MakeDir(driver, account, path_, true)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
67
server/controllers/file/move.go
Normal file
67
server/controllers/file/move.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/drivers/operate"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MoveCopyReq struct {
|
||||||
|
SrcDir string `json:"src_dir"`
|
||||||
|
DstDir string `json:"dst_dir"`
|
||||||
|
Names []string `json:"names"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Move(c *gin.Context) {
|
||||||
|
var req MoveCopyReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.Names) == 0 {
|
||||||
|
common.ErrorStrResp(c, "Empty file names", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if model.AccountsCount() > 1 && (req.SrcDir == "/" || req.DstDir == "/") {
|
||||||
|
common.ErrorStrResp(c, "Can't operate root folder", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srcAccount, srcPath, srcDriver, err := common.ParsePath(utils.Join(req.SrcDir, req.Names[0]))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dstAccount, dstPath, _, err := common.ParsePath(utils.Join(req.DstDir, req.Names[0]))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if srcAccount.Name != dstAccount.Name {
|
||||||
|
common.ErrorStrResp(c, "Can't move files between two accounts", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if srcPath == "/" || dstPath == "/" {
|
||||||
|
common.ErrorStrResp(c, "Can't move root folder", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srcDir, dstDir := utils.Dir(srcPath), utils.Dir(dstPath)
|
||||||
|
for i, name := range req.Names {
|
||||||
|
clearCache := false
|
||||||
|
if i == len(req.Names)-1 {
|
||||||
|
clearCache = true
|
||||||
|
}
|
||||||
|
err := operate.Move(srcDriver, srcAccount, utils.Join(srcDir, name), utils.Join(dstDir, name), clearCache)
|
||||||
|
if err != nil {
|
||||||
|
if i == 0 {
|
||||||
|
_ = base.DeleteCache(srcDir, srcAccount)
|
||||||
|
_ = base.DeleteCache(dstDir, dstAccount)
|
||||||
|
}
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
36
server/controllers/file/raname.go
Normal file
36
server/controllers/file/raname.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/drivers/operate"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RenameReq struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Rename(c *gin.Context) {
|
||||||
|
var req RenameReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account, path_, driver, err := common.ParsePath(req.Path)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if path_ == "/" {
|
||||||
|
common.ErrorStrResp(c, "Can't edit account name here", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = operate.Move(driver, account, path_, utils.Join(utils.Dir(path_), req.Name), true)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
65
server/controllers/file/upload.go
Normal file
65
server/controllers/file/upload.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/drivers/operate"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UploadFiles(c *gin.Context) {
|
||||||
|
path := c.PostForm("path")
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
token := c.GetHeader("Authorization")
|
||||||
|
if token != conf.Token {
|
||||||
|
password := c.PostForm("password")
|
||||||
|
meta, _ := model.GetMetaByPath(path)
|
||||||
|
if meta == nil || !meta.Upload {
|
||||||
|
common.ErrorStrResp(c, "Not allow upload", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if meta.Password != "" && meta.Password != password {
|
||||||
|
common.ErrorStrResp(c, "Wrong password", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
account, path_, driver, err := common.ParsePath(path)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
form, err := c.MultipartForm()
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
}
|
||||||
|
files := form.File["files"]
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, file := range files {
|
||||||
|
open, err := file.Open()
|
||||||
|
fileStream := model.FileStream{
|
||||||
|
File: open,
|
||||||
|
Size: uint64(file.Size),
|
||||||
|
ParentPath: path_,
|
||||||
|
Name: file.Filename,
|
||||||
|
MIMEType: file.Header.Get("Content-Type"),
|
||||||
|
}
|
||||||
|
clearCache := false
|
||||||
|
if i == len(files)-1 {
|
||||||
|
clearCache = true
|
||||||
|
}
|
||||||
|
err = operate.Upload(driver, account, &fileStream, clearCache)
|
||||||
|
if err != nil {
|
||||||
|
if i != 0 {
|
||||||
|
_ = base.DeleteCache(path_, account)
|
||||||
|
}
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
47
server/controllers/link.go
Normal file
47
server/controllers/link.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/server/common"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LinkReq struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
//Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link 返回真实的链接,且携带头,只提供给中转程序使用
|
||||||
|
func Link(c *gin.Context) {
|
||||||
|
var req LinkReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Path = utils.ParsePath(req.Path)
|
||||||
|
rawPath := req.Path
|
||||||
|
rawPath = utils.ParsePath(rawPath)
|
||||||
|
log.Debugf("link: %s", rawPath)
|
||||||
|
account, path, driver, err := common.ParsePath(rawPath)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if driver.Config().OnlyLocal {
|
||||||
|
common.SuccessResp(c, base.Link{
|
||||||
|
Url: fmt.Sprintf("//%s/p%s?d=1&sign=%s", c.Request.Host, req.Path, utils.SignWithToken(utils.Base(rawPath), conf.Token)),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
link, err := driver.Link(base.Args{Path: path, IP: c.ClientIP()}, account)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, link)
|
||||||
|
return
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user