Compare commits

...

30 Commits

Author SHA1 Message Date
a295e7024a 🎨 change link interface 2021-12-09 19:24:34 +08:00
b36eaf08f0 🎨 abstract cache method 2021-12-08 22:58:44 +08:00
bb6e520ab5 🎨 delete useless code 2021-12-08 22:44:45 +08:00
9b64e2e045 🐛 fix cache bug 2021-12-08 22:41:30 +08:00
ee7c12c30f cf workers proxy 2021-12-08 21:40:20 +08:00
96d6d58910 🐛 fix alist driver cache 2021-12-08 21:39:35 +08:00
2bf235a5ac 🐛 fix link req nil 2021-12-08 20:27:39 +08:00
236f9969c0 🚧 support proxy 2021-12-08 20:00:52 +08:00
09e63027d9 🎇 allow delete deprecated setting 2021-12-08 19:36:07 +08:00
a15dae291e support webm video preview 2021-12-08 14:43:38 +08:00
efaaeedfb8 🐛 fix can't get index.html 2021-12-08 10:35:18 +08:00
190c8001a5 🚧 support proxy url 2021-12-08 10:33:26 +08:00
b8698700ef support add another alist 2021-12-08 09:10:00 +08:00
985b81826f 🎇 support add another alist 2021-12-07 20:16:34 +08:00
74d8fa3919 🔥 remove useless remark 2021-12-07 16:34:20 +08:00
43e4928bb9 🐛 fix aliyundrive webdav delete and upload 2021-12-07 16:32:59 +08:00
03580fd76c 🎨 Improve the code structure 2021-12-07 15:56:43 +08:00
6e8d551420 🚧 aliyundrive webdav write 2021-12-06 22:14:32 +08:00
28998d6f8c 🚧 aliyundrive webdav write 2021-12-06 17:49:20 +08:00
1779617cb9 🎨 Improve the code structure 2021-12-06 15:55:05 +08:00
7dfe48339c 🚧 native webdav write 2021-12-05 16:09:39 +08:00
9c5627a382 🚧 webdav write interface 2021-12-05 15:22:19 +08:00
809850321a 🔧 change customize settings 2021-12-05 13:43:08 +08:00
bdc1f68746 📝 update readme 2021-12-04 18:37:26 +08:00
9aaef6c3a3 📝 update readme image 2021-12-03 11:24:43 +08:00
bb50c52d0e 📝 update readme 2021-12-02 23:01:54 +08:00
6041e5a0fa 🎇 Add ISSUE_TEMPLATE 2021-12-02 22:55:01 +08:00
308a86c36e 📝 update readme 2021-12-02 22:38:18 +08:00
ba7c4fc230 🎇 Create LICENSE 2021-12-02 22:37:56 +08:00
d81ec0637d 🎇 md5 and filename 2021-12-02 18:57:29 +08:00
58 changed files with 2337 additions and 889 deletions

39
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View 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
View 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
View File

