Compare commits
47 Commits
v2.0.0-bet
...
v2.0.1
Author | SHA1 | Date | |
---|---|---|---|
a295e7024a | |||
b36eaf08f0 | |||
bb6e520ab5 | |||
9b64e2e045 | |||
ee7c12c30f | |||
96d6d58910 | |||
2bf235a5ac | |||
236f9969c0 | |||
09e63027d9 | |||
a15dae291e | |||
efaaeedfb8 | |||
190c8001a5 | |||
b8698700ef | |||
985b81826f | |||
74d8fa3919 | |||
43e4928bb9 | |||
03580fd76c | |||
6e8d551420 | |||
28998d6f8c | |||
1779617cb9 | |||
7dfe48339c | |||
9c5627a382 | |||
809850321a | |||
bdc1f68746 | |||
9aaef6c3a3 | |||
bb50c52d0e | |||
6041e5a0fa | |||
308a86c36e | |||
ba7c4fc230 | |||
d81ec0637d | |||
1d7d37e642 | |||
b62a716267 | |||
944941db10 | |||
bd91acc5d0 | |||
cd50227835 | |||
50226f66e3 | |||
9dcaa9b07a | |||
fa6c0f78bc | |||
7f35dc6ade | |||
5d6463b75a | |||
733b38b435 | |||
50a02a7af7 | |||
71b1517de7 | |||
ffdd88ec66 | |||
4ff2756572 | |||
d955038ebc | |||
72d5e4e691 |
39
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
39
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: "Bug report"
|
||||
description: Bug report
|
||||
labels: [pending triage]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Alist Version / Alist 版本
|
||||
description: What version of our software are you running?
|
||||
placeholder: v2.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Describe the bug / 问题描述
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction / 复现链接
|
||||
description: |
|
||||
Please provide a link to a repo that can reproduce the problem you ran into.
|
||||
请提供能复现此问题的链接
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 日志 / Logs
|
||||
description: |
|
||||
Please copy and paste any relevant log output.
|
||||
请复制粘贴错误日志,或者截图
|
||||
render: shell
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Questions & Discussions & Feature request
|
||||
url: https://github.com/Xhofe/alist/discussions
|
||||
about: Use GitHub discussions for message-board style questions and discussions or feature request.
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -21,7 +21,7 @@ dist/
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
bin/*
|
||||
alist
|
||||
/alist
|
||||
*.json
|
||||
public/index.html
|
||||
public/assets/
|
||||
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Xhofe
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
67
README.md
Normal file → Executable file
67
README.md
Normal file → Executable file
@ -1,46 +1,61 @@
|
||||
<h2 align="center">Alist</h2>
|
||||
<p align="center">
|
||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="Release version"></a>
|
||||
<div align="center">
|
||||
<h3><a href="https://alist.nn.ci">Alist</a></h3>
|
||||
<p><em>A file list program that supports multiple storage, powered by gin and React.</em></p>
|
||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
|
||||
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
|
||||
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
|
||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square" alt="Downloads"></a>
|
||||
<a href="https://github.com/Xhofe/alist/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a>
|
||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square&color=%239F7AEA" alt="Downloads"></a>
|
||||
<a href="https://github.com/Xhofe/alist/blob/v2/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a>
|
||||
<a href="https://pay.xhofe.top">
|
||||
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 这是什么?
|
||||
English | [中文](./README_cn.md)
|
||||
|
||||
一款支持多种存储的目录文件列表程序,后端基于`gin`,前端使用`react`。
|
||||
## Features
|
||||
|
||||
### 前端项目地址
|
||||
- [x] multiple storage
|
||||
- [x] Local storage
|
||||
- [x] [aliyundrive](https://www.aliyundrive.com/)
|
||||
- [x] OneDrive / Sharepoint ([global](https://www.office.com/), [cn](https://portal.partner.microsoftonline.cn),de,us)
|
||||
- [x] [189cloud](https://cloud.189.cn)
|
||||
- [x] [GoogleDrive](https://drive.google.com/)
|
||||
- [x] [123pan](https://www.123pan.com/)
|
||||
- [x] [lanzou](https://pc.woozooo.com/)
|
||||
- [x] File preview (PDF, markdown, code, plain text, ...)
|
||||
- [x] Image preview in gallery mode
|
||||
- [x] Video and audio preview (mp4, mp3, ...)
|
||||
- [x] Office documents preview (docx, pptx, xlsx, ...)
|
||||
- [x] `README.md` preview rendering
|
||||
- [x] File permalink copy and direct file download
|
||||
- [x] Dark mode
|
||||
- [x] I18n
|
||||
- [x] Protected routes (password protection and authentication)
|
||||
- [x] WebDav (readonly)
|
||||
- [x] [Docker Deploy](https://hub.docker.com/r/xhofe/alist)
|
||||
|
||||
- https://github.com/Xhofe/alist-web
|
||||
## Discussion
|
||||
|
||||
### 演示地址
|
||||
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports only.**
|
||||
|
||||
- https://alist.nn.ci
|
||||
## Demo
|
||||
|
||||
### 预览
|
||||
Available at: <https://alist.nn.ci>.
|
||||
|
||||
<a href="https://alist.nn.ci/"><img src="https://store.heytapimage.com/cdo-portal/feedback/202111/03/695ef77854a144e928518efde38db97a.png"></a>
|
||||

|
||||
|
||||
### 支持的存储
|
||||
## Document
|
||||
|
||||
- 本地存储
|
||||
- 阿里云盘
|
||||
- Onedrive/世纪互联
|
||||
- 天翼云盘
|
||||
- GoogleDrive
|
||||
- 123pan
|
||||
- ...
|
||||
<https://alist-doc.nn.ci/en/>
|
||||
|
||||
### 如何使用
|
||||
## License
|
||||
|
||||
- https://alist-doc.nn.ci/
|
||||
The `AList` is open-source software licensed under the MIT license.
|
||||
|
||||
### License
|
||||
---
|
||||
|
||||
The `AList` is open-source software licensed under the MIT license.
|
||||
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe)
|
60
README_cn.md
Normal file
60
README_cn.md
Normal file
@ -0,0 +1,60 @@
|
||||
<div align="center">
|
||||
<h3><a href="https://alist.nn.ci">Alist</a></h3>
|
||||
<p><em>一个支持多存储的文件列表程序,使用 gin 和 React 。</em></p>
|
||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
|
||||
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
|
||||
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
|
||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square&color=%239F7AEA" alt="Downloads"></a>
|
||||
<a href="https://github.com/Xhofe/alist/blob/v2/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a>
|
||||
<a href="https://pay.xhofe.top">
|
||||
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
[English](./README.md) | 中文
|
||||
|
||||
## 支持
|
||||
|
||||
- [x] 多种存储
|
||||
- [x] 本地存储
|
||||
- [x] [阿里云盘](https://www.aliyundrive.com/)
|
||||
- [x] OneDrive / Sharepoint([国际版](https://www.office.com/), [世纪互联](https://portal.partner.microsoftonline.cn),de,us)
|
||||
- [x] [天翼云盘](https://cloud.189.cn)
|
||||
- [x] [GoogleDrive](https://drive.google.com/)
|
||||
- [x] [123云盘](https://www.123pan.com/)
|
||||
- [x] [蓝奏云](https://pc.woozooo.com/)
|
||||
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
||||
- [x] 画廊模式下的图像预览
|
||||
- [x] 视频和音频预览(mp4、mp3 等)
|
||||
- [x] Office 文档预览(docx、pptx、xlsx、...)
|
||||
- [x] `README.md` 预览渲染
|
||||
- [x] 文件永久链接复制和直接文件下载
|
||||
- [x] 黑暗模式
|
||||
- [x] 国际化
|
||||
- [x] 受保护的路由(密码保护和身份验证)
|
||||
- [x] WebDav(只读)
|
||||
- [x] [Docker 部署](https://hub.docker.com/r/xhofe/alist)
|
||||
|
||||
## 讨论
|
||||
|
||||
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) ,**issue仅针对错误报告。**
|
||||
|
||||
## 演示
|
||||
|
||||
<https://alist.nn.ci>。
|
||||
|
||||

|
||||
|
||||
## 文档
|
||||
|
||||
<https://alist-doc.nn.ci/>
|
||||
|
||||
## 许可
|
||||
|
||||
`AList` 是在 MIT 许可下许可的开源软件。
|
||||
|
||||
---
|
||||
|
||||
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe)
|
125
alist-proxy.js
Normal file
125
alist-proxy.js
Normal file
@ -0,0 +1,125 @@
|
||||
const HOST = "YOUR_HOST";
|
||||
const TOKEN = "YOUR_TOKEN";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
|
||||
"Access-Control-Max-Age": "86400",
|
||||
};
|
||||
|
||||
!function(a){"use strict";function b(a,b){var c=(65535&a)+(65535&b),d=(a>>16)+(b>>16)+(c>>16);return d<<16|65535&c}function c(a,b){return a<<b|a>>>32-b}function d(a,d,e,f,g,h){return b(c(b(b(d,a),b(f,h)),g),e)}function e(a,b,c,e,f,g,h){return d(b&c|~b&e,a,b,f,g,h)}function f(a,b,c,e,f,g,h){return d(b&e|c&~e,a,b,f,g,h)}function g(a,b,c,e,f,g,h){return d(b^c^e,a,b,f,g,h)}function h(a,b,c,e,f,g,h){return d(c^(b|~e),a,b,f,g,h)}function i(a,c){a[c>>5]|=128<<c%32,a[(c+64>>>9<<4)+14]=c;var d,i,j,k,l,m=1732584193,n=-271733879,o=-1732584194,p=271733878;for(d=0;d<a.length;d+=16)i=m,j=n,k=o,l=p,m=e(m,n,o,p,a[d],7,-680876936),p=e(p,m,n,o,a[d+1],12,-389564586),o=e(o,p,m,n,a[d+2],17,606105819),n=e(n,o,p,m,a[d+3],22,-1044525330),m=e(m,n,o,p,a[d+4],7,-176418897),p=e(p,m,n,o,a[d+5],12,1200080426),o=e(o,p,m,n,a[d+6],17,-1473231341),n=e(n,o,p,m,a[d+7],22,-45705983),m=e(m,n,o,p,a[d+8],7,1770035416),p=e(p,m,n,o,a[d+9],12,-1958414417),o=e(o,p,m,n,a[d+10],17,-42063),n=e(n,o,p,m,a[d+11],22,-1990404162),m=e(m,n,o,p,a[d+12],7,1804603682),p=e(p,m,n,o,a[d+13],12,-40341101),o=e(o,p,m,n,a[d+14],17,-1502002290),n=e(n,o,p,m,a[d+15],22,1236535329),m=f(m,n,o,p,a[d+1],5,-165796510),p=f(p,m,n,o,a[d+6],9,-1069501632),o=f(o,p,m,n,a[d+11],14,643717713),n=f(n,o,p,m,a[d],20,-373897302),m=f(m,n,o,p,a[d+5],5,-701558691),p=f(p,m,n,o,a[d+10],9,38016083),o=f(o,p,m,n,a[d+15],14,-660478335),n=f(n,o,p,m,a[d+4],20,-405537848),m=f(m,n,o,p,a[d+9],5,568446438),p=f(p,m,n,o,a[d+14],9,-1019803690),o=f(o,p,m,n,a[d+3],14,-187363961),n=f(n,o,p,m,a[d+8],20,1163531501),m=f(m,n,o,p,a[d+13],5,-1444681467),p=f(p,m,n,o,a[d+2],9,-51403784),o=f(o,p,m,n,a[d+7],14,1735328473),n=f(n,o,p,m,a[d+12],20,-1926607734),m=g(m,n,o,p,a[d+5],4,-378558),p=g(p,m,n,o,a[d+8],11,-2022574463),o=g(o,p,m,n,a[d+11],16,1839030562),n=g(n,o,p,m,a[d+14],23,-35309556),m=g(m,n,o,p,a[d+1],4,-1530992060),p=g(p,m,n,o,a[d+4],11,1272893353),o=g(o,p,m,n,a[d+7],16,-155497632),n=g(n,o,p,m,a[d+10],23,-1094730640),m=g(m,n,o,p,a[d+13],4,681279174),p=g(p,m,n,o,a[d],11,-358537222),o=g(o,p,m,n,a[d+3],16,-722521979),n=g(n,o,p,m,a[d+6],23,76029189),m=g(m,n,o,p,a[d+9],4,-640364487),p=g(p,m,n,o,a[d+12],11,-421815835),o=g(o,p,m,n,a[d+15],16,530742520),n=g(n,o,p,m,a[d+2],23,-995338651),m=h(m,n,o,p,a[d],6,-198630844),p=h(p,m,n,o,a[d+7],10,1126891415),o=h(o,p,m,n,a[d+14],15,-1416354905),n=h(n,o,p,m,a[d+5],21,-57434055),m=h(m,n,o,p,a[d+12],6,1700485571),p=h(p,m,n,o,a[d+3],10,-1894986606),o=h(o,p,m,n,a[d+10],15,-1051523),n=h(n,o,p,m,a[d+1],21,-2054922799),m=h(m,n,o,p,a[d+8],6,1873313359),p=h(p,m,n,o,a[d+15],10,-30611744),o=h(o,p,m,n,a[d+6],15,-1560198380),n=h(n,o,p,m,a[d+13],21,1309151649),m=h(m,n,o,p,a[d+4],6,-145523070),p=h(p,m,n,o,a[d+11],10,-1120210379),o=h(o,p,m,n,a[d+2],15,718787259),n=h(n,o,p,m,a[d+9],21,-343485551),m=b(m,i),n=b(n,j),o=b(o,k),p=b(p,l);return[m,n,o,p]}function j(a){var b,c="";for(b=0;b<32*a.length;b+=8)c+=String.fromCharCode(a[b>>5]>>>b%32&255);return c}function k(a){var b,c=[];for(c[(a.length>>2)-1]=void 0,b=0;b<c.length;b+=1)c[b]=0;for(b=0;b<8*a.length;b+=8)c[b>>5]|=(255&a.charCodeAt(b/8))<<b%32;return c}function l(a){return j(i(k(a),8*a.length))}function m(a,b){var c,d,e=k(a),f=[],g=[];for(f[15]=g[15]=void 0,e.length>16&&(e=i(e,8*a.length)),c=0;16>c;c+=1)f[c]=909522486^e[c],g[c]=1549556828^e[c];return d=i(f.concat(k(b)),512+8*b.length),j(i(g.concat(d),640))}function n(a){var b,c,d="0123456789abcdef",e="";for(c=0;c<a.length;c+=1)b=a.charCodeAt(c),e+=d.charAt(b>>>4&15)+d.charAt(15&b);return e}function o(a){return unescape(encodeURIComponent(a))}function p(a){return l(o(a))}function q(a){return n(p(a))}function r(a,b){return m(o(a),o(b))}function s(a,b){return n(r(a,b))}function t(a,b,c){return b?c?r(b,a):s(b,a):c?p(a):q(a)}"function"==typeof define&&define.amd?define(function(){return t}):a.md5=t}(this);
|
||||
|
||||
async function handleRequest(request) {
|
||||
const origin = request.headers.get("origin");
|
||||
const url = new URL(request.url);
|
||||
const path = url.pathname;
|
||||
const sign = url.searchParams.get("sign");
|
||||
const name = path.split("/").pop();
|
||||
const right = md5(`alist-${TOKEN}-${name}`).slice(8, 24);
|
||||
if (sign !== right){
|
||||
const resp = new Response(
|
||||
JSON.stringify({
|
||||
code: 401,
|
||||
message: `sign mismatch`,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json;charset=UTF-8",
|
||||
},
|
||||
}
|
||||
);
|
||||
resp.headers.set("Access-Control-Allow-Origin", origin);
|
||||
return resp;
|
||||
}
|
||||
|
||||
let resp = await fetch(`${HOST}/api/admin/link`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json;charset=UTF-8",
|
||||
Authorization: TOKEN,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
path: decodeURI(path),
|
||||
}),
|
||||
});
|
||||
let res = await resp.json();
|
||||
if (res.code !== 200) {
|
||||
return new Response(JSON.stringify(res));
|
||||
}
|
||||
request = new Request(res.data.url, request);
|
||||
if (res.data.headers) {
|
||||
for(const header of res.data.headers){
|
||||
request.headers.set(header.name, header.value);
|
||||
}
|
||||
}
|
||||
let response = await fetch(request);
|
||||
|
||||
// Recreate the response so we can modify the headers
|
||||
response = new Response(response.body, response);
|
||||
|
||||
// Set CORS headers
|
||||
response.headers.set("Access-Control-Allow-Origin", origin);
|
||||
|
||||
// Append to/Add Vary header so browser will cache response correctly
|
||||
response.headers.append("Vary", "Origin");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
function handleOptions(request) {
|
||||
// Make sure the necessary headers are present
|
||||
// for this to be a valid pre-flight request
|
||||
let headers = request.headers;
|
||||
if (
|
||||
headers.get("Origin") !== null &&
|
||||
headers.get("Access-Control-Request-Method") !== null
|
||||
// && headers.get("Access-Control-Request-Headers") !== null
|
||||
) {
|
||||
// Handle CORS pre-flight request.
|
||||
// If you want to check or reject the requested method + headers
|
||||
// you can do that here.
|
||||
let respHeaders = {
|
||||
...corsHeaders,
|
||||
// Allow all future content Request headers to go back to browser
|
||||
// such as Authorization (Bearer) or X-Client-Name-Version
|
||||
"Access-Control-Allow-Headers": request.headers.get(
|
||||
"Access-Control-Request-Headers"
|
||||
),
|
||||
};
|
||||
|
||||
return new Response(null, {
|
||||
headers: respHeaders,
|
||||
});
|
||||
} else {
|
||||
// Handle standard OPTIONS request.
|
||||
// If you want to allow other HTTP Methods, you can do that here.
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
Allow: "GET, HEAD, POST, OPTIONS",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener("fetch", (event) => {
|
||||
const request = event.request;
|
||||
// const url = new URL(request.url)
|
||||
if (request.method === "OPTIONS") {
|
||||
// Handle CORS preflight requests
|
||||
event.respondWith(handleOptions(request));
|
||||
} else if (
|
||||
request.method === "GET" ||
|
||||
request.method === "HEAD" ||
|
||||
request.method === "POST"
|
||||
) {
|
||||
// Handle requests to the API server
|
||||
event.respondWith(handleRequest(request));
|
||||
} else {
|
||||
event.respondWith(
|
||||
new Response(null, {
|
||||
status: 405,
|
||||
statusText: "Method Not Allowed",
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
19
alist.go
19
alist.go
@ -1,26 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/bootstrap"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
_ "github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/server"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&conf.ConfigFile, "conf", "data/config.json", "config file")
|
||||
flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
|
||||
flag.BoolVar(&conf.Version, "version", false, "print version info")
|
||||
flag.BoolVar(&conf.Password, "password", false, "print current password")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func Init() bool {
|
||||
bootstrap.InitLog()
|
||||
//bootstrap.InitLog()
|
||||
bootstrap.InitConf()
|
||||
bootstrap.InitCron()
|
||||
bootstrap.InitModel()
|
||||
@ -54,7 +46,12 @@ func main() {
|
||||
server.InitApiRouter(r)
|
||||
base := fmt.Sprintf("%s:%d", conf.Conf.Address, conf.Conf.Port)
|
||||
log.Infof("start server @ %s", base)
|
||||
err := r.Run(base)
|
||||
var err error
|
||||
if conf.Conf.Https {
|
||||
err = r.RunTLS(base, conf.Conf.CertFile, conf.Conf.KeyFile)
|
||||
} else {
|
||||
err = r.Run(base)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("failed to start: %s", err.Error())
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -15,7 +15,7 @@ func InitAccounts() {
|
||||
}
|
||||
for i, account := range accounts {
|
||||
model.RegisterAccount(account)
|
||||
driver, ok := drivers.GetDriver(account.Type)
|
||||
driver, ok := base.GetDriver(account.Type)
|
||||
if !ok {
|
||||
log.Errorf("no [%s] driver", account.Type)
|
||||
} else {
|
||||
|
@ -1,10 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
_ "github.com/Xhofe/alist/drivers/123pan"
|
||||
_ "github.com/Xhofe/alist/drivers/189cloud"
|
||||
_ "github.com/Xhofe/alist/drivers/alidrive"
|
||||
_ "github.com/Xhofe/alist/drivers/googledrive"
|
||||
_ "github.com/Xhofe/alist/drivers/native"
|
||||
_ "github.com/Xhofe/alist/drivers/onedrive"
|
||||
)
|
@ -1,11 +1,12 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// initLog init log
|
||||
// InitLog init log
|
||||
func InitLog() {
|
||||
if conf.Debug {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
@ -18,4 +19,14 @@ func InitLog() {
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
FullTimestamp: true,
|
||||
})
|
||||
log.Infof("init log...")
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&conf.ConfigFile, "conf", "data/config.json", "config file")
|
||||
flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
|
||||
flag.BoolVar(&conf.Version, "version", false, "print version info")
|
||||
flag.BoolVar(&conf.Password, "password", false, "print current password")
|
||||
flag.Parse()
|
||||
InitLog()
|
||||
}
|
@ -15,9 +15,13 @@ func InitSettings() {
|
||||
Description: "version",
|
||||
Type: "string",
|
||||
Group: model.CONST,
|
||||
Version: conf.GitTag,
|
||||
}
|
||||
|
||||
_ = model.SaveSetting(version)
|
||||
err := model.SaveSetting(version)
|
||||
if err != nil {
|
||||
log.Fatalf("failed write setting: %s", err.Error())
|
||||
}
|
||||
|
||||
settings := []model.SettingItem{
|
||||
{
|
||||
@ -36,14 +40,14 @@ func InitSettings() {
|
||||
},
|
||||
{
|
||||
Key: "logo",
|
||||
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
|
||||
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png",
|
||||
Description: "logo",
|
||||
Type: "string",
|
||||
Group: model.PUBLIC,
|
||||
},
|
||||
{
|
||||
Key: "favicon",
|
||||
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
|
||||
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png",
|
||||
Description: "favicon",
|
||||
Type: "string",
|
||||
Group: model.PUBLIC,
|
||||
@ -114,17 +118,34 @@ func InitSettings() {
|
||||
Group: model.PRIVATE,
|
||||
},
|
||||
{
|
||||
Key: "customize style",
|
||||
Value: "",
|
||||
Key: "customize head",
|
||||
Value: `<style>
|
||||
.chakra-ui-light{
|
||||
background-image: linear-gradient(120deg,#e0c3fc 0%,#8ec5fc 100%) !important;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
.main-box {
|
||||
border-radius: 15px !important;
|
||||
}
|
||||
.chakra-ui-light .main-box {
|
||||
background-color: white !important;
|
||||
}
|
||||
.chakra-ui-light .readme-box {
|
||||
background-color: white !important;
|
||||
}
|
||||
.readme-box {
|
||||
border-radius: 15px !important;
|
||||
}
|
||||
</style>`,
|
||||
Type: "text",
|
||||
Description: "customize style, don't need add <style></style>",
|
||||
Description: "Customize head, placed at the beginning of the head",
|
||||
Group: model.PRIVATE,
|
||||
},
|
||||
{
|
||||
Key: "customize script",
|
||||
Key: "customize body",
|
||||
Value: "",
|
||||
Type: "text",
|
||||
Description: "customize script, don't need add <script></script>",
|
||||
Description: "Customize script, placed at the end of the body",
|
||||
Group: model.PRIVATE,
|
||||
},
|
||||
{
|
||||
@ -156,10 +177,22 @@ func InitSettings() {
|
||||
Group: model.PRIVATE,
|
||||
},
|
||||
}
|
||||
for _, v := range settings {
|
||||
_, err := model.GetSettingByKey(v.Key)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
err = model.SaveSetting(v)
|
||||
for i, _ := range settings {
|
||||
v := settings[i]
|
||||
v.Version = conf.GitTag
|
||||
o, err := model.GetSettingByKey(v.Key)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
err = model.SaveSetting(v)
|
||||
if err != nil {
|
||||
log.Fatalf("failed write setting: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Fatal("can't get setting: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
o.Version = conf.GitTag
|
||||
err = model.SaveSetting(*o)
|
||||
if err != nil {
|
||||
log.Fatalf("failed write setting: %s", err.Error())
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ type Config struct {
|
||||
Address string `json:"address"`
|
||||
Port int `json:"port"`
|
||||
Database Database `json:"database"`
|
||||
Https bool `json:"https"`
|
||||
CertFile string `json:"cert_file"`
|
||||
KeyFile string `json:"key_file"`
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
@ -22,11 +25,7 @@ func DefaultConfig() *Config {
|
||||
Port: 5244,
|
||||
Database: Database{
|
||||
Type: "sqlite3",
|
||||
User: "",
|
||||
Password: "",
|
||||
Host: "",
|
||||
Port: 0,
|
||||
Name: "",
|
||||
TablePrefix: "x_",
|
||||
DBFile: "data/data.db",
|
||||
},
|
||||
|
10
conf/var.go
10
conf/var.go
@ -12,7 +12,7 @@ var (
|
||||
GoVersion string
|
||||
GitAuthor string
|
||||
GitCommit string
|
||||
GitTag string
|
||||
GitTag string = "dev"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -31,7 +31,7 @@ var (
|
||||
var (
|
||||
TextTypes = []string{"txt", "go", "md"}
|
||||
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
|
||||
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb"}
|
||||
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm"}
|
||||
AudioTypes = []string{"mp3", "flac", "ogg", "m4a"}
|
||||
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg"}
|
||||
)
|
||||
@ -41,11 +41,9 @@ var (
|
||||
RawIndexHtml string
|
||||
IndexHtml string
|
||||
CheckParent bool
|
||||
//CustomizeStyle string
|
||||
//CustomizeScript string
|
||||
//Favicon string
|
||||
CheckDown bool
|
||||
CheckDown bool
|
||||
|
||||
Token string
|
||||
DavUsername string
|
||||
DavPassword string
|
||||
)
|
||||
|
@ -1,13 +1,12 @@
|
||||
package _23pan
|
||||
package _23
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -54,7 +53,7 @@ func (driver Pan123) Login(account *model.Account) error {
|
||||
var resp Pan123TokenResp
|
||||
_, err := pan123Client.R().
|
||||
SetResult(&resp).
|
||||
SetBody(drivers.Json{
|
||||
SetBody(base.Json{
|
||||
"passport": account.Username,
|
||||
"password": account.Password,
|
||||
}).Post("https://www.123pan.com/api/user/sign_in")
|
||||
@ -77,7 +76,7 @@ func (driver Pan123) FormatFile(file *Pan123File) *model.File {
|
||||
Id: strconv.FormatInt(file.FileId, 10),
|
||||
Name: file.FileName,
|
||||
Size: file.Size,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: file.UpdateAt,
|
||||
}
|
||||
if file.Type == 1 {
|
||||
@ -107,7 +106,6 @@ func (driver Pan123) GetFiles(parentId string, account *model.Account) ([]Pan123
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("%+v", resp)
|
||||
if resp.Code != 0 {
|
||||
if resp.Code == 401 {
|
||||
err := driver.Login(account)
|
||||
@ -131,21 +129,21 @@ func (driver Pan123) GetFile(path string, account *model.Account) (*Pan123File,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
|
||||
parentFiles_, _ := base.GetCache(dir, account)
|
||||
parentFiles, _ := parentFiles_.([]Pan123File)
|
||||
for _, file := range parentFiles {
|
||||
if file.FileName == name {
|
||||
if file.Type != conf.FOLDER {
|
||||
return &file, err
|
||||
} else {
|
||||
return nil, drivers.NotFile
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, drivers.PathNotFound
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers.RegisterDriver(driverName, &Pan123{})
|
||||
base.RegisterDriver(&Pan123{})
|
||||
pan123Client.SetRetryCount(3)
|
||||
}
|
@ -1,60 +1,59 @@
|
||||
package _23pan
|
||||
package _23
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
url "net/url"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Pan123 struct {}
|
||||
type Pan123 struct{}
|
||||
|
||||
var driverName = "123Pan"
|
||||
func (driver Pan123) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "123Pan",
|
||||
OnlyProxy: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Pan123) Items() []drivers.Item {
|
||||
return []drivers.Item{
|
||||
{
|
||||
Name: "proxy",
|
||||
Label: "proxy",
|
||||
Type: "bool",
|
||||
Required: true,
|
||||
Description: "allow proxy",
|
||||
},
|
||||
func (driver Pan123) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "username",
|
||||
Label: "username",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Description: "account username/phone number",
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Label: "password",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Description: "account password",
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: "select",
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,fileId,updateAt,createAt",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: "select",
|
||||
Type: base.TypeSelect,
|
||||
Values: "asc,desc",
|
||||
Required: true,
|
||||
},
|
||||
@ -77,7 +76,7 @@ func (driver Pan123) File(path string, account *model.Account) (*model.File, err
|
||||
Name: account.Name,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
@ -91,13 +90,13 @@ func (driver Pan123) File(path string, account *model.Account) (*model.File, err
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
return nil, drivers.PathNotFound
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver Pan123) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var rawFiles []Pan123File
|
||||
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
rawFiles, _ = cache.([]Pan123File)
|
||||
} else {
|
||||
@ -110,7 +109,7 @@ func (driver Pan123) Files(path string, account *model.Account) ([]model.File, e
|
||||
return nil, err
|
||||
}
|
||||
if len(rawFiles) > 0 {
|
||||
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
|
||||
_ = base.SetCache(path, rawFiles, account)
|
||||
}
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
@ -120,14 +119,14 @@ func (driver Pan123) Files(path string, account *model.Account) ([]model.File, e
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Pan123) Link(path string, account *model.Account) (string, error) {
|
||||
func (driver Pan123) Link(path string, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.GetFile(utils.ParsePath(path), account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
var resp Pan123DownResp
|
||||
_, err = pan123Client.R().SetResult(&resp).SetHeader("authorization", "Bearer "+account.AccessToken).
|
||||
SetBody(drivers.Json{
|
||||
SetBody(base.Json{
|
||||
"driveId": 0,
|
||||
"etag": file.Etag,
|
||||
"fileId": file.FileId,
|
||||
@ -137,19 +136,35 @@ func (driver Pan123) Link(path string, account *model.Account) (string, error) {
|
||||
"type": file.Type,
|
||||
}).Post("https://www.123pan.com/api/file/download_info")
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if resp.Code != 0 {
|
||||
if resp.Code == 401 {
|
||||
err := driver.Login(account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
return driver.Link(path, account)
|
||||
}
|
||||
return "", fmt.Errorf(resp.Message)
|
||||
return nil, fmt.Errorf(resp.Message)
|
||||
}
|
||||
return resp.Data.DownloadUrl, nil
|
||||
u, err := url.Parse(resp.Data.DownloadUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u_ := fmt.Sprintf("https://%s%s", u.Host, u.Path)
|
||||
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Get(u_)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
link := base.Link{}
|
||||
if res.StatusCode() == 302 {
|
||||
link.Url = res.Header().Get("location")
|
||||
}else {
|
||||
link.Url = resp.Data.DownloadUrl
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Pan123) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
@ -159,8 +174,12 @@ func (driver Pan123) Path(path string, account *model.Account) (*model.File, []m
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if file.Type != conf.FOLDER {
|
||||
file.Url, _ = driver.Link(path, account)
|
||||
if !file.IsDir() {
|
||||
link, err := driver.Link(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
file.Url = link.Url
|
||||
return file, nil, nil
|
||||
}
|
||||
files, err := driver.Files(path, account)
|
||||
@ -175,7 +194,27 @@ func (driver Pan123) Proxy(c *gin.Context, account *model.Account) {
|
||||
}
|
||||
|
||||
func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, nil
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
var _ drivers.Driver = (*Pan123)(nil)
|
||||
func (driver Pan123) MakeDir(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Pan123) Move(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Pan123) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Pan123) Delete(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Pan123) Upload(file *model.FileStream, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Pan123)(nil)
|
@ -1,4 +1,4 @@
|
||||
package _89cloud
|
||||
package _89
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
@ -30,7 +30,7 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
|
||||
Id: strconv.FormatInt(file.Id, 10),
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
Driver: "189Cloud",
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: nil,
|
||||
Thumbnail: file.Icon.SmallUrl,
|
||||
Url: file.Url,
|
||||
@ -52,7 +52,7 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
|
||||
//func (c Cloud189) GetFile(path string, account *model.Account) (*Cloud189File, error) {
|
||||
// dir, name := filepath.Split(path)
|
||||
// dir = utils.ParsePath(dir)
|
||||
// _, _, err := c.Path(dir, account)
|
||||
// _, _, err := c.ParentPath(dir, account)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
@ -63,11 +63,11 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
|
||||
// if file.Size != -1 {
|
||||
// return &file, err
|
||||
// } else {
|
||||
// return nil, NotFile
|
||||
// return nil, ErrNotFile
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return nil, PathNotFound
|
||||
// return nil, ErrPathNotFound
|
||||
//}
|
||||
|
||||
type Cloud189Down struct {
|
||||
@ -313,6 +313,6 @@ func b64tohex(a string) string {
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers.RegisterDriver(driverName, &Cloud189{})
|
||||
base.RegisterDriver(&Cloud189{})
|
||||
client189Map = make(map[string]*resty.Client, 0)
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package _89cloud
|
||||
package _89
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -13,48 +13,46 @@ import (
|
||||
|
||||
type Cloud189 struct {}
|
||||
|
||||
var driverName = "189Cloud"
|
||||
func (driver Cloud189) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "189Cloud",
|
||||
OnlyProxy: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Cloud189) Items() []drivers.Item {
|
||||
return []drivers.Item{
|
||||
{
|
||||
Name: "proxy",
|
||||
Label: "proxy",
|
||||
Type: "bool",
|
||||
Required: true,
|
||||
Description: "allow proxy",
|
||||
},
|
||||
func (driver Cloud189) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "username",
|
||||
Label: "username",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Description: "account username/phone number",
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Label: "password",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
Description: "account password",
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: "select",
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,size,lastOpTime,createdDate",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "desc",
|
||||
Type: "select",
|
||||
Type: base.TypeSelect,
|
||||
Values: "true,false",
|
||||
Required: true,
|
||||
},
|
||||
@ -86,7 +84,7 @@ func (driver Cloud189) File(path string, account *model.Account) (*model.File, e
|
||||
Name: account.Name,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
@ -100,13 +98,13 @@ func (driver Cloud189) File(path string, account *model.Account) (*model.File, e
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
return nil, drivers.PathNotFound
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var rawFiles []Cloud189File
|
||||
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
rawFiles, _ = cache.([]Cloud189File)
|
||||
} else {
|
||||
@ -119,7 +117,7 @@ func (driver Cloud189) Files(path string, account *model.Account) ([]model.File,
|
||||
return nil, err
|
||||
}
|
||||
if len(rawFiles) > 0 {
|
||||
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
|
||||
_ = base.SetCache(path, rawFiles, account)
|
||||
}
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
@ -129,17 +127,17 @@ func (driver Cloud189) Files(path string, account *model.Account) ([]model.File,
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) Link(path string, account *model.Account) (string, error) {
|
||||
func (driver Cloud189) Link(path string, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(utils.ParsePath(path), account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if file.Type == conf.FOLDER {
|
||||
return "", drivers.NotFile
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
client, ok := client189Map[account.Name]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("can't find [%s] client", account.Name)
|
||||
return nil, fmt.Errorf("can't find [%s] client", account.Name)
|
||||
}
|
||||
var e Cloud189Error
|
||||
var resp Cloud189Down
|
||||
@ -150,28 +148,31 @@ func (driver Cloud189) Link(path string, account *model.Account) (string, error)
|
||||
"fileId": file.Id,
|
||||
}).Get("https://cloud.189.cn/api/open/file/getFileDownloadUrl.action")
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if e.ErrorCode != "" {
|
||||
if e.ErrorCode == "InvalidSessionKey" {
|
||||
err = driver.Login(account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
return driver.Link(path, account)
|
||||
}
|
||||
}
|
||||
if resp.ResCode != 0 {
|
||||
return "", fmt.Errorf(resp.ResMessage)
|
||||
return nil, fmt.Errorf(resp.ResMessage)
|
||||
}
|
||||
res, err := drivers.NoRedirectClient.R().Get(resp.FileDownloadUrl)
|
||||
res, err := base.NoRedirectClient.R().Get(resp.FileDownloadUrl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
link := base.Link{}
|
||||
if res.StatusCode() == 302 {
|
||||
return res.Header().Get("location"), nil
|
||||
link.Url = res.Header().Get("location")
|
||||
}else {
|
||||
link.Url = resp.FileDownloadUrl
|
||||
}
|
||||
return resp.FileDownloadUrl, nil
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
@ -181,8 +182,12 @@ func (driver Cloud189) Path(path string, account *model.Account) (*model.File, [
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if file.Type != conf.FOLDER {
|
||||
file.Url, _ = driver.Link(path, account)
|
||||
if !file.IsDir() {
|
||||
link, err := driver.Link(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
file.Url = link.Url
|
||||
return file, nil, nil
|
||||
}
|
||||
files, err := driver.Files(path, account)
|
||||
@ -197,7 +202,28 @@ func (driver Cloud189) Proxy(ctx *gin.Context, account *model.Account) {
|
||||
}
|
||||
|
||||
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, nil
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
var _ drivers.Driver = (*Cloud189)(nil)
|
||||
|
||||
func (driver Cloud189) MakeDir(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Cloud189) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Cloud189) Delete(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Cloud189)(nil)
|
@ -1,14 +1,16 @@
|
||||
package alidrive
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -46,7 +48,7 @@ func (driver AliDrive) FormatFile(file *AliFile) *model.File {
|
||||
Size: file.Size,
|
||||
UpdatedAt: file.UpdatedAt,
|
||||
Thumbnail: file.Thumbnail,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
Url: file.Url,
|
||||
}
|
||||
if file.Type == "folder" {
|
||||
@ -76,7 +78,7 @@ func (driver AliDrive) GetFiles(fileId string, account *model.Account) ([]AliFil
|
||||
SetResult(&resp).
|
||||
SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(drivers.Json{
|
||||
SetBody(base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"fields": "*",
|
||||
"image_thumbnail_process": "image/resize,w_400/format,jpeg",
|
||||
@ -117,7 +119,7 @@ func (driver AliDrive) GetFile(path string, account *model.Account) (*AliFile, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
|
||||
parentFiles_, _ := base.GetCache(dir, account)
|
||||
parentFiles, _ := parentFiles_.([]AliFile)
|
||||
for _, file := range parentFiles {
|
||||
if file.Name == name {
|
||||
@ -128,16 +130,16 @@ func (driver AliDrive) GetFile(path string, account *model.Account) (*AliFile, e
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, drivers.PathNotFound
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver AliDrive) RefreshToken(account *model.Account) error {
|
||||
url := "https://auth.aliyundrive.com/v2/account/token"
|
||||
var resp drivers.TokenResp
|
||||
var resp base.TokenResp
|
||||
var e AliRespError
|
||||
_, err := aliClient.R().
|
||||
//ForceContentType("application/json").
|
||||
SetBody(drivers.Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}).
|
||||
SetBody(base.Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}).
|
||||
SetResult(&resp).
|
||||
SetError(&e).
|
||||
Post(url)
|
||||
@ -156,11 +158,86 @@ func (driver AliDrive) RefreshToken(account *model.Account) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver AliDrive) Rename(fileId, name string, account *model.Account) error {
|
||||
var resp base.Json
|
||||
var e AliRespError
|
||||
_, err := aliClient.R().SetResult(&resp).SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"check_name_mode": "refuse",
|
||||
"drive_id": account.DriveId,
|
||||
"file_id": fileId,
|
||||
"name": name,
|
||||
}).Post("https://api.aliyundrive.com/v3/file/update")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.Rename(fileId, name, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if resp["name"] == name {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%+v", resp)
|
||||
}
|
||||
|
||||
func (driver AliDrive) Batch(srcId,dstId string, account *model.Account) error {
|
||||
var e AliRespError
|
||||
res, err := aliClient.R().SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"requests": []base.Json{
|
||||
{
|
||||
"headers": base.Json{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
"method":"POST",
|
||||
"id":srcId,
|
||||
"body":base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"file_id":srcId,
|
||||
"to_drive_id":account.DriveId,
|
||||
"to_parent_file_id":dstId,
|
||||
},
|
||||
},
|
||||
},
|
||||
"resource": "file",
|
||||
}).Post("https://api.aliyundrive.com/v3/batch")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.Batch(srcId, dstId, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if strings.Contains(res.String(), `"status":200`) {
|
||||
return nil
|
||||
}
|
||||
return errors.New(res.String())
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers.RegisterDriver(driverName, &AliDrive{})
|
||||
base.RegisterDriver(&AliDrive{})
|
||||
aliClient.
|
||||
SetRetryCount(3).
|
||||
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36").
|
||||
SetHeader("content-type", "application/json").
|
||||
SetHeader("origin", "https://aliyundrive.com")
|
||||
SetHeader("origin", "https://www.aliyundrive.com")
|
||||
}
|
||||
|
@ -1,60 +1,63 @@
|
||||
package alidrive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/robfig/cron/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type AliDrive struct{}
|
||||
|
||||
var driverName = "AliDrive"
|
||||
func (driver AliDrive) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "AliDrive",
|
||||
OnlyProxy: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver AliDrive) Items() []drivers.Item {
|
||||
return []drivers.Item{
|
||||
func (driver AliDrive) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "proxy",
|
||||
Label: "proxy",
|
||||
Type: "bool",
|
||||
Required: true,
|
||||
Description: "allow proxy",
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: "select",
|
||||
Values: "name,size,updated_at,created_at",
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,size,updated_at,created_at",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: "select",
|
||||
Values: "ASC,DESC",
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: base.TypeSelect,
|
||||
Values: "ASC,DESC",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "refresh_token",
|
||||
Label: "refresh token",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "limit",
|
||||
Label: "limit",
|
||||
Type: "number",
|
||||
Type: base.TypeNumber,
|
||||
Required: false,
|
||||
Description: ">0 and <=200",
|
||||
},
|
||||
@ -75,7 +78,7 @@ func (driver AliDrive) Save(account *model.Account, old *model.Account) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var resp drivers.Json
|
||||
var resp base.Json
|
||||
_, _ = aliClient.R().SetResult(&resp).
|
||||
SetBody("{}").
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
@ -112,7 +115,7 @@ func (driver AliDrive) File(path string, account *model.Account) (*model.File, e
|
||||
Name: account.Name,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
@ -126,13 +129,13 @@ func (driver AliDrive) File(path string, account *model.Account) (*model.File, e
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
return nil, drivers.PathNotFound
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver AliDrive) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var rawFiles []AliFile
|
||||
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
rawFiles, _ = cache.([]AliFile)
|
||||
} else {
|
||||
@ -145,7 +148,7 @@ func (driver AliDrive) Files(path string, account *model.Account) ([]model.File,
|
||||
return nil, err
|
||||
}
|
||||
if len(rawFiles) > 0 {
|
||||
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
|
||||
_ = base.SetCache(path, rawFiles, account)
|
||||
}
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
@ -155,37 +158,39 @@ func (driver AliDrive) Files(path string, account *model.Account) ([]model.File,
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver AliDrive) Link(path string, account *model.Account) (string, error) {
|
||||
func (driver AliDrive) Link(path string, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
var resp drivers.Json
|
||||
var resp base.Json
|
||||
var e AliRespError
|
||||
_, err = aliClient.R().SetResult(&resp).
|
||||
SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(drivers.Json{
|
||||
SetBody(base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"file_id": file.Id,
|
||||
"expire_sec": 14400,
|
||||
}).Post("https://api.aliyundrive.com/v2/file/get_download_url")
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.Link(path, account)
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("%s", e.Message)
|
||||
return nil, fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
return resp["url"].(string), nil
|
||||
return &base.Link{
|
||||
Url: resp["url"].(string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (driver AliDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
@ -195,8 +200,12 @@ func (driver AliDrive) Path(path string, account *model.Account) (*model.File, [
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if file.Type != conf.FOLDER {
|
||||
file.Url, _ = driver.Link(path, account)
|
||||
if !file.IsDir() {
|
||||
link, err := driver.Link(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
file.Url = link.Url
|
||||
return file, nil, nil
|
||||
}
|
||||
files, err := driver.Files(path, account)
|
||||
@ -217,10 +226,10 @@ func (driver AliDrive) Preview(path string, account *model.Account) (interface{}
|
||||
return nil, err
|
||||
}
|
||||
// office
|
||||
var resp drivers.Json
|
||||
var resp base.Json
|
||||
var e AliRespError
|
||||
var url string
|
||||
req := drivers.Json{
|
||||
req := base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"file_id": file.FileId,
|
||||
}
|
||||
@ -236,7 +245,7 @@ func (driver AliDrive) Preview(path string, account *model.Account) (interface{}
|
||||
req["category"] = "live_transcoding"
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("don't support")
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
@ -250,4 +259,221 @@ func (driver AliDrive) Preview(path string, account *model.Account) (interface{}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var _ drivers.Driver = (*AliDrive)(nil)
|
||||
func (driver AliDrive) MakeDir(path string, account *model.Account) error {
|
||||
dir, name := filepath.Split(path)
|
||||
parentFile, err := driver.File(dir, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !parentFile.IsDir() {
|
||||
return base.ErrNotFolder
|
||||
}
|
||||
var resp base.Json
|
||||
var e AliRespError
|
||||
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"check_name_mode": "refuse",
|
||||
"drive_id": account.DriveId,
|
||||
"name": name,
|
||||
"parent_file_id": parentFile.Id,
|
||||
"type": "folder",
|
||||
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.MakeDir(path, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if resp["file_name"] == name {
|
||||
_ = base.DeleteCache(dir, account)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%+v", resp)
|
||||
}
|
||||
|
||||
func (driver AliDrive) Move(src string, dst string, account *model.Account) error {
|
||||
srcDir, _ := filepath.Split(src)
|
||||
dstDir, dstName := filepath.Split(dst)
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// rename
|
||||
if srcDir == dstDir {
|
||||
err = driver.Rename(srcFile.Id, dstName, account)
|
||||
} else {
|
||||
// move
|
||||
dstDirFile, err := driver.File(dstDir, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = driver.Batch(srcFile.Id, dstDirFile.Id, account)
|
||||
}
|
||||
if err != nil {
|
||||
_ = base.DeleteCache(srcDir, account)
|
||||
_ = base.DeleteCache(dstDir, account)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver AliDrive) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver AliDrive) Delete(path string, account *model.Account) error {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var e AliRespError
|
||||
res, err := aliClient.R().SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"file_id": file.Id,
|
||||
}).Post("https://api.aliyundrive.com/v2/recyclebin/trash")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.Delete(path, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if res.StatusCode() == 204 {
|
||||
_ = base.DeleteCache(utils.Dir(path), account)
|
||||
return nil
|
||||
}
|
||||
return errors.New(res.String())
|
||||
}
|
||||
|
||||
type UploadResp struct {
|
||||
FileId string `json:"file_id"`
|
||||
UploadId string `json:"upload_id"`
|
||||
PartInfoList []struct {
|
||||
UploadUrl string `json:"upload_url"`
|
||||
} `json:"part_info_list"`
|
||||
}
|
||||
|
||||
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
|
||||
const DEFAULT uint64 = 10485760
|
||||
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||
var finish uint64 = 0
|
||||
parentFile, err := driver.File(file.ParentPath, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var resp UploadResp
|
||||
var e AliRespError
|
||||
partInfoList := make([]base.Json, 0)
|
||||
var i int64
|
||||
for i = 0; i < count; i++ {
|
||||
partInfoList = append(partInfoList, base.Json{
|
||||
"part_number": i + 1,
|
||||
})
|
||||
}
|
||||
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"check_name_mode": "auto_rename",
|
||||
// content_hash
|
||||
"content_hash_name": "none",
|
||||
"drive_id": account.DriveId,
|
||||
"name": file.GetFileName(),
|
||||
"parent_file_id": parentFile.Id,
|
||||
"part_info_list": partInfoList,
|
||||
//proof_code
|
||||
"proof_version": "v1",
|
||||
"size": file.GetSize(),
|
||||
"type": "file",
|
||||
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders") // /v2/file/create_with_proof
|
||||
//log.Debugf("%+v\n%+v", resp, e)
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
_ = model.SaveAccount(account)
|
||||
return driver.Upload(file, account)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
var byteSize uint64
|
||||
for i = 0; i < count; i++ {
|
||||
byteSize = file.GetSize() - finish
|
||||
if DEFAULT < byteSize {
|
||||
byteSize = DEFAULT
|
||||
}
|
||||
log.Debugf("%d,%d", byteSize, finish)
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(file, byteData)
|
||||
//n, err := file.Read(byteData)
|
||||
//byteData, err := io.ReadAll(file)
|
||||
//n := len(byteData)
|
||||
log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finish += uint64(n)
|
||||
|
||||
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, bytes.NewBuffer(byteData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("%+v", res)
|
||||
//res, err := base.BaseClient.R().
|
||||
// SetHeader("Content-Type","").
|
||||
// SetBody(byteData).Put(resp.PartInfoList[i].UploadUrl)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//log.Debugf("put to %s : %d,%s", resp.PartInfoList[i].UploadUrl, res.StatusCode(),res.String())
|
||||
}
|
||||
var resp2 base.Json
|
||||
_, err = aliClient.R().SetResult(&resp2).SetError(&e).
|
||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"drive_id": account.DriveId,
|
||||
"file_id": resp.FileId,
|
||||
"upload_id": resp.UploadId,
|
||||
}).Post("https://api.aliyundrive.com/v2/file/complete")
|
||||
if e.Code != "" {
|
||||
//if e.Code == "AccessTokenInvalid" {
|
||||
// err = driver.RefreshToken(account)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// } else {
|
||||
// _ = model.SaveAccount(account)
|
||||
// return driver.Upload(file, account)
|
||||
// }
|
||||
//}
|
||||
return fmt.Errorf("%s", e.Message)
|
||||
}
|
||||
if resp2["file_id"] == resp.FileId {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("%+v", resp2)
|
||||
}
|
||||
|
||||
var _ base.Driver = (*AliDrive)(nil)
|
||||
|
40
drivers/alist/alist.go
Normal file
40
drivers/alist/alist.go
Normal file
@ -0,0 +1,40 @@
|
||||
package alist
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
)
|
||||
|
||||
type BaseResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type PathResp struct {
|
||||
BaseResp
|
||||
Data []model.File `json:"data"`
|
||||
}
|
||||
|
||||
type PreviewResp struct {
|
||||
BaseResp
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func (driver *Alist) Login(account *model.Account) error {
|
||||
var resp BaseResp
|
||||
_, err := base.RestyClient.R().SetResult(&resp).
|
||||
SetHeader("Authorization", account.AccessToken).
|
||||
Get(account.SiteUrl+"/api/admin/login")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Code != 200 {
|
||||
return errors.New(resp.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Alist{})
|
||||
}
|
182
drivers/alist/driver.go
Normal file
182
drivers/alist/driver.go
Normal file
@ -0,0 +1,182 @@
|
||||
package alist
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Alist struct{}
|
||||
|
||||
func (driver Alist) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Alist",
|
||||
OnlyProxy: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Alist) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "site_url",
|
||||
Label: "alist site url",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "access_token",
|
||||
Label: "token",
|
||||
Type: base.TypeString,
|
||||
Description: "admin token",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder path",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Alist) Save(account *model.Account, old *model.Account) error {
|
||||
account.SiteUrl = strings.TrimRight(account.SiteUrl, "/")
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "/"
|
||||
}
|
||||
err := driver.Login(account)
|
||||
if err == nil {
|
||||
account.Status = "work"
|
||||
} else {
|
||||
account.Status = err.Error()
|
||||
}
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Alist) File(path string, account *model.Account) (*model.File, error) {
|
||||
now := time.Now()
|
||||
if path == "/" {
|
||||
return &model.File{
|
||||
Id: "root",
|
||||
Name: "root",
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: &now,
|
||||
}, nil
|
||||
}
|
||||
_, files, err := driver.Path(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if files == nil {
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
name := utils.Base(path)
|
||||
for _, file := range files {
|
||||
if file.Name == name {
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver Alist) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
//return nil, base.ErrNotImplement
|
||||
_, files, err := driver.Path(utils.Dir(path), account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if files == nil {
|
||||
return nil, base.ErrNotFolder
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Alist) Link(path string, account *model.Account) (*base.Link, error) {
|
||||
path = utils.ParsePath(path)
|
||||
name := utils.Base(path)
|
||||
flag := "d"
|
||||
if utils.GetFileType(filepath.Ext(path)) == conf.TEXT {
|
||||
flag = "p"
|
||||
}
|
||||
link := base.Link{}
|
||||
link.Url = fmt.Sprintf("%s/%s%s?sign=%s", account.SiteUrl, flag, path, utils.SignWithToken(name, conf.Token))
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Alist) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files := cache.([]model.File)
|
||||
return nil, files, nil
|
||||
}
|
||||
var resp PathResp
|
||||
_, err = base.RestyClient.R().SetResult(&resp).
|
||||
SetHeader("Authorization", account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"path": path,
|
||||
}).Post(account.SiteUrl + "/api/public/path")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.Code != 200 {
|
||||
return nil, nil, errors.New(resp.Message)
|
||||
}
|
||||
if resp.Message == "file" {
|
||||
return &resp.Data[0], nil, nil
|
||||
}
|
||||
if len(resp.Data) > 0 {
|
||||
_ = base.SetCache(path, resp.Data, account)
|
||||
}
|
||||
return nil, resp.Data, nil
|
||||
}
|
||||
|
||||
func (driver Alist) Proxy(c *gin.Context, account *model.Account) {}
|
||||
|
||||
func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
var resp PathResp
|
||||
_, err := base.RestyClient.R().SetResult(&resp).
|
||||
SetHeader("Authorization", account.AccessToken).
|
||||
SetBody(base.Json{
|
||||
"path": path,
|
||||
}).Post(account.SiteUrl + "/api/public/preview")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Code != 200 {
|
||||
return nil, errors.New(resp.Message)
|
||||
}
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
func (driver Alist) MakeDir(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Alist) Move(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Alist) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Alist) Delete(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Alist) Upload(file *model.FileStream, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Alist)(nil)
|
12
drivers/all.go
Normal file
12
drivers/all.go
Normal file
@ -0,0 +1,12 @@
|
||||
package drivers
|
||||
|
||||
import (
|
||||
_ "github.com/Xhofe/alist/drivers/123"
|
||||
_ "github.com/Xhofe/alist/drivers/189"
|
||||
_ "github.com/Xhofe/alist/drivers/alidrive"
|
||||
_ "github.com/Xhofe/alist/drivers/alist"
|
||||
_ "github.com/Xhofe/alist/drivers/google"
|
||||
_ "github.com/Xhofe/alist/drivers/lanzou"
|
||||
_ "github.com/Xhofe/alist/drivers/native"
|
||||
_ "github.com/Xhofe/alist/drivers/onedrive"
|
||||
)
|
25
drivers/base/cache.go
Normal file
25
drivers/base/cache.go
Normal file
@ -0,0 +1,25 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
)
|
||||
|
||||
func KeyCache(path string, account *model.Account) string {
|
||||
path = utils.ParsePath(path)
|
||||
return fmt.Sprintf("%s%s", account.Name, path)
|
||||
}
|
||||
|
||||
func SetCache(path string, obj interface{}, account *model.Account) error {
|
||||
return conf.Cache.Set(conf.Ctx, KeyCache(path, account), obj, nil)
|
||||
}
|
||||
|
||||
func GetCache(path string, account *model.Account) (interface{}, error) {
|
||||
return conf.Cache.Get(conf.Ctx, KeyCache(path, account))
|
||||
}
|
||||
|
||||
func DeleteCache(path string, account *model.Account) error {
|
||||
return conf.Cache.Delete(conf.Ctx, KeyCache(path, account))
|
||||
}
|
112
drivers/base/driver.go
Normal file
112
drivers/base/driver.go
Normal file
@ -0,0 +1,112 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type DriverConfig struct {
|
||||
Name string
|
||||
OnlyProxy bool
|
||||
NoLink bool // 必须本机返回的
|
||||
}
|
||||
|
||||
type Driver interface {
|
||||
Config() DriverConfig
|
||||
Items() []Item
|
||||
Save(account *model.Account, old *model.Account) error
|
||||
File(path string, account *model.Account) (*model.File, error)
|
||||
Files(path string, account *model.Account) ([]model.File, error)
|
||||
Link(path string, account *model.Account) (*Link, error)
|
||||
Path(path string, account *model.Account) (*model.File, []model.File, error)
|
||||
Proxy(c *gin.Context, account *model.Account)
|
||||
Preview(path string, account *model.Account) (interface{}, error)
|
||||
// TODO
|
||||
//Search(path string, keyword string, account *model.Account) ([]*model.File, error)
|
||||
MakeDir(path string, account *model.Account) error
|
||||
Move(src string, dst string, account *model.Account) error
|
||||
Copy(src string, dst string, account *model.Account) error
|
||||
Delete(path string, account *model.Account) error
|
||||
Upload(file *model.FileStream, account *model.Account) error
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type"`
|
||||
Values string `json:"values"`
|
||||
Required bool `json:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
var driversMap = map[string]Driver{}
|
||||
|
||||
func RegisterDriver(driver Driver) {
|
||||
log.Infof("register driver: [%s]", driver.Config().Name)
|
||||
driversMap[driver.Config().Name] = driver
|
||||
}
|
||||
|
||||
func GetDriver(name string) (driver Driver, ok bool) {
|
||||
driver, ok = driversMap[name]
|
||||
return
|
||||
}
|
||||
|
||||
func GetDrivers() map[string][]Item {
|
||||
res := make(map[string][]Item, 0)
|
||||
for k, v := range driversMap {
|
||||
if v.Config().OnlyProxy {
|
||||
res[k] = v.Items()
|
||||
} else {
|
||||
res[k] = append([]Item{
|
||||
//{
|
||||
// Name: "allow_proxy",
|
||||
// Label: "allow_proxy",
|
||||
// Type: TypeBool,
|
||||
// Required: true,
|
||||
// Description: "allow proxy",
|
||||
//},
|
||||
{
|
||||
Name: "proxy",
|
||||
Label: "proxy",
|
||||
Type: TypeBool,
|
||||
Required: true,
|
||||
Description: "web proxy",
|
||||
},
|
||||
{
|
||||
Name: "webdav_proxy",
|
||||
Label: "webdav proxy",
|
||||
Type: TypeBool,
|
||||
Required: true,
|
||||
Description: "Transfer the WebDAV of this account through the server",
|
||||
},
|
||||
}, v.Items()...)
|
||||
}
|
||||
// 不支持给本地文件添加中转
|
||||
if v.Config().Name != "Native" {
|
||||
res[k] = append(res[k], Item{
|
||||
Name: "proxy_url",
|
||||
Label: "proxy_url",
|
||||
Type: TypeString,
|
||||
Required: false,
|
||||
Description: "proxy url",
|
||||
})
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
var NoRedirectClient *resty.Client
|
||||
var RestyClient = resty.New()
|
||||
var HttpClient = &http.Client{}
|
||||
|
||||
func init() {
|
||||
NoRedirectClient = resty.New().SetRedirectPolicy(
|
||||
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}),
|
||||
)
|
||||
NoRedirectClient.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")
|
||||
}
|
37
drivers/base/types.go
Normal file
37
drivers/base/types.go
Normal file
@ -0,0 +1,37 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPathNotFound = errors.New("path not found")
|
||||
ErrNotFile = errors.New("not file")
|
||||
ErrNotImplement = errors.New("not implement")
|
||||
ErrNotSupport = errors.New("not support")
|
||||
ErrNotFolder = errors.New("not a folder")
|
||||
)
|
||||
|
||||
const (
|
||||
TypeString = "string"
|
||||
TypeSelect = "select"
|
||||
TypeBool = "bool"
|
||||
TypeNumber = "number"
|
||||
)
|
||||
|
||||
type Json map[string]interface{}
|
||||
|
||||
type TokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
type Header struct{
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
Url string `json:"url"`
|
||||
Headers []Header `json:"headers"`
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
package drivers
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Driver interface {
|
||||
Items() []Item
|
||||
Save(account *model.Account, old *model.Account) error
|
||||
File(path string, account *model.Account) (*model.File, error)
|
||||
Files(path string, account *model.Account) ([]model.File, error)
|
||||
Link(path string, account *model.Account) (string, error)
|
||||
Path(path string, account *model.Account) (*model.File, []model.File, error)
|
||||
Proxy(c *gin.Context, account *model.Account)
|
||||
Preview(path string, account *model.Account) (interface{}, error)
|
||||
// TODO
|
||||
//Search(path string, keyword string, account *model.Account) ([]*model.File, error)
|
||||
//MakeDir(path string, account *model.Account) error
|
||||
//Move(src string, des string, account *model.Account) error
|
||||
//Delete(path string) error
|
||||
//Upload(file *fs.File, path string, account *model.Account) error
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type"`
|
||||
Values string `json:"values"`
|
||||
Required bool `json:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type TokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
var driversMap = map[string]Driver{}
|
||||
|
||||
func RegisterDriver(name string, driver Driver) {
|
||||
log.Infof("register driver: [%s]", name)
|
||||
driversMap[name] = driver
|
||||
}
|
||||
|
||||
func GetDriver(name string) (driver Driver, ok bool) {
|
||||
driver, ok = driversMap[name]
|
||||
return
|
||||
}
|
||||
|
||||
func GetDrivers() map[string][]Item {
|
||||
res := make(map[string][]Item, 0)
|
||||
for k, v := range driversMap {
|
||||
res[k] = v.Items()
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type Json map[string]interface{}
|
||||
|
||||
var NoRedirectClient *resty.Client
|
||||
|
||||
func init() {
|
||||
NoRedirectClient = resty.New().SetRedirectPolicy(
|
||||
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}),
|
||||
)
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package drivers
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
PathNotFound = fmt.Errorf("path not found")
|
||||
NotFile = fmt.Errorf("not file")
|
||||
)
|
@ -1,9 +1,9 @@
|
||||
package googledrive
|
||||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -13,33 +13,38 @@ import (
|
||||
|
||||
type GoogleDrive struct{}
|
||||
|
||||
var driverName = "GoogleDrive"
|
||||
func (driver GoogleDrive) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "GoogleDrive",
|
||||
OnlyProxy: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Items() []drivers.Item {
|
||||
return []drivers.Item{
|
||||
func (driver GoogleDrive) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "client_id",
|
||||
Label: "client id",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "client_secret",
|
||||
Label: "client secret",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "refresh_token",
|
||||
Label: "refresh token",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: "string",
|
||||
Required: true,
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -52,6 +57,9 @@ func (driver GoogleDrive) Save(account *model.Account, old *model.Account) error
|
||||
_ = model.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "root"
|
||||
}
|
||||
account.Status = "work"
|
||||
_ = model.SaveAccount(account)
|
||||
return nil
|
||||
@ -65,7 +73,7 @@ func (driver GoogleDrive) File(path string, account *model.Account) (*model.File
|
||||
Name: account.Name,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
@ -79,13 +87,13 @@ func (driver GoogleDrive) File(path string, account *model.Account) (*model.File
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
return nil, drivers.PathNotFound
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var rawFiles []GoogleFile
|
||||
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
rawFiles, _ = cache.([]GoogleFile)
|
||||
} else {
|
||||
@ -98,7 +106,7 @@ func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.Fi
|
||||
return nil, err
|
||||
}
|
||||
if len(rawFiles) > 0 {
|
||||
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
|
||||
_ = base.SetCache(path, rawFiles, account)
|
||||
}
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
@ -108,31 +116,40 @@ func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.Fi
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Link(path string, account *model.Account) (string, error) {
|
||||
func (driver GoogleDrive) Link(path string, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if file.Type == conf.FOLDER {
|
||||
return "", drivers.NotFile
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
link := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.Id)
|
||||
url := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.Id)
|
||||
var e GoogleError
|
||||
_, _ = googleClient.R().SetError(&e).
|
||||
SetHeader("Authorization", "Bearer "+account.AccessToken).
|
||||
Get(link)
|
||||
Get(url)
|
||||
if e.Error.Code != 0 {
|
||||
if e.Error.Code == 401 {
|
||||
err = driver.RefreshToken(account)
|
||||
if err != nil {
|
||||
_ = model.SaveAccount(account)
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
return driver.Link(path, account)
|
||||
}
|
||||
return "", fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
||||
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
||||
}
|
||||
return link + "&alt=media", nil
|
||||
link := base.Link{
|
||||
Url: url + "&alt=media",
|
||||
Headers: []base.Header{
|
||||
{
|
||||
Name: "Authorization",
|
||||
Value: "Bearer " + account.AccessToken,
|
||||
},
|
||||
},
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
@ -158,5 +175,27 @@ func (driver GoogleDrive) Proxy(c *gin.Context, account *model.Account) {
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, nil
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) MakeDir(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Move(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Delete(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver GoogleDrive) Upload(file *model.FileStream, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
var _ base.Driver = (*GoogleDrive)(nil)
|
@ -1,9 +1,9 @@
|
||||
package googledrive
|
||||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
@ -21,7 +21,7 @@ type GoogleTokenError struct {
|
||||
|
||||
func (driver GoogleDrive) RefreshToken(account *model.Account) error {
|
||||
url := "https://www.googleapis.com/oauth2/v4/token"
|
||||
var resp drivers.TokenResp
|
||||
var resp base.TokenResp
|
||||
var e GoogleTokenError
|
||||
_, err := googleClient.R().SetResult(&resp).SetError(&e).
|
||||
SetFormData(map[string]string{
|
||||
@ -57,7 +57,7 @@ func (driver GoogleDrive) FormatFile(file *GoogleFile) *model.File {
|
||||
f := &model.File{
|
||||
Id: file.Id,
|
||||
Name: file.Name,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: file.ModifiedTime,
|
||||
Thumbnail: "",
|
||||
Url: "",
|
||||
@ -134,7 +134,7 @@ func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]GoogleF
|
||||
//func (driver GoogleDrive) GetFile(path string, account *model.Account) (*GoogleFile, error) {
|
||||
// dir, name := filepath.Split(path)
|
||||
// dir = utils.ParsePath(dir)
|
||||
// _, _, err := driver.Path(dir, account)
|
||||
// _, _, err := driver.ParentPath(dir, account)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
@ -145,16 +145,14 @@ func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]GoogleF
|
||||
// if !driver.IsDir(file.MimeType) {
|
||||
// return &file, err
|
||||
// } else {
|
||||
// return nil, drivers.NotFile
|
||||
// return nil, drivers.ErrNotFile
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return nil, drivers.PathNotFound
|
||||
// return nil, drivers.ErrPathNotFound
|
||||
//}
|
||||
|
||||
var _ drivers.Driver = (*GoogleDrive)(nil)
|
||||
|
||||
func init() {
|
||||
drivers.RegisterDriver(driverName, &GoogleDrive{})
|
||||
base.RegisterDriver(&GoogleDrive{})
|
||||
googleClient.SetRetryCount(3)
|
||||
}
|
190
drivers/lanzou/driver.go
Normal file
190
drivers/lanzou/driver.go
Normal file
@ -0,0 +1,190 @@
|
||||
package lanzou
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Lanzou struct{}
|
||||
|
||||
func (driver Lanzou) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Lanzou",
|
||||
OnlyProxy: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Lanzou) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "onedrive_type",
|
||||
Label: "lanzou type",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "cookie,url",
|
||||
},
|
||||
{
|
||||
Name: "access_token",
|
||||
Label: "cookie",
|
||||
Type: base.TypeString,
|
||||
Description: "about 15 days valid",
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder file_id",
|
||||
Type: base.TypeString,
|
||||
},
|
||||
{
|
||||
Name: "site_url",
|
||||
Label: "share url",
|
||||
Type: base.TypeString,
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Label: "share password",
|
||||
Type: base.TypeString,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Lanzou) Save(account *model.Account, old *model.Account) error {
|
||||
if account.OnedriveType == "cookie" {
|
||||
if account.RootFolder == "" {
|
||||
account.RootFolder = "-1"
|
||||
}
|
||||
}
|
||||
account.Status = "work"
|
||||
_ = model.SaveAccount(account)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver Lanzou) File(path string, account *model.Account) (*model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
if path == "/" {
|
||||
return &model.File{
|
||||
Id: account.RootFolder,
|
||||
Name: account.Name,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
dir, name := filepath.Split(path)
|
||||
files, err := driver.Files(dir, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
if file.Name == name {
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver Lanzou) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
var rawFiles []LanZouFile
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
rawFiles, _ = cache.([]LanZouFile)
|
||||
} else {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rawFiles) > 0 {
|
||||
_ = base.SetCache(path, rawFiles, account)
|
||||
}
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
for _, file := range rawFiles {
|
||||
files = append(files, *driver.FormatFile(&file))
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Lanzou) Link(path string, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("down file: %+v", file)
|
||||
downId := file.Id
|
||||
if account.OnedriveType == "cookie" {
|
||||
downId, err = driver.GetDownPageId(file.Id, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
url, err := driver.GetLink(downId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link := base.Link{
|
||||
Url: url,
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
log.Debugf("lanzou path: %s", path)
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !file.IsDir() {
|
||||
link, err := driver.Link(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
file.Url = link.Url
|
||||
return file, nil, nil
|
||||
}
|
||||
files, err := driver.Files(path, account)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, files, nil
|
||||
}
|
||||
|
||||
func (driver Lanzou) Proxy(c *gin.Context, account *model.Account) {
|
||||
c.Request.Header.Del("Origin")
|
||||
}
|
||||
|
||||
func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver *Lanzou) MakeDir(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver *Lanzou) Move(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver *Lanzou) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver *Lanzou) Delete(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver *Lanzou) Upload(file *model.FileStream, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Lanzou)(nil)
|
239
drivers/lanzou/lanzou.go
Normal file
239
drivers/lanzou/lanzou.go
Normal file
@ -0,0 +1,239 @@
|
||||
package lanzou
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var lanzouClient = resty.New()
|
||||
|
||||
type LanZouFile struct {
|
||||
Name string `json:"name"`
|
||||
NameAll string `json:"name_all"`
|
||||
Id string `json:"id"`
|
||||
FolId string `json:"fol_id"`
|
||||
Size string `json:"size"`
|
||||
Time string `json:"time"`
|
||||
Folder bool
|
||||
}
|
||||
|
||||
func (driver *Lanzou) FormatFile(file *LanZouFile) *model.File {
|
||||
now := time.Now()
|
||||
f := &model.File{
|
||||
Id: file.Id,
|
||||
Name: file.Name,
|
||||
Driver: driver.Config().Name,
|
||||
SizeStr: file.Size,
|
||||
TimeStr: file.Time,
|
||||
UpdatedAt: &now,
|
||||
}
|
||||
if file.Folder {
|
||||
f.Type = conf.FOLDER
|
||||
f.Id = file.FolId
|
||||
} else {
|
||||
f.Name = file.NameAll
|
||||
f.Type = utils.GetFileType(filepath.Ext(file.NameAll))
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
type LanZouFilesResp struct {
|
||||
Zt int `json:"zt"`
|
||||
Info interface{} `json:"info"`
|
||||
Text []LanZouFile `json:"text"`
|
||||
}
|
||||
|
||||
func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZouFile, error) {
|
||||
if account.OnedriveType == "cookie" {
|
||||
files := make([]LanZouFile, 0)
|
||||
var resp LanZouFilesResp
|
||||
// folders
|
||||
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
SetFormData(map[string]string{
|
||||
"task": "47",
|
||||
"folder_id": folderId,
|
||||
}).Post("https://pc.woozooo.com/doupload.php")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if resp.Zt != 1 && resp.Zt != 2 {
|
||||
return nil, fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
for _, file := range resp.Text {
|
||||
file.Folder = true
|
||||
files = append(files, file)
|
||||
}
|
||||
// files
|
||||
pg := 1
|
||||
for {
|
||||
_, err = lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
SetFormData(map[string]string{
|
||||
"task": "5",
|
||||
"folder_id": folderId,
|
||||
"pg": strconv.Itoa(pg),
|
||||
}).Post("https://pc.woozooo.com/doupload.php")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Zt != 1 {
|
||||
return nil, fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
if len(resp.Text) == 0 {
|
||||
break
|
||||
}
|
||||
files = append(files, resp.Text...)
|
||||
pg++
|
||||
}
|
||||
return files, nil
|
||||
} else {
|
||||
return driver.GetFilesByUrl(account)
|
||||
}
|
||||
}
|
||||
|
||||
func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error) {
|
||||
files := make([]LanZouFile, 0)
|
||||
shareUrl := account.SiteUrl
|
||||
res, err := lanzouClient.R().Get(shareUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lxArr := regexp.MustCompile(`'lx':(.+?),`).FindStringSubmatch(res.String())
|
||||
if len(lxArr) == 0 {
|
||||
return nil, fmt.Errorf("get empty page")
|
||||
}
|
||||
lx := lxArr[1]
|
||||
fid := regexp.MustCompile(`'fid':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
uid := regexp.MustCompile(`'uid':'(.+?)',`).FindStringSubmatch(res.String())[1]
|
||||
rep := regexp.MustCompile(`'rep':'(.+?)',`).FindStringSubmatch(res.String())[1]
|
||||
up := regexp.MustCompile(`'up':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
ls := regexp.MustCompile(`'ls':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
tName := regexp.MustCompile(`'t':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
kName := regexp.MustCompile(`'k':(.+?),`).FindStringSubmatch(res.String())[1]
|
||||
t := regexp.MustCompile(`var ` + tName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
||||
k := regexp.MustCompile(`var ` + kName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
||||
pg := 1
|
||||
for {
|
||||
var resp LanZouFilesResp
|
||||
res, err = lanzouClient.R().SetResult(&resp).SetFormData(map[string]string{
|
||||
"lx": lx,
|
||||
"fid": fid,
|
||||
"uid": uid,
|
||||
"pg": strconv.Itoa(pg),
|
||||
"rep": rep,
|
||||
"t": t,
|
||||
"k": k,
|
||||
"up": up,
|
||||
"ls": ls,
|
||||
"pwd": account.Password,
|
||||
}).Post("https://wwa.lanzouo.com/filemoreajax.php")
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
break
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if resp.Zt != 1 {
|
||||
return nil, fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
if len(resp.Text) == 0 {
|
||||
break
|
||||
}
|
||||
pg++
|
||||
files = append(files, resp.Text...)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
//type LanzouDownInfo struct {
|
||||
// FId string `json:"f_id"`
|
||||
// IsNewd string `json:"is_newd"`
|
||||
//}
|
||||
|
||||
// 获取下载页面的ID
|
||||
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, error) {
|
||||
var resp LanZouFilesResp
|
||||
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||
SetFormData(map[string]string{
|
||||
"task": "22",
|
||||
"file_id": fileId,
|
||||
}).Post("https://pc.woozooo.com/doupload.php")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Debug(res.String())
|
||||
if resp.Zt != 1 {
|
||||
return "", fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
info, ok := resp.Info.(map[string]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%v", resp.Info)
|
||||
}
|
||||
fid, ok := info["f_id"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%v", info["f_id"])
|
||||
}
|
||||
return fid, nil
|
||||
}
|
||||
|
||||
type LanzouLinkResp struct {
|
||||
Dom string `json:"dom"`
|
||||
Url string `json:"url"`
|
||||
Zt int `json:"zt"`
|
||||
}
|
||||
|
||||
func (driver *Lanzou) GetLink(downId string) (string, error) {
|
||||
res, err := lanzouClient.R().Get("https://wwa.lanzouo.com/" + downId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
iframe := regexp.MustCompile(`<iframe class="ifr2" name=".{2,20}" src="(.+?)"`).FindStringSubmatch(res.String())
|
||||
if len(iframe) == 0 {
|
||||
return "", fmt.Errorf("get down empty page")
|
||||
}
|
||||
iframeUrl := "https://wwa.lanzouo.com" + iframe[1]
|
||||
res, err = lanzouClient.R().Get(iframeUrl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ajaxdata := regexp.MustCompile(`var ajaxdata = '(.+?)'`).FindStringSubmatch(res.String())
|
||||
if len(ajaxdata) == 0 {
|
||||
return "", fmt.Errorf("get iframe empty page")
|
||||
}
|
||||
signs := ajaxdata[1]
|
||||
sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
||||
websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
||||
var resp LanzouLinkResp
|
||||
form := map[string]string{
|
||||
"action": "downprocess",
|
||||
"signs": signs,
|
||||
"sign": sign,
|
||||
"ves": "1",
|
||||
"websign": "",
|
||||
"websignkey": websignkey,
|
||||
}
|
||||
log.Debugf("form: %+v", form)
|
||||
_, err = lanzouClient.R().SetResult(&resp).
|
||||
SetHeader("origin", "https://wwa.lanzouo.com").
|
||||
SetHeader("referer", iframeUrl).
|
||||
SetFormData(form).Post("https://wwa.lanzouo.com/ajaxm.php")
|
||||
if resp.Zt == 1 {
|
||||
return resp.Dom + "/file/" + resp.Url, nil
|
||||
}
|
||||
return "", fmt.Errorf("can't get link")
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Lanzou{})
|
||||
lanzouClient.
|
||||
SetRetryCount(3).
|
||||
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
|
||||
}
|
@ -3,11 +3,12 @@ package native
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -16,27 +17,33 @@ import (
|
||||
|
||||
type Native struct{}
|
||||
|
||||
var driverName = "Native"
|
||||
func (driver Native) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Native",
|
||||
OnlyProxy: true,
|
||||
NoLink: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Native) Items() []drivers.Item {
|
||||
return []drivers.Item{
|
||||
func (driver Native) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder path",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: "select",
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,size,updated_at",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: "select",
|
||||
Type: base.TypeSelect,
|
||||
Values: "ASC,DESC",
|
||||
Required: false,
|
||||
},
|
||||
@ -62,7 +69,7 @@ func (driver Native) Save(account *model.Account, old *model.Account) error {
|
||||
func (driver Native) File(path string, account *model.Account) (*model.File, error) {
|
||||
fullPath := filepath.Join(account.RootFolder, path)
|
||||
if !utils.Exists(fullPath) {
|
||||
return nil, drivers.PathNotFound
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
f, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
@ -73,7 +80,7 @@ func (driver Native) File(path string, account *model.Account) (*model.File, err
|
||||
Name: f.Name(),
|
||||
Size: f.Size(),
|
||||
UpdatedAt: &time,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
}
|
||||
if f.IsDir() {
|
||||
file.Type = conf.FOLDER
|
||||
@ -86,7 +93,7 @@ func (driver Native) File(path string, account *model.Account) (*model.File, err
|
||||
func (driver Native) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
fullPath := filepath.Join(account.RootFolder, path)
|
||||
if !utils.Exists(fullPath) {
|
||||
return nil, drivers.PathNotFound
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
files := make([]model.File, 0)
|
||||
rawFiles, err := ioutil.ReadDir(fullPath)
|
||||
@ -103,7 +110,7 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
|
||||
Size: f.Size(),
|
||||
Type: 0,
|
||||
UpdatedAt: &time,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
}
|
||||
if f.IsDir() {
|
||||
file.Type = conf.FOLDER
|
||||
@ -116,16 +123,19 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Native) Link(path string, account *model.Account) (string, error) {
|
||||
func (driver Native) Link(path string, account *model.Account) (*base.Link, error) {
|
||||
fullPath := filepath.Join(account.RootFolder, path)
|
||||
s, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if s.IsDir() {
|
||||
return "", fmt.Errorf("can't down folder")
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
return fullPath, nil
|
||||
link := base.Link{
|
||||
Url: fullPath,
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Native) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
@ -134,7 +144,7 @@ func (driver Native) Path(path string, account *model.Account) (*model.File, []m
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if file.Type != conf.FOLDER {
|
||||
if !file.IsDir() {
|
||||
//file.Url, _ = driver.Link(path, account)
|
||||
return file, nil, nil
|
||||
}
|
||||
@ -151,7 +161,74 @@ func (driver Native) Proxy(c *gin.Context, account *model.Account) {
|
||||
}
|
||||
|
||||
func (driver Native) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, fmt.Errorf("no need")
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
var _ drivers.Driver = (*Native)(nil)
|
||||
func (driver Native) MakeDir(path string, account *model.Account) error {
|
||||
fullPath := filepath.Join(account.RootFolder, path)
|
||||
err := os.MkdirAll(fullPath, 0700)
|
||||
return err
|
||||
}
|
||||
|
||||
func (driver Native) Move(src string, dst string, account *model.Account) error {
|
||||
fullSrc := filepath.Join(account.RootFolder, src)
|
||||
fullDst := filepath.Join(account.RootFolder, dst)
|
||||
return os.Rename(fullSrc, fullDst)
|
||||
}
|
||||
|
||||
func (driver Native) Copy(src string, dst string, account *model.Account) error {
|
||||
fullSrc := filepath.Join(account.RootFolder, src)
|
||||
fullDst := filepath.Join(account.RootFolder, dst)
|
||||
srcFile, err := driver.File(src, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstFile, err := driver.File(dst, account)
|
||||
if err == nil {
|
||||
if !dstFile.IsDir() {
|
||||
return base.ErrNotSupport
|
||||
}
|
||||
}
|
||||
if srcFile.IsDir() {
|
||||
return driver.CopyDir(fullSrc, fullDst)
|
||||
}
|
||||
return driver.CopyFile(fullSrc, fullDst)
|
||||
}
|
||||
|
||||
func (driver Native) Delete(path string, account *model.Account) error {
|
||||
fullPath := filepath.Join(account.RootFolder, path)
|
||||
file, err := driver.File(path, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if file.IsDir() {
|
||||
return os.RemoveAll(fullPath)
|
||||
}
|
||||
return os.Remove(fullPath)
|
||||
}
|
||||
|
||||
func (driver Native) Upload(file *model.FileStream, account *model.Account) error {
|
||||
fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name)
|
||||
_, err := driver.File(filepath.Join(file.ParentPath, file.Name), account)
|
||||
if err == nil {
|
||||
// TODO overwrite?
|
||||
}
|
||||
basePath := filepath.Dir(fullPath)
|
||||
if !utils.Exists(basePath) {
|
||||
err := os.MkdirAll(basePath, 0744)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
out, err := os.Create(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = out.Close()
|
||||
}()
|
||||
_, err = io.Copy(out, file)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Native)(nil)
|
||||
|
@ -1,9 +1,74 @@
|
||||
package native
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.RegisterDriver(driverName, &Native{})
|
||||
// File copies a single file from src to dst
|
||||
func (driver *Native) CopyFile(src, dst string) error {
|
||||
var err error
|
||||
var srcfd *os.File
|
||||
var dstfd *os.File
|
||||
var srcinfo os.FileInfo
|
||||
|
||||
if srcfd, err = os.Open(src); err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcfd.Close()
|
||||
|
||||
if dstfd, err = os.Create(dst); err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstfd.Close()
|
||||
|
||||
if _, err = io.Copy(dstfd, srcfd); err != nil {
|
||||
return err
|
||||
}
|
||||
if srcinfo, err = os.Stat(src); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chmod(dst, srcinfo.Mode())
|
||||
}
|
||||
|
||||
// Dir copies a whole directory recursively
|
||||
func (driver *Native) CopyDir(src string, dst string) error {
|
||||
var err error
|
||||
var fds []os.FileInfo
|
||||
var srcinfo os.FileInfo
|
||||
|
||||
if srcinfo, err = os.Stat(src); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fds, err = ioutil.ReadDir(src); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fd := range fds {
|
||||
srcfp := path.Join(src, fd.Name())
|
||||
dstfp := path.Join(dst, fd.Name())
|
||||
|
||||
if fd.IsDir() {
|
||||
if err = driver.CopyDir(srcfp, dstfp); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
} else {
|
||||
if err = driver.CopyFile(srcfp, dstfp); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.RegisterDriver(&Native{})
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package onedrive
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -14,21 +14,20 @@ import (
|
||||
|
||||
type Onedrive struct{}
|
||||
|
||||
var driverName = "Onedrive"
|
||||
|
||||
func (driver Onedrive) Items() []drivers.Item {
|
||||
return []drivers.Item{
|
||||
{
|
||||
Name: "proxy",
|
||||
Label: "proxy",
|
||||
Type: "bool",
|
||||
Required: true,
|
||||
Description: "allow proxy",
|
||||
},
|
||||
func (driver Onedrive) Config() base.DriverConfig {
|
||||
return base.DriverConfig{
|
||||
Name: "Onedrive",
|
||||
OnlyProxy: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (driver Onedrive) Items() []base.Item {
|
||||
return []base.Item{
|
||||
{
|
||||
Name: "zone",
|
||||
Label: "zone",
|
||||
Type: "select",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "global,cn,us,de",
|
||||
Description: "",
|
||||
@ -36,57 +35,57 @@ func (driver Onedrive) Items() []drivers.Item {
|
||||
{
|
||||
Name: "onedrive_type",
|
||||
Label: "onedrive type",
|
||||
Type: "select",
|
||||
Type: base.TypeSelect,
|
||||
Required: true,
|
||||
Values: "onedrive,sharepoint",
|
||||
},
|
||||
{
|
||||
Name: "client_id",
|
||||
Label: "client id",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "client_secret",
|
||||
Label: "client secret",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "redirect_uri",
|
||||
Label: "redirect uri",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "refresh_token",
|
||||
Label: "refresh token",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Name: "site_id",
|
||||
Label: "site id",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "root_folder",
|
||||
Label: "root folder path",
|
||||
Type: "string",
|
||||
Type: base.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_by",
|
||||
Label: "order_by",
|
||||
Type: "select",
|
||||
Type: base.TypeSelect,
|
||||
Values: "name,size,lastModifiedDateTime",
|
||||
Required: false,
|
||||
},
|
||||
{
|
||||
Name: "order_direction",
|
||||
Label: "order_direction",
|
||||
Type: "select",
|
||||
Type: base.TypeSelect,
|
||||
Values: "asc,desc",
|
||||
Required: false,
|
||||
},
|
||||
@ -136,7 +135,7 @@ func (driver Onedrive) File(path string, account *model.Account) (*model.File, e
|
||||
Name: account.Name,
|
||||
Size: 0,
|
||||
Type: conf.FOLDER,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
@ -150,12 +149,12 @@ func (driver Onedrive) File(path string, account *model.Account) (*model.File, e
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
return nil, drivers.PathNotFound
|
||||
return nil, base.ErrPathNotFound
|
||||
}
|
||||
|
||||
func (driver Onedrive) Files(path string, account *model.Account) ([]model.File, error) {
|
||||
path = utils.ParsePath(path)
|
||||
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
|
||||
cache, err := base.GetCache(path, account)
|
||||
if err == nil {
|
||||
files, _ := cache.([]model.File)
|
||||
return files, nil
|
||||
@ -168,19 +167,24 @@ func (driver Onedrive) Files(path string, account *model.Account) ([]model.File,
|
||||
for _, file := range rawFiles {
|
||||
files = append(files, *driver.FormatFile(&file))
|
||||
}
|
||||
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), files, nil)
|
||||
if len(files) > 0 {
|
||||
_ = base.SetCache(path, files, account)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (driver Onedrive) Link(path string, account *model.Account) (string, error) {
|
||||
func (driver Onedrive) Link(path string, account *model.Account) (*base.Link, error) {
|
||||
file, err := driver.GetFile(account, path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if file.File.MimeType == "" {
|
||||
return "", fmt.Errorf("can't down folder")
|
||||
return nil, base.ErrNotFile
|
||||
}
|
||||
return file.Url, nil
|
||||
link := base.Link{
|
||||
Url: file.Url,
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (driver Onedrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||
@ -189,7 +193,7 @@ func (driver Onedrive) Path(path string, account *model.Account) (*model.File, [
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if file.Type != conf.FOLDER {
|
||||
if !file.IsDir() {
|
||||
//file.Url, _ = driver.Link(path, account)
|
||||
return file, nil, nil
|
||||
}
|
||||
@ -205,5 +209,27 @@ func (driver Onedrive) Proxy(c *gin.Context, account *model.Account) {
|
||||
}
|
||||
|
||||
func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
|
||||
return nil, nil
|
||||
return nil, base.ErrNotSupport
|
||||
}
|
||||
|
||||
func (driver Onedrive) MakeDir(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Onedrive) Move(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Onedrive) Copy(src string, dst string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Onedrive) Delete(path string, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
func (driver Onedrive) Upload(file *model.FileStream, account *model.Account) error {
|
||||
return base.ErrNotImplement
|
||||
}
|
||||
|
||||
var _ base.Driver = (*Onedrive)(nil)
|
@ -3,7 +3,7 @@ package onedrive
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
@ -56,7 +56,7 @@ func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string
|
||||
}
|
||||
case "sharepoint":
|
||||
{
|
||||
if path == "/" {
|
||||
if path == "/" || path == "\\" {
|
||||
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root", host.Api, account.SiteId)
|
||||
} else {
|
||||
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root:%s:", host.Api, account.SiteId, path)
|
||||
@ -74,7 +74,7 @@ type OneTokenErr struct {
|
||||
|
||||
func (driver Onedrive) RefreshToken(account *model.Account) error {
|
||||
url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
|
||||
var resp drivers.TokenResp
|
||||
var resp base.TokenResp
|
||||
var e OneTokenErr
|
||||
_, err := oneClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
|
||||
"grant_type": "refresh_token",
|
||||
@ -105,6 +105,11 @@ type OneFile struct {
|
||||
File struct {
|
||||
MimeType string `json:"mimeType"`
|
||||
} `json:"file"`
|
||||
Thumbnails []struct{
|
||||
Medium struct{
|
||||
Url string `json:"url"`
|
||||
} `json:"medium"`
|
||||
} `json:"thumbnails"`
|
||||
}
|
||||
|
||||
type OneFiles struct {
|
||||
@ -124,9 +129,12 @@ func (driver Onedrive) FormatFile(file *OneFile) *model.File {
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
UpdatedAt: file.LastModifiedDateTime,
|
||||
Driver: driverName,
|
||||
Driver: driver.Config().Name,
|
||||
Url: file.Url,
|
||||
}
|
||||
if len(file.Thumbnails) > 0 {
|
||||
f.Thumbnail = file.Thumbnails[0].Medium.Url
|
||||
}
|
||||
if file.File.MimeType == "" {
|
||||
f.Type = conf.FOLDER
|
||||
} else {
|
||||
@ -137,11 +145,11 @@ func (driver Onedrive) FormatFile(file *OneFile) *model.File {
|
||||
|
||||
func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile, error) {
|
||||
var res []OneFile
|
||||
nextLink := driver.GetMetaUrl(account, false, path) + "/children"
|
||||
nextLink := driver.GetMetaUrl(account, false, path) + "/children?$expand=thumbnails"
|
||||
if account.OrderBy != "" {
|
||||
nextLink += fmt.Sprintf("?orderby=%s", account.OrderBy)
|
||||
nextLink += fmt.Sprintf("&orderby=%s", account.OrderBy)
|
||||
if account.OrderDirection != "" {
|
||||
nextLink += fmt.Sprintf(" %s", account.OrderDirection)
|
||||
nextLink += fmt.Sprintf("%%20%s", account.OrderDirection)
|
||||
}
|
||||
}
|
||||
for nextLink != "" {
|
||||
@ -177,9 +185,7 @@ func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, e
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
var _ drivers.Driver = (*Onedrive)(nil)
|
||||
|
||||
func init() {
|
||||
drivers.RegisterDriver(driverName, &Onedrive{})
|
||||
base.RegisterDriver(&Onedrive{})
|
||||
oneClient.SetRetryCount(3)
|
||||
}
|
@ -7,22 +7,21 @@ import (
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
Name string `json:"name" gorm:"unique" binding:"required"`
|
||||
Index int `json:"index"`
|
||||
Type string `json:"type"`
|
||||
ID uint `json:"id" gorm:"primaryKey"` // 唯一ID
|
||||
Name string `json:"name" gorm:"unique" binding:"required"` // 唯一名称
|
||||
Index int `json:"index"` // 序号 用于排序
|
||||
Type string `json:"type"` // 类型,即driver
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RootFolder string `json:"root_folder"`
|
||||
Status string `json:"status"`
|
||||
Status string `json:"status"` // 状态
|
||||
CronId int
|
||||
DriveId string
|
||||
Limit int `json:"limit"`
|
||||
OrderBy string `json:"order_by"`
|
||||
OrderDirection string `json:"order_direction"`
|
||||
Proxy bool `json:"proxy"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
Search bool `json:"search"`
|
||||
ClientId string `json:"client_id"`
|
||||
@ -32,6 +31,10 @@ type Account struct {
|
||||
SiteUrl string `json:"site_url"`
|
||||
SiteId string `json:"site_id"`
|
||||
OnedriveType string `json:"onedrive_type"`
|
||||
WebdavProxy bool `json:"webdav_proxy"`
|
||||
Proxy bool `json:"proxy"` // 是否中转
|
||||
//AllowProxy bool `json:"allow_proxy"` // 是否允许中转下载
|
||||
ProxyUrl string `json:"proxy_url"` // 用于中转下载服务的URL
|
||||
}
|
||||
|
||||
var accountsMap = map[string]Account{}
|
||||
|
@ -16,6 +16,8 @@ type File struct {
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Url string `json:"url"`
|
||||
SizeStr string `json:"size_str"`
|
||||
TimeStr string `json:"time_str"`
|
||||
}
|
||||
|
||||
func SortFiles(files []File, account *Account) {
|
||||
@ -63,4 +65,4 @@ func (f File) ModTime() time.Time {
|
||||
|
||||
func (f File) IsDir() bool {
|
||||
return f.Type == conf.FOLDER
|
||||
}
|
||||
}
|
||||
|
35
model/file_stream.go
Normal file
35
model/file_stream.go
Normal file
@ -0,0 +1,35 @@
|
||||
package model
|
||||
|
||||
import "io"
|
||||
|
||||
type FileStream struct {
|
||||
File io.ReadCloser
|
||||
Size uint64
|
||||
ParentPath string
|
||||
Name string
|
||||
MIMEType string
|
||||
}
|
||||
|
||||
func (file FileStream) Read(p []byte) (n int, err error) {
|
||||
return file.File.Read(p)
|
||||
}
|
||||
|
||||
func (file FileStream) GetMIMEType() string {
|
||||
return file.MIMEType
|
||||
}
|
||||
|
||||
func (file FileStream) GetSize() uint64 {
|
||||
return file.Size
|
||||
}
|
||||
|
||||
func (file FileStream) Close() error {
|
||||
return file.File.Close()
|
||||
}
|
||||
|
||||
func (file FileStream) GetFileName() string {
|
||||
return file.Name
|
||||
}
|
||||
|
||||
func (file FileStream) GetParentPath() string {
|
||||
return file.ParentPath
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -18,6 +20,7 @@ type SettingItem struct {
|
||||
Type string `json:"type"`
|
||||
Group int `json:"group"`
|
||||
Values string `json:"values"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func SaveSettings(items []SettingItem) error {
|
||||
@ -44,6 +47,13 @@ func GetSettings() (*[]SettingItem, error) {
|
||||
return &items, nil
|
||||
}
|
||||
|
||||
func DeleteSetting(key string) error {
|
||||
setting := SettingItem{
|
||||
Key: key,
|
||||
}
|
||||
return conf.DB.Delete(&setting).Error
|
||||
}
|
||||
|
||||
func GetSettingByKey(key string) (*SettingItem, error) {
|
||||
var items SettingItem
|
||||
if err := conf.DB.Where("`key` = ?", key).First(&items).Error; err != nil {
|
||||
@ -61,7 +71,7 @@ func LoadSettings() {
|
||||
if err == nil {
|
||||
conf.CheckParent = checkParent.Value == "true"
|
||||
}
|
||||
checkDown,err := GetSettingByKey("check down link")
|
||||
checkDown, err := GetSettingByKey("check down link")
|
||||
if err == nil {
|
||||
conf.CheckDown = checkDown.Value == "true"
|
||||
}
|
||||
@ -72,18 +82,20 @@ func LoadSettings() {
|
||||
}
|
||||
title, err := GetSettingByKey("title")
|
||||
if err == nil {
|
||||
//conf.CustomizeStyle = customizeStyle.Value
|
||||
conf.IndexHtml = strings.Replace(conf.IndexHtml, "Loading...", title.Value, 1)
|
||||
}
|
||||
customizeStyle, err := GetSettingByKey("customize style")
|
||||
customizeHead, err := GetSettingByKey("customize head")
|
||||
if err == nil {
|
||||
//conf.CustomizeStyle = customizeStyle.Value
|
||||
conf.IndexHtml = strings.Replace(conf.IndexHtml, "/* customize-style */", customizeStyle.Value, 1)
|
||||
conf.IndexHtml = strings.Replace(conf.IndexHtml, "<!-- customize head -->", customizeHead.Value, 1)
|
||||
}
|
||||
customizeScript, err := GetSettingByKey("customize script")
|
||||
customizeBody, err := GetSettingByKey("customize body")
|
||||
if err == nil {
|
||||
//conf.CustomizeStyle = customizeScript.Value
|
||||
conf.IndexHtml = strings.Replace(conf.IndexHtml, "// customize-js", customizeScript.Value, 1)
|
||||
conf.IndexHtml = strings.Replace(conf.IndexHtml, "<!-- customize body -->", customizeBody.Value, 1)
|
||||
}
|
||||
|
||||
adminPassword, err := GetSettingByKey("password")
|
||||
if err == nil {
|
||||
conf.Token = utils.GetMD5Encode(fmt.Sprintf("https://github.com/Xhofe/alist-%s", adminPassword.Value))
|
||||
}
|
||||
|
||||
davUsername, err := GetSettingByKey("WebDAV username")
|
||||
|
@ -1,80 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func Auth(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
password, err := model.GetSettingByKey("password")
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
ErrorResp(c, fmt.Errorf("password not set"), 400)
|
||||
return
|
||||
}
|
||||
ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if token != utils.GetMD5Encode(password.Value) {
|
||||
ErrorResp(c, fmt.Errorf("wrong password"), 401)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func Login(c *gin.Context) {
|
||||
SuccessResp(c)
|
||||
}
|
||||
|
||||
func CheckAccount(c *gin.Context) {
|
||||
if model.AccountsCount() == 0 {
|
||||
ErrorResp(c, fmt.Errorf("no accounts,please add one first"), 1001)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func CheckParent(path string, password string) bool {
|
||||
meta, err := model.GetMetaByPath(path)
|
||||
if err == nil {
|
||||
if meta.Password != "" && meta.Password != password {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if path == "/" || path == "\\" {
|
||||
return true
|
||||
}
|
||||
return CheckParent(filepath.Dir(path), password)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckDownLink(path string, passwordMd5 string) bool {
|
||||
if !conf.CheckDown {
|
||||
return true
|
||||
}
|
||||
meta, err := model.GetMetaByPath(path)
|
||||
log.Debugf("check down path: %s", path)
|
||||
if err == nil {
|
||||
log.Debugf("check down link: %s,%s", meta.Password, passwordMd5)
|
||||
if meta.Password != "" && utils.Get16MD5Encode(meta.Password) != passwordMd5 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if !conf.CheckParent {
|
||||
return true
|
||||
}
|
||||
if path == "/" || path == "\\" {
|
||||
return true
|
||||
}
|
||||
return CheckDownLink(filepath.Dir(path), passwordMd5)
|
||||
}
|
||||
}
|
51
server/common/check.go
Normal file
51
server/common/check.go
Normal file
@ -0,0 +1,51 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Login(c *gin.Context) {
|
||||
SuccessResp(c)
|
||||
}
|
||||
|
||||
func CheckParent(path string, password string) bool {
|
||||
meta, err := model.GetMetaByPath(path)
|
||||
if err == nil {
|
||||
if meta.Password != "" && meta.Password != password {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if path == "/" {
|
||||
return true
|
||||
}
|
||||
return CheckParent(utils.Dir(path), password)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckDownLink(path string, passwordMd5 string, name string) bool {
|
||||
if !conf.CheckDown {
|
||||
return true
|
||||
}
|
||||
meta, err := model.GetMetaByPath(path)
|
||||
log.Debugf("check down path: %s", path)
|
||||
if err == nil {
|
||||
log.Debugf("check down link: %s,%s", meta.Password, passwordMd5)
|
||||
if meta.Password != "" && utils.SignWithPassword(name, meta.Password) != passwordMd5 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if !conf.CheckParent {
|
||||
return true
|
||||
}
|
||||
if path == "/" {
|
||||
return true
|
||||
}
|
||||
return CheckDownLink(utils.Dir(path), passwordMd5, name)
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package server
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -15,7 +15,12 @@ type Resp struct {
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
|
||||
type PathReq struct {
|
||||
Path string `json:"path"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) {
|
||||
var path, name string
|
||||
switch model.AccountsCount() {
|
||||
case 0:
|
||||
@ -32,7 +37,7 @@ func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
|
||||
if !ok {
|
||||
return nil, "", nil, fmt.Errorf("no [%s] account", name)
|
||||
}
|
||||
driver, ok := drivers.GetDriver(account.Type)
|
||||
driver, ok := base.GetDriver(account.Type)
|
||||
if !ok {
|
||||
return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
package server
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
@ -13,52 +14,52 @@ import (
|
||||
func GetAccounts(c *gin.Context) {
|
||||
accounts, err := model.GetAccounts()
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
SuccessResp(c, accounts)
|
||||
common.SuccessResp(c, accounts)
|
||||
}
|
||||
|
||||
func CreateAccount(c *gin.Context) {
|
||||
var req model.Account
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
driver, ok := drivers.GetDriver(req.Type)
|
||||
driver, ok := base.GetDriver(req.Type)
|
||||
if !ok {
|
||||
ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
|
||||
common.ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
req.UpdatedAt = &now
|
||||
if err := model.CreateAccount(&req); err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
log.Debugf("new account: %+v", req)
|
||||
err = driver.Save(&req, nil)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
SuccessResp(c)
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func SaveAccount(c *gin.Context) {
|
||||
var req model.Account
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
driver, ok := drivers.GetDriver(req.Type)
|
||||
driver, ok := base.GetDriver(req.Type)
|
||||
if !ok {
|
||||
ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
|
||||
common.ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
|
||||
return
|
||||
}
|
||||
old, err := model.GetAccountById(req.ID)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
@ -67,15 +68,15 @@ func SaveAccount(c *gin.Context) {
|
||||
model.DeleteAccountFromMap(old.Name)
|
||||
}
|
||||
if err := model.SaveAccount(&req); err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
log.Debugf("save account: %+v", req)
|
||||
err = driver.Save(&req, old)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
SuccessResp(c)
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,12 +84,12 @@ func DeleteAccount(c *gin.Context) {
|
||||
idStr := c.Query("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := model.DeleteAccount(uint(id)); err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
SuccessResp(c)
|
||||
common.SuccessResp(c)
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
package server
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ClearCache(c *gin.Context) {
|
||||
err := conf.Cache.Clear(conf.Ctx)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
SuccessResp(c)
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
143
server/controllers/down.go
Normal file
143
server/controllers/down.go
Normal file
@ -0,0 +1,143 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Down(c *gin.Context) {
|
||||
rawPath := c.Param("path")
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
log.Debugf("down: %s", rawPath)
|
||||
account, path, driver, err := common.ParsePath(rawPath)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if driver.Config().OnlyProxy || account.Proxy {
|
||||
Proxy(c)
|
||||
return
|
||||
}
|
||||
link, err := driver.Link(path, account)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
c.Redirect(302, link.Url)
|
||||
return
|
||||
}
|
||||
|
||||
func Proxy(c *gin.Context) {
|
||||
rawPath := c.Param("path")
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
log.Debugf("proxy: %s", rawPath)
|
||||
account, path, driver, err := common.ParsePath(rawPath)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
// 只有三种情况允许中转:
|
||||
// 1. 账号开启中转
|
||||
// 2. driver只能中转
|
||||
// 3. 是文本类型文件
|
||||
if !account.Proxy && !driver.Config().OnlyProxy && utils.GetFileType(filepath.Ext(rawPath)) != conf.TEXT {
|
||||
common.ErrorResp(c, fmt.Errorf("[%s] not allowed proxy", account.Name), 403)
|
||||
return
|
||||
}
|
||||
// 中转时有中转机器使用中转机器
|
||||
if account.ProxyUrl != "" {
|
||||
name := utils.Base(rawPath)
|
||||
link := fmt.Sprintf("%s%s?sign=%s", account.ProxyUrl, rawPath, utils.SignWithToken(name, conf.Token))
|
||||
c.Redirect(302, link)
|
||||
return
|
||||
}
|
||||
link, err := driver.Link(path, account)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
// 本机文件直接返回文件
|
||||
if account.Type == "Native" {
|
||||
// 对于名称为index.html的文件需要特殊处理
|
||||
if utils.Base(rawPath) == "index.html" {
|
||||
file, err := os.Open(link.Url)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
protocol := "http://"
|
||||
if strings.HasPrefix(link.Url, "https://") {
|
||||
protocol = "https://"
|
||||
}
|
||||
targetHost, err := url.Parse(fmt.Sprintf("%s%s", protocol, target.Host))
|
||||
proxy := httputil.NewSingleHostReverseProxy(targetHost)
|
||||
r.URL = target
|
||||
r.Host = target.Host
|
||||
proxy.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
var client *resty.Client
|
||||
|
||||
func init() {
|
||||
client = resty.New()
|
||||
client.SetRetryCount(3)
|
||||
}
|
||||
|
||||
func Text(c *gin.Context, link *base.Link) {
|
||||
res, err := client.R().Get(link.Url)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
text := res.String()
|
||||
t := utils.GetStrCoding(res.Body())
|
||||
log.Debugf("text type: %s", t)
|
||||
if t != utils.UTF8 {
|
||||
body, err := utils.GbkToUtf8(res.Body())
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
text = string(body)
|
||||
}
|
||||
c.String(200, text)
|
||||
}
|
11
server/controllers/driver.go
Normal file
11
server/controllers/driver.go
Normal file
@ -0,0 +1,11 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetDrivers(c *gin.Context) {
|
||||
common.SuccessResp(c, base.GetDrivers())
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package server
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
@ -10,37 +11,37 @@ import (
|
||||
func GetMetas(c *gin.Context) {
|
||||
metas,err := model.GetMetas()
|
||||
if err != nil {
|
||||
ErrorResp(c,err,500)
|
||||
common.ErrorResp(c,err,500)
|
||||
return
|
||||
}
|
||||
SuccessResp(c, metas)
|
||||
common.SuccessResp(c, metas)
|
||||
}
|
||||
|
||||
func CreateMeta(c *gin.Context) {
|
||||
var req model.Meta
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
req.Path = utils.ParsePath(req.Path)
|
||||
if err := model.CreateMeta(req); err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
SuccessResp(c)
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func SaveMeta(c *gin.Context) {
|
||||
var req model.Meta
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
req.Path = utils.ParsePath(req.Path)
|
||||
if err := model.SaveMeta(req); err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
SuccessResp(c)
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,13 +49,13 @@ func DeleteMeta(c *gin.Context) {
|
||||
idStr := c.Query("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
//path = utils.ParsePath(path)
|
||||
if err := model.DeleteMeta(uint(id)); err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
SuccessResp(c)
|
||||
common.SuccessResp(c)
|
||||
}
|
@ -1,75 +1,59 @@
|
||||
package server
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PathReq struct {
|
||||
Path string `json:"Path"`
|
||||
Password string `json:"Password"`
|
||||
}
|
||||
|
||||
func Path(c *gin.Context) {
|
||||
var req PathReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
req.Path = utils.ParsePath(req.Path)
|
||||
log.Debugf("path: %s", req.Path)
|
||||
meta, err := model.GetMetaByPath(req.Path)
|
||||
if err == nil {
|
||||
if meta.Password != "" && meta.Password != req.Password {
|
||||
ErrorResp(c, fmt.Errorf("wrong password"), 401)
|
||||
return
|
||||
}
|
||||
// TODO hide or ignore?
|
||||
} else if conf.CheckParent {
|
||||
if !CheckParent(filepath.Dir(req.Path), req.Password) {
|
||||
ErrorResp(c, fmt.Errorf("wrong password"), 401)
|
||||
return
|
||||
}
|
||||
}
|
||||
reqV, _ := c.Get("req")
|
||||
req := reqV.(common.PathReq)
|
||||
if model.AccountsCount() > 1 && req.Path == "/" {
|
||||
files, err := model.GetAccountFiles()
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
c.JSON(200, Resp{
|
||||
c.JSON(200, common.Resp{
|
||||
Code: 200,
|
||||
Message: "folder",
|
||||
Data: files,
|
||||
})
|
||||
return
|
||||
}
|
||||
account, path, driver, err := ParsePath(req.Path)
|
||||
account, path, driver, err := common.ParsePath(req.Path)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
file, files, err := driver.Path(path, account)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if file != nil {
|
||||
if account.Type == "Native" {
|
||||
file.Url = fmt.Sprintf("//%s/d%s", c.Request.Host, req.Path)
|
||||
// 对于中转文件或只能中转,将链接修改为中转链接
|
||||
if driver.Config().OnlyProxy || account.Proxy {
|
||||
if account.ProxyUrl != "" {
|
||||
file.Url = fmt.Sprintf("%s%s?sign=%s", account.ProxyUrl, req.Path, utils.SignWithToken(file.Name, conf.Token))
|
||||
} else {
|
||||
file.Url = fmt.Sprintf("//%s/d%s", c.Request.Host, req.Path)
|
||||
}
|
||||
}
|
||||
c.JSON(200, Resp{
|
||||
c.JSON(200, common.Resp{
|
||||
Code: 200,
|
||||
Message: "file",
|
||||
Data: []*model.File{file},
|
||||
})
|
||||
} else {
|
||||
meta, _ := model.GetMetaByPath(req.Path)
|
||||
if meta != nil && meta.Hide != "" {
|
||||
tmpFiles := make([]model.File, 0)
|
||||
hideFiles := strings.Split(meta.Hide, ",")
|
||||
@ -80,7 +64,7 @@ func Path(c *gin.Context) {
|
||||
}
|
||||
files = tmpFiles
|
||||
}
|
||||
c.JSON(200, Resp{
|
||||
c.JSON(200, common.Resp{
|
||||
Code: 200,
|
||||
Message: "folder",
|
||||
Data: files,
|
||||
@ -88,56 +72,53 @@ func Path(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 返回真实的链接,且携带头,只提供给中转程序使用
|
||||
func Link(c *gin.Context) {
|
||||
var req PathReq
|
||||
var req common.PathReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
req.Path = utils.ParsePath(req.Path)
|
||||
rawPath := req.Path
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
log.Debugf("link: %s", rawPath)
|
||||
account, path, driver, err := ParsePath(rawPath)
|
||||
account, path, driver, err := common.ParsePath(rawPath)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
link, err := driver.Link(path, account)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if account.Type == "Native" {
|
||||
SuccessResp(c, gin.H{
|
||||
"url": fmt.Sprintf("//%s/d%s", c.Request.Host, req.Path),
|
||||
if driver.Config().NoLink {
|
||||
common.SuccessResp(c, base.Link{
|
||||
Url: fmt.Sprintf("//%s/d%s?sign=%s", c.Request.Host, req.Path, utils.SignWithToken(utils.Base(rawPath), conf.Token)),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
SuccessResp(c, gin.H{
|
||||
"url": link,
|
||||
})
|
||||
common.SuccessResp(c, link)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func Preview(c *gin.Context) {
|
||||
var req PathReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
reqV, _ := c.Get("req")
|
||||
req := reqV.(common.PathReq)
|
||||
rawPath := req.Path
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
log.Debugf("preview: %s", rawPath)
|
||||
account, path, driver, err := ParsePath(rawPath)
|
||||
account, path, driver, err := common.ParsePath(rawPath)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
data, err := driver.Preview(path, account)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
SuccessResp(c, data)
|
||||
common.SuccessResp(c, data)
|
||||
}
|
||||
}
|
@ -1,38 +1,48 @@
|
||||
package server
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SaveSettings(c *gin.Context) {
|
||||
var req []model.SettingItem
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := model.SaveSettings(req); err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
model.LoadSettings()
|
||||
SuccessResp(c)
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func GetSettings(c *gin.Context) {
|
||||
settings, err := model.GetSettings()
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
SuccessResp(c, settings)
|
||||
common.SuccessResp(c, settings)
|
||||
}
|
||||
|
||||
func GetSettingsPublic(c *gin.Context) {
|
||||
settings, err := model.GetSettingsPublic()
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 400)
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
SuccessResp(c, settings)
|
||||
common.SuccessResp(c, settings)
|
||||
}
|
||||
|
||||
func DeleteSetting(c *gin.Context) {
|
||||
key := c.Query("key")
|
||||
if err := model.DeleteSetting(key); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c)
|
||||
}
|
132
server/down.go
132
server/down.go
@ -1,132 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Down(c *gin.Context) {
|
||||
rawPath, err := url.PathUnescape(c.Param("path"))
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
log.Debugf("down: %s", rawPath)
|
||||
pw := c.Query("pw")
|
||||
if !CheckDownLink(filepath.Dir(rawPath), pw) {
|
||||
ErrorResp(c, fmt.Errorf("wrong password"), 401)
|
||||
return
|
||||
}
|
||||
account, path, driver, err := ParsePath(rawPath)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if account.Type == "GoogleDrive" {
|
||||
Proxy(c)
|
||||
return
|
||||
}
|
||||
link, err := driver.Link(path, account)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if account.Type == "Native" {
|
||||
c.File(link)
|
||||
return
|
||||
} else {
|
||||
c.Redirect(302, link)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func Proxy(c *gin.Context) {
|
||||
rawPath, err := url.PathUnescape(c.Param("path"))
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
log.Debugf("proxy: %s", rawPath)
|
||||
pw := c.Query("pw")
|
||||
if !CheckDownLink(filepath.Dir(rawPath), pw) {
|
||||
ErrorResp(c, fmt.Errorf("wrong password"), 401)
|
||||
return
|
||||
}
|
||||
account, path, driver, err := ParsePath(rawPath)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if !account.Proxy && utils.GetFileType(filepath.Ext(rawPath)) != conf.TEXT {
|
||||
ErrorResp(c, fmt.Errorf("[%s] not allowed proxy", account.Name), 403)
|
||||
return
|
||||
}
|
||||
link, err := driver.Link(path, account)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if account.Type == "Native" {
|
||||
c.File(link)
|
||||
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)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
protocol := "http://"
|
||||
if strings.HasPrefix(link, "https://") {
|
||||
protocol = "https://"
|
||||
}
|
||||
targetHost, err := url.Parse(fmt.Sprintf("%s%s", protocol, target.Host))
|
||||
proxy := httputil.NewSingleHostReverseProxy(targetHost)
|
||||
r.URL = target
|
||||
r.Host = target.Host
|
||||
proxy.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
var client *resty.Client
|
||||
|
||||
func init() {
|
||||
client = resty.New()
|
||||
client.SetRetryCount(3)
|
||||
}
|
||||
|
||||
func Text(c *gin.Context, link string) {
|
||||
res, err := client.R().Get(link)
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
text := res.String()
|
||||
t := utils.GetStrCoding(res.Body())
|
||||
log.Debugf("text type: %s", t)
|
||||
if t != utils.UTF8 {
|
||||
body, err := utils.GbkToUtf8(res.Body())
|
||||
if err != nil {
|
||||
ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
text = string(body)
|
||||
}
|
||||
c.String(200, text)
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetDrivers(c *gin.Context) {
|
||||
SuccessResp(c, drivers.GetDrivers())
|
||||
}
|
16
server/middlewares/account.go
Normal file
16
server/middlewares/account.go
Normal file
@ -0,0 +1,16 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CheckAccount(c *gin.Context) {
|
||||
if model.AccountsCount() == 0 {
|
||||
common.ErrorResp(c, fmt.Errorf("no accounts,please add one first"), 1001)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
27
server/middlewares/auth.go
Normal file
27
server/middlewares/auth.go
Normal file
@ -0,0 +1,27 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Auth(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
//password, err := model.GetSettingByKey("password")
|
||||
//if err != nil {
|
||||
// if err == gorm.ErrRecordNotFound {
|
||||
// common.ErrorResp(c, fmt.Errorf("password not set"), 400)
|
||||
// return
|
||||
// }
|
||||
// common.ErrorResp(c, err, 500)
|
||||
// return
|
||||
//}
|
||||
//if token != utils.GetMD5Encode(password.Value) {
|
||||
if token != conf.Token {
|
||||
common.ErrorResp(c, fmt.Errorf("wrong password"), 401)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
27
server/middlewares/down.go
Normal file
27
server/middlewares/down.go
Normal file
@ -0,0 +1,27 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func DownCheck(c *gin.Context) {
|
||||
sign := c.Query("sign")
|
||||
rawPath := c.Param("path")
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
name := utils.Base(rawPath)
|
||||
if sign == utils.SignWithToken(name, conf.Token) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
pw := c.Query("pw")
|
||||
if !common.CheckDownLink(utils.Dir(rawPath), pw, utils.Base(rawPath)) {
|
||||
common.ErrorResp(c, fmt.Errorf("wrong password"), 401)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
40
server/middlewares/path.go
Normal file
40
server/middlewares/path.go
Normal file
@ -0,0 +1,40 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func PathCheck(c *gin.Context) {
|
||||
var req common.PathReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
req.Path = utils.ParsePath(req.Path)
|
||||
c.Set("req",req)
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == conf.Token {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
meta, err := model.GetMetaByPath(req.Path)
|
||||
if err == nil {
|
||||
if meta.Password != "" && meta.Password != req.Password {
|
||||
common.ErrorResp(c, fmt.Errorf("wrong password"), 401)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
} else if conf.CheckParent {
|
||||
if !common.CheckParent(utils.Dir(req.Path), req.Password) {
|
||||
common.ErrorResp(c, fmt.Errorf("wrong password"), 401)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Next()
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/Xhofe/alist/server/common"
|
||||
"github.com/Xhofe/alist/server/controllers"
|
||||
"github.com/Xhofe/alist/server/middlewares"
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -9,35 +12,42 @@ func InitApiRouter(r *gin.Engine) {
|
||||
|
||||
// TODO from settings
|
||||
Cors(r)
|
||||
r.GET("/d/*path", Down)
|
||||
r.GET("/p/*path", Proxy)
|
||||
r.GET("/d/*path", middlewares.DownCheck, controllers.Down)
|
||||
r.GET("/p/*path", middlewares.DownCheck, controllers.Proxy)
|
||||
|
||||
api := r.Group("/api")
|
||||
public := api.Group("/public")
|
||||
{
|
||||
public.POST("/path", CheckAccount, Path)
|
||||
public.POST("/preview", CheckAccount, Preview)
|
||||
public.GET("/settings", GetSettingsPublic)
|
||||
public.POST("/link", CheckAccount, Link)
|
||||
path := public.Group("", middlewares.PathCheck, middlewares.CheckAccount)
|
||||
path.POST("/path", controllers.Path)
|
||||
path.POST("/preview", controllers.Preview)
|
||||
|
||||
//path.POST("/link",middlewares.Auth, controllers.Link)
|
||||
|
||||
public.GET("/settings", controllers.GetSettingsPublic)
|
||||
}
|
||||
|
||||
admin := api.Group("/admin")
|
||||
{
|
||||
admin.Use(Auth)
|
||||
admin.GET("/login", Login)
|
||||
admin.GET("/settings", GetSettings)
|
||||
admin.POST("/settings", SaveSettings)
|
||||
admin.POST("/account/create", CreateAccount)
|
||||
admin.POST("/account/save", SaveAccount)
|
||||
admin.GET("/accounts", GetAccounts)
|
||||
admin.DELETE("/account", DeleteAccount)
|
||||
admin.GET("/drivers", GetDrivers)
|
||||
admin.GET("/clear_cache", ClearCache)
|
||||
admin.Use(middlewares.Auth)
|
||||
admin.GET("/login", common.Login)
|
||||
admin.GET("/settings", controllers.GetSettings)
|
||||
admin.POST("/settings", controllers.SaveSettings)
|
||||
admin.DELETE("/setting", controllers.DeleteSetting)
|
||||
|
||||
admin.GET("/metas", GetMetas)
|
||||
admin.POST("/meta/create", CreateMeta)
|
||||
admin.POST("/meta/save", SaveMeta)
|
||||
admin.DELETE("/meta", DeleteMeta)
|
||||
admin.POST("/account/create", controllers.CreateAccount)
|
||||
admin.POST("/account/save", controllers.SaveAccount)
|
||||
admin.GET("/accounts", controllers.GetAccounts)
|
||||
admin.DELETE("/account", controllers.DeleteAccount)
|
||||
admin.GET("/drivers", controllers.GetDrivers)
|
||||
admin.GET("/clear_cache", controllers.ClearCache)
|
||||
|
||||
admin.GET("/metas", controllers.GetMetas)
|
||||
admin.POST("/meta/create", controllers.CreateMeta)
|
||||
admin.POST("/meta/save", controllers.SaveMeta)
|
||||
admin.DELETE("/meta", controllers.DeleteMeta)
|
||||
|
||||
admin.POST("/link", controllers.Link)
|
||||
}
|
||||
Static(r)
|
||||
WebDav(r)
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/Xhofe/alist/conf"
|
||||
"github.com/Xhofe/alist/drivers"
|
||||
"github.com/Xhofe/alist/drivers/base"
|
||||
"github.com/Xhofe/alist/model"
|
||||
"github.com/Xhofe/alist/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -21,28 +21,28 @@ import (
|
||||
|
||||
type FileSystem struct{}
|
||||
|
||||
func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
|
||||
var path, name string
|
||||
func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) {
|
||||
var internalPath, name string
|
||||
switch model.AccountsCount() {
|
||||
case 0:
|
||||
return nil, "", nil, fmt.Errorf("no accounts,please add one first")
|
||||
case 1:
|
||||
path = rawPath
|
||||
internalPath = rawPath
|
||||
break
|
||||
default:
|
||||
paths := strings.Split(rawPath, "/")
|
||||
path = "/" + strings.Join(paths[2:], "/")
|
||||
internalPath = "/" + strings.Join(paths[2:], "/")
|
||||
name = paths[1]
|
||||
}
|
||||
account, ok := model.GetAccount(name)
|
||||
if !ok {
|
||||
return nil, "", nil, fmt.Errorf("no [%s] account", name)
|
||||
}
|
||||
driver, ok := drivers.GetDriver(account.Type)
|
||||
driver, ok := base.GetDriver(account.Type)
|
||||
if !ok {
|
||||
return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
|
||||
}
|
||||
return &account, path, driver, nil
|
||||
return &account, internalPath, driver, nil
|
||||
}
|
||||
|
||||
func (fs *FileSystem) File(rawPath string) (*model.File, error) {
|
||||
@ -80,29 +80,30 @@ func (fs *FileSystem) Files(rawPath string) ([]model.File, error) {
|
||||
return driver.Files(path_, account)
|
||||
}
|
||||
|
||||
func GetPW(path string) string {
|
||||
if !conf.CheckDown {
|
||||
return ""
|
||||
}
|
||||
meta, err := model.GetMetaByPath(path)
|
||||
if err == nil {
|
||||
if meta.Password != "" {
|
||||
utils.Get16MD5Encode(meta.Password)
|
||||
}
|
||||
return ""
|
||||
} else {
|
||||
if !conf.CheckParent {
|
||||
return ""
|
||||
}
|
||||
if path == "/" || path == "\\" {
|
||||
return ""
|
||||
}
|
||||
return GetPW(filepath.Dir(path))
|
||||
}
|
||||
}
|
||||
//func GetPW(path string, name string) string {
|
||||
// if !conf.CheckDown {
|
||||
// return ""
|
||||
// }
|
||||
// meta, err := model.GetMetaByPath(path)
|
||||
// if err == nil {
|
||||
// if meta.Password != "" {
|
||||
// return utils.SignWithPassword(name, meta.Password)
|
||||
// }
|
||||
// return ""
|
||||
// } else {
|
||||
// if !conf.CheckParent {
|
||||
// return ""
|
||||
// }
|
||||
// if path == "/" {
|
||||
// return ""
|
||||
// }
|
||||
// return GetPW(utils.Dir(path), name)
|
||||
// }
|
||||
//}
|
||||
|
||||
func (fs *FileSystem) Link(host, rawPath string) (string, error) {
|
||||
func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) {
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
log.Debugf("get link path: %s", rawPath)
|
||||
if model.AccountsCount() > 1 && rawPath == "/" {
|
||||
// error
|
||||
}
|
||||
@ -110,20 +111,81 @@ func (fs *FileSystem) Link(host, rawPath string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if account.Type == "Native" || account.Type == "GoogleDrive" {
|
||||
link := fmt.Sprintf("//%s/p%s", host, rawPath)
|
||||
if conf.CheckDown {
|
||||
pw := GetPW(filepath.Dir(rawPath))
|
||||
link += "?pw" + pw
|
||||
}
|
||||
log.Debugf("proxy link: %s", link)
|
||||
return link, nil
|
||||
link := ""
|
||||
protocol := "http"
|
||||
if r.TLS != nil {
|
||||
protocol = "https"
|
||||
}
|
||||
return driver.Link(path_, account)
|
||||
if driver.Config().OnlyProxy || account.WebdavProxy {
|
||||
link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath)
|
||||
if conf.CheckDown {
|
||||
sign := utils.SignWithToken(utils.Base(rawPath), conf.Token)
|
||||
link += "?sign" + sign
|
||||
}
|
||||
} else {
|
||||
link_, err := driver.Link(path_, account)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
link = link_.Url
|
||||
}
|
||||
log.Debugf("webdav get link: %s", link)
|
||||
return link, err
|
||||
}
|
||||
|
||||
func (fs *FileSystem) CreateDirectory(ctx context.Context, reqPath string) (interface{}, error) {
|
||||
return nil, nil
|
||||
func (fs *FileSystem) CreateDirectory(ctx context.Context, rawPath string) error {
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
if rawPath == "/" {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
if model.AccountsCount() > 1 && len(strings.Split(rawPath, "/")) < 2 {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
account, path_, driver, err := ParsePath(rawPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return driver.MakeDir(path_,account)
|
||||
}
|
||||
|
||||
func (fs *FileSystem) Upload(ctx context.Context, r *http.Request, rawPath string) error {
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
if model.AccountsCount() > 1 && rawPath == "/" {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
account, path_, driver, err := ParsePath(rawPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//fileSize, err := strconv.ParseUint(r.Header.Get("Content-Length"), 10, 64)
|
||||
fileSize := uint64(r.ContentLength)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
filePath, fileName := filepath.Split(path_)
|
||||
fileData := model.FileStream{
|
||||
MIMEType: r.Header.Get("Content-Type"),
|
||||
File: r.Body,
|
||||
Size: fileSize,
|
||||
Name: fileName,
|
||||
ParentPath: filePath,
|
||||
}
|
||||
return driver.Upload(&fileData, account)
|
||||
}
|
||||
|
||||
func (fs *FileSystem) Delete(rawPath string) error {
|
||||
rawPath = utils.ParsePath(rawPath)
|
||||
if rawPath == "/" {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
if model.AccountsCount() > 1 && len(strings.Split(rawPath, "/")) < 2 {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
account, path_, driver, err := ParsePath(rawPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return driver.Delete(path_, account)
|
||||
}
|
||||
|
||||
// slashClean is equivalent to but slightly more efficient than
|
||||
@ -138,16 +200,55 @@ func slashClean(name string) string {
|
||||
// moveFiles moves files and/or directories from src to dst.
|
||||
//
|
||||
// See section 9.9.4 for when various HTTP status codes apply.
|
||||
func moveFiles(ctx context.Context, fs *FileSystem, src FileInfo, dst string, overwrite bool) (status int, err error) {
|
||||
|
||||
func moveFiles(ctx context.Context, fs *FileSystem, src string, dst string, overwrite bool) (status int, err error) {
|
||||
src = utils.ParsePath(src)
|
||||
dst = utils.ParsePath(dst)
|
||||
if src == dst {
|
||||
return http.StatusMethodNotAllowed, errDestinationEqualsSource
|
||||
}
|
||||
srcAccount, srcPath, driver, err := ParsePath(src)
|
||||
if err != nil {
|
||||
return http.StatusMethodNotAllowed, err
|
||||
}
|
||||
dstAccount, dstPath, _, err := ParsePath(dst)
|
||||
if err != nil {
|
||||
return http.StatusMethodNotAllowed, err
|
||||
}
|
||||
if srcAccount.Name != dstAccount.Name {
|
||||
return http.StatusMethodNotAllowed, errInvalidDestination
|
||||
}
|
||||
err = driver.Move(srcPath,dstPath,srcAccount)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
return http.StatusNoContent, nil
|
||||
}
|
||||
|
||||
// copyFiles copies files and/or directories from src to dst.
|
||||
//
|
||||
// See section 9.8.5 for when various HTTP status codes apply.
|
||||
func copyFiles(ctx context.Context, fs *FileSystem, src FileInfo, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
|
||||
|
||||
func copyFiles(ctx context.Context, fs *FileSystem, src string, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
|
||||
src = utils.ParsePath(src)
|
||||
dst = utils.ParsePath(dst)
|
||||
if src == dst {
|
||||
return http.StatusMethodNotAllowed, errDestinationEqualsSource
|
||||
}
|
||||
srcAccount, srcPath, driver, err := ParsePath(src)
|
||||
if err != nil {
|
||||
return http.StatusMethodNotAllowed, err
|
||||
}
|
||||
dstAccount, dstPath, _, err := ParsePath(dst)
|
||||
if err != nil {
|
||||
return http.StatusMethodNotAllowed, err
|
||||
}
|
||||
if srcAccount.Name != dstAccount.Name {
|
||||
// TODO 跨账号复制
|
||||
return http.StatusMethodNotAllowed, errInvalidDestination
|
||||
}
|
||||
err = driver.Copy(srcPath,dstPath,srcAccount)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
return http.StatusNoContent, nil
|
||||
}
|
||||
|
||||
|
@ -233,8 +233,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *
|
||||
}
|
||||
w.Header().Set("ETag", etag)
|
||||
log.Debugf("url: %+v", r.URL)
|
||||
host := r.Host
|
||||
link, err := fs.Link(host, reqPath)
|
||||
link, err := fs.Link(r, reqPath)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
@ -254,26 +253,11 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request, fs *FileS
|
||||
return status, err
|
||||
}
|
||||
defer release()
|
||||
|
||||
//ctx := r.Context()
|
||||
|
||||
//// 尝试作为文件删除
|
||||
//if ok, file := fs.IsFileExist(reqPath); ok {
|
||||
// if err := fs.Delete(ctx, []uint{}, []uint{file.ID}, false); err != nil {
|
||||
// return http.StatusMethodNotAllowed, err
|
||||
// }
|
||||
// return http.StatusNoContent, nil
|
||||
//}
|
||||
//
|
||||
//// 尝试作为目录删除
|
||||
//if ok, folder := fs.IsPathExist(reqPath); ok {
|
||||
// if err := fs.Delete(ctx, []uint{folder.ID}, []uint{}, false); err != nil {
|
||||
// return http.StatusMethodNotAllowed, err
|
||||
// }
|
||||
// return http.StatusNoContent, nil
|
||||
//}
|
||||
|
||||
return http.StatusNotFound, nil
|
||||
err = fs.Delete(reqPath)
|
||||
if err != nil {
|
||||
return http.StatusMethodNotAllowed, err
|
||||
}
|
||||
return http.StatusNoContent, nil
|
||||
}
|
||||
|
||||
// OK
|
||||
@ -292,7 +276,13 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *FileSyst
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
etag, err := findETag(ctx, fs, h.LockSystem, reqPath, nil)
|
||||
err = fs.Upload(ctx, r, reqPath)
|
||||
if err != nil {
|
||||
return http.StatusMethodNotAllowed, err
|
||||
}
|
||||
|
||||
_, fi := isPathExist(ctx, fs, reqPath)
|
||||
etag, err := findETag(ctx, fs, h.LockSystem, reqPath, fi)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
@ -322,7 +312,7 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request, fs *FileSy
|
||||
// ctx = context.WithValue(ctx, fsctx.IgnoreDirectoryConflictCtx, true)
|
||||
//}
|
||||
}
|
||||
if _, err := fs.CreateDirectory(ctx, reqPath); err != nil {
|
||||
if err := fs.CreateDirectory(ctx, reqPath); err != nil {
|
||||
return http.StatusConflict, err
|
||||
}
|
||||
return http.StatusCreated, nil
|
||||
@ -362,7 +352,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *Fil
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
isExist, target := isPathExist(ctx, fs, src)
|
||||
isExist, _ := isPathExist(ctx, fs, src)
|
||||
|
||||
if !isExist {
|
||||
return http.StatusNotFound, nil
|
||||
@ -391,7 +381,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *Fil
|
||||
return http.StatusBadRequest, errInvalidDepth
|
||||
}
|
||||
}
|
||||
return copyFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") != "F", depth, 0)
|
||||
return copyFiles(ctx, fs, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
|
||||
}
|
||||
|
||||
// windows下,某些情况下(网盘根目录下)Office保存文件时附带的锁token只包含源文件,
|
||||
@ -410,7 +400,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *Fil
|
||||
return http.StatusBadRequest, errInvalidDepth
|
||||
}
|
||||
}
|
||||
return moveFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") == "T")
|
||||
return moveFiles(ctx, fs, src, dst, r.Header.Get("Overwrite") == "T")
|
||||
}
|
||||
|
||||
// OK
|
||||
|
@ -94,4 +94,23 @@ func RemoveLastSlash(path string) string {
|
||||
return strings.TrimSuffix(path, "/")
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func Dir(path string) string {
|
||||
idx := strings.LastIndex(path, "/")
|
||||
if idx == 0 {
|
||||
return "/"
|
||||
}
|
||||
if idx == -1 {
|
||||
return path
|
||||
}
|
||||
return path[:idx]
|
||||
}
|
||||
|
||||
func Base(path string) string {
|
||||
idx := strings.LastIndex(path, "/")
|
||||
if idx == -1 {
|
||||
return path
|
||||
}
|
||||
return path[idx+1:]
|
||||
}
|
@ -3,6 +3,7 @@ package utils
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GetMD5Encode
|
||||
@ -16,3 +17,11 @@ func GetMD5Encode(data string) string {
|
||||
func Get16MD5Encode(data string) string {
|
||||
return GetMD5Encode(data)[8:24]
|
||||
}
|
||||
|
||||
func SignWithPassword(name, password string) string {
|
||||
return Get16MD5Encode(fmt.Sprintf("alist-%s-%s", password, name))
|
||||
}
|
||||
|
||||
func SignWithToken(name, token string) string {
|
||||
return Get16MD5Encode(fmt.Sprintf("alist-%s-%s", token, name))
|
||||
}
|
||||
|
Reference in New Issue
Block a user