Compare commits

...

121 Commits

Author SHA1 Message Date
3ccf5ee620 fix: ipa plist key 2022-03-05 15:33:04 +08:00
b44243c021 feat: ipa name decode 2022-03-05 15:13:19 +08:00
4ae81b5a79 feat: aliyundrive fast upload (#652) 2022-03-05 13:14:57 +08:00
189f4c19a5 release: release v2.1.1 2022-03-04 21:00:39 +08:00
92a3d74af5 chore: Merge pull request #675 from Xhofe/all-contributors/add-vg-land [skip ci]
docs: add vg-land as a contributor for code
2022-03-04 20:16:17 +08:00
bb73a10332 docs: update .all-contributorsrc [skip ci] 2022-03-04 12:14:55 +00:00
3baf1e8c7b docs: update README_cn.md [skip ci] 2022-03-04 12:14:54 +00:00
fdb49f5fb4 docs: update README.md [skip ci] 2022-03-04 12:14:53 +00:00
2eedcc1626 feat: opus preview (#638) 2022-03-04 10:05:15 +08:00
6faecbd5d8 feat: add cache for xunlei 2022-03-04 09:55:37 +08:00
34ed05c62f style: go mod tidy 2022-03-04 09:52:08 +08:00
ce83d6eb40 build: fix dev build 2022-03-04 09:50:47 +08:00
2271cb6c7c docs: replace preview image [skip ci] 2022-03-03 23:30:45 +08:00
a42b30c96e docs: update .all-contributorsrc [skip ci] 2022-03-03 23:28:37 +08:00
ce25d16222 docs: update README_cn.md [skip ci] 2022-03-03 23:28:37 +08:00
b392e093e3 docs: update README.md [skip ci] 2022-03-03 23:28:37 +08:00
0d5b7298db docs: update .all-contributorsrc [skip ci] 2022-03-03 23:27:19 +08:00
2063ebb74d docs: update README_cn.md [skip ci] 2022-03-03 23:27:19 +08:00
0408d7ab5d docs: update README.md [skip ci] 2022-03-03 23:27:19 +08:00
d52451f9d2 docs: update .all-contributorsrc [skip ci] 2022-03-03 23:24:46 +08:00
ca9f77006a docs: update README_cn.md [skip ci] 2022-03-03 23:24:46 +08:00
e8e8d925f3 docs: update README.md [skip ci] 2022-03-03 23:24:46 +08:00
623aab4c28 docs: move all contributors 2022-03-03 23:17:13 +08:00
3bc81d471e docs: create .all-contributorsrc [skip ci] 2022-03-03 23:00:34 +08:00
dfddb5cfa1 docs: update README.md [skip ci] 2022-03-03 23:00:34 +08:00
80f5bde0cb build: just upx linux/amd64 2022-03-03 19:44:13 +08:00
9de072161e docs: add xunlei cloud (#659) 2022-03-03 19:38:59 +08:00
d08a7440bc 🎇 import uss 2022-03-03 19:33:40 +08:00
7a4bb2496d add welcome bot 2022-03-03 19:16:30 +08:00
f68ab40d26 🔀 Merge pull request #659 from foxxorcat/dev
 xunleicloud support
2022-03-03 17:03:23 +08:00
796d490fb7 🐛 fix #658 onedrive file/folder judge 2022-03-03 16:01:24 +08:00
2964d5a6db xunleicloud support 2022-03-03 15:45:33 +08:00
90b57dacee 🎇 remove set Content-Type for native 2022-03-02 19:27:40 +08:00
6af17e2509 🔒 fix #645 xss vulnerability 2022-03-01 20:09:25 +08:00
5193b2aa7d 🎨 fix some warning 2022-02-27 20:28:42 +08:00
3f644f07db ✏️ fix readme logo 2022-02-27 20:26:05 +08:00
d988f98b81 💚 fix upx 2022-02-26 00:12:00 +08:00
10634c7b77 👷 add build for macos 2022-02-25 23:59:27 +08:00
135d505192 back to cgo sqlite3 2022-02-25 23:55:57 +08:00
3f2be8a6ca 🔧 change default assets path 2022-02-25 22:08:12 +08:00
79bef09ee7 🔀 Merge branch 'feature/upyun' into dev 2022-02-25 21:06:49 +08:00
3534f6afac 💚 fix cal md5 2022-02-24 23:07:35 +08:00
106c1d069c 💚 change build platform 2022-02-24 22:55:50 +08:00
8ed0afe80d 🎇 add unupx version 2022-02-24 22:42:15 +08:00
6a6e3944d5 🔀 Merge branch 'feature/purego' into dev 2022-02-24 16:26:20 +08:00
94d5b5e47e direct but proxy types 2022-02-24 16:25:17 +08:00
e61b0f8e34 🔥 remove cgo to pure go 2022-02-24 16:08:49 +08:00
f7fbe1de6c 👷 build for all branch 2022-02-23 20:17:50 +08:00
01de01630e 🔥 remove placeholder for uss 2022-02-23 20:16:57 +08:00
f9f92e2198 ✏️ fix typo 2022-02-23 20:13:52 +08:00
7d5f50b04a 👷 build for all branch 2022-02-23 20:12:21 +08:00
72b5d25e4c ✏️ fix label typo 2022-02-23 20:07:45 +08:00
cae7f36531 🎇 refresh one folder 2022-02-23 19:16:33 +08:00
aa79f49e25 🐛 fix #600 aliyundrive move file 2022-02-23 14:56:17 +08:00
b4ad301d53 🐛 fix #599 lanzou url without password 2022-02-23 11:18:51 +08:00
00ed54c4c9 upyun uss support 2022-02-23 11:07:19 +08:00
ffa52794db 🍺 change login http method 2022-02-22 15:57:39 +08:00
24058d0c36 💚 fix dev build 2022-02-21 21:47:40 +08:00
641ca67671 💚 fix web replace 2022-02-21 20:31:01 +08:00
52ee2e0a8b 🐛 close #581 teambition update time error 2022-02-21 17:20:05 +08:00
724fc7f37e 💚 fix build web 2022-02-20 16:46:47 +08:00
9d279b104b dynamic public path 2022-02-20 15:14:18 +08:00
eb61f70164 🐛 fix show balance account 2022-02-20 13:06:59 +08:00
b3a8201768 🐛 fix that only two accounts can be load balanced 2022-02-19 21:49:37 +08:00
185795954b 🔊 add reason of failed to auto migrate model 2022-02-19 17:25:50 +08:00
cc62cc99d2 🐛 fix plist ipa name 2022-02-18 19:01:30 +08:00
270349f37c 🐛 fix write status sequence 2022-02-18 18:58:02 +08:00
977888070a 🐛 close #558 fix local file can't download 2022-02-18 18:50:01 +08:00
192d0f2bf3 🐛 fix update can't start 2022-02-18 18:49:40 +08:00
f2ec7884ec 🐛 fix only proxy webdav_direct 2022-02-17 17:49:03 +08:00
815975a4d2 🐛 fix 139 delete dir 2022-02-17 17:37:45 +08:00
f96a0238fc 👷 change issues-month-statistics 2022-02-17 17:29:45 +08:00
f695bd0959 🔧 change bundle-version 2022-02-17 16:54:34 +08:00
efe8f46e17 ✏️ fix typo 2022-02-17 11:56:00 +08:00
515daa22a9 🐛 fix #551 add S3ForcePathStyle config 2022-02-17 11:30:34 +08:00
f11e22deaf 🚧 change base64 characters 2022-02-17 09:14:21 +08:00
5be976169f 🚧 fix bundle-identifier 2022-02-17 00:44:30 +08:00
a6e08f3bf4 📝 update readme 2022-02-17 00:08:37 +08:00
944e68a979 🚧 plist generate 2022-02-17 00:06:10 +08:00
b3a6e33ce1 🎇 quark support 2022-02-16 20:20:39 +08:00
cb53ddc8e8 🐛 fix nil pointer 2022-02-16 16:10:39 +08:00
693417be4f 🔧 add hide files setting item 2022-02-15 14:51:50 +08:00
5c3f91bb55 support empty password 2022-02-15 14:42:24 +08:00
8a219d0732 👷 add issue bot 2022-02-14 20:03:42 +08:00
146a544af3 ✏️ fix typo 2022-02-14 19:43:43 +08:00
48dccc6c0b 📝 update readme 2022-02-14 15:41:20 +08:00
ce1740cec4 ✏️ fix typo 2022-02-14 15:36:21 +08:00
d40dbeae3e 💬 add issue template 2022-02-14 15:33:48 +08:00
5094b673c4 👷 add some issue bot 2022-02-14 15:32:25 +08:00
228e6d10e7 🔧 close #519 customize temp dir 2022-02-14 15:06:57 +08:00
e90b979d15 close #535 request set timeout 2022-02-14 14:59:00 +08:00
fb05a6ca48 🐛 fix #533 only encode fileName 2022-02-14 14:31:09 +08:00
e055ed3afa 🎇 lanzou proxy add user-agent 2022-02-13 17:46:07 +08:00
4371c470b3 webdav direct proxy 2022-02-13 15:57:42 +08:00
7bb237d0ef 🐛 fix #527 189 upload file name contains % 2022-02-13 13:03:34 +08:00
5c42354b01 🐛 fix #527 189 upload name contains + 2022-02-12 20:27:38 +08:00
387e8af422 🐛 fix 189 upload while filename contains & 2022-02-12 13:08:46 +08:00
5dca777caf 🔒 fix baidu direct link 2022-02-12 12:04:10 +08:00
0814778a14 🐛 fix no account error while only one 2022-02-11 17:11:02 +08:00
6827af3997 🎇 close #522 hide account for guest webdav 2022-02-11 16:19:55 +08:00
435bdea8f7 🔧 #523 add some default setting 2022-02-11 16:17:07 +08:00
4f81735af6 🎇 close #512 favicon redirect 2022-02-08 18:07:13 +08:00
bef3d2f88d 🐛 Fix the temp folder is not created at the first startup 2022-02-08 16:22:53 +08:00
ba99c7dc03 🐛 fix multiple accounts with the same prefix cannot be load balanced 2022-02-08 16:02:47 +08:00
f5c5162a9b load balance 2022-02-08 15:51:58 +08:00
a22903533e 🐛 set random seed 2022-02-04 16:08:15 +08:00
86cda58b22 🚧 echo password 2022-02-04 14:58:48 +08:00
7804cf9d5c 🔒 random webdav admin password 2022-02-04 14:39:11 +08:00
2bb7036110 🔧 change logo 2022-02-03 21:28:13 +08:00
ba545555cf 📝 update readme 2022-02-03 21:00:08 +08:00
be55ca690c 🔧 change default logo 2022-02-03 20:43:33 +08:00
9013add749 🔒 random initial password 2022-02-03 12:27:50 +08:00
3201b6da76 🐛 fix #376 windows webdav upload 2022-02-02 18:48:34 +08:00
feb42f1f4b 🔥 delete proxy interface 2022-02-02 18:03:54 +08:00
6f14d0eb5c 🎇 baidu disk support 2022-02-02 17:32:11 +08:00
7530d8f5b2 🐛 fix lanzou for download change 2022-02-01 22:33:19 +08:00
e25fe05a53 yandex disk support 2022-02-01 17:15:11 +08:00
8e0ab8f780 🔥 remove onedrive refresh token cron 2022-02-01 15:20:17 +08:00
cb2a3c2b42 🎨 change proxy interface 2022-02-01 14:28:21 +08:00
1b6ec94f33 🐛 fix s3 custom host 2022-02-01 11:34:32 +08:00
cb23edc1fe 🐛 fix #462 check connect while get ftp client 2022-01-31 11:13:29 +08:00
87 changed files with 4198 additions and 489 deletions

54
.all-contributorsrc Normal file
View File

@ -0,0 +1,54 @@
{
"files": [
"README.md",
"README_cn.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "Xhofe",
"name": "Xhofe",
"avatar_url": "https://avatars.githubusercontent.com/u/36558727?v=4",
"profile": "http://nn.ci",
"contributions": [
"code",
"ideas",
"doc"
]
},
{
"login": "foxxorcat",
"name": "foxxorcat",
"avatar_url": "https://avatars.githubusercontent.com/u/95907542?v=4",
"profile": "https://github.com/foxxorcat",
"contributions": [
"code"
]
},
{
"login": "DaoChen6",
"name": "道辰",
"avatar_url": "https://avatars.githubusercontent.com/u/63903027?v=4",
"profile": "https://www.iflu.cf/",
"contributions": [
"doc"
]
},
{
"login": "vg-land",
"name": "vg-land",
"avatar_url": "https://avatars.githubusercontent.com/u/16739728?v=4",
"profile": "https://vg-land.github.io/",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
"projectName": "alist",
"projectOwner": "Xhofe",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true
}

View File

@ -5,7 +5,7 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
Thanks for taking the time to fill out this bug report! Thanks for taking the time to fill out this bug report, please confirm that your issue is not a duplicate issue
- type: input - type: input
id: version id: version
attributes: attributes:

View File

@ -1,5 +1,5 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Questions & Discussions & Feature request - name: Questions & Discussions
url: https://github.com/Xhofe/alist/discussions url: https://github.com/Xhofe/alist/discussions
about: Use GitHub discussions for message-board style questions and discussions or feature request. about: Use GitHub discussions for message-board style questions and discussions.

View File

@ -0,0 +1,24 @@
name: "Feature request"
description: Feature request
labels: ["enhancement: pending triage"]
body:
- type: textarea
id: feature-description
attributes:
label: Description of the feature / 需求描述
validations:
required: true
- type: textarea
id: suggested-solution
attributes:
label: Suggested solution / 实现思路
description: |
Solutions to achieve this requirement.
实现此需求的解决思路。
- type: textarea
id: additional-context
attributes:
label: Additional context / 附件
description: |
Any other context or screenshots about the feature request here, or information you find helpful.
相关的任何其他上下文或截图,或者你觉得有帮助的信息

21
.github/config.yml vendored Normal file
View File

@ -0,0 +1,21 @@
# Configuration for welcome - https://github.com/behaviorbot/welcome
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
Thanks for opening your first issue here! Be sure to follow the issue template!
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
newPRWelcomeComment: >
Thanks for opening this pull request! Please check out our contributing guidelines.
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
# Comment to be posted to on pull requests merged by a first time user
firstPRMergeComment: >
Congrats on merging your first pull request! We here at behavior bot are proud of you!
# It is recommend to include as many gifs and emojis as possible

View File

@ -2,9 +2,9 @@ name: build
on: on:
push: push:
branches: [ v2 ] branches: [ '**' ]
pull_request: pull_request:
branches: [ v2 ] branches: [ '**' ]
jobs: jobs:
build: build:
@ -28,10 +28,9 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
ref: v2
path: alist path: alist
- name: Set up xgo - name: Install upx
run: | run: |
docker pull techknowlogick/xgo:latest docker pull techknowlogick/xgo:latest
go install src.techknowlogick.com/xgo@latest go install src.techknowlogick.com/xgo@latest

View File

@ -0,0 +1,20 @@
name: Check need info
on:
schedule:
- cron: "0 0 */7 * *"
jobs:
check-need-info:
runs-on: ubuntu-latest
steps:
- name: close-issues
uses: actions-cool/issues-helper@v2
with:
actions: 'close-issues'
token: ${{ secrets.GITHUB_TOKEN }}
labels: 'question'
inactive-day: 7
body: |
Hello @${{ github.event.issue.user.login }}, this issue was closed due to no activities in 7 days.
你好 @${{ github.event.issue.user.login }}此issue因超过7天未回复被关闭。

25
.github/workflows/issue_duplicate.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Issue Duplicate
on:
issues:
types: [labeled]
jobs:
create-comment:
runs-on: ubuntu-latest
if: github.event.label.name == 'duplicate'
steps:
- name: Create comment
uses: actions-cool/issues-helper@v2
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}, your issue is a duplicate and will be closed.
你好 @${{ github.event.issue.user.login }}你的issue是重复的将被关闭。
- name: Close issue
uses: actions-cool/issues-helper@v2
with:
actions: 'close-issue'
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,16 @@
name: Issue Month Statistics
on:
schedule:
- cron: "0 1 1 * *"
jobs:
month-statistics:
runs-on: ubuntu-latest
steps:
- name: month-statistics
uses: actions-cool/issues-month-statistics@v1
with:
count-lables: true
count-comments: true
emoji: 'eyes'

20
.github/workflows/issue_question.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Issue Question
on:
issues:
types: [labeled]
jobs:
create-comment:
runs-on: ubuntu-latest
if: github.event.label.name == 'question'
steps:
- name: Create comment
uses: actions-cool/issues-helper@v2.0.0
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}, please input issue by template and add detail. Issues labeled by `question` will be closed if no activities in 7 days.
你好 @${{ github.event.issue.user.login }}请按照issue模板填写, 并详细说明问题/复现步骤/实现思路或提供更多信息等, 7天内未回复issue自动关闭。

25
.github/workflows/issue_wontfix.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Issue Wontfix
on:
issues:
types: [labeled]
jobs:
lock-issue:
runs-on: ubuntu-latest
if: github.event.label.name == 'wontfix'
steps:
- name: Create comment
uses: actions-cool/issues-helper@v2
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}, this issue will not be worked on and will be closed.
你好 @${{ github.event.issue.user.login }},这不会被处理,将被关闭。
- name: Close issue
uses: actions-cool/issues-helper@v2
with:
actions: 'close-issue'
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -27,7 +27,7 @@ jobs:
persist-credentials: false persist-credentials: false
fetch-depth: 0 fetch-depth: 0
- name: Set up xgo - name: Install upx
run: | run: |
docker pull techknowlogick/xgo:latest docker pull techknowlogick/xgo:latest
go install src.techknowlogick.com/xgo@latest go install src.techknowlogick.com/xgo@latest

View File

@ -1,5 +1,5 @@
<div align="center"> <div align="center">
<h3><a href="https://alist.nn.ci">Alist</a></h3> <a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
<p><em>🗂Another file list program that supports multiple storage, powered by Gin and React.</em></p> <p><em>🗂Another file list program that supports multiple storage, powered by Gin and React.</em></p>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a> <a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a> <a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
@ -11,11 +11,14 @@
</a> </a>
</div> </div>
--- ---
English | [中文](./README_cn.md) English | [中文](./README_cn.md)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
## Features ## Features
- [x] Multiple storage - [x] Multiple storage
@ -35,6 +38,10 @@ English | [中文](./README_cn.md)
- [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ )) - [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ ))
- [x] [Mediatrack](https://www.mediatrack.cn/) - [x] [Mediatrack](https://www.mediatrack.cn/)
- [x] [139yun](https://yun.139.com/) (Personal, Family) - [x] [139yun](https://yun.139.com/) (Personal, Family)
- [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [Baidu Disk](http://pan.baidu.com/)
- [x] [Quark](https://pan.quark.cn)
- [x] [XunleiCloud](https://pan.xunlei.com/)
- [x] Easy to deploy and out-of-the-box - [x] Easy to deploy and out-of-the-box
- [x] File preview (PDF, markdown, code, plain text, ...) - [x] File preview (PDF, markdown, code, plain text, ...)
- [x] Image preview in gallery mode - [x] Image preview in gallery mode
@ -54,22 +61,45 @@ English | [中文](./README_cn.md)
## Discussion ## Discussion
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports only.** Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports and feature request only.**
## Demo ## Demo
Available at: <https://alist.nn.ci>. Available at: <https://alist.nn.ci>.
![demo](https://inews.gtimg.com/newsapp_ls/0/14256614096/0) ![demo](https://store.heytapimage.com/cdo-portal/feedback/202202/20/b271627971e29f0c7c9d59935b6ef381.png)
## Document ## Document
<https://alist-doc.nn.ci/en/> <https://alist-doc.nn.ci/en/>
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=foxxorcat" title="Code">💻</a></td>
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=vg-land" title="Code">💻</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
## License ## License
The `AList` is open-source software licensed under the AGPL-3.0 license. The `AList` is open-source software licensed under the AGPL-3.0 license.
--- ---
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe) > [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)

View File

@ -1,5 +1,5 @@
<div align="center"> <div align="center">
<h3><a href="https://alist.nn.ci">Alist</a></h3> <a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
<p><em>🗂️一个支持多存储的文件列表程序,使用 Gin 和 React 。</em></p> <p><em>🗂️一个支持多存储的文件列表程序,使用 Gin 和 React 。</em></p>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a> <a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a> <a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
@ -15,6 +15,10 @@
[English](./README.md) | 中文 [English](./README.md) | 中文
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
## 支持 ## 支持
- [x] 多种存储 - [x] 多种存储
@ -34,6 +38,10 @@
- [x] Teambition[中国](https://www.teambition.com/ )[国际](https://us.teambition.com/ ) - [x] Teambition[中国](https://www.teambition.com/ )[国际](https://us.teambition.com/ )
- [x] [分秒帧](https://www.mediatrack.cn/) - [x] [分秒帧](https://www.mediatrack.cn/)
- [x] [和彩云](https://yun.139.com/) (个人云, 家庭云) - [x] [和彩云](https://yun.139.com/) (个人云, 家庭云)
- [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [百度网盘](http://pan.baidu.com/)
- [x] [夸克网盘](https://pan.quark.cn)
- [x] [迅雷云盘](https://pan.xunlei.com/)
- [x] 部署方便,开箱即用 - [x] 部署方便,开箱即用
- [x] 文件预览PDF、markdown、代码、纯文本…… - [x] 文件预览PDF、markdown、代码、纯文本……
- [x] 画廊模式下的图像预览 - [x] 画廊模式下的图像预览
@ -53,22 +61,45 @@
## 讨论 ## 讨论
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) **issue仅针对错误报告。** 一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) **issue仅针对错误报告和功能请求。**
## 演示 ## 演示
<https://alist.nn.ci> <https://alist.nn.ci>
![演示](https://inews.gtimg.com/newsapp_ls/0/14256614096/0) ![演示](https://store.heytapimage.com/cdo-portal/feedback/202202/20/b271627971e29f0c7c9d59935b6ef381.png)
## 文档 ## 文档
<https://alist-doc.nn.ci/> <https://alist-doc.nn.ci/>
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=foxxorcat" title="Code">💻</a></td>
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=vg-land" title="Code">💻</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
## 许可 ## 许可
`AList` 是在 AGPL-3.0 许可下许可的开源软件。 `AList` 是在 AGPL-3.0 许可下许可的开源软件。
--- ---
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe) > [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)

View File

@ -22,7 +22,7 @@ func Init() bool {
log.Errorf(err.Error()) log.Errorf(err.Error())
return false return false
} }
log.Infof("current password: %s", pass.Value) fmt.Printf("your password: %s\n", pass.Value)
return false return false
} }
server.InitIndex() server.InitIndex()
@ -34,7 +34,8 @@ func Init() bool {
func main() { func main() {
if conf.Version { if conf.Version {
fmt.Printf("Built At: %s\nGo Version: %s\nAuthor: %s\nCommit ID: %s\nVersion: %s\n", conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.GitTag) fmt.Printf("Built At: %s\nGo Version: %s\nAuthor: %s\nCommit ID: %s\nVersion: %s\nWebVersion: %s\n",
conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.GitTag, conf.WebTag)
return return
} }
if !Init() { if !Init() {

View File

@ -21,13 +21,12 @@ func InitConf() {
if !utils.WriteToJson(conf.ConfigFile, conf.Conf) { if !utils.WriteToJson(conf.ConfigFile, conf.Conf) {
log.Fatalf("failed to create default config file") log.Fatalf("failed to create default config file")
} }
return } else {
}
config, err := ioutil.ReadFile(conf.ConfigFile) config, err := ioutil.ReadFile(conf.ConfigFile)
if err != nil { if err != nil {
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 = conf.DefaultConfig()
err = utils.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())
@ -42,7 +41,8 @@ func InitConf() {
if err != nil { if err != nil {
log.Fatalf("update config struct error: %s", err.Error()) log.Fatalf("update config struct error: %s", err.Error())
} }
err = os.MkdirAll("data/temp", 0700) }
err := os.MkdirAll(conf.Conf.TempDir, 0700)
if err != nil { if err != nil {
log.Fatalf("create temp dir error: %s", err.Error()) log.Fatalf("create temp dir error: %s", err.Error())
} }

View File

@ -12,6 +12,9 @@ func InitLog() {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
log.SetReportCaller(true) log.SetReportCaller(true)
} }
if conf.Password || conf.Version {
log.SetLevel(log.WarnLevel)
}
log.SetFormatter(&log.TextFormatter{ log.SetFormatter(&log.TextFormatter{
//DisableColors: true, //DisableColors: true,
ForceColors: true, ForceColors: true,

View File

@ -79,6 +79,6 @@ func InitModel() {
err = conf.DB.AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{}) 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: %s", err.Error())
} }
} }

View File

@ -3,6 +3,7 @@ package bootstrap
import ( import (
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
"strings" "strings"
@ -27,7 +28,7 @@ func InitSettings() {
}, },
{ {
Key: "password", Key: "password",
Value: "alist", Value: utils.RandomStr(8),
Description: "password", Description: "password",
Type: "string", Type: "string",
Access: model.PRIVATE, Access: model.PRIVATE,
@ -35,7 +36,7 @@ func InitSettings() {
}, },
{ {
Key: "logo", Key: "logo",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png", Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/can_circle.svg",
Description: "logo", Description: "logo",
Type: "string", Type: "string",
Access: model.PUBLIC, Access: model.PUBLIC,
@ -43,7 +44,7 @@ func InitSettings() {
}, },
{ {
Key: "favicon", Key: "favicon",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png", Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg",
Description: "favicon", Description: "favicon",
Type: "string", Type: "string",
Access: model.PUBLIC, Access: model.PUBLIC,
@ -65,15 +66,23 @@ func InitSettings() {
Group: model.FRONT, Group: model.FRONT,
}, },
{ {
Key: "hide readme file", Key: "d_proxy types",
Value: "true", Value: strings.Join(conf.DProxyTypes, ","),
Type: "bool", Type: "string",
Description: "hide readme file? ", Description: "/d but proxy",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "hide files",
Value: "/\\/README.md/i",
Type: "text",
Description: "hide files, support RegExp, one per line",
Group: model.FRONT, Group: model.FRONT,
}, },
{ {
Key: "music cover", Key: "music cover",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png", Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/circle_center.svg",
Description: "music cover image", Description: "music cover image",
Type: "string", Type: "string",
Access: model.PUBLIC, Access: model.PUBLIC,
@ -157,7 +166,7 @@ func InitSettings() {
}, },
{ {
Key: "WebDAV username", Key: "WebDAV username",
Value: "alist_admin", Value: "admin",
Description: "WebDAV username", Description: "WebDAV username",
Type: "string", Type: "string",
Access: model.PRIVATE, Access: model.PRIVATE,
@ -165,7 +174,7 @@ func InitSettings() {
}, },
{ {
Key: "WebDAV password", Key: "WebDAV password",
Value: "alist_admin", Value: utils.RandomStr(8),
Description: "WebDAV password", Description: "WebDAV password",
Type: "string", Type: "string",
Access: model.PRIVATE, Access: model.PRIVATE,
@ -189,7 +198,7 @@ func InitSettings() {
}, },
{ {
Key: "Visitor WebDAV username", Key: "Visitor WebDAV username",
Value: "alist_visitor", Value: "guest",
Description: "Visitor WebDAV username", Description: "Visitor WebDAV username",
Type: "string", Type: "string",
Access: model.PRIVATE, Access: model.PRIVATE,
@ -197,7 +206,7 @@ func InitSettings() {
}, },
{ {
Key: "Visitor WebDAV password", Key: "Visitor WebDAV password",
Value: "alist_visitor", Value: "guest",
Description: "Visitor WebDAV password", Description: "Visitor WebDAV password",
Type: "string", Type: "string",
Access: model.PRIVATE, Access: model.PRIVATE,
@ -235,6 +244,9 @@ func InitSettings() {
if err != nil { if err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
err = model.SaveSetting(v) err = model.SaveSetting(v)
if v.Key == "password" {
log.Infof("Initial password: %s", v.Value)
}
if err != nil { if err != nil {
log.Fatalf("failed write setting: %s", err.Error()) log.Fatalf("failed write setting: %s", err.Error())
} }

View File

@ -6,8 +6,11 @@ BUILD_WEB() {
cd alist-web cd alist-web
yarn yarn
yarn build yarn build
sed -i -e "s/\/CDN_URL\//\//g" dist/index.html
sed -i -e "s/assets/\/assets/g" dist/index.html
rm -f dist/index.html-e
mv dist .. mv dist ..
cd .. cd .. || exit
rm -rf alist-web rm -rf alist-web
} }
@ -25,6 +28,7 @@ BUILD_DOCKER() {
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD) gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1) gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always) gitTag=$(git describe --long --tags --dirty --always)
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
ldflags="\ ldflags="\
-w -s \ -w -s \
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \ -X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
@ -32,6 +36,7 @@ BUILD_DOCKER() {
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \ -X 'github.com/Xhofe/alist/conf.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' \
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
" "
go build -o ./bin/alist -ldflags="$ldflags" -tags=jsoniter alist.go go build -o ./bin/alist -ldflags="$ldflags" -tags=jsoniter alist.go
} }
@ -44,6 +49,7 @@ BUILD() {
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD) gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1) gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always) gitTag=$(git describe --long --tags --dirty --always)
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
echo "build version: $gitTag" echo "build version: $gitTag"
ldflags="\ ldflags="\
-w -s \ -w -s \
@ -52,23 +58,25 @@ BUILD() {
-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' \
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
" "
if [ "$1" == "release" ]; then if [ "$1" == "release" ]; then
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter . xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
else else
xgo -targets=linux/amd64,windows/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter . xgo -targets=linux/amd64,windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
fi fi
mkdir -p "build" mkdir -p "build"
mv alist-* build mv alist-* build
if [ "$1" != "release" ]; then if [ "$1" != "release" ]; then
cd build cd build
upx -9 ./* upx -9 ./alist-linux*
upx -9 ./alist-windows*
find . -type f -print0 | xargs -0 md5sum >md5.txt find . -type f -print0 | xargs -0 md5sum >md5.txt
cat md5.txt cat md5.txt
cd .. cd .. || exit
fi fi
cd .. cd .. || exit
} }
BUILD_MUSL() { BUILD_MUSL() {
@ -86,6 +94,7 @@ BUILD_MUSL() {
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD) gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1) gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always) gitTag=$(git describe --long --tags --dirty --always)
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
ldflags="\ ldflags="\
-w -s \ -w -s \
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \ -X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
@ -93,6 +102,7 @@ BUILD_MUSL() {
-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' \
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
" "
OS_ARCHES=(linux-musl-amd64 linux-musl-arm64 linux-musl-arm linux-musl-mips linux-musl-mips64 linux-musl-mips64le linux-musl-mipsle linux-musl-ppc64le linux-musl-s390x) 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) CGO_ARGS=(x86_64-linux-musl-gcc aarch64-linux-musl-gcc arm-linux-musleabihf-gcc mips-linux-musl-gcc mips64-linux-musl-gcc mips64el-linux-musl-gcc mipsel-linux-musl-gcc powerpc64le-linux-musl-gcc s390x-linux-musl-gcc)
@ -106,12 +116,13 @@ BUILD_MUSL() {
export CGO_ENABLED=1 export CGO_ENABLED=1
go build -o ./build/$appName-$os_arch -ldflags="$ldflags" -tags=jsoniter alist.go go build -o ./build/$appName-$os_arch -ldflags="$ldflags" -tags=jsoniter alist.go
done done
cd .. cd .. || exit
} }
RELEASE() { RELEASE() {
cd alist/build cd alist/build
upx -9 ./* upx -9 ./alist-linux-amd64
upx -9 ./alist-windows*
find . -type f -print0 | xargs -0 md5sum >md5.txt find . -type f -print0 | xargs -0 md5sum >md5.txt
cat md5.txt cat md5.txt
mkdir compress mkdir compress
@ -125,7 +136,7 @@ RELEASE() {
for i in $(find . -type f -name "$appName-windows-*"); do for i in $(find . -type f -name "$appName-windows-*"); do
zip compress/$(echo $i | sed 's/\.[^.]*$//').zip "$i" zip compress/$(echo $i | sed 's/\.[^.]*$//').zip "$i"
done done
cd ../.. cd ../.. || exit
} }
if [ "$1" = "web" ]; then if [ "$1" = "web" ]; then

View File

@ -30,13 +30,15 @@ type Config struct {
Database Database `json:"database"` Database Database `json:"database"`
Scheme Scheme `json:"scheme"` Scheme Scheme `json:"scheme"`
Cache CacheConfig `json:"cache"` Cache CacheConfig `json:"cache"`
TempDir string `json:"temp_dir"`
} }
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", Assets: "https://npm.elemecdn.com/alist-web@$version/dist",
TempDir: "data/temp",
Database: Database{ Database: Database{
Type: "sqlite3", Type: "sqlite3",
Port: 0, Port: 0,

View File

@ -14,6 +14,7 @@ var (
GitAuthor string GitAuthor string
GitCommit string GitCommit string
GitTag string = "dev" GitTag string = "dev"
WebTag string
) )
var ( var (
@ -33,9 +34,10 @@ var (
TextTypes = []string{"txt", "htm", "html", "xml", "java", "properties", "sql", TextTypes = []string{"txt", "htm", "html", "xml", "java", "properties", "sql",
"js", "md", "json", "conf", "ini", "vue", "php", "py", "bat", "gitignore", "yml", "js", "md", "json", "conf", "ini", "vue", "php", "py", "bat", "gitignore", "yml",
"go", "sh", "c", "cpp", "h", "hpp", "tsx", "vtt", "srt", "ass"} "go", "sh", "c", "cpp", "h", "hpp", "tsx", "vtt", "srt", "ass"}
DProxyTypes = []string{"m3u8"}
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"} OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"} VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"}
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav"} AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav", "opus"}
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"} ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
) )
@ -78,7 +80,7 @@ var (
"check parent folder", "check down link", "WebDAV username", "WebDAV password", "check parent folder", "check down link", "WebDAV username", "WebDAV password",
"Visitor WebDAV username", "Visitor WebDAV password", "Visitor WebDAV username", "Visitor WebDAV password",
"default page size", "load type", "default page size", "load type",
"ocr api", "ocr api", "favicon",
} }
) )

View File

@ -12,7 +12,6 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"io/ioutil" "io/ioutil"
@ -58,6 +57,7 @@ func (driver Pan123) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "name,fileId,updateAt,createAt", Values: "name,fileId,updateAt,createAt",
Required: true, Required: true,
Default: "name",
}, },
{ {
Name: "order_direction", Name: "order_direction",
@ -65,6 +65,7 @@ func (driver Pan123) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "asc,desc", Values: "asc,desc",
Required: true, Required: true,
Default: "asc",
}, },
} }
} }
@ -197,9 +198,9 @@ func (driver Pan123) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil return nil, files, nil
} }
func (driver Pan123) Proxy(c *gin.Context, account *model.Account) { //func (driver Pan123) Proxy(r *http.Request, account *model.Account) {
c.Request.Header.Del("origin") // r.Header.Del("origin")
} //}
func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) { func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport
@ -299,7 +300,7 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
return base.ErrNotFolder return base.ErrNotFolder
} }
parentFileId, _ := strconv.Atoi(parentFile.Id) parentFileId, _ := strconv.Atoi(parentFile.Id)
tempFile, err := ioutil.TempFile("data/temp", "file-*") tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil { if err != nil {
return err return err
} }

View File

@ -17,7 +17,7 @@ import (
func (driver Cloud139) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) { 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 url := "https://yun.139.com" + pathname
req := base.RestyClient.R() req := base.RestyClient.R()
randStr := randomStr(16) randStr := utils.RandomStr(16)
ts := time.Now().Format("2006-01-02 15:04:05") ts := time.Now().Format("2006-01-02 15:04:05")
log.Debugf("%+v", data) log.Debugf("%+v", data)
body, err := utils.Json.Marshal(data) body, err := utils.Json.Marshal(data)

View File

@ -7,7 +7,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"math" "math"
@ -163,9 +162,9 @@ func (driver Cloud139) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil return nil, files, nil
} }
func (driver Cloud139) Proxy(c *gin.Context, account *model.Account) { //func (driver Cloud139) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver Cloud139) Preview(path string, account *model.Account) (interface{}, error) { func (driver Cloud139) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport
@ -335,7 +334,7 @@ func (driver Cloud139) Delete(path string, account *model.Account) error {
"taskInfo": base.Json{ "taskInfo": base.Json{
"newCatalogID": "", "newCatalogID": "",
"contentInfoList": contentInfoList, "contentInfoList": contentInfoList,
"catalogInfoList": contentInfoList, "catalogInfoList": catalogInfoList,
}, },
"commonAccountInfo": base.Json{ "commonAccountInfo": base.Json{
"account": account.Username, "account": account.Username,

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"math/rand"
"net/url" "net/url"
"sort" "sort"
"strconv" "strconv"
@ -13,16 +12,6 @@ import (
"time" "time"
) )
func randomStr(n int) string {
builder := strings.Builder{}
t := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for i := 0; i < n; i++ {
r := rand.Intn(len(t))
builder.WriteString(t[r : r+1])
}
return builder.String()
}
func encodeURIComponent(str string) string { func encodeURIComponent(str string) string {
r := url.QueryEscape(str) r := url.QueryEscape(str)
r = strings.Replace(r, "+", "%20", -1) r = strings.Replace(r, "+", "%20", -1)

View File

@ -16,7 +16,6 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"math" "math"
mathRand "math/rand"
"net/http" "net/http"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -108,6 +107,7 @@ func (driver Cloud189) Login(account *model.Account) error {
//cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) //cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
client = resty.New() client = resty.New()
//client.SetCookieJar(cookieJar) //client.SetCookieJar(cookieJar)
client.SetTimeout(base.DefaultTimeout)
client.SetRetryCount(3) client.SetRetryCount(3)
client.SetHeader("Referer", "https://cloud.189.cn/") client.SetHeader("Referer", "https://cloud.189.cn/")
} }
@ -462,7 +462,7 @@ func (driver Cloud189) UploadRequest(uri string, form map[string]string, account
c := strconv.FormatInt(time.Now().UnixMilli(), 10) c := strconv.FormatInt(time.Now().UnixMilli(), 10)
r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx") r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx") l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
l = l[0 : 16+int(16*mathRand.Float32())] l = l[0 : 16+int(16*utils.Rand.Float32())]
e := qs(form) e := qs(form)
data := AesEncrypt([]byte(e), []byte(l[0:16])) data := AesEncrypt([]byte(e), []byte(l[0:16]))
@ -525,7 +525,7 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
} }
res, err := driver.UploadRequest("/person/initMultiUpload", map[string]string{ res, err := driver.UploadRequest("/person/initMultiUpload", map[string]string{
"parentFolderId": parentFile.Id, "parentFolderId": parentFile.Id,
"fileName": file.Name, "fileName": encode(file.Name),
"fileSize": strconv.FormatInt(int64(file.Size), 10), "fileSize": strconv.FormatInt(int64(file.Size), 10),
"sliceSize": strconv.FormatInt(int64(DEFAULT), 10), "sliceSize": strconv.FormatInt(int64(DEFAULT), 10),
"lazyCheck": "1", "lazyCheck": "1",

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
) )
@ -172,7 +171,12 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
if err != nil { if err != nil {
return nil, err return nil, err
} }
link := base.Link{} link := base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
{Name: "Authorization", Value: ""},
},
}
if res.StatusCode() == 302 { if res.StatusCode() == 302 {
link.Url = res.Header().Get("location") link.Url = res.Header().Get("location")
} else { } else {
@ -198,9 +202,9 @@ func (driver Cloud189) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil return nil, files, nil
} }
func (driver Cloud189) Proxy(ctx *gin.Context, account *model.Account) { //func (driver Cloud189) Proxy(r *http.Request, account *model.Account) {
ctx.Request.Header.Del("Origin") // r.Header.Del("Origin")
} //}
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) { func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -14,18 +14,17 @@ import (
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
mathRand "math/rand"
"net/url" "net/url"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time"
) )
func random() string { func random() string {
return fmt.Sprintf("0.%17v", mathRand.New(mathRand.NewSource(time.Now().UnixNano())).Int63n(100000000000000000)) return fmt.Sprintf("0.%17v", utils.Rand.Int63n(100000000000000000))
} }
func RsaEncode(origData []byte, j_rsakey string, hex bool) string { func RsaEncode(origData []byte, j_rsakey string, hex bool) string {
@ -116,12 +115,24 @@ func EncodeParam(v url.Values) string {
} }
buf.WriteString(k) buf.WriteString(k)
buf.WriteByte('=') buf.WriteByte('=')
//if k == "fileName" {
// buf.WriteString(encode(v))
//} else {
buf.WriteString(v) buf.WriteString(v)
//}
} }
} }
return buf.String() return buf.String()
} }
func encode(str string) string {
//str = strings.ReplaceAll(str, "%", "%25")
//str = strings.ReplaceAll(str, "&", "%26")
//str = strings.ReplaceAll(str, "+", "%2B")
//return str
return url.QueryEscape(str)
}
func AesEncrypt(data, key []byte) []byte { func AesEncrypt(data, key []byte) []byte {
block, _ := aes.NewCipher(key) block, _ := aes.NewCipher(key)
if block == nil { if block == nil {
@ -169,7 +180,7 @@ func Random(v string) string {
reg := regexp.MustCompilePOSIX("[xy]") reg := regexp.MustCompilePOSIX("[xy]")
data := reg.ReplaceAllFunc([]byte(v), func(msg []byte) []byte { data := reg.ReplaceAllFunc([]byte(v), func(msg []byte) []byte {
var i int64 var i int64
t := int64(16 * mathRand.Float32()) t := int64(16 * utils.Rand.Float32())
if msg[0] == 120 { if msg[0] == 120 {
i = t i = t
} else { } else {

View File

@ -8,9 +8,9 @@ import (
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
"strings"
"time" "time"
) )
@ -190,7 +190,7 @@ func (driver AliDrive) rename(fileId, name string, account *model.Account) error
return fmt.Errorf("%+v", resp) return fmt.Errorf("%+v", resp)
} }
func (driver AliDrive) batch(srcId, dstId string, account *model.Account) error { func (driver AliDrive) batch(srcId, dstId string, url string, account *model.Account) error {
var e AliRespError var e AliRespError
res, err := aliClient.R().SetError(&e). res, err := aliClient.R().SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken). SetHeader("authorization", "Bearer\t"+account.AccessToken).
@ -208,6 +208,7 @@ func (driver AliDrive) batch(srcId, dstId string, account *model.Account) error
"to_drive_id": account.DriveId, "to_drive_id": account.DriveId,
"to_parent_file_id": dstId, "to_parent_file_id": dstId,
}, },
"url": url,
}, },
}, },
"resource": "file", "resource": "file",
@ -222,12 +223,13 @@ func (driver AliDrive) batch(srcId, dstId string, account *model.Account) error
return err return err
} else { } else {
_ = model.SaveAccount(account) _ = model.SaveAccount(account)
return driver.batch(srcId, dstId, account) return driver.batch(srcId, dstId, url, account)
} }
} }
return fmt.Errorf("%s", e.Message) return fmt.Errorf("%s", e.Message)
} }
if strings.Contains(res.String(), `"status":200`) { status := jsoniter.Get(res.Body(), "status").ToInt()
if status < 400 && status >= 100 {
return nil return nil
} }
return errors.New(res.String()) return errors.New(res.String())
@ -236,6 +238,7 @@ func (driver AliDrive) batch(srcId, dstId string, account *model.Account) error
func init() { func init() {
base.RegisterDriver(&AliDrive{}) base.RegisterDriver(&AliDrive{})
aliClient. aliClient.
SetTimeout(base.DefaultTimeout).
SetRetryCount(3). 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("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("content-type", "application/json").

View File

@ -2,19 +2,25 @@ package alidrive
import ( import (
"bytes" "bytes"
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil"
"math"
"math/big"
"net/http"
"os"
"path/filepath"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io"
"math"
"net/http"
"path/filepath"
) )
type AliDrive struct{} type AliDrive struct{}
@ -191,6 +197,12 @@ func (driver AliDrive) Link(args base.Args, account *model.Account) (*base.Link,
return nil, fmt.Errorf("%s", e.Message) return nil, fmt.Errorf("%s", e.Message)
} }
return &base.Link{ return &base.Link{
Headers: []base.Header{
{
Name: "Referer",
Value: "https://www.aliyundrive.com/",
},
},
Url: resp["url"].(string), Url: resp["url"].(string),
}, nil }, nil
} }
@ -212,10 +224,10 @@ func (driver AliDrive) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil return nil, files, nil
} }
func (driver AliDrive) Proxy(c *gin.Context, account *model.Account) { //func (driver AliDrive) Proxy(r *http.Request, account *model.Account) {
c.Request.Header.Del("Origin") // r.Header.Del("Origin")
c.Request.Header.Set("Referer", "https://www.aliyundrive.com/") // r.Header.Set("Referer", "https://www.aliyundrive.com/")
} //}
func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) { func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) {
file, err := driver.GetFile(path, account) file, err := driver.GetFile(path, account)
@ -304,7 +316,7 @@ func (driver AliDrive) Move(src string, dst string, account *model.Account) erro
if err != nil { if err != nil {
return err return err
} }
err = driver.batch(srcFile.Id, dstDirFile.Id, account) err = driver.batch(srcFile.Id, dstDirFile.Id, "/file/move", account)
return err return err
} }
@ -319,7 +331,17 @@ func (driver AliDrive) Rename(src string, dst string, account *model.Account) er
} }
func (driver AliDrive) Copy(src string, dst string, account *model.Account) error { func (driver AliDrive) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotSupport dstDir, _ := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(dstDir, account)
if err != nil {
return err
}
err = driver.batch(srcFile.Id, dstDirFile.Id, "/file/copy", account)
return err
} }
func (driver AliDrive) Delete(path string, account *model.Account) error { func (driver AliDrive) Delete(path string, account *model.Account) error {
@ -361,15 +383,16 @@ type UploadResp struct {
PartInfoList []struct { PartInfoList []struct {
UploadUrl string `json:"upload_url"` UploadUrl string `json:"upload_url"`
} `json:"part_info_list"` } `json:"part_info_list"`
RapidUpload bool `json:"rapid_upload"`
} }
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error { func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
if file == nil { if file == nil {
return base.ErrEmptyFile return base.ErrEmptyFile
} }
const DEFAULT uint64 = 10485760 const DEFAULT int64 = 10485760
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT))) var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
var finish uint64 = 0
parentFile, err := driver.File(file.ParentPath, account) parentFile, err := driver.File(file.ParentPath, account)
if err != nil { if err != nil {
return err return err
@ -377,32 +400,38 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
if !parentFile.IsDir() { if !parentFile.IsDir() {
return base.ErrNotFolder return base.ErrNotFolder
} }
var resp UploadResp
var e AliRespError partInfoList := make([]base.Json, 0, count)
partInfoList := make([]base.Json, 0)
var i int64 var i int64
for i = 0; i < count; i++ { for i = 0; i < count; i++ {
partInfoList = append(partInfoList, base.Json{ partInfoList = append(partInfoList, base.Json{
"part_number": i + 1, "part_number": i + 1,
}) })
} }
_, err = aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken). buf := make([]byte, 1024)
SetBody(base.Json{ n, _ := file.Read(buf[:])
reqBody := base.Json{
"check_name_mode": "auto_rename", "check_name_mode": "auto_rename",
// content_hash
"content_hash_name": "none",
"drive_id": account.DriveId, "drive_id": account.DriveId,
"name": file.GetFileName(), "name": file.GetFileName(),
"parent_file_id": parentFile.Id, "parent_file_id": parentFile.Id,
"part_info_list": partInfoList, "part_info_list": partInfoList,
//proof_code
"proof_version": "v1",
"size": file.GetSize(), "size": file.GetSize(),
"type": "file", "type": "file",
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders") // /v2/file/create_with_proof "pre_hash": utils.GetSHA1Encode(string(buf[:n])),
//log.Debugf("%+v\n%+v", resp, e) }
if e.Code != "" { fileReader := io.MultiReader(bytes.NewReader(buf[:n]), file.File)
var resp UploadResp
var e AliRespError
client := aliClient.R().SetResult(&resp).SetError(&e).SetHeader("authorization", "Bearer\t"+account.AccessToken).SetBody(reqBody)
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
if err != nil {
return err
}
if e.Code != "" && e.Code != "PreHashMatched" {
if e.Code == "AccessTokenInvalid" { if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account) err = driver.RefreshToken(account)
if err != nil { if err != nil {
@ -414,26 +443,59 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
} }
return fmt.Errorf("%s", e.Message) return fmt.Errorf("%s", e.Message)
} }
var byteSize uint64
for i = 0; i < count; i++ { if e.Code == "PreHashMatched" {
byteSize = file.GetSize() - finish tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if DEFAULT < byteSize {
byteSize = DEFAULT
}
log.Debugf("%d,%d", byteSize, finish)
byteData := make([]byte, byteSize)
n, err := io.ReadFull(file, byteData)
//n, err := file.Read(byteData)
//byteData, err := io.ReadAll(file)
//n := len(byteData)
log.Debug(err, n)
if err != nil { if err != nil {
return err return err
} }
finish += uint64(n) defer tempFile.Close()
defer os.Remove(tempFile.Name())
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, bytes.NewBuffer(byteData)) delete(reqBody, "pre_hash")
h := sha1.New()
if _, err = io.Copy(tempFile, io.TeeReader(fileReader, h)); err != nil {
return err
}
reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil))
reqBody["content_hash_name"] = "sha1"
reqBody["proof_version"] = "v1"
/*
js 隐性转换太坑不知道有没有bug
var n = e.access_token
r = new BigNumber('0x'.concat(md5(n).slice(0, 16)))
i = new BigNumber(t.file.size)
o = i ? r.mod(i) : new gt.BigNumber(0);
(t.file.slice(o.toNumber(), Math.min(o.plus(8).toNumber(), t.file.size)))
*/
r, _ := new(big.Int).SetString(utils.GetMD5Encode(account.AccessToken)[:16], 16)
i := new(big.Int).SetUint64(file.Size)
o := r.Mod(r, i)
n, _ = io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
if err != nil {
return err
}
if e.Code != "" && e.Code != "PreHashMatched" {
return fmt.Errorf("%s", e.Message)
}
if resp.RapidUpload {
return nil
}
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
return err
}
fileReader = tempFile
}
for i = 0; i < count; i++ {
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, io.LimitReader(fileReader, DEFAULT))
if err != nil { if err != nil {
return err return err
} }
@ -458,6 +520,9 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
"file_id": resp.FileId, "file_id": resp.FileId,
"upload_id": resp.UploadId, "upload_id": resp.UploadId,
}).Post("https://api.aliyundrive.com/v2/file/complete") }).Post("https://api.aliyundrive.com/v2/file/complete")
if err != nil {
return err
}
if e.Code != "" { if e.Code != "" {
//if e.Code == "AccessTokenInvalid" { //if e.Code == "AccessTokenInvalid" {
// err = driver.RefreshToken(account) // err = driver.RefreshToken(account)

View File

@ -7,7 +7,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -148,7 +147,7 @@ func (driver Alist) Path(path string, account *model.Account) (*model.File, []mo
return nil, resp.Data.Files, nil return nil, resp.Data.Files, nil
} }
func (driver Alist) Proxy(c *gin.Context, account *model.Account) {} //func (driver Alist) Proxy(r *http.Request, account *model.Account) {}
func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) { func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) {
var resp PathResp var resp PathResp

View File

@ -6,6 +6,7 @@ import (
_ "github.com/Xhofe/alist/drivers/189" _ "github.com/Xhofe/alist/drivers/189"
_ "github.com/Xhofe/alist/drivers/alidrive" _ "github.com/Xhofe/alist/drivers/alidrive"
_ "github.com/Xhofe/alist/drivers/alist" _ "github.com/Xhofe/alist/drivers/alist"
_ "github.com/Xhofe/alist/drivers/baidu"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
_ "github.com/Xhofe/alist/drivers/ftp" _ "github.com/Xhofe/alist/drivers/ftp"
_ "github.com/Xhofe/alist/drivers/google" _ "github.com/Xhofe/alist/drivers/google"
@ -14,10 +15,14 @@ import (
_ "github.com/Xhofe/alist/drivers/native" _ "github.com/Xhofe/alist/drivers/native"
_ "github.com/Xhofe/alist/drivers/onedrive" _ "github.com/Xhofe/alist/drivers/onedrive"
_ "github.com/Xhofe/alist/drivers/pikpak" _ "github.com/Xhofe/alist/drivers/pikpak"
_ "github.com/Xhofe/alist/drivers/quark"
_ "github.com/Xhofe/alist/drivers/s3" _ "github.com/Xhofe/alist/drivers/s3"
_ "github.com/Xhofe/alist/drivers/shandian" _ "github.com/Xhofe/alist/drivers/shandian"
_ "github.com/Xhofe/alist/drivers/teambition" _ "github.com/Xhofe/alist/drivers/teambition"
_ "github.com/Xhofe/alist/drivers/uss"
_ "github.com/Xhofe/alist/drivers/webdav" _ "github.com/Xhofe/alist/drivers/webdav"
_ "github.com/Xhofe/alist/drivers/xunlei"
_ "github.com/Xhofe/alist/drivers/yandex"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"strings" "strings"
) )

186
drivers/baidu/baidu.go Normal file
View 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
View 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().SetHeader("User-Agent", "pan.baidu.com").Head(u)
if err != nil {
return nil, err
}
//if res.StatusCode() == 302 {
u = res.Header().Get("location")
//}
return &base.Link{
Url: u,
Headers: []base.Header{
{Name: "User-Agent", Value: "pan.baidu.com"},
}}, nil
}
func (driver Baidu) Path(path string, account *model.Account) (*model.File, []model.File, error) {
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Baidu) Proxy(r *http.Request, account *model.Account) {
// r.Header.Set("User-Agent", "pan.baidu.com")
//}
func (driver Baidu) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Baidu) MakeDir(path string, account *model.Account) error {
_, err := driver.create(utils.Join(account.RootFolder, path), 0, 1, "", "", account)
return err
}
func (driver Baidu) Move(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
data := []base.Json{
{
"path": path,
"dest": dest,
"newname": newname,
},
}
_, err := driver.manage("move", data, account)
return err
}
func (driver Baidu) Rename(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
newname := utils.Base(dst)
data := []base.Json{
{
"path": path,
"newname": newname,
},
}
_, err := driver.manage("rename", data, account)
return err
}
func (driver Baidu) Copy(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
data := []base.Json{
{
"path": path,
"dest": dest,
"newname": newname,
},
}
_, err := driver.manage("copy", data, account)
return err
}
func (driver Baidu) Delete(path string, account *model.Account) error {
path = utils.Join(account.RootFolder, path)
data := []string{path}
_, err := driver.manage("delete", data, account)
return err
}
func (driver Baidu) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
var Default uint64 = 4 * 1024 * 1024
defaultByteData := make([]byte, Default)
count := int(math.Ceil(float64(file.GetSize()) / float64(Default)))
var SliceSize uint64 = 256 * 1024
// cal md5
h1 := md5.New()
h2 := md5.New()
block_list := make([]string, 0)
content_md5 := ""
slice_md5 := ""
left := file.GetSize()
for i := 0; i < count; i++ {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
h1.Write(byteData)
h2.Write(byteData)
block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil))))
h2.Reset()
}
content_md5 = hex.EncodeToString(h1.Sum(nil))
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
if file.GetSize() <= SliceSize {
slice_md5 = content_md5
} else {
sliceData := make([]byte, SliceSize)
_, err = io.ReadFull(tempFile, sliceData)
if err != nil {
return err
}
h2.Write(sliceData)
slice_md5 = hex.EncodeToString(h2.Sum(nil))
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
}
path := encodeURIComponent(utils.Join(account.RootFolder, file.ParentPath, file.Name))
block_list_str := fmt.Sprintf("[%s]", strings.Join(block_list, ","))
data := fmt.Sprintf("path=%s&size=%d&isdir=0&autoinit=1&block_list=%s&content-md5=%s&slice-md5=%s",
path, file.GetSize(),
block_list_str,
content_md5, slice_md5)
params := map[string]string{
"method": "precreate",
}
var precreateResp PrecreateResp
_, err = driver.Post("/xpan/file", params, data, &precreateResp, account)
if err != nil {
return err
}
log.Debugf("%+v", precreateResp)
if precreateResp.ReturnType == 2 {
return nil
}
params = map[string]string{
"method": "upload",
"access_token": account.AccessToken,
"type": "tmpfile",
"path": path,
"uploadid": precreateResp.Uploadid,
}
left = file.GetSize()
for _, partseq := range precreateResp.BlockList {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
u := "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2"
params["partseq"] = strconv.Itoa(partseq)
res, err := base.RestyClient.R().SetQueryParams(params).SetFileReader("file", file.Name, bytes.NewReader(byteData)).Post(u)
if err != nil {
return err
}
log.Debugln(res.String())
}
_, err = driver.create(path, file.GetSize(), 0, precreateResp.Uploadid, block_list_str, account)
return err
}
var _ base.Driver = (*Baidu)(nil)

84
drivers/baidu/types.go Normal file
View 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
View 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
}

View File

@ -2,10 +2,10 @@ package base
import ( import (
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net/http" "net/http"
"time"
) )
type DriverConfig struct { type DriverConfig struct {
@ -38,8 +38,8 @@ type Driver interface {
Link(args Args, account *model.Account) (*Link, error) Link(args Args, account *model.Account) (*Link, error)
// Path 取路径(文件或文件夹) // Path 取路径(文件或文件夹)
Path(path string, account *model.Account) (*model.File, []model.File, error) Path(path string, account *model.Account) (*model.File, []model.File, error)
// Proxy 代理处理 // Deprecated Proxy 代理处理
Proxy(c *gin.Context, account *model.Account) //Proxy(r *http.Request, account *model.Account)
// Preview 预览 // Preview 预览
Preview(path string, account *model.Account) (interface{}, error) Preview(path string, account *model.Account) (interface{}, error)
// MakeDir 创建文件夹 // MakeDir 创建文件夹
@ -62,6 +62,7 @@ type Item struct {
Name string `json:"name"` Name string `json:"name"`
Label string `json:"label"` Label string `json:"label"`
Type string `json:"type"` Type string `json:"type"`
Default string `json:"default"`
Values string `json:"values"` Values string `json:"values"`
Required bool `json:"required"` Required bool `json:"required"`
Description string `json:"description"` Description string `json:"description"`
@ -86,17 +87,19 @@ func GetDriversMap() map[string]Driver {
func GetDrivers() map[string][]Item { func GetDrivers() map[string][]Item {
res := make(map[string][]Item) res := make(map[string][]Item)
for k, v := range driversMap { for k, v := range driversMap {
webdavDirect := Item{
Name: "webdav_direct",
Label: "webdav direct",
Type: TypeBool,
Required: true,
Description: "Transfer the WebDAV of this account through the native",
}
if v.Config().OnlyProxy { if v.Config().OnlyProxy {
res[k] = v.Items() res[k] = append([]Item{
webdavDirect,
}, v.Items()...)
} else { } else {
res[k] = append([]Item{ res[k] = append([]Item{
//{
// Name: "allow_proxy",
// Label: "allow_proxy",
// Type: TypeBool,
// Required: true,
// Description: "allow proxy",
//},
{ {
Name: "proxy", Name: "proxy",
Label: "proxy", Label: "proxy",
@ -111,6 +114,7 @@ func GetDrivers() map[string][]Item {
Required: true, Required: true,
Description: "Transfer the WebDAV of this account through the server", Description: "Transfer the WebDAV of this account through the server",
}, },
webdavDirect,
}, v.Items()...) }, v.Items()...)
} }
res[k] = append([]Item{ res[k] = append([]Item{
@ -160,6 +164,8 @@ func GetDrivers() map[string][]Item {
var NoRedirectClient *resty.Client var NoRedirectClient *resty.Client
var RestyClient = resty.New() var RestyClient = resty.New()
var HttpClient = &http.Client{} var HttpClient = &http.Client{}
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
var DefaultTimeout = time.Second * 20
func init() { func init() {
NoRedirectClient = resty.New().SetRedirectPolicy( NoRedirectClient = resty.New().SetRedirectPolicy(
@ -167,8 +173,8 @@ func init() {
return http.ErrUseLastResponse 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)
NoRedirectClient.SetHeader("user-agent", userAgent) RestyClient.SetHeader("user-agent", UserAgent)
RestyClient.SetHeader("user-agent", userAgent)
RestyClient.SetRetryCount(3) RestyClient.SetRetryCount(3)
RestyClient.SetTimeout(DefaultTimeout)
} }

View File

@ -13,6 +13,7 @@ var (
ErrNotFolder = errors.New("not a folder") ErrNotFolder = errors.New("not a folder")
ErrEmptyFile = errors.New("empty file") ErrEmptyFile = errors.New("empty file")
ErrRelativePath = errors.New("access using relative path is not allowed") ErrRelativePath = errors.New("access using relative path is not allowed")
ErrEmptyToken = errors.New("empty token")
) )
const ( const (
@ -46,4 +47,5 @@ type Link struct {
Url string `json:"url"` Url string `json:"url"`
Headers []Header `json:"headers"` Headers []Header `json:"headers"`
Data io.ReadCloser Data io.ReadCloser
FilePath string `json:"path"` // for native
} }

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/jlaffaye/ftp" "github.com/jlaffaye/ftp"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
@ -186,9 +185,9 @@ func (driver FTP) Path(path string, account *model.Account) (*model.File, []mode
return nil, files, nil return nil, files, nil
} }
func (driver FTP) Proxy(c *gin.Context, account *model.Account) { //func (driver FTP) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver FTP) Preview(path string, account *model.Account) (interface{}, error) { func (driver FTP) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -11,7 +11,12 @@ var connMap map[string]*ftp.ServerConn
func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) { func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) {
conn, ok := connMap[account.Name] conn, ok := connMap[account.Name]
if ok { if ok {
_, err := conn.CurrentDir()
if err == nil {
return conn, nil return conn, nil
} else {
delete(connMap, account.Name)
}
} }
conn, err := ftp.Connect(account.SiteUrl) conn, err := ftp.Connect(account.SiteUrl)
if err != nil { if err != nil {

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
@ -178,9 +177,9 @@ func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File
return nil, files, nil return nil, files, nil
} }
func (driver GoogleDrive) Proxy(c *gin.Context, account *model.Account) { //func (driver GoogleDrive) Proxy(r *http.Request, account *model.Account) {
c.Request.Header.Add("Authorization", "Bearer "+account.AccessToken) // r.Header.Add("Authorization", "Bearer "+account.AccessToken)
} //}
func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) { func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
) )
@ -138,6 +137,9 @@ func (driver Lanzou) Link(args base.Args, account *model.Account) (*base.Link, e
} }
link := base.Link{ link := base.Link{
Url: url, Url: url,
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
},
} }
return &link, nil return &link, nil
} }
@ -159,9 +161,9 @@ func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil return nil, files, nil
} }
func (driver Lanzou) Proxy(c *gin.Context, account *model.Account) { //func (driver Lanzou) Proxy(r *http.Request, account *model.Account) {
c.Request.Header.Del("Origin") // r.Header.Del("Origin")
} //}
func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) { func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net/url" "net/url"
"path/filepath" "path/filepath"
@ -15,8 +14,6 @@ import (
"time" "time"
) )
var lanzouClient = resty.New()
type LanZouFile struct { type LanZouFile struct {
Name string `json:"name"` Name string `json:"name"`
NameAll string `json:"name_all"` NameAll string `json:"name_all"`
@ -58,7 +55,7 @@ func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZo
files := make([]LanZouFile, 0) files := make([]LanZouFile, 0)
var resp LanZouFilesResp var resp LanZouFilesResp
// folders // folders
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken). res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{ SetFormData(map[string]string{
"task": "47", "task": "47",
"folder_id": folderId, "folder_id": folderId,
@ -77,7 +74,7 @@ func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZo
// files // files
pg := 1 pg := 1
for { for {
_, err = lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken). _, err = base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{ SetFormData(map[string]string{
"task": "5", "task": "5",
"folder_id": folderId, "folder_id": folderId,
@ -108,7 +105,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
if err != nil { if err != nil {
return nil, err return nil, err
} }
res, err := lanzouClient.R().Get(shareUrl) res, err := base.RestyClient.R().Get(shareUrl)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -121,7 +118,10 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
uid := regexp.MustCompile(`'uid':'(.+?)',`).FindStringSubmatch(res.String())[1] uid := regexp.MustCompile(`'uid':'(.+?)',`).FindStringSubmatch(res.String())[1]
rep := regexp.MustCompile(`'rep':'(.+?)',`).FindStringSubmatch(res.String())[1] rep := regexp.MustCompile(`'rep':'(.+?)',`).FindStringSubmatch(res.String())[1]
up := regexp.MustCompile(`'up':(.+?),`).FindStringSubmatch(res.String())[1] up := regexp.MustCompile(`'up':(.+?),`).FindStringSubmatch(res.String())[1]
ls := regexp.MustCompile(`'ls':(.+?),`).FindStringSubmatch(res.String())[1] ls := ""
if account.Password != "" {
ls = regexp.MustCompile(`'ls':(.+?),`).FindStringSubmatch(res.String())[1]
}
tName := regexp.MustCompile(`'t':(.+?),`).FindStringSubmatch(res.String())[1] tName := regexp.MustCompile(`'t':(.+?),`).FindStringSubmatch(res.String())[1]
kName := regexp.MustCompile(`'k':(.+?),`).FindStringSubmatch(res.String())[1] kName := regexp.MustCompile(`'k':(.+?),`).FindStringSubmatch(res.String())[1]
t := regexp.MustCompile(`var ` + tName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1] t := regexp.MustCompile(`var ` + tName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
@ -129,7 +129,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
pg := 1 pg := 1
for { for {
var resp LanZouFilesResp var resp LanZouFilesResp
res, err = lanzouClient.R().SetResult(&resp).SetFormData(map[string]string{ res, err = base.RestyClient.R().SetResult(&resp).SetFormData(map[string]string{
"lx": lx, "lx": lx,
"fid": fid, "fid": fid,
"uid": uid, "uid": uid,
@ -166,7 +166,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
// GetDownPageId 获取下载页面的ID // GetDownPageId 获取下载页面的ID
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, error) { func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, error) {
var resp LanZouFilesResp var resp LanZouFilesResp
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken). res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{ SetFormData(map[string]string{
"task": "22", "task": "22",
"file_id": fileId, "file_id": fileId,
@ -201,7 +201,7 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
if err != nil { if err != nil {
return "", err return "", err
} }
res, err := lanzouClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId)) res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -209,19 +209,24 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
if len(iframe) == 0 { if len(iframe) == 0 {
return "", fmt.Errorf("get down empty page") return "", fmt.Errorf("get down empty page")
} }
iframeUrl := "https://wwa.lanzouo.com" + iframe[1] iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1])
res, err = lanzouClient.R().Get(iframeUrl) res, err = base.RestyClient.R().Get(iframeUrl)
if err != nil { if err != nil {
return "", err return "", err
} }
log.Debugln(res.String())
ajaxdata := regexp.MustCompile(`var ajaxdata = '(.+?)'`).FindStringSubmatch(res.String()) ajaxdata := regexp.MustCompile(`var ajaxdata = '(.+?)'`).FindStringSubmatch(res.String())
if len(ajaxdata) == 0 { if len(ajaxdata) == 0 {
return "", fmt.Errorf("get iframe empty page") return "", fmt.Errorf("get iframe empty page")
} }
signs := ajaxdata[1] signs := ajaxdata[1]
sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1] //sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1] sign := regexp.MustCompile(`'sign':'(.+?)',`).FindStringSubmatch(res.String())[1]
websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).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 var resp LanzouLinkResp
form := map[string]string{ form := map[string]string{
"action": "downprocess", "action": "downprocess",
@ -232,7 +237,7 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
"websignkey": websignkey, "websignkey": websignkey,
} }
log.Debugf("form: %+v", form) log.Debugf("form: %+v", form)
res, err = lanzouClient.R().SetResult(&resp). res, err = base.RestyClient.R().SetResult(&resp).
SetHeader("origin", "https://"+u.Host). SetHeader("origin", "https://"+u.Host).
SetHeader("referer", iframeUrl). SetHeader("referer", iframeUrl).
SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host)) SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
@ -248,7 +253,4 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
func init() { func init() {
base.RegisterDriver(&Lanzou{}) 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")
} }

View File

@ -12,7 +12,6 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -51,6 +50,7 @@ func (driver MediaTrack) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "updated_at,title,size", Values: "updated_at,title,size",
Required: true, Required: true,
Description: "title",
}, },
{ {
Name: "order_direction", Name: "order_direction",
@ -58,6 +58,7 @@ func (driver MediaTrack) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "true,false", Values: "true,false",
Required: true, Required: true,
Default: "false",
}, },
} }
} }
@ -148,9 +149,9 @@ func (driver MediaTrack) Path(path string, account *model.Account) (*model.File,
return nil, files, nil return nil, files, nil
} }
func (driver MediaTrack) Proxy(c *gin.Context, account *model.Account) { //func (driver MediaTrack) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver MediaTrack) Preview(path string, account *model.Account) (interface{}, error) { func (driver MediaTrack) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotImplement return nil, base.ErrNotImplement
@ -266,7 +267,7 @@ func (driver MediaTrack) Upload(file *model.FileStream, account *model.Account)
if err != nil { if err != nil {
return err return err
} }
tempFile, err := ioutil.TempFile("data/temp", "file-*") tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil { if err != nil {
return err return err
} }

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"io/ioutil" "io/ioutil"
@ -133,7 +132,7 @@ func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, e
return nil, base.ErrNotFile return nil, base.ErrNotFile
} }
link := base.Link{ link := base.Link{
Url: fullPath, FilePath: fullPath,
} }
return &link, nil return &link, nil
} }
@ -156,9 +155,9 @@ func (driver Native) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil return nil, files, nil
} }
func (driver Native) Proxy(c *gin.Context, account *model.Account) { //func (driver Native) Proxy(r *http.Request, account *model.Account) {
// unnecessary // // unnecessary
} //}
func (driver Native) Preview(path string, account *model.Account) (interface{}, error) { func (driver Native) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -6,8 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
) )
@ -92,9 +90,9 @@ func (driver Onedrive) Items() []base.Item {
} }
func (driver Onedrive) Save(account *model.Account, old *model.Account) error { func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
if old != nil { //if old != nil {
conf.Cron.Remove(cron.EntryID(old.CronId)) // conf.Cron.Remove(cron.EntryID(old.CronId))
} //}
if account == nil { if account == nil {
return nil return nil
} }
@ -104,28 +102,25 @@ func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
} }
account.RootFolder = utils.ParsePath(account.RootFolder) account.RootFolder = utils.ParsePath(account.RootFolder)
err := driver.RefreshToken(account) err := driver.RefreshToken(account)
_ = model.SaveAccount(account)
if err != nil { if err != nil {
return err return err
} }
cronId, err := conf.Cron.AddFunc("@every 1h", func() { //cronId, err := conf.Cron.AddFunc("@every 1h", func() {
name := account.Name // name := account.Name
log.Debugf("onedrive account name: %s", name) // log.Debugf("onedrive account name: %s", name)
newAccount, ok := model.GetAccount(name) // newAccount, ok := model.GetAccount(name)
log.Debugf("onedrive account: %+v", newAccount) // log.Debugf("onedrive account: %+v", newAccount)
if !ok { // if !ok {
return // return
} // }
err = driver.RefreshToken(&newAccount) // err = driver.RefreshToken(&newAccount)
_ = model.SaveAccount(&newAccount) // _ = model.SaveAccount(&newAccount)
}) //})
if err != nil { //if err != nil {
return err // return err
} //}
account.CronId = int(cronId) //account.CronId = int(cronId)
err = model.SaveAccount(account)
if err != nil {
return err
}
return nil return nil
} }
@ -180,7 +175,7 @@ func (driver Onedrive) Link(args base.Args, account *model.Account) (*base.Link,
if err != nil { if err != nil {
return nil, err return nil, err
} }
if file.File.MimeType == "" { if file.File == nil {
return nil, base.ErrNotFile return nil, base.ErrNotFile
} }
link := base.Link{ link := base.Link{
@ -205,9 +200,9 @@ func (driver Onedrive) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil return nil, files, nil
} }
func (driver Onedrive) Proxy(c *gin.Context, account *model.Account) { //func (driver Onedrive) Proxy(r *http.Request, account *model.Account) {
c.Request.Header.Del("Origin") // r.Header.Del("Origin")
} //}
func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) { func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -19,8 +19,6 @@ import (
"time" "time"
) )
var oneClient = resty.New()
type Host struct { type Host struct {
Oauth string Oauth string
Api string Api string
@ -47,7 +45,7 @@ var onedriveHostMap = map[string]Host{
func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string { func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string {
path = filepath.Join(account.RootFolder, path) path = filepath.Join(account.RootFolder, path)
log.Debugf(path) //log.Debugf(path)
host, _ := onedriveHostMap[account.Zone] host, _ := onedriveHostMap[account.Zone]
if auth { if auth {
return host.Oauth return host.Oauth
@ -81,7 +79,7 @@ type OneTokenErr struct {
func (driver Onedrive) RefreshToken(account *model.Account) error { func (driver Onedrive) RefreshToken(account *model.Account) error {
err := driver.refreshToken(account) err := driver.refreshToken(account)
if err != nil && err.Error() == "empty refresh_token" { if err != nil && err == base.ErrEmptyToken {
return driver.refreshToken(account) return driver.refreshToken(account)
} }
return err return err
@ -91,7 +89,7 @@ func (driver Onedrive) refreshToken(account *model.Account) error {
url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token" url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
var resp base.TokenResp var resp base.TokenResp
var e OneTokenErr var e OneTokenErr
_, err := oneClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{ _, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token", "grant_type": "refresh_token",
"client_id": account.ClientId, "client_id": account.ClientId,
"client_secret": account.ClientSecret, "client_secret": account.ClientSecret,
@ -109,8 +107,8 @@ func (driver Onedrive) refreshToken(account *model.Account) error {
account.Status = "work" account.Status = "work"
} }
if resp.RefreshToken == "" { if resp.RefreshToken == "" {
account.Status = "empty refresh_token" account.Status = base.ErrEmptyToken.Error()
return errors.New("empty refresh_token") return base.ErrEmptyToken
} }
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
return nil return nil
@ -122,7 +120,7 @@ type OneFile struct {
Size int64 `json:"size"` Size int64 `json:"size"`
LastModifiedDateTime *time.Time `json:"lastModifiedDateTime"` LastModifiedDateTime *time.Time `json:"lastModifiedDateTime"`
Url string `json:"@microsoft.graph.downloadUrl"` Url string `json:"@microsoft.graph.downloadUrl"`
File struct { File *struct {
MimeType string `json:"mimeType"` MimeType string `json:"mimeType"`
} `json:"file"` } `json:"file"`
Thumbnails []struct { Thumbnails []struct {
@ -159,7 +157,7 @@ func (driver Onedrive) FormatFile(file *OneFile) *model.File {
if len(file.Thumbnails) > 0 { if len(file.Thumbnails) > 0 {
f.Thumbnail = file.Thumbnails[0].Medium.Url f.Thumbnail = file.Thumbnails[0].Medium.Url
} }
if file.File.MimeType == "" { if file.File == nil {
f.Type = conf.FOLDER f.Type = conf.FOLDER
} else { } else {
f.Type = utils.GetFileType(filepath.Ext(file.Name)) f.Type = utils.GetFileType(filepath.Ext(file.Name))
@ -178,16 +176,17 @@ func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile,
} }
for nextLink != "" { for nextLink != "" {
var files OneFiles var files OneFiles
var e OneRespErr _, err := driver.Request(nextLink, base.Get, nil, nil, nil, nil, &files, account)
_, err := oneClient.R().SetResult(&files).SetError(&e). //var e OneRespErr
SetHeader("Authorization", "Bearer "+account.AccessToken). //_, err := oneClient.R().SetResult(&files).SetError(&e).
Get(nextLink) // SetHeader("Authorization", "Bearer "+account.AccessToken).
// Get(nextLink)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if e.Error.Code != "" { //if e.Error.Code != "" {
return nil, fmt.Errorf("%s", e.Error.Message) // return nil, fmt.Errorf("%s", e.Error.Message)
} //}
res = append(res, files.Value...) res = append(res, files.Value...)
nextLink = files.NextLink nextLink = files.NextLink
} }
@ -196,16 +195,18 @@ func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile,
func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) { func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) {
var file OneFile var file OneFile
var e OneRespErr //var e OneRespErr
_, err := oneClient.R().SetResult(&file).SetError(&e). u := driver.GetMetaUrl(account, false, path)
SetHeader("Authorization", "Bearer "+account.AccessToken). _, err := driver.Request(u, base.Get, nil, nil, nil, nil, &file, account)
Get(driver.GetMetaUrl(account, false, path)) //_, err := oneClient.R().SetResult(&file).SetError(&e).
// SetHeader("Authorization", "Bearer "+account.AccessToken).
// Get(driver.GetMetaUrl(account, false, path))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if e.Error.Code != "" { //if e.Error.Code != "" {
return nil, fmt.Errorf("%s", e.Error.Message) // return nil, fmt.Errorf("%s", e.Error.Message)
} //}
return &file, nil return &file, nil
} }
@ -252,7 +253,7 @@ func (driver Onedrive) Request(url string, method int, headers, query, form map[
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug(res.String()) //log.Debug(res.String())
if e.Error.Code != "" { if e.Error.Code != "" {
if e.Error.Code == "InvalidAuthenticationToken" { if e.Error.Code == "InvalidAuthenticationToken" {
err = driver.RefreshToken(account) err = driver.RefreshToken(account)
@ -314,5 +315,4 @@ func (driver Onedrive) UploadBig(file *model.FileStream, account *model.Account)
func init() { func init() {
base.RegisterDriver(&Onedrive{}) base.RegisterDriver(&Onedrive{})
oneClient.SetRetryCount(3)
} }

View File

@ -10,7 +10,6 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
@ -142,9 +141,9 @@ func (driver PikPak) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil return nil, files, nil
} }
func (driver PikPak) Proxy(c *gin.Context, account *model.Account) { //func (driver PikPak) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver PikPak) Preview(path string, account *model.Account) (interface{}, error) { func (driver PikPak) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

320
drivers/quark/driver.go Normal file
View File

@ -0,0 +1,320 @@
package quark
import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"os"
"path/filepath"
)
type Quark struct{}
func (driver Quark) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Quark",
OnlyProxy: true,
}
}
func (driver Quark) Items() []base.Item {
return []base.Item{
{
Name: "access_token",
Label: "Cookie",
Type: base.TypeString,
Required: true,
Description: "Unknown expiration time",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: true,
Default: "0",
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "file_type,file_name,updated_at",
Required: true,
Default: "file_name",
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Required: true,
Default: "asc",
},
}
}
func (driver Quark) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
_, err := driver.Get("/config", nil, nil, account)
return err
}
func (driver Quark) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Quark) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
files, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, nil
}
func (driver Quark) Link(args base.Args, account *model.Account) (*base.Link, error) {
path := args.Path
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
data := base.Json{
"fids": []string{file.Id},
}
var resp DownResp
_, err = driver.Post("/file/download", data, &resp, account)
if err != nil {
return nil, err
}
return &base.Link{
Url: resp.Data[0].DownloadUrl,
Headers: []base.Header{
{Name: "Cookie", Value: account.AccessToken},
},
}, nil
}
func (driver Quark) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("quark path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Quark) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Quark) MakeDir(path string, account *model.Account) error {
parentFile, err := driver.File(utils.Dir(path), account)
if err != nil {
return err
}
data := base.Json{
"dir_init_lock": false,
"dir_path": "",
"file_name": utils.Base(path),
"pdir_fid": parentFile.Id,
}
_, err = driver.Post("/file", data, nil, account)
return err
}
func (driver Quark) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
data := base.Json{
"action_type": 1,
"exclude_fids": []string{},
"filelist": []string{srcFile.Id},
"to_pdir_fid": dstParentFile.Id,
}
_, err = driver.Post("/file/move", data, nil, account)
return err
}
func (driver Quark) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
data := base.Json{
"fid": srcFile.Id,
"file_name": utils.Base(dst),
}
_, err = driver.Post("/file/rename", data, nil, account)
return err
}
func (driver Quark) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotSupport
}
func (driver Quark) Delete(path string, account *model.Account) error {
srcFile, err := driver.File(path, account)
if err != nil {
return err
}
data := base.Json{
"action_type": 1,
"exclude_fids": []string{},
"filelist": []string{srcFile.Id},
}
_, err = driver.Post("/file/delete", data, nil, account)
return err
}
func (driver Quark) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
m := md5.New()
_, err = io.Copy(m, tempFile)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
md5Str := hex.EncodeToString(m.Sum(nil))
s := sha1.New()
_, err = io.Copy(s, tempFile)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
sha1Str := hex.EncodeToString(s.Sum(nil))
// pre
pre, err := driver.UpPre(file, parentFile.Id, account)
if err != nil {
return err
}
log.Debugln("hash: ", md5Str, sha1Str)
// hash
finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account)
if err != nil {
return err
}
if finish {
return nil
}
// part up
partSize := pre.Metadata.PartSize
var bytes []byte
md5s := make([]string, 0)
defaultBytes := make([]byte, partSize)
left := int64(file.GetSize())
partNumber := 1
for left > 0 {
if left > int64(partSize) {
bytes = defaultBytes
} else {
bytes = make([]byte, left)
}
_, err := io.ReadFull(tempFile, bytes)
if err != nil {
return err
}
left -= int64(partSize)
log.Debugf("left: %d", left)
m, err := driver.UpPart(pre, file.GetMIMEType(), partNumber, bytes, account)
//m, err := driver.UpPart(pre, file.GetMIMEType(), partNumber, bytes, account, md5Str, sha1Str)
if err != nil {
return err
}
if m == "finish" {
return nil
}
md5s = append(md5s, m)
partNumber++
}
err = driver.UpCommit(pre, md5s, account)
if err != nil {
return err
}
return driver.UpFinish(pre, account)
}
var _ base.Driver = (*Quark)(nil)

266
drivers/quark/quark.go Normal file
View File

@ -0,0 +1,266 @@
package quark
import (
"crypto/md5"
"encoding/base64"
"errors"
"fmt"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
"strconv"
"strings"
"time"
)
func (driver Quark) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
u := "https://drive.quark.cn/1/clouddrive" + pathname
req := base.RestyClient.R()
req.SetHeaders(map[string]string{
"Cookie": account.AccessToken,
"Accept": "application/json, text/plain, */*",
"Referer": "https://pan.quark.cn/",
})
req.SetQueryParam("pr", "ucpro")
req.SetQueryParam("fr", "pc")
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var e Resp
var err error
var res *resty.Response
req.SetError(&e)
switch method {
case base.Get:
res, err = req.Get(u)
case base.Post:
res, err = req.Post(u)
case base.Delete:
res, err = req.Delete(u)
case base.Patch:
res, err = req.Patch(u)
case base.Put:
res, err = req.Put(u)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
log.Debugf("%s response: %s", pathname, res.String())
if e.Status >= 400 || e.Code != 0 {
return nil, errors.New(e.Message)
}
return res.Body(), nil
}
func (driver Quark) Get(pathname string, query map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Get, nil, query, nil, nil, resp, account)
}
func (driver Quark) Post(pathname string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Post, nil, nil, nil, data, resp, account)
}
func (driver Quark) GetFiles(parent string, account *model.Account) ([]model.File, error) {
files := make([]model.File, 0)
page := 1
size := 100
query := map[string]string{
"pdir_fid": parent,
"_size": strconv.Itoa(size),
"_fetch_total": "1",
"_sort": "file_type:asc," + account.OrderBy + ":" + account.OrderDirection,
}
for {
query["_page"] = strconv.Itoa(page)
var resp SortResp
_, err := driver.Get("/file/sort", query, &resp, account)
if err != nil {
return nil, err
}
for _, f := range resp.Data.List {
files = append(files, *driver.formatFile(&f))
}
if page*size >= resp.Metadata.Count {
break
}
page++
}
return files, nil
}
func (driver Quark) UpPre(file *model.FileStream, parentId string, account *model.Account) (UpPreResp, error) {
now := time.Now()
data := base.Json{
"ccp_hash_update": true,
"dir_name": "",
"file_name": file.Name,
"format_type": file.MIMEType,
"l_created_at": now.UnixMilli(),
"l_updated_at": now.UnixMilli(),
"pdir_fid": parentId,
"size": file.Size,
}
log.Debugf("uppre data: %+v", data)
var resp UpPreResp
_, err := driver.Post("/file/upload/pre", data, &resp, account)
return resp, err
}
func (driver Quark) UpHash(md5, sha1, taskId string, account *model.Account) (bool, error) {
data := base.Json{
"md5": md5,
"sha1": sha1,
"task_id": taskId,
}
log.Debugf("hash: %+v", data)
var resp HashResp
_, err := driver.Post("/file/update/hash", data, &resp, account)
return resp.Data.Finish, err
}
func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account) (string, error) {
//func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account, md5Str, sha1Str string) (string, error) {
timeStr := time.Now().UTC().Format(http.TimeFormat)
data := base.Json{
"auth_info": pre.Data.AuthInfo,
"auth_meta": fmt.Sprintf(`PUT
%s
%s
x-oss-date:%s
x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
/%s/%s?partNumber=%d&uploadId=%s`,
mineType, timeStr, timeStr, pre.Data.Bucket, pre.Data.ObjKey, partNumber, pre.Data.UploadId),
"task_id": pre.Data.TaskId,
}
var resp UpAuthResp
_, err := driver.Post("/file/upload/auth", data, &resp, account)
if err != nil {
return "", err
}
//if partNumber == 1 {
// finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account)
// if err != nil {
// return "", err
// }
// if finish {
// return "finish", nil
// }
//}
u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey)
res, err := base.RestyClient.R().
SetHeaders(map[string]string{
"Authorization": resp.Data.AuthKey,
"Content-Type": mineType,
"Referer": "https://pan.quark.cn/",
"x-oss-date": timeStr,
"x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit",
}).
SetQueryParams(map[string]string{
"partNumber": strconv.Itoa(partNumber),
"uploadId": pre.Data.UploadId,
}).SetBody(bytes).Put(u)
if res.StatusCode() != 200 {
return "", fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String())
}
return res.Header().Get("ETag"), nil
}
func (driver Quark) UpCommit(pre UpPreResp, md5s []string, account *model.Account) error {
timeStr := time.Now().UTC().Format(http.TimeFormat)
log.Debugf("md5s: %+v", md5s)
bodyBuilder := strings.Builder{}
bodyBuilder.WriteString(`<?xml version="1.0" encoding="UTF-8"?>
<CompleteMultipartUpload>
`)
for i, m := range md5s {
bodyBuilder.WriteString(fmt.Sprintf(`<Part>
<PartNumber>%d</PartNumber>
<ETag>%s</ETag>
</Part>
`, i+1, m))
}
bodyBuilder.WriteString("</CompleteMultipartUpload>")
body := bodyBuilder.String()
m := md5.New()
m.Write([]byte(body))
contentMd5 := base64.StdEncoding.EncodeToString(m.Sum(nil))
callbackBytes, err := utils.Json.Marshal(pre.Data.Callback)
if err != nil {
return err
}
callbackBase64 := base64.StdEncoding.EncodeToString(callbackBytes)
data := base.Json{
"auth_info": pre.Data.AuthInfo,
"auth_meta": fmt.Sprintf(`POST
%s
application/xml
%s
x-oss-callback:%s
x-oss-date:%s
x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
/%s/%s?uploadId=%s`,
contentMd5, timeStr, callbackBase64, timeStr,
pre.Data.Bucket, pre.Data.ObjKey, pre.Data.UploadId),
"task_id": pre.Data.TaskId,
}
log.Debugf("xml: %s", body)
log.Debugf("auth data: %+v", data)
var resp UpAuthResp
_, err = driver.Post("/file/upload/auth", data, &resp, account)
if err != nil {
return err
}
u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey)
res, err := base.RestyClient.R().
SetHeaders(map[string]string{
"Authorization": resp.Data.AuthKey,
"Content-MD5": contentMd5,
"Content-Type": "application/xml",
"Referer": "https://pan.quark.cn/",
"x-oss-callback": callbackBase64,
"x-oss-date": timeStr,
"x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit",
}).
SetQueryParams(map[string]string{
"uploadId": pre.Data.UploadId,
}).SetBody(body).Post(u)
if res.StatusCode() != 200 {
return fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String())
}
return nil
}
func (driver Quark) UpFinish(pre UpPreResp, account *model.Account) error {
data := base.Json{
"obj_key": pre.Data.ObjKey,
"task_id": pre.Data.TaskId,
}
_, err := driver.Post("/file/upload/finish", data, nil, account)
if err != nil {
return err
}
time.Sleep(time.Second)
return nil
}
func init() {
base.RegisterDriver(&Quark{})
}

134
drivers/quark/types.go Normal file
View File

@ -0,0 +1,134 @@
package quark
type Resp struct {
Status int `json:"status"`
Code int `json:"code"`
Message string `json:"message"`
//ReqId string `json:"req_id"`
//Timestamp int `json:"timestamp"`
}
type File struct {
Fid string `json:"fid"`
FileName string `json:"file_name"`
//PdirFid string `json:"pdir_fid"`
//Category int `json:"category"`
//FileType int `json:"file_type"`
Size int64 `json:"size"`
//FormatType string `json:"format_type"`
//Status int `json:"status"`
//Tags string `json:"tags,omitempty"`
//LCreatedAt int64 `json:"l_created_at"`
LUpdatedAt int64 `json:"l_updated_at"`
//NameSpace int `json:"name_space"`
//IncludeItems int `json:"include_items,omitempty"`
//RiskType int `json:"risk_type"`
//BackupSign int `json:"backup_sign"`
//Duration int `json:"duration"`
//FileSource string `json:"file_source"`
File bool `json:"file"`
//CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
//PrivateExtra struct {} `json:"_private_extra"`
//ObjCategory string `json:"obj_category,omitempty"`
//Thumbnail string `json:"thumbnail,omitempty"`
}
type SortResp struct {
Resp
Data struct {
List []File `json:"list"`
} `json:"data"`
Metadata struct {
Size int `json:"_size"`
Page int `json:"_page"`
Count int `json:"_count"`
Total int `json:"_total"`
Way string `json:"way"`
} `json:"metadata"`
}
type DownResp struct {
Resp
Data []struct {
Fid string `json:"fid"`
FileName string `json:"file_name"`
PdirFid string `json:"pdir_fid"`
Category int `json:"category"`
FileType int `json:"file_type"`
Size int `json:"size"`
FormatType string `json:"format_type"`
Status int `json:"status"`
Tags string `json:"tags"`
LCreatedAt int64 `json:"l_created_at"`
LUpdatedAt int64 `json:"l_updated_at"`
NameSpace int `json:"name_space"`
Thumbnail string `json:"thumbnail"`
DownloadUrl string `json:"download_url"`
Md5 string `json:"md5"`
RiskType int `json:"risk_type"`
RangeSize int `json:"range_size"`
BackupSign int `json:"backup_sign"`
ObjCategory string `json:"obj_category"`
Duration int `json:"duration"`
FileSource string `json:"file_source"`
File bool `json:"file"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
PrivateExtra struct {
} `json:"_private_extra"`
} `json:"data"`
Metadata struct {
Acc2 string `json:"acc2"`
Acc1 string `json:"acc1"`
} `json:"metadata"`
}
type UpPreResp struct {
Resp
Data struct {
TaskId string `json:"task_id"`
Finish bool `json:"finish"`
UploadId string `json:"upload_id"`
ObjKey string `json:"obj_key"`
UploadUrl string `json:"upload_url"`
Fid string `json:"fid"`
Bucket string `json:"bucket"`
Callback struct {
CallbackUrl string `json:"callbackUrl"`
CallbackBody string `json:"callbackBody"`
} `json:"callback"`
FormatType string `json:"format_type"`
Size int `json:"size"`
AuthInfo string `json:"auth_info"`
} `json:"data"`
Metadata struct {
PartThread int `json:"part_thread"`
Acc2 string `json:"acc2"`
Acc1 string `json:"acc1"`
PartSize int `json:"part_size"` // 分片大小
} `json:"metadata"`
}
type HashResp struct {
Resp
Data struct {
Finish bool `json:"finish"`
Fid string `json:"fid"`
Thumbnail string `json:"thumbnail"`
FormatType string `json:"format_type"`
} `json:"data"`
Metadata struct {
} `json:"metadata"`
}
type UpAuthResp struct {
Resp
Data struct {
AuthKey string `json:"auth_key"`
Speed int `json:"speed"`
Headers []interface{} `json:"headers"`
} `json:"data"`
Metadata struct {
} `json:"metadata"`
}

31
drivers/quark/util.go Normal file
View File

@ -0,0 +1,31 @@
package quark
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"path"
"time"
)
func getTime(t int64) *time.Time {
tm := time.UnixMilli(t)
//log.Debugln(tm)
return &tm
}
func (driver Quark) formatFile(f *File) *model.File {
file := model.File{
Id: f.Fid,
Name: f.FileName,
Size: f.Size,
Driver: driver.Config().Name,
UpdatedAt: getTime(f.UpdatedAt),
}
if f.File {
file.Type = utils.GetFileType(path.Ext(f.FileName))
} else {
file.Type = conf.FOLDER
}
return &file
}

View File

@ -8,7 +8,6 @@ import (
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net/url" "net/url"
"path/filepath" "path/filepath"
@ -77,9 +76,21 @@ func (driver S3) Items() []base.Item {
{ {
Name: "zone", Name: "zone",
Label: "placeholder filename", Label: "placeholder filename",
Type: base.TypeNumber, Type: base.TypeString,
Description: "default empty string", Description: "default empty string",
}, },
{
Name: "bool_1",
Label: "S3ForcePathStyle",
Type: base.TypeBool,
},
{
Name: "internal_type",
Label: "ListObject Version",
Type: base.TypeSelect,
Values: "v1,v2",
Default: "v1",
},
} }
} }
@ -132,8 +143,12 @@ func (driver S3) Files(path string, account *model.Account) ([]model.File, error
cache, err := base.GetCache(path, account) cache, err := base.GetCache(path, account)
if err == nil { if err == nil {
files, _ = cache.([]model.File) files, _ = cache.([]model.File)
} else {
if account.InternalType == "v2" {
files, err = driver.ListV2(path, account)
} else { } else {
files, err = driver.List(path, account) files, err = driver.List(path, account)
}
if err == nil && len(files) > 0 { if err == nil && len(files) > 0 {
_ = base.SetCache(path, files, account) _ = base.SetCache(path, files, account)
} }
@ -142,7 +157,7 @@ func (driver S3) Files(path string, account *model.Account) ([]model.File, error
} }
func (driver S3) Link(args base.Args, account *model.Account) (*base.Link, error) { func (driver S3) Link(args base.Args, account *model.Account) (*base.Link, error) {
client, err := driver.GetClient(account) client, err := driver.GetClient(account, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -189,9 +204,9 @@ func (driver S3) Path(path string, account *model.Account) (*model.File, []model
return nil, files, nil return nil, files, nil
} }
func (driver S3) Proxy(c *gin.Context, account *model.Account) { //func (driver S3) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver S3) Preview(path string, account *model.Account) (interface{}, error) { func (driver S3) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport
@ -215,7 +230,7 @@ func (driver S3) Rename(src string, dst string, account *model.Account) error {
} }
func (driver S3) Copy(src string, dst string, account *model.Account) error { func (driver S3) Copy(src string, dst string, account *model.Account) error {
client, err := driver.GetClient(account) client, err := driver.GetClient(account, false)
if err != nil { if err != nil {
return err return err
} }
@ -235,7 +250,7 @@ func (driver S3) Copy(src string, dst string, account *model.Account) error {
} }
func (driver S3) Delete(path string, account *model.Account) error { func (driver S3) Delete(path string, account *model.Account) error {
client, err := driver.GetClient(account) client, err := driver.GetClient(account, false)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,6 +1,7 @@
package s3 package s3
import ( import (
"errors"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
@ -25,17 +26,18 @@ func (driver S3) NewSession(account *model.Account) (*session.Session, error) {
Credentials: credentials.NewStaticCredentials(account.AccessKey, account.AccessSecret, ""), Credentials: credentials.NewStaticCredentials(account.AccessKey, account.AccessSecret, ""),
Region: &account.Region, Region: &account.Region,
Endpoint: &account.Endpoint, Endpoint: &account.Endpoint,
S3ForcePathStyle: aws.Bool(account.Bool1),
} }
return session.NewSession(cfg) return session.NewSession(cfg)
} }
func (driver S3) GetClient(account *model.Account) (*s3.S3, error) { func (driver S3) GetClient(account *model.Account, link bool) (*s3.S3, error) {
s, ok := sessionsMap[account.Name] s, ok := sessionsMap[account.Name]
if !ok { if !ok {
return nil, fmt.Errorf("can't find [%s] session", account.Name) return nil, fmt.Errorf("can't find [%s] session", account.Name)
} }
client := s3.New(s) client := s3.New(s)
if account.CustomHost != "" { if link && account.CustomHost != "" {
cURL, err := url.Parse(account.CustomHost) cURL, err := url.Parse(account.CustomHost)
if err != nil { if err != nil {
return nil, err return nil, err
@ -54,7 +56,7 @@ func (driver S3) GetClient(account *model.Account) (*s3.S3, error) {
func (driver S3) List(prefix string, account *model.Account) ([]model.File, error) { func (driver S3) List(prefix string, account *model.Account) ([]model.File, error) {
prefix = driver.GetKey(prefix, account, true) prefix = driver.GetKey(prefix, account, true)
log.Debugf("list: %s", prefix) log.Debugf("list: %s", prefix)
client, err := driver.GetClient(account) client, err := driver.GetClient(account, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -99,6 +101,9 @@ func (driver S3) List(prefix string, account *model.Account) ([]model.File, erro
} }
files = append(files, file) files = append(files, file)
} }
if listObjectsResult.IsTruncated == nil {
return nil, errors.New("IsTruncated nil")
}
if *listObjectsResult.IsTruncated { if *listObjectsResult.IsTruncated {
marker = *listObjectsResult.NextMarker marker = *listObjectsResult.NextMarker
} else { } else {
@ -108,6 +113,73 @@ func (driver S3) List(prefix string, account *model.Account) ([]model.File, erro
return files, nil return files, nil
} }
func (driver S3) ListV2(prefix string, account *model.Account) ([]model.File, error) {
prefix = driver.GetKey(prefix, account, true)
//if prefix == "" {
// prefix = "/"
//}
log.Debugf("list: %s", prefix)
client, err := driver.GetClient(account, false)
if err != nil {
return nil, err
}
files := make([]model.File, 0)
var continuationToken, startAfter *string
for {
input := &s3.ListObjectsV2Input{
Bucket: &account.Bucket,
ContinuationToken: continuationToken,
Prefix: &prefix,
Delimiter: aws.String("/"),
StartAfter: startAfter,
}
listObjectsResult, err := client.ListObjectsV2(input)
if err != nil {
return nil, err
}
log.Debugf("resp: %+v", listObjectsResult)
for _, object := range listObjectsResult.CommonPrefixes {
name := utils.Base(strings.Trim(*object.Prefix, "/"))
file := model.File{
//Id: *object.Key,
Name: name,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
TimeStr: "-",
Type: conf.FOLDER,
}
files = append(files, file)
}
for _, object := range listObjectsResult.Contents {
name := utils.Base(*object.Key)
if name == account.Zone {
continue
}
file := model.File{
//Id: *object.Key,
Name: name,
Size: *object.Size,
Driver: driver.Config().Name,
UpdatedAt: object.LastModified,
Type: utils.GetFileType(path.Ext(*object.Key)),
}
files = append(files, file)
}
if !aws.BoolValue(listObjectsResult.IsTruncated) {
break
}
if listObjectsResult.NextContinuationToken != nil {
continuationToken = listObjectsResult.NextContinuationToken
continue
}
if len(listObjectsResult.Contents) == 0 {
break
}
startAfter = listObjectsResult.Contents[len(listObjectsResult.Contents)-1].Key
}
return files, nil
}
func (driver S3) GetKey(path string, account *model.Account, dir bool) string { func (driver S3) GetKey(path string, account *model.Account, dir bool) string {
path = utils.Join(account.RootFolder, path) path = utils.Join(account.RootFolder, path)
path = strings.TrimPrefix(path, "/") path = strings.TrimPrefix(path, "/")

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -154,9 +153,9 @@ func (driver Shandian) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil return nil, files, nil
} }
func (driver Shandian) Proxy(c *gin.Context, account *model.Account) { //func (driver Shandian) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver Shandian) Preview(path string, account *model.Account) (interface{}, error) { func (driver Shandian) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
) )
@ -52,6 +51,7 @@ func (driver Teambition) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "fileName,fileSize,updated,created", Values: "fileName,fileSize,updated,created",
Required: true, Required: true,
Default: "fileName",
}, },
{ {
Name: "order_direction", Name: "order_direction",
@ -59,6 +59,7 @@ func (driver Teambition) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "Asc,Desc", Values: "Asc,Desc",
Required: true, Required: true,
Default: "Asc",
}, },
} }
} }
@ -149,9 +150,9 @@ func (driver Teambition) Path(path string, account *model.Account) (*model.File,
return nil, files, nil return nil, files, nil
} }
func (driver Teambition) Proxy(c *gin.Context, account *model.Account) { //func (driver Teambition) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver Teambition) Preview(path string, account *model.Account) (interface{}, error) { func (driver Teambition) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -98,7 +98,7 @@ func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]mo
Size: 0, Size: 0,
Type: conf.FOLDER, Type: conf.FOLDER,
Driver: driver.Config().Name, Driver: driver.Config().Name,
UpdatedAt: &collection.Updated, UpdatedAt: collection.Updated,
}) })
} }
} }
@ -126,7 +126,7 @@ func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]mo
Size: work.FileSize, Size: work.FileSize,
Type: utils.GetFileType(path.Ext(work.FileName)), Type: utils.GetFileType(path.Ext(work.FileName)),
Driver: driver.Config().Name, Driver: driver.Config().Name,
UpdatedAt: &work.Updated, UpdatedAt: work.Updated,
Thumbnail: work.Thumbnail, Thumbnail: work.Thumbnail,
Url: work.DownloadURL, Url: work.DownloadURL,
}) })
@ -157,8 +157,10 @@ func (driver Teambition) upload(file *model.FileStream, token string, account *m
func (driver Teambition) chunkUpload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) { func (driver Teambition) chunkUpload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
prefix := "tcs" prefix := "tcs"
referer := "https://www.teambition.com/"
if account.InternalType == "International" { if account.InternalType == "International" {
prefix = "us-tcs" prefix = "us-tcs"
referer = "https://us.teambition.com/"
} }
var newChunk ChunkUpload var newChunk ChunkUpload
_, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token). _, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token).
@ -187,7 +189,7 @@ func (driver Teambition) chunkUpload(file *model.FileStream, token string, accou
res, err := base.RestyClient.R().SetHeaders(map[string]string{ res, err := base.RestyClient.R().SetHeaders(map[string]string{
"Authorization": token, "Authorization": token,
"Content-Type": "application/octet-stream", "Content-Type": "application/octet-stream",
"Referer": "https://www.teambition.com/", "Referer": referer,
}).SetBody(chunkData).Post(u) }).SetBody(chunkData).Post(u)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -5,7 +5,7 @@ import "time"
type Collection struct { type Collection struct {
ID string `json:"_id"` ID string `json:"_id"`
Title string `json:"title"` Title string `json:"title"`
Updated time.Time `json:"updated"` Updated *time.Time `json:"updated"`
} }
type Work struct { type Work struct {
@ -17,7 +17,7 @@ type Work struct {
DownloadURL string `json:"downloadUrl"` DownloadURL string `json:"downloadUrl"`
ThumbnailURL string `json:"thumbnailUrl"` ThumbnailURL string `json:"thumbnailUrl"`
Thumbnail string `json:"thumbnail"` Thumbnail string `json:"thumbnail"`
Updated time.Time `json:"updated"` Updated *time.Time `json:"updated"`
PreviewURL string `json:"previewUrl"` PreviewURL string `json:"previewUrl"`
} }

237
drivers/uss/driver.go Normal file
View File

@ -0,0 +1,237 @@
package uss
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"github.com/upyun/go-sdk/v3/upyun"
"net/url"
"path/filepath"
"strings"
"time"
)
type USS struct {
}
func (driver USS) Config() base.DriverConfig {
return base.DriverConfig{
Name: "USS",
LocalSort: true,
}
}
func (driver USS) Items() []base.Item {
return []base.Item{
{
Name: "bucket",
Label: "Bucket",
Type: base.TypeString,
Required: true,
},
{
Name: "endpoint",
Label: "Endpoint",
Type: base.TypeString,
Required: true,
},
{
Name: "access_key",
Label: "Operator Name",
Type: base.TypeString,
Required: true,
},
{
Name: "access_secret",
Label: "Operator Password",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Required: false,
},
{
Name: "custom_host",
Label: "Custom Host",
Type: base.TypeString,
},
{
Name: "limit",
Label: "Sign url expire time(hours)",
Type: base.TypeNumber,
Default: "4",
Description: "default 4 hours",
},
//{
// Name: "zone",
// Label: "placeholder filename",
// Type: base.TypeString,
// Description: "default empty string",
//},
}
}
func (driver USS) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
if account.Limit == 0 {
account.Limit = 4
}
client, err := driver.NewUpYun(account)
if err != nil {
account.Status = err.Error()
} else {
clientsMap[account.Name] = client
account.Status = "work"
}
_ = model.SaveAccount(account)
return err
}
func (driver USS) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver USS) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
files, err = driver.List(path, account)
if err == nil && len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, err
}
func (driver USS) Link(args base.Args, account *model.Account) (*base.Link, error) {
key := driver.GetKey(args.Path, account, false)
host := account.CustomHost
if host == "" {
host = account.Endpoint
}
if strings.Contains(host, "://") {
host = "https://" + host
}
u := fmt.Sprintf("%s/%s", host, key)
downExp := time.Hour * time.Duration(account.Limit)
expireAt := time.Now().Add(downExp).Unix()
upd := url.QueryEscape(utils.Base(args.Path))
signStr := strings.Join([]string{account.AccessSecret, fmt.Sprint(expireAt), fmt.Sprintf("/%s", key)}, "&")
upt := utils.GetMD5Encode(signStr)[12:20] + fmt.Sprint(expireAt)
link := fmt.Sprintf("%s?_upd=%s&_upt=%s", u, upd, upt)
return &base.Link{Url: link}, nil
}
func (driver USS) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("s3 path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver USS) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver USS) MakeDir(path string, account *model.Account) error {
client, err := driver.GetClient(account)
if err != nil {
return err
}
return client.Mkdir(driver.GetKey(path, account, true))
}
func (driver USS) Move(src string, dst string, account *model.Account) error {
client, err := driver.GetClient(account)
if err != nil {
return err
}
return client.Move(&upyun.MoveObjectConfig{
SrcPath: driver.GetKey(src, account, false),
DestPath: driver.GetKey(dst, account, false),
})
}
func (driver USS) Rename(src string, dst string, account *model.Account) error {
return driver.Move(src, dst, account)
}
func (driver USS) Copy(src string, dst string, account *model.Account) error {
client, err := driver.GetClient(account)
if err != nil {
return err
}
return client.Copy(&upyun.CopyObjectConfig{
SrcPath: driver.GetKey(src, account, false),
DestPath: driver.GetKey(dst, account, false),
})
}
func (driver USS) Delete(path string, account *model.Account) error {
client, err := driver.GetClient(account)
if err != nil {
return err
}
return client.Delete(&upyun.DeleteObjectConfig{
Path: driver.GetKey(path, account, false),
Async: false,
})
}
func (driver USS) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
client, err := driver.GetClient(account)
if err != nil {
return err
}
return client.Put(&upyun.PutObjectConfig{
Path: driver.GetKey(utils.Join(file.ParentPath, file.GetFileName()), account, false),
Reader: file,
})
}
var _ base.Driver = (*USS)(nil)

82
drivers/uss/uss.go Normal file
View File

@ -0,0 +1,82 @@
package uss
import (
"errors"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/upyun/go-sdk/v3/upyun"
"path"
"strings"
)
var clientsMap map[string]*upyun.UpYun
func (driver USS) NewUpYun(account *model.Account) (*upyun.UpYun, error) {
return upyun.NewUpYun(&upyun.UpYunConfig{
Bucket: account.Bucket,
Operator: account.AccessKey,
Password: account.AccessToken,
}), nil
}
func (driver USS) GetClient(account *model.Account) (*upyun.UpYun, error) {
client, ok := clientsMap[account.Name]
if ok {
return client, nil
}
return nil, errors.New("can't get client")
}
func (driver USS) List(prefix string, account *model.Account) ([]model.File, error) {
prefix = driver.GetKey(prefix, account, true)
client, err := driver.GetClient(account)
if err != nil {
return nil, err
}
objsChan := make(chan *upyun.FileInfo, 10)
defer close(objsChan)
go func() {
err = client.List(&upyun.GetObjectsConfig{
Path: prefix,
ObjectsChan: objsChan,
MaxListObjects: 0,
MaxListLevel: 1,
})
}()
if err != nil {
return nil, err
}
res := make([]model.File, 0)
for obj := range objsChan {
t := obj.Time
f := model.File{
Name: obj.Name,
Size: obj.Size,
UpdatedAt: &t,
Driver: driver.Config().Name,
}
if obj.IsDir {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(path.Ext(obj.Name))
}
res = append(res, f)
}
return res, err
}
func (driver USS) GetKey(path string, account *model.Account, dir bool) string {
path = utils.Join(account.RootFolder, path)
path = strings.TrimPrefix(path, "/")
if dir {
path += "/"
}
return path
}
func init() {
clientsMap = make(map[string]*upyun.UpYun)
base.RegisterDriver(&USS{})
}

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"path/filepath" "path/filepath"
) )
@ -137,9 +136,9 @@ func (driver WebDav) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil return nil, files, nil
} }
func (driver WebDav) Proxy(c *gin.Context, account *model.Account) { //func (driver WebDav) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver WebDav) Preview(path string, account *model.Account) (interface{}, error) { func (driver WebDav) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

282
drivers/xunlei/driver.go Normal file
View File

@ -0,0 +1,282 @@
package xunlei
import (
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strconv"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
)
type XunLeiCloud struct{}
func init() {
base.RegisterDriver(new(XunLeiCloud))
}
func (driver XunLeiCloud) Config() base.DriverConfig {
return base.DriverConfig{
Name: "XunLeiCloud",
LocalSort: true,
}
}
func (driver XunLeiCloud) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "username",
Type: base.TypeString,
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: base.TypeString,
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: true,
},
}
}
func (driver XunLeiCloud) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
return GetState(account).Login(account)
}
func (driver XunLeiCloud) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.File, error) {
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
file, err := driver.File(utils.ParsePath(path), account)
if err != nil {
return nil, err
}
var fileList FileList
u := fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files?parent_id=%s&page_token=%s&with_audit=true&filters=%s", file.Id, "", url.QueryEscape(`{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`))
if err = GetState(account).Request("GET", u, nil, &fileList, account); err != nil {
return nil, err
}
files := make([]model.File, 0, len(fileList.Files))
for _, file := range fileList.Files {
if file.Kind == FOLDER || (file.Kind == FILE && file.Audit.Status == "STATUS_OK") {
files = append(files, *driver.formatFile(&file))
}
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver XunLeiCloud) formatFile(file *Files) *model.File {
size, _ := strconv.ParseInt(file.Size, 10, 64)
tp := conf.FOLDER
if file.Kind == FILE {
tp = utils.GetFileType(file.FileExtension)
}
return &model.File{
Id: file.ID,
Name: file.Name,
Size: size,
Type: tp,
Driver: driver.Config().Name,
UpdatedAt: file.CreatedTime,
Thumbnail: file.ThumbnailLink,
}
}
func (driver XunLeiCloud) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(utils.ParsePath(args.Path), account)
if err != nil {
return nil, err
}
if file.Type == conf.FOLDER {
return nil, base.ErrNotFile
}
var lFile Files
if err = GetState(account).Request("GET", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s?&with_audit=true", file.Id), nil, &lFile, account); err != nil {
return nil, err
}
return &base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
},
Url: lFile.WebContentLink,
}, nil
}
func (driver XunLeiCloud) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("xunlei path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver XunLeiCloud) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver XunLeiCloud) MakeDir(path string, account *model.Account) error {
dir, name := filepath.Split(path)
parentFile, err := driver.File(dir, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files", &base.Json{"kind": FOLDER, "name": name, "parent_id": parentFile.Id}, nil, account)
}
func (driver XunLeiCloud) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(filepath.Dir(dst), account)
if err != nil {
return err
}
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files:batchMove", &base.Json{"to": base.Json{"parent_id": dstDirFile.Id}, "ids": []string{srcFile.Id}}, nil, account)
}
func (driver XunLeiCloud) Copy(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(filepath.Dir(dst), account)
if err != nil {
return err
}
return GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files:batchCopy", &base.Json{"to": base.Json{"parent_id": dstDirFile.Id}, "ids": []string{srcFile.Id}}, nil, account)
}
func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
srcFile, err := driver.File(path, account)
if err != nil {
return err
}
return GetState(account).Request("PATCH", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s/trash", srcFile.Id), &base.Json{}, nil, account)
}
func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer tempFile.Close()
defer os.Remove(tempFile.Name())
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
if err != nil {
return err
}
var rep UploadTaskResponse
err = GetState(account).Request("POST", "https://api-pan.xunlei.com/drive/v1/files", &base.Json{
"kind": FILE,
"parent_id": parentFile.Id,
"name": file.Name,
"size": fmt.Sprint(file.Size),
"hash": gcid,
"upload_type": UPLOAD_TYPE_RESUMABLE,
}, &rep, account)
if err != nil {
return err
}
param := rep.Resumable.Params
client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true), oss.HTTPClient(xunleiClient.GetClient()))
if err != nil {
return err
}
bucket, err := client.Bucket(param.Bucket)
if err != nil {
return err
}
return bucket.UploadFile(param.Key, tempFile.Name(), 4*1024*1024, oss.Routines(3), oss.Checkpoint(true, ""), oss.Expires(param.Expiration))
}
func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account) error {
_, dstName := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
return GetState(account).Request("PATCH", fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files/%s", srcFile.Id), &base.Json{"name": dstName}, nil, account)
}
var _ base.Driver = (*XunLeiCloud)(nil)

154
drivers/xunlei/types.go Normal file
View File

@ -0,0 +1,154 @@
package xunlei
import (
"time"
)
type Erron struct {
Error string `json:"error"`
ErrorCode int64 `json:"error_code"`
ErrorDescription string `json:"error_description"`
// ErrorDetails interface{} `json:"error_details"`
}
type CaptchaTokenRequest struct {
Action string `json:"action"`
CaptchaToken string `json:"captcha_token"`
ClientID string `json:"client_id"`
DeviceID string `json:"device_id"`
Meta map[string]string `json:"meta"`
//RedirectUri string `json:"redirect_uri"`
}
type CaptchaTokenResponse struct {
CaptchaToken string `json:"captcha_token"`
ExpiresIn int64 `json:"expires_in"`
Url string `json:"url"`
}
type TokenResponse struct {
TokenType string `json:"token_type"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int64 `json:"expires_in"`
Sub string `json:"sub"`
UserID string `json:"user_id"`
}
type SignInRequest struct {
CaptchaToken string `json:"captcha_token"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Username string `json:"username"`
Password string `json:"password"`
}
type FileList struct {
Kind string `json:"kind"`
NextPageToken string `json:"next_page_token"`
Files []Files `json:"files"`
Version string `json:"version"`
VersionOutdated bool `json:"version_outdated"`
}
type Files struct {
Kind string `json:"kind"`
ID string `json:"id"`
ParentID string `json:"parent_id"`
Name string `json:"name"`
UserID string `json:"user_id"`
Size string `json:"size"`
Revision string `json:"revision"`
FileExtension string `json:"file_extension"`
MimeType string `json:"mime_type"`
Starred bool `json:"starred"`
WebContentLink string `json:"web_content_link"`
CreatedTime *time.Time `json:"created_time"`
ModifiedTime *time.Time `json:"modified_time"`
IconLink string `json:"icon_link"`
ThumbnailLink string `json:"thumbnail_link"`
Md5Checksum string `json:"md5_checksum"`
Hash string `json:"hash"`
//Links struct{} `json:"links"`
Phase string `json:"phase"`
Audit struct {
Status string `json:"status"`
Message string `json:"message"`
Title string `json:"title"`
} `json:"audit"`
/* Medias []struct {
Category string `json:"category"`
IconLink string `json:"icon_link"`
IsDefault bool `json:"is_default"`
IsOrigin bool `json:"is_origin"`
IsVisible bool `json:"is_visible"`
//Link interface{} `json:"link"`
MediaID string `json:"media_id"`
MediaName string `json:"media_name"`
NeedMoreQuota bool `json:"need_more_quota"`
Priority int `json:"priority"`
RedirectLink string `json:"redirect_link"`
ResolutionName string `json:"resolution_name"`
Video struct {
AudioCodec string `json:"audio_codec"`
BitRate int `json:"bit_rate"`
Duration int `json:"duration"`
FrameRate int `json:"frame_rate"`
Height int `json:"height"`
VideoCodec string `json:"video_codec"`
VideoType string `json:"video_type"`
Width int `json:"width"`
} `json:"video"`
VipTypes []string `json:"vip_types"`
} `json:"medias"` */
Trashed bool `json:"trashed"`
DeleteTime string `json:"delete_time"`
OriginalURL string `json:"original_url"`
//Params struct{} `json:"params"`
OriginalFileIndex int `json:"original_file_index"`
Space string `json:"space"`
//Apps []interface{} `json:"apps"`
Writable bool `json:"writable"`
FolderType string `json:"folder_type"`
//Collection interface{} `json:"collection"`
}
type UploadTaskResponse struct {
UploadType string `json:"upload_type"`
/*//UPLOAD_TYPE_FORM
Form struct {
//Headers struct{} `json:"headers"`
Kind string `json:"kind"`
Method string `json:"method"`
MultiParts struct {
OSSAccessKeyID string `json:"OSSAccessKeyId"`
Signature string `json:"Signature"`
Callback string `json:"callback"`
Key string `json:"key"`
Policy string `json:"policy"`
XUserData string `json:"x:user_data"`
} `json:"multi_parts"`
URL string `json:"url"`
} `json:"form"`*/
//UPLOAD_TYPE_RESUMABLE
Resumable struct {
Kind string `json:"kind"`
Params struct {
AccessKeyID string `json:"access_key_id"`
AccessKeySecret string `json:"access_key_secret"`
Bucket string `json:"bucket"`
Endpoint string `json:"endpoint"`
Expiration time.Time `json:"expiration"`
Key string `json:"key"`
SecurityToken string `json:"security_token"`
} `json:"params"`
Provider string `json:"provider"`
} `json:"resumable"`
File Files `json:"file"`
}

103
drivers/xunlei/util.go Normal file
View File

@ -0,0 +1,103 @@
package xunlei
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"net/url"
"github.com/Xhofe/alist/utils"
)
const (
// 小米浏览器
CLIENT_ID = "X7MtiU0Gb5YqWv-6"
CLIENT_SECRET = "84MYEih3Eeu2HF4RrGce3Q"
CLIENT_VERSION = "5.1.0.51045"
ALG_VERSION = "1"
PACKAGE_NAME = "com.xunlei.xcloud.lib"
)
var Algorithms = []string{
"",
"BXza40wm+P4zw8rEFpHA",
"UfZLfKfYRmKTA0",
"OMBGVt/9Wcaln1XaBz",
"Jn217F4rk5FPPWyhoeV",
"w5OwkGo0pGpb0Xe/XZ5T3",
"5guM3DNiY4F78x49zQ97q75",
"QXwn4D2j884wJgrYXjGClM/IVrJX",
"NXBRosYvbHIm6w8vEB",
"2kZ8Ie1yW2ib4O2iAkNpJobP",
"11CoVJJQEc",
"xf3QWysVwnVsNv5DCxU+cgNT1rK",
"9eEfKkrqkfw",
"T78dnANexYRbiZy",
}
const (
FOLDER = "drive#folder"
FILE = "drive#file"
RESUMABLE = "drive#resumable"
)
const (
UPLOAD_TYPE_UNKNOWN = "UPLOAD_TYPE_UNKNOWN"
//UPLOAD_TYPE_FORM = "UPLOAD_TYPE_FORM"
UPLOAD_TYPE_RESUMABLE = "UPLOAD_TYPE_RESUMABLE"
UPLOAD_TYPE_URL = "UPLOAD_TYPE_URL"
)
func captchaSign(driverID string, time int64) string {
str := fmt.Sprint(CLIENT_ID, CLIENT_VERSION, PACKAGE_NAME, driverID, time)
for _, algorithm := range Algorithms {
str = utils.GetMD5Encode(fmt.Sprint(str, algorithm))
}
return fmt.Sprint(ALG_VERSION, ".", str)
}
func getAction(method string, u string) string {
c, _ := url.Parse(u)
return fmt.Sprint(method, ":", c.Path)
}
func getGcid(r io.Reader, size int64) (string, error) {
calcBlockSize := func(j int64) int64 {
if j >= 0 && j <= 134217728 {
return 262144
}
if j <= 134217728 || j > 268435456 {
if j <= 268435456 || j > 536870912 {
return 2097152
}
return 1048576
}
return 524288
}
/*
calcBlockSize := func(j int64) int64 {
psize := int64(0x40000)
for j/psize > 0x200 {
psize <<= 1
}
return psize
}
*/
hash1 := sha1.New()
hash2 := sha1.New()
for {
hash2.Reset()
if n, err := io.CopyN(hash2, r, calcBlockSize(size)); err != nil && n == 0 {
if err != io.EOF {
return "", err
}
break
}
hash1.Write(hash2.Sum(nil))
}
return hex.EncodeToString(hash1.Sum(nil)), nil
}

288
drivers/xunlei/xunlei.go Normal file
View File

@ -0,0 +1,288 @@
package xunlei
import (
"fmt"
"sync"
"time"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
var xunleiClient = resty.New().SetTimeout(120 * time.Second)
// 一个账户只允许登陆一次
var userStateCache = struct {
sync.Mutex
States map[string]*State
}{States: make(map[string]*State)}
func GetState(account *model.Account) *State {
userStateCache.Lock()
defer userStateCache.Unlock()
if v, ok := userStateCache.States[account.Username]; ok && v != nil {
return v
}
state := new(State).Init()
userStateCache.States[account.Username] = state
return state
}
type State struct {
sync.Mutex
captchaToken string
captchaTokenExpiresTime int64
tokenType string
accessToken string
refreshToken string
tokenExpiresTime int64 //Milli
userID string
}
func (s *State) init() *State {
s.captchaToken = ""
s.captchaTokenExpiresTime = 0
s.tokenType = ""
s.accessToken = ""
s.refreshToken = ""
s.tokenExpiresTime = 0
s.userID = "0"
return s
}
func (s *State) getToken(account *model.Account) (string, error) {
if s.isTokensExpires() {
if err := s.refreshToken_(account); err != nil {
return "", err
}
}
return fmt.Sprint(s.tokenType, " ", s.accessToken), nil
}
func (s *State) getCaptchaToken(action string, account *model.Account) (string, error) {
if s.isCaptchaTokenExpires() {
return s.newCaptchaToken(action, nil, account)
}
return s.captchaToken, nil
}
func (s *State) isCaptchaTokenExpires() bool {
return time.Now().UnixMilli() >= s.captchaTokenExpiresTime || s.captchaToken == "" || s.tokenType == ""
}
func (s *State) isTokensExpires() bool {
return time.Now().UnixMilli() >= s.tokenExpiresTime || s.accessToken == ""
}
func (s *State) newCaptchaToken(action string, meta map[string]string, account *model.Account) (string, error) {
ctime := time.Now().UnixMilli()
driverID := utils.GetMD5Encode(account.Username)
creq := CaptchaTokenRequest{
Action: action,
CaptchaToken: s.captchaToken,
ClientID: CLIENT_ID,
DeviceID: driverID,
Meta: map[string]string{
"captcha_sign": captchaSign(driverID, ctime),
"client_version": CLIENT_VERSION,
"package_name": PACKAGE_NAME,
"timestamp": fmt.Sprint(ctime),
"user_id": s.userID,
},
}
for k, v := range meta {
creq.Meta[k] = v
}
var e Erron
var resp CaptchaTokenResponse
_, err := xunleiClient.R().
SetHeader("X-Device-Id", driverID).
SetBody(&creq).
SetError(&e).
SetResult(&resp).
Post("https://xluser-ssl.xunlei.com/v1/shield/captcha/init?client_id=" + CLIENT_ID)
if err != nil {
return "", err
}
if e.ErrorCode != 0 {
log.Debugf("%+v\n %+v", e, account)
return "", fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
if resp.Url != "" {
return "", fmt.Errorf("需要验证验证码")
}
s.captchaTokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000
s.captchaToken = resp.CaptchaToken
log.Debugf("%+v\n %+v", s.captchaToken, account)
return s.captchaToken, nil
}
func (s *State) refreshToken_(account *model.Account) error {
var e Erron
var resp TokenResponse
_, err := xunleiClient.R().
SetResult(&resp).SetError(&e).
SetBody(&base.Json{
"grant_type": "refresh_token",
"refresh_token": s.refreshToken,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
}).
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).SetQueryParam("client_id", CLIENT_ID).
Post("https://xluser-ssl.xunlei.com/v1/auth/token")
if err != nil {
return err
}
switch e.ErrorCode {
case 4122, 4121:
return s.login(account)
case 0:
s.tokenExpiresTime = (time.Now().UnixMilli() + resp.ExpiresIn*1000) - 30000
s.tokenType = resp.TokenType
s.accessToken = resp.AccessToken
s.refreshToken = resp.RefreshToken
s.userID = resp.UserID
return nil
default:
log.Debugf("%+v\n %+v", e, account)
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
}
func (s *State) login(account *model.Account) error {
s.init()
ctime := time.Now().UnixMilli()
url := "https://xluser-ssl.xunlei.com/v1/auth/signin"
captchaToken, err := s.newCaptchaToken(getAction("POST", url), map[string]string{"username": account.Username}, account)
if err != nil {
return err
}
signReq := SignInRequest{
CaptchaToken: captchaToken,
ClientID: CLIENT_ID,
ClientSecret: CLIENT_SECRET,
Username: account.Username,
Password: account.Password,
}
var e Erron
var resp TokenResponse
_, err = xunleiClient.R().
SetResult(&resp).
SetError(&e).
SetBody(&signReq).
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).
SetQueryParam("client_id", CLIENT_ID).
Post(url)
if err != nil {
return err
}
defer model.SaveAccount(account)
if e.ErrorCode != 0 {
account.Status = e.Error
log.Debugf("%+v\n %+v", e, account)
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
account.Status = "work"
s.tokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000
s.tokenType = resp.TokenType
s.accessToken = resp.AccessToken
s.refreshToken = resp.RefreshToken
s.userID = resp.UserID
log.Debugf("%+v\n %+v", resp, account)
return nil
}
func (s *State) Request(method string, url string, body interface{}, resp interface{}, account *model.Account) error {
s.Lock()
token, err := s.getToken(account)
if err != nil {
return err
}
captchaToken, err := s.getCaptchaToken(getAction(method, url), account)
if err != nil {
return err
}
s.Unlock()
var e Erron
req := xunleiClient.R().
SetError(&e).
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).
SetHeader("Authorization", token).
SetHeader("X-Captcha-Token", captchaToken).
SetQueryParam("client_id", CLIENT_ID)
if body != nil {
req.SetBody(body)
}
if resp != nil {
req.SetResult(resp)
}
switch method {
case "GET":
_, err = req.Get(url)
case "POST":
_, err = req.Post(url)
case "DELETE":
_, err = req.Delete(url)
case "PATCH":
_, err = req.Patch(url)
case "PUT":
_, err = req.Put(url)
default:
return base.ErrNotSupport
}
if err != nil {
return err
}
switch e.ErrorCode {
case 0:
return nil
case 9:
s.newCaptchaToken(getAction(method, url), nil, account)
fallthrough
case 4122, 4121:
return s.Request(method, url, body, resp, account)
default:
log.Debugf("%+v\n %+v", e, account)
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
}
func (s *State) Init() *State {
s.Lock()
defer s.Unlock()
return s.init()
}
func (s *State) GetCaptchaToken(action string, account *model.Account) (string, error) {
s.Lock()
defer s.Unlock()
return s.getCaptchaToken(action, account)
}
func (s *State) GetToken(account *model.Account) (string, error) {
s.Lock()
defer s.Unlock()
return s.getToken(account)
}
func (s *State) Login(account *model.Account) error {
s.Lock()
defer s.Unlock()
return s.login(account)
}

224
drivers/yandex/driver.go Normal file
View 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
View 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
View File

@ -0,0 +1 @@
package yandex

154
drivers/yandex/yandex.go Normal file
View 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{})
}

27
go.mod
View File

@ -8,21 +8,29 @@ require (
github.com/gin-contrib/cors v1.3.1 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-resty/resty/v2 v2.6.0 github.com/go-resty/resty/v2 v2.6.0
github.com/google/uuid v1.3.0
github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b
github.com/json-iterator/go v1.1.12 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
github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f
github.com/upyun/go-sdk/v3 v3.0.2
golang.org/x/text v0.3.7 golang.org/x/text v0.3.7
gorm.io/driver/mysql v1.2.3 gorm.io/driver/mysql v1.3.2
gorm.io/driver/postgres v1.2.3 gorm.io/driver/postgres v1.3.1
gorm.io/driver/sqlite v1.2.6 gorm.io/driver/sqlite v1.3.1
gorm.io/gorm v1.22.5 gorm.io/gorm v1.23.1
)
require (
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
) )
require ( require (
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
github.com/cenkalti/backoff/v4 v4.1.0 // indirect github.com/cenkalti/backoff/v4 v4.1.0 // indirect
@ -35,21 +43,20 @@ require (
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.1 // indirect github.com/jackc/pgconn v1.11.0 // 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.2.0 // 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.9.1 // indirect github.com/jackc/pgtype v1.10.0 // indirect
github.com/jackc/pgx/v4 v4.14.1 // indirect github.com/jackc/pgx/v4 v4.15.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect github.com/jinzhu/now v1.1.4 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // 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.10 // indirect github.com/mattn/go-sqlite3 v1.14.11 // 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
@ -63,7 +70,7 @@ 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-20220112180741-5e0467b6c7ce // indirect golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // 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
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect

70
go.sum
View File

@ -20,6 +20,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible h1:uuJIwCFhbZy+zdvLy5zrcIToPEQP0s5CFOZ0Zj03O/w=
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/allegro/bigcache/v2 v2.2.5 h1:mRc8r6GQjuJsmSKQNPsR5jQVXc8IJ1xsW5YXUYMLfqI= github.com/allegro/bigcache/v2 v2.2.5 h1:mRc8r6GQjuJsmSKQNPsR5jQVXc8IJ1xsW5YXUYMLfqI=
github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY= github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@ -32,6 +34,8 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -229,10 +233,9 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU=
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/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ=
github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0 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=
@ -248,7 +251,6 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.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 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
@ -258,29 +260,23 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
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/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-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/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/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-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/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 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/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b h1:Ur6QAxsHCK99Quj9PaWafoV4unb0DO/HWiKExD+TN5g= github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b h1:Ur6QAxsHCK99Quj9PaWafoV4unb0DO/HWiKExD+TN5g=
@ -342,11 +338,9 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.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.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ=
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 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=
@ -465,6 +459,7 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
@ -513,6 +508,8 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/upyun/go-sdk/v3 v3.0.2 h1:Ke+iOipK5CT0xzMwsgJsi7faJV7ID4lAs+wrH1RH0dA=
github.com/upyun/go-sdk/v3 v3.0.2/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
@ -555,10 +552,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 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-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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=
@ -593,6 +589,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/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=
@ -658,6 +655,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -747,26 +745,14 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M= gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM= gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
gorm.io/driver/mysql v1.2.3 h1:cZqzlOfg5Kf1VIdLC1D9hT6Cy9BgxhExLj/2tIgUe7Y= gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g=
gorm.io/driver/mysql v1.2.3/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo= gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU=
gorm.io/driver/postgres v1.1.2 h1:Amy3hCvLqM+/ICzjCnQr8wKFLVJTeOTdlMT7kCP+J1Q= gorm.io/driver/sqlite v1.3.1 h1:bwfE+zTEWklBYoEodIOIBwuWHpnx52Z9zJFW5F33WLk=
gorm.io/driver/postgres v1.1.2/go.mod h1:/AGV0zvqF3mt9ZtzLzQmXWQ/5vr+1V1TyHZGZVjzmwI= gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg=
gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To= gorm.io/gorm v1.23.1 h1:aj5IlhDzEPsoIyOPtTRVI+SyaN1u6k613sbt4pwbxG0=
gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs= gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
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.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.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/gorm v1.21.16 h1:YBIQLtP5PLfZQz59qfrq7xbrK7KWQ+JsXXCH/THlMqs=
gorm.io/gorm v1.21.16/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
gorm.io/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=

View File

@ -2,6 +2,9 @@ package model
import ( import (
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
"strings"
"sync"
"time" "time"
) )
@ -32,6 +35,7 @@ type Account struct {
InternalType string `json:"internal_type"` InternalType string `json:"internal_type"`
WebdavProxy bool `json:"webdav_proxy"` // 开启之后只会webdav走中转 WebdavProxy bool `json:"webdav_proxy"` // 开启之后只会webdav走中转
Proxy bool `json:"proxy"` // 是否中转,开启之后web和webdav都会走中转 Proxy bool `json:"proxy"` // 是否中转,开启之后web和webdav都会走中转
WebdavDirect bool `json:"webdav_direct"` // webdav 下载不跳转
//AllowProxy bool `json:"allow_proxy"` // 是否允许中转下载 //AllowProxy bool `json:"allow_proxy"` // 是否允许中转下载
DownProxyUrl string `json:"down_proxy_url"` // 用于中转下载服务的URL 两处 1. path请求中返回的链接 2. down下载时进行302 DownProxyUrl string `json:"down_proxy_url"` // 用于中转下载服务的URL 两处 1. path请求中返回的链接 2. down下载时进行302
APIProxyUrl string `json:"api_proxy_url"` // 用于中转api的地址 APIProxyUrl string `json:"api_proxy_url"` // 用于中转api的地址
@ -43,9 +47,12 @@ type Account struct {
AccessSecret string `json:"access_secret"` AccessSecret string `json:"access_secret"`
CustomHost string `json:"custom_host"` CustomHost string `json:"custom_host"`
ExtractFolder string `json:"extract_folder"` ExtractFolder string `json:"extract_folder"`
Bool1 bool `json:"bool_1"`
} }
var accountsMap = map[string]Account{} var accountsMap = make(map[string]Account)
var balance = ".balance"
// SaveAccount save account to database // SaveAccount save account to database
func SaveAccount(account *Account) error { func SaveAccount(account *Account) error {
@ -100,6 +107,46 @@ func GetAccount(name string) (Account, bool) {
return account, ok return account, ok
} }
func GetAccountsByName(name string) []Account {
accounts := make([]Account, 0)
if AccountsCount() == 1 {
account, _ := GetAccount("")
accounts = append(accounts, account)
return accounts
}
for _, v := range accountsMap {
if v.Name == name || strings.HasPrefix(v.Name, name+balance) {
accounts = append(accounts, v)
}
}
return accounts
}
var balanceMap sync.Map
func GetBalancedAccount(name string) (Account, bool) {
accounts := GetAccountsByName(name)
accountNum := len(accounts)
switch accountNum {
case 0:
return Account{}, false
case 1:
return accounts[0], true
default:
cur, ok := balanceMap.Load(name)
if ok {
i := cur.(int)
i = (i + 1) % accountNum
balanceMap.Store(name, i)
log.Debugln("use: ", i)
return accounts[i], true
} else {
balanceMap.Store(name, 0)
return accounts[0], true
}
}
}
func GetAccountById(id uint) (*Account, error) { func GetAccountById(id uint) (*Account, error) {
var account Account var account Account
account.ID = id account.ID = id
@ -116,6 +163,9 @@ func GetAccountFiles() ([]File, error) {
return nil, err return nil, err
} }
for _, v := range accounts { for _, v := range accounts {
if strings.Contains(v.Name, balance) {
continue
}
files = append(files, File{ files = append(files, File{
Name: v.Name, Name: v.Name,
Size: 0, Size: 0,

View File

@ -93,11 +93,15 @@ func LoadSettings() {
if err == nil { if err == nil {
conf.TextTypes = strings.Split(textTypes.Value, ",") conf.TextTypes = strings.Split(textTypes.Value, ",")
} }
dProxyTypes, err := GetSettingByKey("d_proxy types")
if err == nil {
conf.DProxyTypes = strings.Split(dProxyTypes.Value, ",")
}
// html // html
favicon, err := GetSettingByKey("favicon") favicon, err := GetSettingByKey("favicon")
if err == nil { if err == nil {
//conf.Favicon = favicon.Value //conf.Favicon = favicon.Value
conf.ManageHtml = strings.Replace(conf.RawIndexHtml, "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png", favicon.Value, 1) conf.ManageHtml = strings.Replace(conf.RawIndexHtml, "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", favicon.Value, 1)
} }
title, err := GetSettingByKey("title") title, err := GetSettingByKey("title")
if err == nil { if err == nil {
@ -114,7 +118,11 @@ func LoadSettings() {
// token // token
adminPassword, err := GetSettingByKey("password") adminPassword, err := GetSettingByKey("password")
if err == nil { if err == nil {
if adminPassword.Value != "" {
conf.Token = utils.GetMD5Encode(fmt.Sprintf("https://github.com/Xhofe/alist-%s", adminPassword.Value)) conf.Token = utils.GetMD5Encode(fmt.Sprintf("https://github.com/Xhofe/alist-%s", adminPassword.Value))
} else {
conf.Token = ""
}
} }
// load settings // load settings
for _, key := range conf.LoadSettings { for _, key := range conf.LoadSettings {

View File

@ -40,7 +40,7 @@ func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) {
path = "/" + strings.Join(paths[2:], "/") path = "/" + strings.Join(paths[2:], "/")
name = paths[1] name = paths[1]
} }
account, ok := model.GetAccount(name) account, ok := model.GetBalancedAccount(name)
if !ok { if !ok {
return nil, "", nil, fmt.Errorf("no [%s] account", name) return nil, "", nil, fmt.Errorf("no [%s] account", name)
} }

88
server/common/proxy.go Normal file
View File

@ -0,0 +1,88 @@
package common
import (
"errors"
"fmt"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"strconv"
)
var HttpClient = &http.Client{}
func Proxy(w http.ResponseWriter, r *http.Request, link *base.Link, file *model.File) error {
// 本机读取数据
var err error
if link.Data != nil {
//c.Data(http.StatusOK, "application/octet-stream", link.Data)
defer func() {
_ = link.Data.Close()
}()
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%s`, url.QueryEscape(file.Name)))
w.Header().Set("Content-Length", strconv.FormatInt(file.Size, 10))
w.WriteHeader(http.StatusOK)
_, err = io.Copy(w, link.Data)
if err != nil {
return err
}
return nil
}
// 本机文件直接返回文件
if link.FilePath != "" {
f, err := os.Open(link.FilePath)
if err != nil {
return err
}
defer func() {
_ = f.Close()
}()
fileStat, err := os.Stat(link.FilePath)
if err != nil {
return err
}
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%s`, url.QueryEscape(file.Name)))
http.ServeContent(w, r, file.Name, fileStat.ModTime(), f)
return nil
} else {
req, err := http.NewRequest(r.Method, link.Url, nil)
if err != nil {
return err
}
for h, val := range r.Header {
req.Header[h] = val
}
for _, header := range link.Headers {
req.Header.Set(header.Name, header.Value)
}
res, err := HttpClient.Do(req)
if err != nil {
return err
}
defer func() {
_ = res.Body.Close()
}()
log.Debugf("proxy status: %d", res.StatusCode)
for h, v := range res.Header {
w.Header()[h] = v
}
w.WriteHeader(res.StatusCode)
if res.StatusCode >= 400 {
all, _ := ioutil.ReadAll(res.Body)
msg := string(all)
log.Debugln(msg)
return errors.New(msg)
}
_, err = io.Copy(w, res.Body)
if err != nil {
return err
}
return nil
}
}

View File

@ -1,27 +1,29 @@
package controllers package controllers
import ( import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/server/common" "github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path"
) )
func Down(c *gin.Context) { func Down(c *gin.Context) {
rawPath := c.Param("path") rawPath := c.Param("path")
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
log.Debugf("down: %s", rawPath) log.Debugf("down: %s", rawPath)
account, path, driver, err := common.ParsePath(rawPath) account, path_, driver, err := common.ParsePath(rawPath)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
if driver.Config().OnlyProxy || account.Proxy { if driver.Config().OnlyProxy || account.Proxy || utils.IsContain(conf.DProxyTypes, path.Ext(rawPath)) {
Proxy(c) Proxy(c)
return return
} }
link, err := driver.Link(base.Args{Path: path, IP: c.ClientIP()}, account) link, err := driver.Link(base.Args{Path: path_, IP: c.ClientIP()}, account)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return

View File

@ -0,0 +1,30 @@
package file
import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin"
)
type RefreshReq struct {
Path string `json:"path"`
}
func RefreshFolder(c *gin.Context) {
var req RefreshReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
account, path_, _, err := common.ParsePath(req.Path)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
err = base.DeleteCache(path_, account)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
common.SuccessResp(c)
}

View File

@ -0,0 +1,80 @@
package controllers
import (
"encoding/base64"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"net/url"
"strings"
)
func Favicon(c *gin.Context) {
c.Redirect(302, conf.GetStr("favicon"))
}
func Plist(c *gin.Context) {
data := c.Param("data")
data = strings.ReplaceAll(data, "_", "/")
data = strings.ReplaceAll(data, "-", "=")
bytes, err := base64.StdEncoding.DecodeString(data)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
u := string(bytes)
uUrl, err := url.Parse(u)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
name := utils.Base(u)
u = uUrl.String()
ipaIndex := strings.Index(name, ".ipa")
decodeName := name
if ipaIndex != -1 {
name = name[:ipaIndex]
decodeName = name
tmp, err := url.PathUnescape(name)
if err == nil {
decodeName = tmp
}
}
name = strings.ReplaceAll(name, "<", "[")
name = strings.ReplaceAll(name, ">", "]")
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>%s</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>ci.nn.%s</string>
<key>bundle-version</key>
<string>4.4</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>%s</string>
</dict>
</dict>
</array>
</dict>
</plist>`, u, name, decodeName)
c.Header("Content-Type", "application/xml;charset=utf-8")
c.Status(200)
_, _ = c.Writer.WriteString(plist)
}

View File

@ -10,12 +10,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io"
"net/http"
"net/url"
"os"
"path/filepath" "path/filepath"
"strconv"
) )
func Proxy(c *gin.Context) { func Proxy(c *gin.Context) {
@ -32,7 +27,9 @@ func Proxy(c *gin.Context) {
// 2. driver只能中转 // 2. driver只能中转
// 3. 是文本类型文件 // 3. 是文本类型文件
// 4. 开启webdav中转需要验证sign // 4. 开启webdav中转需要验证sign
if !account.Proxy && !driver.Config().OnlyProxy && utils.GetFileType(filepath.Ext(rawPath)) != conf.TEXT { if !account.Proxy && !driver.Config().OnlyProxy &&
utils.GetFileType(filepath.Ext(rawPath)) != conf.TEXT &&
!utils.IsContain(conf.DProxyTypes, filepath.Ext(rawPath)) {
// 只开启了webdav中转验证sign // 只开启了webdav中转验证sign
ok := false ok := false
if account.WebdavProxy { if account.WebdavProxy {
@ -62,92 +59,17 @@ func Proxy(c *gin.Context) {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
// 本机读取数据 err = common.Proxy(c.Writer, c.Request, link, file)
if link.Data != nil {
//c.Data(http.StatusOK, "application/octet-stream", link.Data)
defer func() {
_ = link.Data.Close()
}()
c.Status(http.StatusOK)
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename=%s`, url.QueryEscape(file.Name)))
c.Header("Content-Length", strconv.FormatInt(file.Size, 10))
_, err = io.Copy(c.Writer, link.Data)
if err != nil {
_, _ = c.Writer.WriteString(err.Error())
}
return
}
// 本机文件直接返回文件
if account.Type == "Native" {
// 对于名称为index.html的文件需要特殊处理
if utils.Base(rawPath) == "index.html" {
file, err := os.Open(link.Url)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return
}
defer func() {
_ = file.Close()
}()
fileStat, err := os.Stat(link.Url)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
http.ServeContent(c.Writer, c.Request, utils.Base(rawPath), fileStat.ModTime(), file)
return
}
c.File(link.Url)
return
} else {
//if utils.GetFileType(filepath.Ext(rawPath)) == conf.TEXT {
// Text(c, link)
// return
//}
driver.Proxy(c, account)
r := c.Request
w := c.Writer
//target, err := url.Parse(link.Url)
//if err != nil {
// common.ErrorResp(c, err, 500)
// return
//}
req, err := http.NewRequest("GET", link.Url, nil)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
for h, val := range r.Header {
req.Header[h] = val
}
res, err := HttpClient.Do(req)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
defer func() {
_ = res.Body.Close()
}()
log.Debugf("proxy status: %d", res.StatusCode)
w.WriteHeader(res.StatusCode)
for h, v := range res.Header {
w.Header()[h] = v
}
_, err = io.Copy(w, res.Body)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
} }
} }
var client *resty.Client var client *resty.Client
var HttpClient = &http.Client{}
func init() { func init() {
client = resty.New() client = resty.New()
client.SetRetryCount(3) client.SetRetryCount(3).SetTimeout(base.DefaultTimeout)
} }
func Text(c *gin.Context, link *base.Link) { func Text(c *gin.Context, link *base.Link) {

View File

@ -15,6 +15,8 @@ func InitApiRouter(r *gin.Engine) {
Cors(r) Cors(r)
r.GET("/d/*path", middlewares.DownCheck, controllers.Down) r.GET("/d/*path", middlewares.DownCheck, controllers.Down)
r.GET("/p/*path", middlewares.DownCheck, controllers.Proxy) r.GET("/p/*path", middlewares.DownCheck, controllers.Proxy)
r.GET("/favicon.ico", controllers.Favicon)
r.GET("/i/:data/ipa.plist", controllers.Plist)
api := r.Group("/api") api := r.Group("/api")
public := api.Group("/public") public := api.Group("/public")
@ -32,7 +34,7 @@ func InitApiRouter(r *gin.Engine) {
admin := api.Group("/admin") admin := api.Group("/admin")
{ {
admin.Use(middlewares.Auth) admin.Use(middlewares.Auth)
admin.GET("/login", common.Login) admin.Any("/login", common.Login)
admin.GET("/settings", controllers.GetSettings) admin.GET("/settings", controllers.GetSettings)
admin.POST("/settings", controllers.SaveSettings) admin.POST("/settings", controllers.SaveSettings)
admin.DELETE("/setting", controllers.DeleteSetting) admin.DELETE("/setting", controllers.DeleteSetting)
@ -56,6 +58,7 @@ func InitApiRouter(r *gin.Engine) {
admin.POST("/move", file.Move) admin.POST("/move", file.Move)
admin.POST("/copy", file.Copy) admin.POST("/copy", file.Copy)
admin.POST("/folder", file.Folder) admin.POST("/folder", file.Folder)
admin.POST("/refresh", file.RefreshFolder)
} }
WebDav(r) WebDav(r)
Static(r) Static(r)

View File

@ -1,7 +1,6 @@
package server package server
import ( import (
"fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/public" "github.com/Xhofe/alist/public"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -15,24 +14,21 @@ import (
func InitIndex() { func InitIndex() {
var index fs.File var index fs.File
var err error var err error
//if conf.Conf.Local { if !strings.Contains(conf.Conf.Assets, "/") {
// index, err = public.Public.Open("local.html")
//} else {
// index, err = public.Public.Open("index.html")
//}
if conf.Conf.Assets == "" {
conf.Conf.Assets = conf.DefaultConfig().Assets conf.Conf.Assets = conf.DefaultConfig().Assets
} }
index, err = public.Public.Open(fmt.Sprintf("%s.html", conf.Conf.Assets))
if err != nil {
log.Error(err.Error())
index, err = public.Public.Open("index.html") index, err = public.Public.Open("index.html")
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
} }
}
data, _ := ioutil.ReadAll(index) data, _ := ioutil.ReadAll(index)
cdnUrl := strings.ReplaceAll(conf.Conf.Assets, "$version", conf.WebTag)
cdnUrl = strings.TrimRight(cdnUrl, "/")
conf.RawIndexHtml = string(data) conf.RawIndexHtml = string(data)
if strings.Contains(conf.RawIndexHtml, "CDN_URL") {
conf.RawIndexHtml = strings.ReplaceAll(conf.RawIndexHtml, "/CDN_URL", cdnUrl)
conf.RawIndexHtml = strings.ReplaceAll(conf.RawIndexHtml, "assets/", cdnUrl+"/assets/")
}
} }
func Static(r *gin.Engine) { func Static(r *gin.Engine) {
@ -48,8 +44,8 @@ func Static(r *gin.Engine) {
r.StaticFS("/assets/", http.FS(assets)) r.StaticFS("/assets/", http.FS(assets))
r.StaticFS("/public/", http.FS(pub)) r.StaticFS("/public/", http.FS(pub))
r.NoRoute(func(c *gin.Context) { r.NoRoute(func(c *gin.Context) {
c.Status(200)
c.Header("Content-Type", "text/html") c.Header("Content-Type", "text/html")
c.Status(200)
if strings.HasPrefix(c.Request.URL.Path, "/@manage") { if strings.HasPrefix(c.Request.URL.Path, "/@manage") {
_, _ = c.Writer.WriteString(conf.ManageHtml) _, _ = c.Writer.WriteString(conf.ManageHtml)
} else { } else {

View File

@ -24,8 +24,13 @@ import (
type FileSystem struct{} type FileSystem struct{}
var upFileMap = make(map[string]*model.File)
func (fs *FileSystem) File(rawPath string) (*model.File, error) { func (fs *FileSystem) File(rawPath string) (*model.File, error) {
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
if f, ok := upFileMap[rawPath]; ok {
return f, nil
}
if model.AccountsCount() > 1 && rawPath == "/" { if model.AccountsCount() > 1 && rawPath == "/" {
now := time.Now() now := time.Now()
return &model.File{ return &model.File{
@ -45,18 +50,17 @@ func (fs *FileSystem) File(rawPath string) (*model.File, error) {
func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) { func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) {
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
var files []model.File
var err error
if model.AccountsCount() > 1 && rawPath == "/" { if model.AccountsCount() > 1 && rawPath == "/" {
files, err := model.GetAccountFiles() files, err = model.GetAccountFiles()
if err != nil { } else {
return nil, err
}
return files, nil
}
account, path_, driver, err := common.ParsePath(rawPath) account, path_, driver, err := common.ParsePath(rawPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
files, err := operate.Files(driver, account, path_) files, err = operate.Files(driver, account, path_)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -91,7 +95,7 @@ func ClientIP(r *http.Request) string {
return "" return ""
} }
func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) { func (fs *FileSystem) Link(w http.ResponseWriter, r *http.Request, rawPath string) (string, error) {
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
log.Debugf("get link path: %s", rawPath) log.Debugf("get link path: %s", rawPath)
if model.AccountsCount() > 1 && rawPath == "/" { if model.AccountsCount() > 1 && rawPath == "/" {
@ -106,6 +110,19 @@ func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) {
if r.TLS != nil { if r.TLS != nil {
protocol = "https" protocol = "https"
} }
// 直接返回
if account.WebdavDirect {
file, err := fs.File(rawPath)
if err != nil {
return "", err
}
link_, err := driver.Link(base.Args{Path: path_}, account)
if err != nil {
return "", err
}
err = common.Proxy(w, r, link_, file)
return "", err
}
if driver.Config().OnlyProxy || account.WebdavProxy { if driver.Config().OnlyProxy || account.WebdavProxy {
link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath) link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath)
if conf.GetBool("check down link") { if conf.GetBool("check down link") {
@ -154,6 +171,17 @@ func (fs *FileSystem) Upload(ctx context.Context, r *http.Request, rawPath strin
// return err // return err
//} //}
filePath, fileName := filepath.Split(path_) filePath, fileName := filepath.Split(path_)
now := time.Now()
if fileSize == 0 {
upFileMap[rawPath] = &model.File{
Name: fileName,
Size: 0,
UpdatedAt: &now,
}
return nil
} else {
delete(upFileMap, rawPath)
}
fileData := model.FileStream{ fileData := model.FileStream{
MIMEType: r.Header.Get("Content-Type"), MIMEType: r.Header.Get("Content-Type"),
File: r.Body, File: r.Body,

View File

@ -237,11 +237,14 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *
} }
w.Header().Set("ETag", etag) w.Header().Set("ETag", etag)
log.Debugf("url: %+v", r.URL) log.Debugf("url: %+v", r.URL)
link, err := fs.Link(r, reqPath) link, err := fs.Link(w, r, reqPath)
if err != nil { if err != nil {
log.Debugf("webdav link error: %s", err.Error())
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
if link != "" {
http.Redirect(w, r, link, 302) http.Redirect(w, r, link, 302)
}
return 0, nil return 0, nil
} }

View File

@ -123,3 +123,15 @@ func Join(elem ...string) string {
} }
return res return res
} }
func Split(p string) (string, string) {
return path.Split(p)
}
// FormatName TODO
func FormatName(name string) string {
name = strings.ReplaceAll(name, "/", " ")
name = strings.ReplaceAll(name, "#", " ")
name = strings.ReplaceAll(name, "?", " ")
return name
}

View File

@ -2,10 +2,17 @@ package utils
import ( import (
"crypto/md5" "crypto/md5"
"crypto/sha1"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
) )
func GetSHA1Encode(data string) string {
h := sha1.New()
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
// GetMD5Encode // GetMD5Encode
func GetMD5Encode(data string) string { func GetMD5Encode(data string) string {
h := md5.New() h := md5.New()

25
utils/random.go Normal file
View File

@ -0,0 +1,25 @@
package utils
import (
"math/rand"
"strings"
"time"
)
var Rand *rand.Rand
func init() {
s := rand.NewSource(time.Now().UnixNano())
Rand = rand.New(s)
}
func RandomStr(n int) string {
builder := strings.Builder{}
t := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for i := 0; i < n; i++ {
r := Rand.Intn(len(t))
builder.WriteString(t[r : r+1])
}
return builder.String()
}