@ -21,7 +21,7 @@ dist/
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
bin/* bin/*
alist /alist
*.json *.json
public/index.html public/index.html
public/assets/ public/assets/

21
LICENSE Normal file
View 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
View File

@ -1,46 +1,61 @@
<h2 align="center">Alist</h2> <div align="center">
<p align="center"> <h3><a href="https://alist.nn.ci">Alist</a></h3>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="Release version"></a> <p><em>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/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square" alt="Downloads"></a> <a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square&color=%239F7AEA" alt="Downloads"></a>
<a href="https://github.com/Xhofe/alist/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a> <a href="https://github.com/Xhofe/alist/blob/v2/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a>
<a href="https://pay.xhofe.top"> <a href="https://pay.xhofe.top">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate"> <img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate">
</a> </a>
</p> </div>
--- ---
### 这是什么? English | [中文](./README_cn.md)
一款支持多种存储的目录文件列表程序,后端基于`gin`,前端使用`react` ## Features
### 前端项目地址 - [x] multiple storage
- [x] Local storage
- [x] [aliyundrive](https://www.aliyundrive.com/)
- [x] OneDrive / Sharepoint ([global](https://www.office.com/), [cn](https://portal.partner.microsoftonline.cn),de,us
- [x] [189cloud](https://cloud.189.cn)
- [x] [GoogleDrive](https://drive.google.com/)
- [x] [123pan](https://www.123pan.com/)
- [x] [lanzou](https://pc.woozooo.com/)
- [x] 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> ![demo](https://inews.gtimg.com/newsapp_ls/0/14256614096/0)
### 支持的存储 ## Document
- 本地存储 <https://alist-doc.nn.ci/en/>
- 阿里云盘
- Onedrive/世纪互联
- 天翼云盘
- GoogleDrive
- 123pan
- ...
### 如何使用 ## 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
View 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://inews.gtimg.com/newsapp_ls/0/14256614096/0)
## 文档
<https://alist-doc.nn.ci/>
## 许可
`AList` 是在 MIT 许可下许可的开源软件。
---
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe)

125
alist-proxy.js Normal file
View 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",
})
);
}
});

View File

@ -1,26 +1,18 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"github.com/Xhofe/alist/bootstrap" "github.com/Xhofe/alist/bootstrap"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
_ "github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server" "github.com/Xhofe/alist/server"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
func init() {
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 { func Init() bool {
bootstrap.InitLog() //bootstrap.InitLog()
bootstrap.InitConf() bootstrap.InitConf()
bootstrap.InitCron() bootstrap.InitCron()
bootstrap.InitModel() bootstrap.InitModel()

View File

@ -2,7 +2,7 @@ package bootstrap
import ( import (
"github.com/Xhofe/alist/conf" "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/model"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -15,7 +15,7 @@ func InitAccounts() {
} }
for i, account := range accounts { for i, account := range accounts {
model.RegisterAccount(account) model.RegisterAccount(account)
driver, ok := drivers.GetDriver(account.Type) driver, ok := base.GetDriver(account.Type)
if !ok { if !ok {
log.Errorf("no [%s] driver", account.Type) log.Errorf("no [%s] driver", account.Type)
} else { } else {

View File

@ -1,6 +1,7 @@
package bootstrap package bootstrap
import ( import (
"flag"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -19,4 +20,13 @@ func InitLog() {
FullTimestamp: true, FullTimestamp: true,
}) })
log.Infof("init log...") 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()
} }

View File

@ -15,9 +15,13 @@ func InitSettings() {
Description: "version", Description: "version",
Type: "string", Type: "string",
Group: model.CONST, 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{ settings := []model.SettingItem{
{ {
@ -36,14 +40,14 @@ func InitSettings() {
}, },
{ {
Key: "logo", 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", Description: "logo",
Type: "string", Type: "string",
Group: model.PUBLIC, Group: model.PUBLIC,
}, },
{ {
Key: "favicon", 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", Description: "favicon",
Type: "string", Type: "string",
Group: model.PUBLIC, Group: model.PUBLIC,
@ -114,17 +118,34 @@ func InitSettings() {
Group: model.PRIVATE, Group: model.PRIVATE,
}, },
{ {
Key: "customize style", Key: "customize head",
Value: "", 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", Type: "text",
Description: "customize style, don't need add <style></style>", Description: "Customize head, placed at the beginning of the head",
Group: model.PRIVATE, Group: model.PRIVATE,
}, },
{ {
Key: "customize script", Key: "customize body",
Value: "", Value: "",
Type: "text", Type: "text",
Description: "customize script, don't need add <script></script>", Description: "Customize script, placed at the end of the body",
Group: model.PRIVATE, Group: model.PRIVATE,
}, },
{ {
@ -156,10 +177,22 @@ func InitSettings() {
Group: model.PRIVATE, Group: model.PRIVATE,
}, },
} }
for _, v := range settings { for i, _ := range settings {
_, err := model.GetSettingByKey(v.Key) v := settings[i]
if err == gorm.ErrRecordNotFound { v.Version = conf.GitTag
err = model.SaveSetting(v) 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 { if err != nil {
log.Fatalf("failed write setting: %s", err.Error()) log.Fatalf("failed write setting: %s", err.Error())
} }

View File

@ -12,7 +12,7 @@ var (
GoVersion string GoVersion string
GitAuthor string GitAuthor string
GitCommit string GitCommit string
GitTag string GitTag string = "dev"
) )
var ( var (
@ -31,7 +31,7 @@ var (
var ( var (
TextTypes = []string{"txt", "go", "md"} TextTypes = []string{"txt", "go", "md"}
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"} OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb"} VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm"}
AudioTypes = []string{"mp3", "flac", "ogg", "m4a"} AudioTypes = []string{"mp3", "flac", "ogg", "m4a"}
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg"} ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg"}
) )
@ -41,11 +41,9 @@ var (
RawIndexHtml string RawIndexHtml string
IndexHtml string IndexHtml string
CheckParent bool CheckParent bool
//CustomizeStyle string CheckDown bool
//CustomizeScript string
//Favicon string
CheckDown bool
Token string
DavUsername string DavUsername string
DavPassword string DavPassword string
) )

View File

@ -1,8 +1,9 @@
package drivers package _23
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
@ -52,7 +53,7 @@ func (driver Pan123) Login(account *model.Account) error {
var resp Pan123TokenResp var resp Pan123TokenResp
_, err := pan123Client.R(). _, err := pan123Client.R().
SetResult(&resp). SetResult(&resp).
SetBody(Json{ SetBody(base.Json{
"passport": account.Username, "passport": account.Username,
"password": account.Password, "password": account.Password,
}).Post("https://www.123pan.com/api/user/sign_in") }).Post("https://www.123pan.com/api/user/sign_in")
@ -128,21 +129,21 @@ func (driver Pan123) GetFile(path string, account *model.Account) (*Pan123File,
if err != nil { if err != nil {
return nil, err return nil, err
} }
parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir)) parentFiles_, _ := base.GetCache(dir, account)
parentFiles, _ := parentFiles_.([]Pan123File) parentFiles, _ := parentFiles_.([]Pan123File)
for _, file := range parentFiles { for _, file := range parentFiles {
if file.FileName == name { if file.FileName == name {
if file.Type != conf.FOLDER { if file.Type != conf.FOLDER {
return &file, err return &file, err
} else { } else {
return nil, NotFile return nil, base.ErrNotFile
} }
} }
} }
return nil, PathNotFound return nil, base.ErrPathNotFound
} }
func init() { func init() {
RegisterDriver(&Pan123{}) base.RegisterDriver(&Pan123{})
pan123Client.SetRetryCount(3) pan123Client.SetRetryCount(3)
} }

View File

@ -1,8 +1,9 @@
package drivers package _23
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -11,48 +12,48 @@ import (
"path/filepath" "path/filepath"
) )
type Pan123 struct {} type Pan123 struct{}
func (driver Pan123) Config() DriverConfig { func (driver Pan123) Config() base.DriverConfig {
return DriverConfig{ return base.DriverConfig{
Name: "123Pan", Name: "123Pan",
OnlyProxy: false, OnlyProxy: false,
} }
} }
func (driver Pan123) Items() []Item { func (driver Pan123) Items() []base.Item {
return []Item{ return []base.Item{
{ {
Name: "username", Name: "username",
Label: "username", Label: "username",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
Description: "account username/phone number", Description: "account username/phone number",
}, },
{ {
Name: "password", Name: "password",
Label: "password", Label: "password",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
Description: "account password", Description: "account password",
}, },
{ {
Name: "root_folder", Name: "root_folder",
Label: "root folder file_id", Label: "root folder file_id",
Type: "string", Type: base.TypeString,
Required: false, Required: false,
}, },
{ {
Name: "order_by", Name: "order_by",
Label: "order_by", Label: "order_by",
Type: "select", Type: base.TypeSelect,
Values: "name,fileId,updateAt,createAt", Values: "name,fileId,updateAt,createAt",
Required: true, Required: true,
}, },
{ {
Name: "order_direction", Name: "order_direction",
Label: "order_direction", Label: "order_direction",
Type: "select", Type: base.TypeSelect,
Values: "asc,desc", Values: "asc,desc",
Required: true, Required: true,
}, },
@ -89,13 +90,13 @@ func (driver Pan123) File(path string, account *model.Account) (*model.File, err
return &file, nil return &file, nil
} }
} }
return nil, PathNotFound return nil, base.ErrPathNotFound
} }
func (driver Pan123) Files(path string, account *model.Account) ([]model.File, error) { func (driver Pan123) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path) path = utils.ParsePath(path)
var rawFiles []Pan123File 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 { if err == nil {
rawFiles, _ = cache.([]Pan123File) rawFiles, _ = cache.([]Pan123File)
} else { } else {
@ -108,7 +109,7 @@ func (driver Pan123) Files(path string, account *model.Account) ([]model.File, e
return nil, err return nil, err
} }
if len(rawFiles) > 0 { 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) files := make([]model.File, 0)
@ -118,14 +119,14 @@ func (driver Pan123) Files(path string, account *model.Account) ([]model.File, e
return files, nil 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) file, err := driver.GetFile(utils.ParsePath(path), account)
if err != nil { if err != nil {
return "", err return nil, err
} }
var resp Pan123DownResp var resp Pan123DownResp
_, err = pan123Client.R().SetResult(&resp).SetHeader("authorization", "Bearer "+account.AccessToken). _, err = pan123Client.R().SetResult(&resp).SetHeader("authorization", "Bearer "+account.AccessToken).
SetBody(Json{ SetBody(base.Json{
"driveId": 0, "driveId": 0,
"etag": file.Etag, "etag": file.Etag,
"fileId": file.FileId, "fileId": file.FileId,
@ -135,32 +136,35 @@ func (driver Pan123) Link(path string, account *model.Account) (string, error) {
"type": file.Type, "type": file.Type,
}).Post("https://www.123pan.com/api/file/download_info") }).Post("https://www.123pan.com/api/file/download_info")
if err != nil { if err != nil {
return "", err return nil, err
} }
if resp.Code != 0 { if resp.Code != 0 {
if resp.Code == 401 { if resp.Code == 401 {
err := driver.Login(account) err := driver.Login(account)
if err != nil { if err != nil {
return "", err return nil, err
} }
return driver.Link(path, account) return driver.Link(path, account)
} }
return "", fmt.Errorf(resp.Message) return nil, fmt.Errorf(resp.Message)
} }
u,err := url.Parse(resp.Data.DownloadUrl) u, err := url.Parse(resp.Data.DownloadUrl)
if err != nil { if err != nil {
return "", err return nil, err
} }
u_ := fmt.Sprintf("https://%s%s",u.Host,u.Path) u_ := fmt.Sprintf("https://%s%s", u.Host, u.Path)
res, err := NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Get(u_) res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Get(u_)
if err != nil { if err != nil {
return "", err return nil, err
} }
log.Debug(res.String()) log.Debug(res.String())
link := base.Link{}
if res.StatusCode() == 302 { if res.StatusCode() == 302 {
return res.Header().Get("location"), nil link.Url = res.Header().Get("location")
}else {
link.Url = resp.Data.DownloadUrl
} }
return resp.Data.DownloadUrl, nil return &link, nil
} }
func (driver Pan123) Path(path string, account *model.Account) (*model.File, []model.File, error) { func (driver Pan123) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -170,8 +174,12 @@ func (driver Pan123) Path(path string, account *model.Account) (*model.File, []m
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if file.Type != conf.FOLDER { if !file.IsDir() {
file.Url, _ = driver.Link(path, account) link, err := driver.Link(path, account)
if err != nil {
return nil, nil, err
}
file.Url = link.Url
return file, nil, nil return file, nil, nil
} }
files, err := driver.Files(path, account) files, err := driver.Files(path, account)
@ -186,7 +194,27 @@ func (driver Pan123) Proxy(c *gin.Context, account *model.Account) {
} }
func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) { func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) {
return nil, NotSupport return nil, base.ErrNotSupport
} }
var _ 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)

View File

@ -1,4 +1,4 @@
package drivers package _89
import ( import (
"crypto/rand" "crypto/rand"
@ -9,6 +9,7 @@ import (
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
@ -51,7 +52,7 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
//func (c Cloud189) GetFile(path string, account *model.Account) (*Cloud189File, error) { //func (c Cloud189) GetFile(path string, account *model.Account) (*Cloud189File, error) {
// dir, name := filepath.Split(path) // dir, name := filepath.Split(path)
// dir = utils.ParsePath(dir) // dir = utils.ParsePath(dir)
// _, _, err := c.Path(dir, account) // _, _, err := c.ParentPath(dir, account)
// if err != nil { // if err != nil {
// return nil, err // return nil, err
// } // }
@ -62,11 +63,11 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
// if file.Size != -1 { // if file.Size != -1 {
// return &file, err // return &file, err
// } else { // } else {
// return nil, NotFile // return nil, ErrNotFile
// } // }
// } // }
// } // }
// return nil, PathNotFound // return nil, ErrPathNotFound
//} //}
type Cloud189Down struct { type Cloud189Down struct {
@ -312,6 +313,6 @@ func b64tohex(a string) string {
} }
func init() { func init() {
RegisterDriver(&Cloud189{}) base.RegisterDriver(&Cloud189{})
client189Map = make(map[string]*resty.Client, 0) client189Map = make(map[string]*resty.Client, 0)
} }

View File

@ -1,8 +1,9 @@
package drivers package _89
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -12,46 +13,46 @@ import (
type Cloud189 struct {} type Cloud189 struct {}
func (driver Cloud189) Config() DriverConfig { func (driver Cloud189) Config() base.DriverConfig {
return DriverConfig{ return base.DriverConfig{
Name: "189Cloud", Name: "189Cloud",
OnlyProxy: false, OnlyProxy: false,
} }
} }
func (driver Cloud189) Items() []Item { func (driver Cloud189) Items() []base.Item {
return []Item{ return []base.Item{
{ {
Name: "username", Name: "username",
Label: "username", Label: "username",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
Description: "account username/phone number", Description: "account username/phone number",
}, },
{ {
Name: "password", Name: "password",
Label: "password", Label: "password",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
Description: "account password", Description: "account password",
}, },
{ {
Name: "root_folder", Name: "root_folder",
Label: "root folder file_id", Label: "root folder file_id",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
}, },
{ {
Name: "order_by", Name: "order_by",
Label: "order_by", Label: "order_by",
Type: "select", Type: base.TypeSelect,
Values: "name,size,lastOpTime,createdDate", Values: "name,size,lastOpTime,createdDate",
Required: true, Required: true,
}, },
{ {
Name: "order_direction", Name: "order_direction",
Label: "desc", Label: "desc",
Type: "select", Type: base.TypeSelect,
Values: "true,false", Values: "true,false",
Required: true, Required: true,
}, },
@ -97,13 +98,13 @@ func (driver Cloud189) File(path string, account *model.Account) (*model.File, e
return &file, nil return &file, nil
} }
} }
return nil, PathNotFound return nil, base.ErrPathNotFound
} }
func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) { func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path) path = utils.ParsePath(path)
var rawFiles []Cloud189File 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 { if err == nil {
rawFiles, _ = cache.([]Cloud189File) rawFiles, _ = cache.([]Cloud189File)
} else { } else {
@ -116,7 +117,7 @@ func (driver Cloud189) Files(path string, account *model.Account) ([]model.File,
return nil, err return nil, err
} }
if len(rawFiles) > 0 { 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) files := make([]model.File, 0)
@ -126,17 +127,17 @@ func (driver Cloud189) Files(path string, account *model.Account) ([]model.File,
return files, nil 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) file, err := driver.File(utils.ParsePath(path), account)
if err != nil { if err != nil {
return "", err return nil, err
} }
if file.Type == conf.FOLDER { if file.Type == conf.FOLDER {
return "", NotFile return nil, base.ErrNotFile
} }
client, ok := client189Map[account.Name] client, ok := client189Map[account.Name]
if !ok { 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 e Cloud189Error
var resp Cloud189Down var resp Cloud189Down
@ -147,28 +148,31 @@ func (driver Cloud189) Link(path string, account *model.Account) (string, error)
"fileId": file.Id, "fileId": file.Id,
}).Get("https://cloud.189.cn/api/open/file/getFileDownloadUrl.action") }).Get("https://cloud.189.cn/api/open/file/getFileDownloadUrl.action")
if err != nil { if err != nil {
return "", err return nil, err
} }
if e.ErrorCode != "" { if e.ErrorCode != "" {
if e.ErrorCode == "InvalidSessionKey" { if e.ErrorCode == "InvalidSessionKey" {
err = driver.Login(account) err = driver.Login(account)
if err != nil { if err != nil {
return "", err return nil, err
} }
return driver.Link(path, account) return driver.Link(path, account)
} }
} }
if resp.ResCode != 0 { if resp.ResCode != 0 {
return "", fmt.Errorf(resp.ResMessage) return nil, fmt.Errorf(resp.ResMessage)
} }
res, err := NoRedirectClient.R().Get(resp.FileDownloadUrl) res, err := base.NoRedirectClient.R().Get(resp.FileDownloadUrl)
if err != nil { if err != nil {
return "", err return nil, err
} }
link := base.Link{}
if res.StatusCode() == 302 { 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) { func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -178,8 +182,12 @@ func (driver Cloud189) Path(path string, account *model.Account) (*model.File, [
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if file.Type != conf.FOLDER { if !file.IsDir() {
file.Url, _ = driver.Link(path, account) link, err := driver.Link(path, account)
if err != nil {
return nil, nil, err
}
file.Url = link.Url
return file, nil, nil return file, nil, nil
} }
files, err := driver.Files(path, account) files, err := driver.Files(path, account)
@ -194,7 +202,28 @@ func (driver Cloud189) Proxy(ctx *gin.Context, account *model.Account) {
} }
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) { func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
return nil, NotSupport return nil, base.ErrNotSupport
} }
var _ 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)

View File

@ -1,250 +0,0 @@
package drivers
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type AliDrive struct{}
func (driver AliDrive) Config() DriverConfig {
return DriverConfig{
Name: "AliDrive",
OnlyProxy: false,
}
}
func (driver AliDrive) Items() []Item {
return []Item{
{
Name: "order_by",
Label: "order_by",
Type: "select",
Values: "name,size,updated_at,created_at",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: "select",
Values: "ASC,DESC",
Required: false,
},
{
Name: "refresh_token",
Label: "refresh token",
Type: "string",
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: "string",
Required: false,
},
{
Name: "limit",
Label: "limit",
Type: "number",
Required: false,
Description: ">0 and <=200",
},
}
}
func (driver AliDrive) Save(account *model.Account, old *model.Account) error {
if old != nil {
conf.Cron.Remove(cron.EntryID(old.CronId))
}
if account.RootFolder == "" {
account.RootFolder = "root"
}
if account.Limit == 0 {
account.Limit = 200
}
err := driver.RefreshToken(account)
if err != nil {
return err
}
var resp Json
_, _ = aliClient.R().SetResult(&resp).
SetBody("{}").
SetHeader("authorization", "Bearer\t"+account.AccessToken).
Post("https://api.aliyundrive.com/v2/user/get")
log.Debugf("user info: %+v", resp)
account.DriveId = resp["default_drive_id"].(string)
cronId, err := conf.Cron.AddFunc("@every 2h", func() {
name := account.Name
log.Debugf("ali account name: %s", name)
newAccount, ok := model.GetAccount(name)
log.Debugf("ali account: %+v", newAccount)
if !ok {
return
}
err = driver.RefreshToken(&newAccount)
_ = model.SaveAccount(&newAccount)
})
if err != nil {
return err
}
account.CronId = int(cronId)
err = model.SaveAccount(account)
if err != nil {
return err
}
return nil
}
func (driver AliDrive) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, PathNotFound
}
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))
if err == nil {
rawFiles, _ = cache.([]AliFile)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver AliDrive) Link(path string, account *model.Account) (string, error) {
file, err := driver.File(path, account)
if err != nil {
return "", err
}
var resp Json
var e AliRespError
_, err = aliClient.R().SetResult(&resp).
SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(Json{
"drive_id": account.DriveId,
"file_id": file.Id,
"expire_sec": 14400,
}).Post("https://api.aliyundrive.com/v2/file/get_download_url")
if err != nil {
return "", err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return "", err
} else {
_ = model.SaveAccount(account)
return driver.Link(path, account)
}
}
return "", fmt.Errorf("%s", e.Message)
}
return resp["url"].(string), nil
}
func (driver AliDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("ali path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver AliDrive) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Del("Origin")
c.Request.Header.Set("Referer", "https://www.aliyundrive.com/")
}
func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) {
file, err := driver.GetFile(path, account)
if err != nil {
return nil, err
}
// office
var resp Json
var e AliRespError
var url string
req := Json{
"drive_id": account.DriveId,
"file_id": file.FileId,
}
switch file.Category {
case "doc":
{
url = "https://api.aliyundrive.com/v2/file/get_office_preview_url"
req["access_token"] = account.AccessToken
}
case "video":
{
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
req["category"] = "live_transcoding"
}
default:
return nil, NotSupport
}
_, err = aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(req).Post(url)
if err != nil {
return nil, err
}
if e.Code != "" {
return nil, fmt.Errorf("%s", e.Message)
}
return resp, nil
}
var _ Driver = (*AliDrive)(nil)

View File

@ -1,13 +1,16 @@
package drivers package alidrive
import ( import (
"errors"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
"strings"
"time" "time"
) )
@ -75,7 +78,7 @@ func (driver AliDrive) GetFiles(fileId string, account *model.Account) ([]AliFil
SetResult(&resp). SetResult(&resp).
SetError(&e). SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken). SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(Json{ SetBody(base.Json{
"drive_id": account.DriveId, "drive_id": account.DriveId,
"fields": "*", "fields": "*",
"image_thumbnail_process": "image/resize,w_400/format,jpeg", "image_thumbnail_process": "image/resize,w_400/format,jpeg",
@ -116,7 +119,7 @@ func (driver AliDrive) GetFile(path string, account *model.Account) (*AliFile, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir)) parentFiles_, _ := base.GetCache(dir, account)
parentFiles, _ := parentFiles_.([]AliFile) parentFiles, _ := parentFiles_.([]AliFile)
for _, file := range parentFiles { for _, file := range parentFiles {
if file.Name == name { if file.Name == name {
@ -127,16 +130,16 @@ func (driver AliDrive) GetFile(path string, account *model.Account) (*AliFile, e
} }
} }
} }
return nil, PathNotFound return nil, base.ErrPathNotFound
} }
func (driver AliDrive) RefreshToken(account *model.Account) error { func (driver AliDrive) RefreshToken(account *model.Account) error {
url := "https://auth.aliyundrive.com/v2/account/token" url := "https://auth.aliyundrive.com/v2/account/token"
var resp TokenResp var resp base.TokenResp
var e AliRespError var e AliRespError
_, err := aliClient.R(). _, err := aliClient.R().
//ForceContentType("application/json"). //ForceContentType("application/json").
SetBody(Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}). SetBody(base.Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}).
SetResult(&resp). SetResult(&resp).
SetError(&e). SetError(&e).
Post(url) Post(url)
@ -155,11 +158,86 @@ func (driver AliDrive) RefreshToken(account *model.Account) error {
return nil 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() { func init() {
RegisterDriver(&AliDrive{}) base.RegisterDriver(&AliDrive{})
aliClient. aliClient.
SetRetryCount(3). SetRetryCount(3).
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"). SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36").
SetHeader("content-type", "application/json"). SetHeader("content-type", "application/json").
SetHeader("origin", "https://aliyundrive.com") SetHeader("origin", "https://www.aliyundrive.com")
} }

479
drivers/alidrive/driver.go Normal file
View File

@ -0,0 +1,479 @@
package alidrive
import (
"bytes"
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
"io"
"math"
"net/http"
"path/filepath"
)
type AliDrive struct{}
func (driver AliDrive) Config() base.DriverConfig {
return base.DriverConfig{
Name: "AliDrive",
OnlyProxy: false,
}
}
func (driver AliDrive) Items() []base.Item {
return []base.Item{
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "name,size,updated_at,created_at",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "ASC,DESC",
Required: false,
},
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: false,
},
{
Name: "limit",
Label: "limit",
Type: base.TypeNumber,
Required: false,
Description: ">0 and <=200",
},
}
}
func (driver AliDrive) Save(account *model.Account, old *model.Account) error {
if old != nil {
conf.Cron.Remove(cron.EntryID(old.CronId))
}
if account.RootFolder == "" {
account.RootFolder = "root"
}
if account.Limit == 0 {
account.Limit = 200
}
err := driver.RefreshToken(account)
if err != nil {
return err
}
var resp base.Json
_, _ = aliClient.R().SetResult(&resp).
SetBody("{}").
SetHeader("authorization", "Bearer\t"+account.AccessToken).
Post("https://api.aliyundrive.com/v2/user/get")
log.Debugf("user info: %+v", resp)
account.DriveId = resp["default_drive_id"].(string)
cronId, err := conf.Cron.AddFunc("@every 2h", func() {
name := account.Name
log.Debugf("ali account name: %s", name)
newAccount, ok := model.GetAccount(name)
log.Debugf("ali account: %+v", newAccount)
if !ok {
return
}
err = driver.RefreshToken(&newAccount)
_ = model.SaveAccount(&newAccount)
})
if err != nil {
return err
}
account.CronId = int(cronId)
err = model.SaveAccount(account)
if err != nil {
return err
}
return nil
}
func (driver AliDrive) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver AliDrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []AliFile
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]AliFile)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver AliDrive) Link(path string, account *model.Account) (*base.Link, error) {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
var resp base.Json
var e AliRespError
_, err = aliClient.R().SetResult(&resp).
SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"drive_id": account.DriveId,
"file_id": file.Id,
"expire_sec": 14400,
}).Post("https://api.aliyundrive.com/v2/file/get_download_url")
if err != nil {
return nil, err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return nil, err
} else {
_ = model.SaveAccount(account)
return driver.Link(path, account)
}
}
return nil, fmt.Errorf("%s", e.Message)
}
return &base.Link{
Url: resp["url"].(string),
}, nil
}
func (driver AliDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("ali path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
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 AliDrive) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Del("Origin")
c.Request.Header.Set("Referer", "https://www.aliyundrive.com/")
}
func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) {
file, err := driver.GetFile(path, account)
if err != nil {
return nil, err
}
// office
var resp base.Json
var e AliRespError
var url string
req := base.Json{
"drive_id": account.DriveId,
"file_id": file.FileId,
}
switch file.Category {
case "doc":
{
url = "https://api.aliyundrive.com/v2/file/get_office_preview_url"
req["access_token"] = account.AccessToken
}
case "video":
{
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
req["category"] = "live_transcoding"
}
default:
return nil, base.ErrNotSupport
}
_, err = aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(req).Post(url)
if err != nil {
return nil, err
}
if e.Code != "" {
return nil, fmt.Errorf("%s", e.Message)
}
return resp, nil
}
func (driver AliDrive) MakeDir(path string, account *model.Account) error {
dir, name := filepath.Split(path)
parentFile, err := driver.File(dir, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
var resp base.Json
var e AliRespError
_, err = aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"check_name_mode": "refuse",
"drive_id": account.DriveId,
"name": name,
"parent_file_id": parentFile.Id,
"type": "folder",
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.MakeDir(path, account)
}
}
return fmt.Errorf("%s", e.Message)
}
if resp["file_name"] == name {
_ = 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
View 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
View 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
View 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
View 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))
}

View File

@ -1,4 +1,4 @@
package drivers package base
import ( import (
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
@ -9,8 +9,9 @@ import (
) )
type DriverConfig struct { type DriverConfig struct {
Name string Name string
OnlyProxy bool OnlyProxy bool
NoLink bool // 必须本机返回的
} }
type Driver interface { type Driver interface {
@ -19,16 +20,17 @@ type Driver interface {
Save(account *model.Account, old *model.Account) error Save(account *model.Account, old *model.Account) error
File(path string, account *model.Account) (*model.File, error) File(path string, account *model.Account) (*model.File, error)
Files(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) Link(path string, account *model.Account) (*Link, error)
Path(path string, account *model.Account) (*model.File, []model.File, error) Path(path string, account *model.Account) (*model.File, []model.File, error)
Proxy(c *gin.Context, account *model.Account) Proxy(c *gin.Context, account *model.Account)
Preview(path string, account *model.Account) (interface{}, error) Preview(path string, account *model.Account) (interface{}, error)
// TODO // TODO
//Search(path string, keyword string, account *model.Account) ([]*model.File, error) //Search(path string, keyword string, account *model.Account) ([]*model.File, error)
//MakeDir(path string, account *model.Account) error MakeDir(path string, account *model.Account) error
//Move(src string, des string, account *model.Account) error Move(src string, dst string, account *model.Account) error
//Delete(path string) error Copy(src string, dst string, account *model.Account) error
//Upload(file *fs.File, path string, account *model.Account) error Delete(path string, account *model.Account) error
Upload(file *model.FileStream, account *model.Account) error
} }
type Item struct { type Item struct {
@ -40,11 +42,6 @@ type Item struct {
Description string `json:"description"` Description string `json:"description"`
} }
type TokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
var driversMap = map[string]Driver{} var driversMap = map[string]Driver{}
func RegisterDriver(driver Driver) { func RegisterDriver(driver Driver) {
@ -64,29 +61,46 @@ func GetDrivers() map[string][]Item {
res[k] = v.Items() res[k] = v.Items()
} else { } else {
res[k] = append([]Item{ res[k] = append([]Item{
//{
// Name: "allow_proxy",
// Label: "allow_proxy",
// Type: TypeBool,
// Required: true,
// Description: "allow proxy",
//},
{ {
Name: "proxy", Name: "proxy",
Label: "proxy", Label: "proxy",
Type: "bool", Type: TypeBool,
Required: true, Required: true,
Description: "allow proxy", Description: "web proxy",
}, },
{ {
Name: "webdav_proxy", Name: "webdav_proxy",
Label: "webdav proxy", Label: "webdav proxy",
Type: "bool", Type: TypeBool,
Required: true, Required: true,
Description: "Transfer the WebDAV of this account through the server", Description: "Transfer the WebDAV of this account through the server",
}, },
}, v.Items()...) }, 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 return res
} }
type Json map[string]interface{}
var NoRedirectClient *resty.Client var NoRedirectClient *resty.Client
var RestyClient = resty.New()
var HttpClient = &http.Client{}
func init() { func init() {
NoRedirectClient = resty.New().SetRedirectPolicy( NoRedirectClient = resty.New().SetRedirectPolicy(

37
drivers/base/types.go Normal file
View 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"`
}

View File

@ -1,8 +1,9 @@
package drivers package google
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -12,37 +13,37 @@ import (
type GoogleDrive struct{} type GoogleDrive struct{}
func (driver GoogleDrive) Config() DriverConfig { func (driver GoogleDrive) Config() base.DriverConfig {
return DriverConfig{ return base.DriverConfig{
Name: "GoogleDrive", Name: "GoogleDrive",
OnlyProxy: true, OnlyProxy: true,
} }
} }
func (driver GoogleDrive) Items() []Item { func (driver GoogleDrive) Items() []base.Item {
return []Item{ return []base.Item{
{ {
Name: "client_id", Name: "client_id",
Label: "client id", Label: "client id",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
}, },
{ {
Name: "client_secret", Name: "client_secret",
Label: "client secret", Label: "client secret",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
}, },
{ {
Name: "refresh_token", Name: "refresh_token",
Label: "refresh token", Label: "refresh token",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
}, },
{ {
Name: "root_folder", Name: "root_folder",
Label: "root folder file_id", Label: "root folder file_id",
Type: "string", Type: base.TypeString,
Required: false, Required: false,
}, },
} }
@ -86,13 +87,13 @@ func (driver GoogleDrive) File(path string, account *model.Account) (*model.File
return &file, nil return &file, nil
} }
} }
return nil, PathNotFound return nil, base.ErrPathNotFound
} }
func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.File, error) { func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path) path = utils.ParsePath(path)
var rawFiles []GoogleFile 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 { if err == nil {
rawFiles, _ = cache.([]GoogleFile) rawFiles, _ = cache.([]GoogleFile)
} else { } else {
@ -105,7 +106,7 @@ func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.Fi
return nil, err return nil, err
} }
if len(rawFiles) > 0 { 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) files := make([]model.File, 0)
@ -115,31 +116,40 @@ func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.Fi
return files, nil 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) file, err := driver.File(path, account)
if err != nil { if err != nil {
return "", err return nil, err
} }
if file.Type == conf.FOLDER { if file.Type == conf.FOLDER {
return "", 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 var e GoogleError
_, _ = googleClient.R().SetError(&e). _, _ = googleClient.R().SetError(&e).
SetHeader("Authorization", "Bearer "+account.AccessToken). SetHeader("Authorization", "Bearer "+account.AccessToken).
Get(link) Get(url)
if e.Error.Code != 0 { if e.Error.Code != 0 {
if e.Error.Code == 401 { if e.Error.Code == 401 {
err = driver.RefreshToken(account) err = driver.RefreshToken(account)
if err != nil { if err != nil {
_ = model.SaveAccount(account) _ = model.SaveAccount(account)
return "", err return nil, err
} }
return driver.Link(path, account) 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) { func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -165,7 +175,27 @@ func (driver GoogleDrive) Proxy(c *gin.Context, account *model.Account) {
} }
func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) { func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, NotSupport return nil, base.ErrNotSupport
} }
var _ Driver = (*GoogleDrive)(nil) 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)

View File

@ -1,8 +1,9 @@
package drivers package google
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
@ -20,7 +21,7 @@ type GoogleTokenError struct {
func (driver GoogleDrive) RefreshToken(account *model.Account) error { func (driver GoogleDrive) RefreshToken(account *model.Account) error {
url := "https://www.googleapis.com/oauth2/v4/token" url := "https://www.googleapis.com/oauth2/v4/token"
var resp TokenResp var resp base.TokenResp
var e GoogleTokenError var e GoogleTokenError
_, err := googleClient.R().SetResult(&resp).SetError(&e). _, err := googleClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{ SetFormData(map[string]string{
@ -133,7 +134,7 @@ func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]GoogleF
//func (driver GoogleDrive) GetFile(path string, account *model.Account) (*GoogleFile, error) { //func (driver GoogleDrive) GetFile(path string, account *model.Account) (*GoogleFile, error) {
// dir, name := filepath.Split(path) // dir, name := filepath.Split(path)
// dir = utils.ParsePath(dir) // dir = utils.ParsePath(dir)
// _, _, err := driver.Path(dir, account) // _, _, err := driver.ParentPath(dir, account)
// if err != nil { // if err != nil {
// return nil, err // return nil, err
// } // }
@ -144,14 +145,14 @@ func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]GoogleF
// if !driver.IsDir(file.MimeType) { // if !driver.IsDir(file.MimeType) {
// return &file, err // return &file, err
// } else { // } else {
// return nil, drivers.NotFile // return nil, drivers.ErrNotFile
// } // }
// } // }
// } // }
// return nil, drivers.PathNotFound // return nil, drivers.ErrPathNotFound
//} //}
func init() { func init() {
RegisterDriver(&GoogleDrive{}) base.RegisterDriver(&GoogleDrive{})
googleClient.SetRetryCount(3) googleClient.SetRetryCount(3)
} }

View File

@ -1,8 +1,8 @@
package drivers package lanzou
import ( import (
"fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -12,42 +12,42 @@ import (
type Lanzou struct{} type Lanzou struct{}
func (driver Lanzou) Config() DriverConfig { func (driver Lanzou) Config() base.DriverConfig {
return DriverConfig{ return base.DriverConfig{
Name: "Lanzou", Name: "Lanzou",
OnlyProxy: false, OnlyProxy: false,
} }
} }
func (driver Lanzou) Items() []Item { func (driver Lanzou) Items() []base.Item {
return []Item{ return []base.Item{
{ {
Name: "onedrive_type", Name: "onedrive_type",
Label: "lanzou type", Label: "lanzou type",
Type: SELECT, Type: base.TypeSelect,
Required: true, Required: true,
Values: "cookie,url", Values: "cookie,url",
}, },
{ {
Name: "access_token", Name: "access_token",
Label: "cookie", Label: "cookie",
Type: STRING, Type: base.TypeString,
Description: "about 15 days valid", Description: "about 15 days valid",
}, },
{ {
Name: "root_folder", Name: "root_folder",
Label: "root folder file_id", Label: "root folder file_id",
Type: STRING, Type: base.TypeString,
}, },
{ {
Name: "site_url", Name: "site_url",
Label: "share url", Label: "share url",
Type: STRING, Type: base.TypeString,
}, },
{ {
Name: "password", Name: "password",
Label: "share password", Label: "share password",
Type: STRING, Type: base.TypeString,
}, },
} }
} }
@ -85,13 +85,13 @@ func (driver Lanzou) File(path string, account *model.Account) (*model.File, err
return &file, nil return &file, nil
} }
} }
return nil, PathNotFound return nil, base.ErrPathNotFound
} }
func (driver Lanzou) Files(path string, account *model.Account) ([]model.File, error) { func (driver Lanzou) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path) path = utils.ParsePath(path)
var rawFiles []LanZouFile var rawFiles []LanZouFile
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path)) cache, err := base.GetCache(path, account)
if err == nil { if err == nil {
rawFiles, _ = cache.([]LanZouFile) rawFiles, _ = cache.([]LanZouFile)
} else { } else {
@ -104,7 +104,7 @@ func (driver Lanzou) Files(path string, account *model.Account) ([]model.File, e
return nil, err return nil, err
} }
if len(rawFiles) > 0 { 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) files := make([]model.File, 0)
@ -114,24 +114,27 @@ func (driver Lanzou) Files(path string, account *model.Account) ([]model.File, e
return files, nil return files, nil
} }
func (driver Lanzou) Link(path string, account *model.Account) (string, error) { func (driver Lanzou) Link(path string, account *model.Account) (*base.Link, error) {
file, err := driver.File(path, account) file, err := driver.File(path, account)
if err != nil { if err != nil {
return "", err return nil, err
} }
log.Debugf("down file: %+v", file) log.Debugf("down file: %+v", file)
downId := file.Id downId := file.Id
if account.OnedriveType == "cookie" { if account.OnedriveType == "cookie" {
downId, err = driver.GetDownPageId(file.Id, account) downId, err = driver.GetDownPageId(file.Id, account)
if err != nil { if err != nil {
return "", err return nil, err
} }
} }
link, err := driver.GetLink(downId) url, err := driver.GetLink(downId)
if err != nil { if err != nil {
return "", err return nil, err
} }
return link, nil link := base.Link{
Url: url,
}
return &link, nil
} }
func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []model.File, error) { func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -141,8 +144,12 @@ func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []m
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if file.Type != conf.FOLDER { if !file.IsDir() {
file.Url, _ = driver.Link(path, account) link, err := driver.Link(path, account)
if err != nil {
return nil, nil, err
}
file.Url = link.Url
return file, nil, nil return file, nil, nil
} }
files, err := driver.Files(path, account) files, err := driver.Files(path, account)
@ -157,7 +164,27 @@ func (driver Lanzou) Proxy(c *gin.Context, account *model.Account) {
} }
func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) { func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) {
return nil, NotSupport return nil, base.ErrNotSupport
} }
var _ Driver = (*Lanzou)(nil) 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)

View File

@ -1,8 +1,9 @@
package drivers package lanzou
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
@ -231,7 +232,7 @@ func (driver *Lanzou) GetLink(downId string) (string, error) {
} }
func init() { func init() {
RegisterDriver(&Lanzou{}) base.RegisterDriver(&Lanzou{})
lanzouClient. lanzouClient.
SetRetryCount(3). SetRetryCount(3).
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36") SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")

View File

@ -1,5 +0,0 @@
package drivers
func init() {
RegisterDriver(&Native{})
}

View File

@ -1,12 +1,14 @@
package drivers package native
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -15,32 +17,33 @@ import (
type Native struct{} type Native struct{}
func (driver Native) Config() DriverConfig { func (driver Native) Config() base.DriverConfig {
return DriverConfig{ return base.DriverConfig{
Name: "Native", Name: "Native",
OnlyProxy: true, OnlyProxy: true,
NoLink: true,
} }
} }
func (driver Native) Items() []Item { func (driver Native) Items() []base.Item {
return []Item{ return []base.Item{
{ {
Name: "root_folder", Name: "root_folder",
Label: "root folder path", Label: "root folder path",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
}, },
{ {
Name: "order_by", Name: "order_by",
Label: "order_by", Label: "order_by",
Type: "select", Type: base.TypeSelect,
Values: "name,size,updated_at", Values: "name,size,updated_at",
Required: false, Required: false,
}, },
{ {
Name: "order_direction", Name: "order_direction",
Label: "order_direction", Label: "order_direction",
Type: "select", Type: base.TypeSelect,
Values: "ASC,DESC", Values: "ASC,DESC",
Required: false, Required: false,
}, },
@ -66,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) { func (driver Native) File(path string, account *model.Account) (*model.File, error) {
fullPath := filepath.Join(account.RootFolder, path) fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) { if !utils.Exists(fullPath) {
return nil, PathNotFound return nil, base.ErrPathNotFound
} }
f, err := os.Stat(fullPath) f, err := os.Stat(fullPath)
if err != nil { if err != nil {
@ -90,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) { func (driver Native) Files(path string, account *model.Account) ([]model.File, error) {
fullPath := filepath.Join(account.RootFolder, path) fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) { if !utils.Exists(fullPath) {
return nil, PathNotFound return nil, base.ErrPathNotFound
} }
files := make([]model.File, 0) files := make([]model.File, 0)
rawFiles, err := ioutil.ReadDir(fullPath) rawFiles, err := ioutil.ReadDir(fullPath)
@ -120,16 +123,19 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
return files, nil 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) fullPath := filepath.Join(account.RootFolder, path)
s, err := os.Stat(fullPath) s, err := os.Stat(fullPath)
if err != nil { if err != nil {
return "", err return nil, err
} }
if s.IsDir() { 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) { func (driver Native) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -138,7 +144,7 @@ func (driver Native) Path(path string, account *model.Account) (*model.File, []m
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if file.Type != conf.FOLDER { if !file.IsDir() {
//file.Url, _ = driver.Link(path, account) //file.Url, _ = driver.Link(path, account)
return file, nil, nil return file, nil, nil
} }
@ -155,7 +161,74 @@ func (driver Native) Proxy(c *gin.Context, account *model.Account) {
} }
func (driver Native) Preview(path string, account *model.Account) (interface{}, error) { func (driver Native) Preview(path string, account *model.Account) (interface{}, error) {
return nil, NotSupport return nil, base.ErrNotSupport
} }
var _ 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)

74
drivers/native/native.go Normal file
View File

@ -0,0 +1,74 @@
package native
import (
"fmt"
"github.com/Xhofe/alist/drivers/base"
"io"
"io/ioutil"
"os"
"path"
)
// File copies a single file from src to dst
func (driver *Native) CopyFile(src, dst string) error {
var err error
var srcfd *os.File
var dstfd *os.File
var srcinfo os.FileInfo
if srcfd, err = os.Open(src); err != nil {
return err
}
defer srcfd.Close()
if dstfd, err = os.Create(dst); err != nil {
return err
}
defer dstfd.Close()
if _, err = io.Copy(dstfd, srcfd); err != nil {
return err
}
if srcinfo, err = os.Stat(src); err != nil {
return err
}
return os.Chmod(dst, srcinfo.Mode())
}
// Dir copies a whole directory recursively
func (driver *Native) CopyDir(src string, dst string) error {
var err error
var fds []os.FileInfo
var srcinfo os.FileInfo
if srcinfo, err = os.Stat(src); err != nil {
return err
}
if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil {
return err
}
if fds, err = ioutil.ReadDir(src); err != nil {
return err
}
for _, fd := range fds {
srcfp := path.Join(src, fd.Name())
dstfp := path.Join(dst, fd.Name())
if fd.IsDir() {
if err = driver.CopyDir(srcfp, dstfp); err != nil {
fmt.Println(err)
}
} else {
if err = driver.CopyFile(srcfp, dstfp); err != nil {
fmt.Println(err)
}
}
}
return nil
}
func init() {
base.RegisterDriver(&Native{})
}

View File

@ -1,8 +1,9 @@
package drivers package onedrive
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -13,19 +14,20 @@ import (
type Onedrive struct{} type Onedrive struct{}
func (driver Onedrive) Config() DriverConfig {
return DriverConfig{ func (driver Onedrive) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Onedrive", Name: "Onedrive",
OnlyProxy: false, OnlyProxy: false,
} }
} }
func (driver Onedrive) Items() []Item { func (driver Onedrive) Items() []base.Item {
return []Item{ return []base.Item{
{ {
Name: "zone", Name: "zone",
Label: "zone", Label: "zone",
Type: "select", Type: base.TypeSelect,
Required: true, Required: true,
Values: "global,cn,us,de", Values: "global,cn,us,de",
Description: "", Description: "",
@ -33,57 +35,57 @@ func (driver Onedrive) Items() []Item {
{ {
Name: "onedrive_type", Name: "onedrive_type",
Label: "onedrive type", Label: "onedrive type",
Type: "select", Type: base.TypeSelect,
Required: true, Required: true,
Values: "onedrive,sharepoint", Values: "onedrive,sharepoint",
}, },
{ {
Name: "client_id", Name: "client_id",
Label: "client id", Label: "client id",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
}, },
{ {
Name: "client_secret", Name: "client_secret",
Label: "client secret", Label: "client secret",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
}, },
{ {
Name: "redirect_uri", Name: "redirect_uri",
Label: "redirect uri", Label: "redirect uri",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
}, },
{ {
Name: "refresh_token", Name: "refresh_token",
Label: "refresh token", Label: "refresh token",
Type: "string", Type: base.TypeString,
Required: true, Required: true,
}, },
{ {
Name: "site_id", Name: "site_id",
Label: "site id", Label: "site id",
Type: "string", Type: base.TypeString,
Required: false, Required: false,
}, },
{ {
Name: "root_folder", Name: "root_folder",
Label: "root folder path", Label: "root folder path",
Type: "string", Type: base.TypeString,
Required: false, Required: false,
}, },
{ {
Name: "order_by", Name: "order_by",
Label: "order_by", Label: "order_by",
Type: "select", Type: base.TypeSelect,
Values: "name,size,lastModifiedDateTime", Values: "name,size,lastModifiedDateTime",
Required: false, Required: false,
}, },
{ {
Name: "order_direction", Name: "order_direction",
Label: "order_direction", Label: "order_direction",
Type: "select", Type: base.TypeSelect,
Values: "asc,desc", Values: "asc,desc",
Required: false, Required: false,
}, },
@ -147,12 +149,12 @@ func (driver Onedrive) File(path string, account *model.Account) (*model.File, e
return &file, nil return &file, nil
} }
} }
return nil, PathNotFound return nil, base.ErrPathNotFound
} }
func (driver Onedrive) Files(path string, account *model.Account) ([]model.File, error) { func (driver Onedrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path) 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 { if err == nil {
files, _ := cache.([]model.File) files, _ := cache.([]model.File)
return files, nil return files, nil
@ -166,20 +168,23 @@ func (driver Onedrive) Files(path string, account *model.Account) ([]model.File,
files = append(files, *driver.FormatFile(&file)) files = append(files, *driver.FormatFile(&file))
} }
if len(files) > 0 { if len(files) > 0 {
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), files, nil) _ = base.SetCache(path, files, account)
} }
return files, nil 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) file, err := driver.GetFile(account, path)
if err != nil { if err != nil {
return "", err return nil, err
} }
if file.File.MimeType == "" { 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) { func (driver Onedrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -188,7 +193,7 @@ func (driver Onedrive) Path(path string, account *model.Account) (*model.File, [
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if file.Type != conf.FOLDER { if !file.IsDir() {
//file.Url, _ = driver.Link(path, account) //file.Url, _ = driver.Link(path, account)
return file, nil, nil return file, nil, nil
} }
@ -204,7 +209,27 @@ func (driver Onedrive) Proxy(c *gin.Context, account *model.Account) {
} }
func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) { func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, NotSupport return nil, base.ErrNotSupport
} }
var _ Driver = (*Onedrive)(nil) 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)

View File

@ -1,8 +1,9 @@
package drivers package onedrive
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
@ -73,7 +74,7 @@ type OneTokenErr struct {
func (driver Onedrive) RefreshToken(account *model.Account) error { func (driver Onedrive) RefreshToken(account *model.Account) error {
url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token" url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
var resp TokenResp var resp base.TokenResp
var e OneTokenErr var e OneTokenErr
_, err := oneClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{ _, err := oneClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token", "grant_type": "refresh_token",
@ -185,6 +186,6 @@ func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, e
} }
func init() { func init() {
RegisterDriver(&Onedrive{}) base.RegisterDriver(&Onedrive{})
oneClient.SetRetryCount(3) oneClient.SetRetryCount(3)
} }

View File

@ -1,16 +0,0 @@
package drivers
import "fmt"
var (
PathNotFound = fmt.Errorf("path not found")
NotFile = fmt.Errorf("not file")
NotImplement = fmt.Errorf("not implement")
NotSupport = fmt.Errorf("not support")
)
const (
STRING = "string"
SELECT = "select"
BOOL = "bool"
)

View File

@ -7,22 +7,21 @@ import (
) )
type Account struct { type Account struct {
ID uint `json:"id" gorm:"primaryKey"` ID uint `json:"id" gorm:"primaryKey"` // 唯一ID
Name string `json:"name" gorm:"unique" binding:"required"` Name string `json:"name" gorm:"unique" binding:"required"` // 唯一名称
Index int `json:"index"` Index int `json:"index"` // 序号 用于排序
Type string `json:"type"` Type string `json:"type"` // 类型即driver
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
RootFolder string `json:"root_folder"` RootFolder string `json:"root_folder"`
Status string `json:"status"` Status string `json:"status"` // 状态
CronId int CronId int
DriveId string DriveId string
Limit int `json:"limit"` Limit int `json:"limit"`
OrderBy string `json:"order_by"` OrderBy string `json:"order_by"`
OrderDirection string `json:"order_direction"` OrderDirection string `json:"order_direction"`
Proxy bool `json:"proxy"`
UpdatedAt *time.Time `json:"updated_at"` UpdatedAt *time.Time `json:"updated_at"`
Search bool `json:"search"` Search bool `json:"search"`
ClientId string `json:"client_id"` ClientId string `json:"client_id"`
@ -33,6 +32,9 @@ type Account struct {
SiteId string `json:"site_id"` SiteId string `json:"site_id"`
OnedriveType string `json:"onedrive_type"` OnedriveType string `json:"onedrive_type"`
WebdavProxy bool `json:"webdav_proxy"` 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{} var accountsMap = map[string]Account{}

35
model/file_stream.go Normal file
View 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
}

View File

@ -1,7 +1,9 @@
package model package model
import ( import (
"fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
"strings" "strings"
) )
@ -18,6 +20,7 @@ type SettingItem struct {
Type string `json:"type"` Type string `json:"type"`
Group int `json:"group"` Group int `json:"group"`
Values string `json:"values"` Values string `json:"values"`
Version string `json:"version"`
} }
func SaveSettings(items []SettingItem) error { func SaveSettings(items []SettingItem) error {
@ -44,6 +47,13 @@ func GetSettings() (*[]SettingItem, error) {
return &items, nil return &items, nil
} }
func DeleteSetting(key string) error {
setting := SettingItem{
Key: key,
}
return conf.DB.Delete(&setting).Error
}
func GetSettingByKey(key string) (*SettingItem, error) { func GetSettingByKey(key string) (*SettingItem, error) {
var items SettingItem var items SettingItem
if err := conf.DB.Where("`key` = ?", key).First(&items).Error; err != nil { if err := conf.DB.Where("`key` = ?", key).First(&items).Error; err != nil {
@ -61,7 +71,7 @@ func LoadSettings() {
if err == nil { if err == nil {
conf.CheckParent = checkParent.Value == "true" conf.CheckParent = checkParent.Value == "true"
} }
checkDown,err := GetSettingByKey("check down link") checkDown, err := GetSettingByKey("check down link")
if err == nil { if err == nil {
conf.CheckDown = checkDown.Value == "true" conf.CheckDown = checkDown.Value == "true"
} }
@ -72,18 +82,20 @@ func LoadSettings() {
} }
title, err := GetSettingByKey("title") title, err := GetSettingByKey("title")
if err == nil { if err == nil {
//conf.CustomizeStyle = customizeStyle.Value
conf.IndexHtml = strings.Replace(conf.IndexHtml, "Loading...", title.Value, 1) conf.IndexHtml = strings.Replace(conf.IndexHtml, "Loading...", title.Value, 1)
} }
customizeStyle, err := GetSettingByKey("customize style") customizeHead, err := GetSettingByKey("customize head")
if err == nil { if err == nil {
//conf.CustomizeStyle = customizeStyle.Value conf.IndexHtml = strings.Replace(conf.IndexHtml, "<!-- customize head -->", customizeHead.Value, 1)
conf.IndexHtml = strings.Replace(conf.IndexHtml, "/* customize-style */", customizeStyle.Value, 1)
} }
customizeScript, err := GetSettingByKey("customize script") customizeBody, err := GetSettingByKey("customize body")
if err == nil { if err == nil {
//conf.CustomizeStyle = customizeScript.Value conf.IndexHtml = strings.Replace(conf.IndexHtml, "<!-- customize body -->", customizeBody.Value, 1)
conf.IndexHtml = strings.Replace(conf.IndexHtml, "// customize-js", customizeScript.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") davUsername, err := GetSettingByKey("WebDAV username")

View File

@ -1,45 +1,17 @@
package server package common
import ( import (
"fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/gorm"
) )
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) { func Login(c *gin.Context) {
SuccessResp(c) 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 { func CheckParent(path string, password string) bool {
meta, err := model.GetMetaByPath(path) meta, err := model.GetMetaByPath(path)
if err == nil { if err == nil {
@ -55,7 +27,7 @@ func CheckParent(path string, password string) bool {
} }
} }
func CheckDownLink(path string, passwordMd5 string) bool { func CheckDownLink(path string, passwordMd5 string, name string) bool {
if !conf.CheckDown { if !conf.CheckDown {
return true return true
} }
@ -63,7 +35,7 @@ func CheckDownLink(path string, passwordMd5 string) bool {
log.Debugf("check down path: %s", path) log.Debugf("check down path: %s", path)
if err == nil { if err == nil {
log.Debugf("check down link: %s,%s", meta.Password, passwordMd5) log.Debugf("check down link: %s,%s", meta.Password, passwordMd5)
if meta.Password != "" && utils.Get16MD5Encode(meta.Password) != passwordMd5 { if meta.Password != "" && utils.SignWithPassword(name, meta.Password) != passwordMd5 {
return false return false
} }
return true return true
@ -74,6 +46,6 @@ func CheckDownLink(path string, passwordMd5 string) bool {
if path == "/" { if path == "/" {
return true return true
} }
return CheckDownLink(utils.Dir(path), passwordMd5) return CheckDownLink(utils.Dir(path), passwordMd5, name)
} }
} }

View File

@ -1,8 +1,8 @@
package server package common
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/drivers" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -15,7 +15,12 @@ type Resp struct {
Data interface{} `json:"data"` Data interface{} `json:"data"`
} }
func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) { type PathReq struct {
Path string `json:"path"`
Password string `json:"password"`
}
func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) {
var path, name string var path, name string
switch model.AccountsCount() { switch model.AccountsCount() {
case 0: case 0:
@ -32,7 +37,7 @@ func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
if !ok { if !ok {
return nil, "", nil, fmt.Errorf("no [%s] account", name) return nil, "", nil, fmt.Errorf("no [%s] account", name)
} }
driver, ok := drivers.GetDriver(account.Type) driver, ok := base.GetDriver(account.Type)
if !ok { if !ok {
return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type) return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
} }

View File

@ -1,9 +1,10 @@
package server package controllers
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/drivers" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"strconv" "strconv"
@ -13,52 +14,52 @@ import (
func GetAccounts(c *gin.Context) { func GetAccounts(c *gin.Context) {
accounts, err := model.GetAccounts() accounts, err := model.GetAccounts()
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
SuccessResp(c, accounts) common.SuccessResp(c, accounts)
} }
func CreateAccount(c *gin.Context) { func CreateAccount(c *gin.Context) {
var req model.Account var req model.Account
if err := c.ShouldBind(&req); err != nil { if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
driver, ok := drivers.GetDriver(req.Type) driver, ok := base.GetDriver(req.Type)
if !ok { if !ok {
ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400) common.ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
return return
} }
now := time.Now() now := time.Now()
req.UpdatedAt = &now req.UpdatedAt = &now
if err := model.CreateAccount(&req); err != nil { if err := model.CreateAccount(&req); err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
} else { } else {
log.Debugf("new account: %+v", req) log.Debugf("new account: %+v", req)
err = driver.Save(&req, nil) err = driver.Save(&req, nil)
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
SuccessResp(c) common.SuccessResp(c)
} }
} }
func SaveAccount(c *gin.Context) { func SaveAccount(c *gin.Context) {
var req model.Account var req model.Account
if err := c.ShouldBind(&req); err != nil { if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
driver, ok := drivers.GetDriver(req.Type) driver, ok := base.GetDriver(req.Type)
if !ok { if !ok {
ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400) common.ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
return return
} }
old, err := model.GetAccountById(req.ID) old, err := model.GetAccountById(req.ID)
if err != nil { if err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
now := time.Now() now := time.Now()
@ -67,15 +68,15 @@ func SaveAccount(c *gin.Context) {
model.DeleteAccountFromMap(old.Name) model.DeleteAccountFromMap(old.Name)
} }
if err := model.SaveAccount(&req); err != nil { if err := model.SaveAccount(&req); err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
} else { } else {
log.Debugf("save account: %+v", req) log.Debugf("save account: %+v", req)
err = driver.Save(&req, old) err = driver.Save(&req, old)
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
SuccessResp(c) common.SuccessResp(c)
} }
} }
@ -83,12 +84,12 @@ func DeleteAccount(c *gin.Context) {
idStr := c.Query("id") idStr := c.Query("id")
id, err := strconv.Atoi(idStr) id, err := strconv.Atoi(idStr)
if err != nil { if err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
if err := model.DeleteAccount(uint(id)); err != nil { if err := model.DeleteAccount(uint(id)); err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
SuccessResp(c) common.SuccessResp(c)
} }

View File

@ -1,15 +1,16 @@
package server package controllers
import ( import (
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func ClearCache(c *gin.Context) { func ClearCache(c *gin.Context) {
err := conf.Cache.Clear(conf.Ctx) err := conf.Cache.Clear(conf.Ctx)
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
} else { } else {
SuccessResp(c) common.SuccessResp(c)
} }
} }

143
server/controllers/down.go Normal file
View 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)
}

View 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())
}

View File

@ -1,7 +1,8 @@
package server package controllers
import ( import (
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strconv" "strconv"
@ -10,37 +11,37 @@ import (
func GetMetas(c *gin.Context) { func GetMetas(c *gin.Context) {
metas,err := model.GetMetas() metas,err := model.GetMetas()
if err != nil { if err != nil {
ErrorResp(c,err,500) common.ErrorResp(c,err,500)
return return
} }
SuccessResp(c, metas) common.SuccessResp(c, metas)
} }
func CreateMeta(c *gin.Context) { func CreateMeta(c *gin.Context) {
var req model.Meta var req model.Meta
if err := c.ShouldBind(&req); err != nil { if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
req.Path = utils.ParsePath(req.Path) req.Path = utils.ParsePath(req.Path)
if err := model.CreateMeta(req); err != nil { if err := model.CreateMeta(req); err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
} else { } else {
SuccessResp(c) common.SuccessResp(c)
} }
} }
func SaveMeta(c *gin.Context) { func SaveMeta(c *gin.Context) {
var req model.Meta var req model.Meta
if err := c.ShouldBind(&req); err != nil { if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
req.Path = utils.ParsePath(req.Path) req.Path = utils.ParsePath(req.Path)
if err := model.SaveMeta(req); err != nil { if err := model.SaveMeta(req); err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
} else { } else {
SuccessResp(c) common.SuccessResp(c)
} }
} }
@ -48,13 +49,13 @@ func DeleteMeta(c *gin.Context) {
idStr := c.Query("id") idStr := c.Query("id")
id, err := strconv.Atoi(idStr) id, err := strconv.Atoi(idStr)
if err != nil { if err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
//path = utils.ParsePath(path) //path = utils.ParsePath(path)
if err := model.DeleteMeta(uint(id)); err != nil { if err := model.DeleteMeta(uint(id)); err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
SuccessResp(c) common.SuccessResp(c)
} }

View File

@ -1,74 +1,59 @@
package server package controllers
import ( import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"strings" "strings"
) )
type PathReq struct {
Path string `json:"Path"`
Password string `json:"Password"`
}
func Path(c *gin.Context) { func Path(c *gin.Context) {
var req PathReq reqV, _ := c.Get("req")
if err := c.ShouldBind(&req); err != nil { req := reqV.(common.PathReq)
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(utils.Dir(req.Path), req.Password) {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
}
if model.AccountsCount() > 1 && req.Path == "/" { if model.AccountsCount() > 1 && req.Path == "/" {
files, err := model.GetAccountFiles() files, err := model.GetAccountFiles()
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
c.JSON(200, Resp{ c.JSON(200, common.Resp{
Code: 200, Code: 200,
Message: "folder", Message: "folder",
Data: files, Data: files,
}) })
return return
} }
account, path, driver, err := ParsePath(req.Path) account, path, driver, err := common.ParsePath(req.Path)
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
file, files, err := driver.Path(path, account) file, files, err := driver.Path(path, account)
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
if file != nil { 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, Code: 200,
Message: "file", Message: "file",
Data: []*model.File{file}, Data: []*model.File{file},
}) })
} else { } else {
meta, _ := model.GetMetaByPath(req.Path)
if meta != nil && meta.Hide != "" { if meta != nil && meta.Hide != "" {
tmpFiles := make([]model.File, 0) tmpFiles := make([]model.File, 0)
hideFiles := strings.Split(meta.Hide, ",") hideFiles := strings.Split(meta.Hide, ",")
@ -79,7 +64,7 @@ func Path(c *gin.Context) {
} }
files = tmpFiles files = tmpFiles
} }
c.JSON(200, Resp{ c.JSON(200, common.Resp{
Code: 200, Code: 200,
Message: "folder", Message: "folder",
Data: files, Data: files,
@ -87,56 +72,53 @@ func Path(c *gin.Context) {
} }
} }
// 返回真实的链接,且携带头,只提供给中转程序使用
func Link(c *gin.Context) { func Link(c *gin.Context) {
var req PathReq var req common.PathReq
if err := c.ShouldBind(&req); err != nil { if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
req.Path = utils.ParsePath(req.Path)
rawPath := req.Path rawPath := req.Path
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
log.Debugf("link: %s", rawPath) log.Debugf("link: %s", rawPath)
account, path, driver, err := ParsePath(rawPath) account, path, driver, err := common.ParsePath(rawPath)
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
link, err := driver.Link(path, account) link, err := driver.Link(path, account)
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
if account.Type == "Native" { if driver.Config().NoLink {
SuccessResp(c, gin.H{ common.SuccessResp(c, base.Link{
"url": fmt.Sprintf("//%s/d%s", c.Request.Host, req.Path), Url: fmt.Sprintf("//%s/d%s?sign=%s", c.Request.Host, req.Path, utils.SignWithToken(utils.Base(rawPath), conf.Token)),
}) })
return return
} else { } else {
SuccessResp(c, gin.H{ common.SuccessResp(c, link)
"url": link,
})
return return
} }
} }
func Preview(c *gin.Context) { func Preview(c *gin.Context) {
var req PathReq reqV, _ := c.Get("req")
if err := c.ShouldBind(&req); err != nil { req := reqV.(common.PathReq)
ErrorResp(c, err, 400)
return
}
rawPath := req.Path rawPath := req.Path
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
log.Debugf("preview: %s", rawPath) log.Debugf("preview: %s", rawPath)
account, path, driver, err := ParsePath(rawPath) account, path, driver, err := common.ParsePath(rawPath)
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
data, err := driver.Preview(path, account) data, err := driver.Preview(path, account)
if err != nil { if err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
} else { } else {
SuccessResp(c, data) common.SuccessResp(c, data)
} }
} }

View File

@ -1,38 +1,48 @@
package server package controllers
import ( import (
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func SaveSettings(c *gin.Context) { func SaveSettings(c *gin.Context) {
var req []model.SettingItem var req []model.SettingItem
if err := c.ShouldBind(&req); err != nil { if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
if err := model.SaveSettings(req); err != nil { if err := model.SaveSettings(req); err != nil {
ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
} else { } else {
model.LoadSettings() model.LoadSettings()
SuccessResp(c) common.SuccessResp(c)
} }
} }
func GetSettings(c *gin.Context) { func GetSettings(c *gin.Context) {
settings, err := model.GetSettings() settings, err := model.GetSettings()
if err != nil { if err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
SuccessResp(c, settings) common.SuccessResp(c, settings)
} }
func GetSettingsPublic(c *gin.Context) { func GetSettingsPublic(c *gin.Context) {
settings, err := model.GetSettingsPublic() settings, err := model.GetSettingsPublic()
if err != nil { if err != nil {
ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return 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)
} }

View File

@ -1,124 +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 := c.Param("path")
rawPath = utils.ParsePath(rawPath)
log.Debugf("down: %s", rawPath)
pw := c.Query("pw")
if !CheckDownLink(utils.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 := c.Param("path")
rawPath = utils.ParsePath(rawPath)
log.Debugf("proxy: %s", rawPath)
pw := c.Query("pw")
if !CheckDownLink(utils.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)
}

View File

@ -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())
}

View 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()
}

View 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()
}

View 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()
}

View 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()
}

View File

@ -1,6 +1,9 @@
package server package server
import ( 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-contrib/cors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -9,35 +12,42 @@ func InitApiRouter(r *gin.Engine) {
// TODO from settings // TODO from settings
Cors(r) Cors(r)
r.GET("/d/*path", Down) r.GET("/d/*path", middlewares.DownCheck, controllers.Down)
r.GET("/p/*path", Proxy) r.GET("/p/*path", middlewares.DownCheck, controllers.Proxy)
api := r.Group("/api") api := r.Group("/api")
public := api.Group("/public") public := api.Group("/public")
{ {
public.POST("/path", CheckAccount, Path) path := public.Group("", middlewares.PathCheck, middlewares.CheckAccount)
public.POST("/preview", CheckAccount, Preview) path.POST("/path", controllers.Path)
public.GET("/settings", GetSettingsPublic) path.POST("/preview", controllers.Preview)
public.POST("/link", CheckAccount, Link)
//path.POST("/link",middlewares.Auth, controllers.Link)
public.GET("/settings", controllers.GetSettingsPublic)
} }
admin := api.Group("/admin") admin := api.Group("/admin")
{ {
admin.Use(Auth) admin.Use(middlewares.Auth)
admin.GET("/login", Login) admin.GET("/login", common.Login)
admin.GET("/settings", GetSettings) admin.GET("/settings", controllers.GetSettings)
admin.POST("/settings", SaveSettings) admin.POST("/settings", controllers.SaveSettings)
admin.POST("/account/create", CreateAccount) admin.DELETE("/setting", controllers.DeleteSetting)
admin.POST("/account/save", SaveAccount)
admin.GET("/accounts", GetAccounts)
admin.DELETE("/account", DeleteAccount)
admin.GET("/drivers", GetDrivers)
admin.GET("/clear_cache", ClearCache)
admin.GET("/metas", GetMetas) admin.POST("/account/create", controllers.CreateAccount)
admin.POST("/meta/create", CreateMeta) admin.POST("/account/save", controllers.SaveAccount)
admin.POST("/meta/save", SaveMeta) admin.GET("/accounts", controllers.GetAccounts)
admin.DELETE("/meta", DeleteMeta) 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) Static(r)
WebDav(r) WebDav(r)

View File

@ -8,7 +8,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "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/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -21,28 +21,28 @@ import (
type FileSystem struct{} type FileSystem struct{}
func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) { func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) {
var path, name string var internalPath, name string
switch model.AccountsCount() { switch model.AccountsCount() {
case 0: case 0:
return nil, "", nil, fmt.Errorf("no accounts,please add one first") return nil, "", nil, fmt.Errorf("no accounts,please add one first")
case 1: case 1:
path = rawPath internalPath = rawPath
break break
default: default:
paths := strings.Split(rawPath, "/") paths := strings.Split(rawPath, "/")
path = "/" + strings.Join(paths[2:], "/") internalPath = "/" + strings.Join(paths[2:], "/")
name = paths[1] name = paths[1]
} }
account, ok := model.GetAccount(name) account, ok := model.GetAccount(name)
if !ok { if !ok {
return nil, "", nil, fmt.Errorf("no [%s] account", name) return nil, "", nil, fmt.Errorf("no [%s] account", name)
} }
driver, ok := drivers.GetDriver(account.Type) driver, ok := base.GetDriver(account.Type)
if !ok { if !ok {
return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type) return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
} }
return &account, path, driver, nil return &account, internalPath, driver, nil
} }
func (fs *FileSystem) File(rawPath string) (*model.File, error) { func (fs *FileSystem) File(rawPath string) (*model.File, error) {
@ -80,26 +80,26 @@ func (fs *FileSystem) Files(rawPath string) ([]model.File, error) {
return driver.Files(path_, account) return driver.Files(path_, account)
} }
func GetPW(path string) string { //func GetPW(path string, name string) string {
if !conf.CheckDown { // if !conf.CheckDown {
return "" // return ""
} // }
meta, err := model.GetMetaByPath(path) // meta, err := model.GetMetaByPath(path)
if err == nil { // if err == nil {
if meta.Password != "" { // if meta.Password != "" {
utils.Get16MD5Encode(meta.Password) // return utils.SignWithPassword(name, meta.Password)
} // }
return "" // return ""
} else { // } else {
if !conf.CheckParent { // if !conf.CheckParent {
return "" // return ""
} // }
if path == "/" { // if path == "/" {
return "" // return ""
} // }
return GetPW(utils.Dir(path)) // return GetPW(utils.Dir(path), name)
} // }
} //}
func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) { func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) {
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
@ -119,18 +119,73 @@ func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) {
if driver.Config().OnlyProxy || account.WebdavProxy { if driver.Config().OnlyProxy || account.WebdavProxy {
link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath) link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath)
if conf.CheckDown { if conf.CheckDown {
pw := GetPW(utils.Dir(rawPath)) sign := utils.SignWithToken(utils.Base(rawPath), conf.Token)
link += "?pw" + pw link += "?sign" + sign
} }
} else { } else {
link, err = driver.Link(path_, account) link_, err := driver.Link(path_, account)
if err != nil {
return "", err
}
link = link_.Url
} }
log.Debugf("webdav get link: %s", link) log.Debugf("webdav get link: %s", link)
return link, err return link, err
} }
func (fs *FileSystem) CreateDirectory(ctx context.Context, reqPath string) (interface{}, error) { func (fs *FileSystem) CreateDirectory(ctx context.Context, rawPath string) error {
return nil, nil 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 // slashClean is equivalent to but slightly more efficient than
@ -145,16 +200,55 @@ func slashClean(name string) string {
// moveFiles moves files and/or directories from src to dst. // moveFiles moves files and/or directories from src to dst.
// //
// See section 9.9.4 for when various HTTP status codes apply. // 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 return http.StatusNoContent, nil
} }
// copyFiles copies files and/or directories from src to dst. // copyFiles copies files and/or directories from src to dst.
// //
// See section 9.8.5 for when various HTTP status codes apply. // 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 return http.StatusNoContent, nil
} }

View File

@ -253,26 +253,11 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request, fs *FileS
return status, err return status, err
} }
defer release() defer release()
err = fs.Delete(reqPath)
//ctx := r.Context() if err != nil {
return http.StatusMethodNotAllowed, err
//// 尝试作为文件删除 }
//if ok, file := fs.IsFileExist(reqPath); ok { return http.StatusNoContent, nil
// 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
} }
// OK // OK
@ -291,7 +276,13 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *FileSyst
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() 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 { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
@ -321,7 +312,7 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request, fs *FileSy
// ctx = context.WithValue(ctx, fsctx.IgnoreDirectoryConflictCtx, true) // 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.StatusConflict, err
} }
return http.StatusCreated, nil return http.StatusCreated, nil
@ -361,7 +352,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *Fil
ctx := r.Context() ctx := r.Context()
isExist, target := isPathExist(ctx, fs, src) isExist, _ := isPathExist(ctx, fs, src)
if !isExist { if !isExist {
return http.StatusNotFound, nil return http.StatusNotFound, nil
@ -390,7 +381,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *Fil
return http.StatusBadRequest, errInvalidDepth 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只包含源文件 // windows下某些情况下网盘根目录下Office保存文件时附带的锁token只包含源文件
@ -409,7 +400,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *Fil
return http.StatusBadRequest, errInvalidDepth 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 // OK

View File

@ -105,4 +105,12 @@ func Dir(path string) string {
return path return path
} }
return path[:idx] return path[:idx]
}
func Base(path string) string {
idx := strings.LastIndex(path, "/")
if idx == -1 {
return path
}
return path[idx+1:]
} }

View File

@ -3,6 +3,7 @@ package utils
import ( import (
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"fmt"
) )
// GetMD5Encode // GetMD5Encode
@ -16,3 +17,11 @@ func GetMD5Encode(data string) string {
func Get16MD5Encode(data string) string { func Get16MD5Encode(data string) string {
return GetMD5Encode(data)[8:24] 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))
}