Compare commits

..

40 Commits

Author SHA1 Message Date
cc62cc99d2 🐛 fix plist ipa name 2022-02-18 19:01:30 +08:00
270349f37c 🐛 fix write status sequence 2022-02-18 18:58:02 +08:00
977888070a 🐛 close #558 fix local file can't download 2022-02-18 18:50:01 +08:00
192d0f2bf3 🐛 fix update can't start 2022-02-18 18:49:40 +08:00
f2ec7884ec 🐛 fix only proxy webdav_direct 2022-02-17 17:49:03 +08:00
815975a4d2 🐛 fix 139 delete dir 2022-02-17 17:37:45 +08:00
f96a0238fc 👷 change issues-month-statistics 2022-02-17 17:29:45 +08:00
f695bd0959 🔧 change bundle-version 2022-02-17 16:54:34 +08:00
efe8f46e17 ✏️ fix typo 2022-02-17 11:56:00 +08:00
515daa22a9 🐛 fix #551 add S3ForcePathStyle config 2022-02-17 11:30:34 +08:00
f11e22deaf 🚧 change base64 characters 2022-02-17 09:14:21 +08:00
5be976169f 🚧 fix bundle-identifier 2022-02-17 00:44:30 +08:00
a6e08f3bf4 📝 update readme 2022-02-17 00:08:37 +08:00
944e68a979 🚧 plist generate 2022-02-17 00:06:10 +08:00
b3a6e33ce1 🎇 quark support 2022-02-16 20:20:39 +08:00
cb53ddc8e8 🐛 fix nil pointer 2022-02-16 16:10:39 +08:00
693417be4f 🔧 add hide files setting item 2022-02-15 14:51:50 +08:00
5c3f91bb55 support empty password 2022-02-15 14:42:24 +08:00
8a219d0732 👷 add issue bot 2022-02-14 20:03:42 +08:00
146a544af3 ✏️ fix typo 2022-02-14 19:43:43 +08:00
48dccc6c0b 📝 update readme 2022-02-14 15:41:20 +08:00
ce1740cec4 ✏️ fix typo 2022-02-14 15:36:21 +08:00
d40dbeae3e 💬 add issue template 2022-02-14 15:33:48 +08:00
5094b673c4 👷 add some issue bot 2022-02-14 15:32:25 +08:00
228e6d10e7 🔧 close #519 customize temp dir 2022-02-14 15:06:57 +08:00
e90b979d15 close #535 request set timeout 2022-02-14 14:59:00 +08:00
fb05a6ca48 🐛 fix #533 only encode fileName 2022-02-14 14:31:09 +08:00
e055ed3afa 🎇 lanzou proxy add user-agent 2022-02-13 17:46:07 +08:00
4371c470b3 webdav direct proxy 2022-02-13 15:57:42 +08:00
7bb237d0ef 🐛 fix #527 189 upload file name contains % 2022-02-13 13:03:34 +08:00
5c42354b01 🐛 fix #527 189 upload name contains + 2022-02-12 20:27:38 +08:00
387e8af422 🐛 fix 189 upload while filename contains & 2022-02-12 13:08:46 +08:00
5dca777caf 🔒 fix baidu direct link 2022-02-12 12:04:10 +08:00
0814778a14 🐛 fix no account error while only one 2022-02-11 17:11:02 +08:00
6827af3997 🎇 close #522 hide account for guest webdav 2022-02-11 16:19:55 +08:00
435bdea8f7 🔧 #523 add some default setting 2022-02-11 16:17:07 +08:00
4f81735af6 🎇 close #512 favicon redirect 2022-02-08 18:07:13 +08:00
bef3d2f88d 🐛 Fix the temp folder is not created at the first startup 2022-02-08 16:22:53 +08:00
ba99c7dc03 🐛 fix multiple accounts with the same prefix cannot be load balanced 2022-02-08 16:02:47 +08:00
f5c5162a9b load balance 2022-02-08 15:51:58 +08:00
48 changed files with 1350 additions and 187 deletions

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

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

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

View File

@ -38,6 +38,7 @@ English | [中文](./README_cn.md)
- [x] [139yun](https://yun.139.com/) (Personal, Family) - [x] [139yun](https://yun.139.com/) (Personal, Family)
- [x] [Yandex.Disk](https://disk.yandex.com/) - [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [Baidu Disk](http://pan.baidu.com/) - [x] [Baidu Disk](http://pan.baidu.com/)
- [x] [Quark](https://pan.quark.cn)
- [x] Easy to deploy and out-of-the-box - [x] Easy to deploy and out-of-the-box
- [x] File preview (PDF, markdown, code, plain text, ...) - [x] File preview (PDF, markdown, code, plain text, ...)
- [x] Image preview in gallery mode - [x] Image preview in gallery mode
@ -57,7 +58,7 @@ English | [中文](./README_cn.md)
## Discussion ## Discussion
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports only.** Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports and feature request only.**
## Demo ## Demo

View File

@ -36,7 +36,8 @@
- [x] [分秒帧](https://www.mediatrack.cn/) - [x] [分秒帧](https://www.mediatrack.cn/)
- [x] [和彩云](https://yun.139.com/) (个人云, 家庭云) - [x] [和彩云](https://yun.139.com/) (个人云, 家庭云)
- [x] [Yandex.Disk](https://disk.yandex.com/) - [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [百度](http://pan.baidu.com/) - [x] [百度网盘](http://pan.baidu.com/)
- [x] [夸克网盘](https://pan.quark.cn)
- [x] 部署方便,开箱即用 - [x] 部署方便,开箱即用
- [x] 文件预览PDF、markdown、代码、纯文本…… - [x] 文件预览PDF、markdown、代码、纯文本……
- [x] 画廊模式下的图像预览 - [x] 画廊模式下的图像预览
@ -56,7 +57,7 @@
## 讨论 ## 讨论
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) **issue仅针对错误报告。** 一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) **issue仅针对错误报告和功能请求。**
## 演示 ## 演示

View File

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

View File

@ -65,16 +65,23 @@ func InitSettings() {
Description: "text type extensions", Description: "text type extensions",
Group: model.FRONT, Group: model.FRONT,
}, },
//{
// Key: "hide readme file",
// Value: "true",
// Type: "bool",
// Description: "hide readme file? ",
// Group: model.FRONT,
//},
{ {
Key: "hide readme file", Key: "hide files",
Value: "true", Value: "/\\/README.md/i",
Type: "bool", Type: "text",
Description: "hide readme file? ", Description: "hide files, support RegExp, one per line",
Group: model.FRONT, Group: model.FRONT,
}, },
{ {
Key: "music cover", Key: "music cover",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png", Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/circle_center.svg",
Description: "music cover image", Description: "music cover image",
Type: "string", Type: "string",
Access: model.PUBLIC, Access: model.PUBLIC,

View File

@ -30,6 +30,7 @@ type Config struct {
Database Database `json:"database"` Database Database `json:"database"`
Scheme Scheme `json:"scheme"` Scheme Scheme `json:"scheme"`
Cache CacheConfig `json:"cache"` Cache CacheConfig `json:"cache"`
TempDir string `json:"temp_dir"`
} }
func DefaultConfig() *Config { func DefaultConfig() *Config {
@ -37,6 +38,7 @@ func DefaultConfig() *Config {
Address: "0.0.0.0", Address: "0.0.0.0",
Port: 5244, Port: 5244,
Assets: "jsdelivr", Assets: "jsdelivr",
TempDir: "data/temp",
Database: Database{ Database: Database{
Type: "sqlite3", Type: "sqlite3",
Port: 0, Port: 0,

View File

@ -78,7 +78,7 @@ var (
"check parent folder", "check down link", "WebDAV username", "WebDAV password", "check parent folder", "check down link", "WebDAV username", "WebDAV password",
"Visitor WebDAV username", "Visitor WebDAV password", "Visitor WebDAV username", "Visitor WebDAV password",
"default page size", "load type", "default page size", "load type",
"ocr api", "ocr api", "favicon",
} }
) )

View File

@ -57,6 +57,7 @@ func (driver Pan123) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "name,fileId,updateAt,createAt", Values: "name,fileId,updateAt,createAt",
Required: true, Required: true,
Default: "name",
}, },
{ {
Name: "order_direction", Name: "order_direction",
@ -64,6 +65,7 @@ func (driver Pan123) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "asc,desc", Values: "asc,desc",
Required: true, Required: true,
Default: "asc",
}, },
} }
} }
@ -298,7 +300,7 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
return base.ErrNotFolder return base.ErrNotFolder
} }
parentFileId, _ := strconv.Atoi(parentFile.Id) parentFileId, _ := strconv.Atoi(parentFile.Id)
tempFile, err := ioutil.TempFile("data/temp", "file-*") tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil { if err != nil {
return err return err
} }

View File

@ -334,7 +334,7 @@ func (driver Cloud139) Delete(path string, account *model.Account) error {
"taskInfo": base.Json{ "taskInfo": base.Json{
"newCatalogID": "", "newCatalogID": "",
"contentInfoList": contentInfoList, "contentInfoList": contentInfoList,
"catalogInfoList": contentInfoList, "catalogInfoList": catalogInfoList,
}, },
"commonAccountInfo": base.Json{ "commonAccountInfo": base.Json{
"account": account.Username, "account": account.Username,

View File

@ -107,6 +107,7 @@ func (driver Cloud189) Login(account *model.Account) error {
//cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) //cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
client = resty.New() client = resty.New()
//client.SetCookieJar(cookieJar) //client.SetCookieJar(cookieJar)
client.SetTimeout(base.DefaultTimeout)
client.SetRetryCount(3) client.SetRetryCount(3)
client.SetHeader("Referer", "https://cloud.189.cn/") client.SetHeader("Referer", "https://cloud.189.cn/")
} }
@ -524,7 +525,7 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
} }
res, err := driver.UploadRequest("/person/initMultiUpload", map[string]string{ res, err := driver.UploadRequest("/person/initMultiUpload", map[string]string{
"parentFolderId": parentFile.Id, "parentFolderId": parentFile.Id,
"fileName": file.Name, "fileName": encode(file.Name),
"fileSize": strconv.FormatInt(int64(file.Size), 10), "fileSize": strconv.FormatInt(int64(file.Size), 10),
"sliceSize": strconv.FormatInt(int64(DEFAULT), 10), "sliceSize": strconv.FormatInt(int64(DEFAULT), 10),
"lazyCheck": "1", "lazyCheck": "1",

View File

@ -171,7 +171,12 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
if err != nil { if err != nil {
return nil, err return nil, err
} }
link := base.Link{} link := base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
{Name: "Authorization", Value: ""},
},
}
if res.StatusCode() == 302 { if res.StatusCode() == 302 {
link.Url = res.Header().Get("location") link.Url = res.Header().Get("location")
} else { } else {

View File

@ -115,12 +115,24 @@ func EncodeParam(v url.Values) string {
} }
buf.WriteString(k) buf.WriteString(k)
buf.WriteByte('=') buf.WriteByte('=')
//if k == "fileName" {
// buf.WriteString(encode(v))
//} else {
buf.WriteString(v) buf.WriteString(v)
//}
} }
} }
return buf.String() return buf.String()
} }
func encode(str string) string {
//str = strings.ReplaceAll(str, "%", "%25")
//str = strings.ReplaceAll(str, "&", "%26")
//str = strings.ReplaceAll(str, "+", "%2B")
//return str
return url.QueryEscape(str)
}
func AesEncrypt(data, key []byte) []byte { func AesEncrypt(data, key []byte) []byte {
block, _ := aes.NewCipher(key) block, _ := aes.NewCipher(key)
if block == nil { if block == nil {

View File

@ -236,6 +236,7 @@ func (driver AliDrive) batch(srcId, dstId string, account *model.Account) error
func init() { func init() {
base.RegisterDriver(&AliDrive{}) base.RegisterDriver(&AliDrive{})
aliClient. aliClient.
SetTimeout(base.DefaultTimeout).
SetRetryCount(3). SetRetryCount(3).
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"). SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36").
SetHeader("content-type", "application/json"). SetHeader("content-type", "application/json").

View File

@ -15,6 +15,7 @@ import (
_ "github.com/Xhofe/alist/drivers/native" _ "github.com/Xhofe/alist/drivers/native"
_ "github.com/Xhofe/alist/drivers/onedrive" _ "github.com/Xhofe/alist/drivers/onedrive"
_ "github.com/Xhofe/alist/drivers/pikpak" _ "github.com/Xhofe/alist/drivers/pikpak"
_ "github.com/Xhofe/alist/drivers/quark"
_ "github.com/Xhofe/alist/drivers/s3" _ "github.com/Xhofe/alist/drivers/s3"
_ "github.com/Xhofe/alist/drivers/shandian" _ "github.com/Xhofe/alist/drivers/shandian"
_ "github.com/Xhofe/alist/drivers/teambition" _ "github.com/Xhofe/alist/drivers/teambition"

View File

@ -140,13 +140,13 @@ func (driver Baidu) Link(args base.Args, account *model.Account) (*base.Link, er
return nil, err return nil, err
} }
u := fmt.Sprintf("%s&access_token=%s", resp.List[0].Dlink, account.AccessToken) u := fmt.Sprintf("%s&access_token=%s", resp.List[0].Dlink, account.AccessToken)
res, err := base.NoRedirectClient.R().Head(u) res, err := base.NoRedirectClient.R().SetHeader("User-Agent", "pan.baidu.com").Head(u)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if res.StatusCode() == 302 { //if res.StatusCode() == 302 {
u = res.Header().Get("location") u = res.Header().Get("location")
} //}
return &base.Link{ return &base.Link{
Url: u, Url: u,
Headers: []base.Header{ Headers: []base.Header{
@ -234,7 +234,7 @@ func (driver Baidu) Upload(file *model.FileStream, account *model.Account) error
if file == nil { if file == nil {
return base.ErrEmptyFile return base.ErrEmptyFile
} }
tempFile, err := ioutil.TempFile("data/temp", "file-*") tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil { if err != nil {
return err return err
} }

View File

@ -5,6 +5,7 @@ import (
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net/http" "net/http"
"time"
) )
type DriverConfig struct { type DriverConfig struct {
@ -86,17 +87,19 @@ func GetDriversMap() map[string]Driver {
func GetDrivers() map[string][]Item { func GetDrivers() map[string][]Item {
res := make(map[string][]Item) res := make(map[string][]Item)
for k, v := range driversMap { for k, v := range driversMap {
webdavDirect := Item{
Name: "webdav_direct",
Label: "webdav direct",
Type: TypeBool,
Required: true,
Description: "Transfer the WebDAV of this account through the native",
}
if v.Config().OnlyProxy { if v.Config().OnlyProxy {
res[k] = v.Items() res[k] = append([]Item{
webdavDirect,
}, v.Items()...)
} else { } else {
res[k] = append([]Item{ res[k] = append([]Item{
//{
// Name: "allow_proxy",
// Label: "allow_proxy",
// Type: TypeBool,
// Required: true,
// Description: "allow proxy",
//},
{ {
Name: "proxy", Name: "proxy",
Label: "proxy", Label: "proxy",
@ -111,6 +114,7 @@ func GetDrivers() map[string][]Item {
Required: true, Required: true,
Description: "Transfer the WebDAV of this account through the server", Description: "Transfer the WebDAV of this account through the server",
}, },
webdavDirect,
}, v.Items()...) }, v.Items()...)
} }
res[k] = append([]Item{ res[k] = append([]Item{
@ -160,6 +164,8 @@ func GetDrivers() map[string][]Item {
var NoRedirectClient *resty.Client var NoRedirectClient *resty.Client
var RestyClient = resty.New() var RestyClient = resty.New()
var HttpClient = &http.Client{} var HttpClient = &http.Client{}
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
var DefaultTimeout = time.Second * 20
func init() { func init() {
NoRedirectClient = resty.New().SetRedirectPolicy( NoRedirectClient = resty.New().SetRedirectPolicy(
@ -167,8 +173,8 @@ func init() {
return http.ErrUseLastResponse return http.ErrUseLastResponse
}), }),
) )
userAgent := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" NoRedirectClient.SetHeader("user-agent", UserAgent)
NoRedirectClient.SetHeader("user-agent", userAgent) RestyClient.SetHeader("user-agent", UserAgent)
RestyClient.SetHeader("user-agent", userAgent)
RestyClient.SetRetryCount(3) RestyClient.SetRetryCount(3)
RestyClient.SetTimeout(DefaultTimeout)
} }

View File

@ -47,4 +47,5 @@ type Link struct {
Url string `json:"url"` Url string `json:"url"`
Headers []Header `json:"headers"` Headers []Header `json:"headers"`
Data io.ReadCloser Data io.ReadCloser
FilePath string `json:"path"` // for native
} }

View File

@ -137,6 +137,9 @@ func (driver Lanzou) Link(args base.Args, account *model.Account) (*base.Link, e
} }
link := base.Link{ link := base.Link{
Url: url, Url: url,
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
},
} }
return &link, nil return &link, nil
} }

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net/url" "net/url"
"path/filepath" "path/filepath"
@ -15,8 +14,6 @@ import (
"time" "time"
) )
var lanzouClient = resty.New()
type LanZouFile struct { type LanZouFile struct {
Name string `json:"name"` Name string `json:"name"`
NameAll string `json:"name_all"` NameAll string `json:"name_all"`
@ -58,7 +55,7 @@ func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZo
files := make([]LanZouFile, 0) files := make([]LanZouFile, 0)
var resp LanZouFilesResp var resp LanZouFilesResp
// folders // folders
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken). res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{ SetFormData(map[string]string{
"task": "47", "task": "47",
"folder_id": folderId, "folder_id": folderId,
@ -77,7 +74,7 @@ func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZo
// files // files
pg := 1 pg := 1
for { for {
_, err = lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken). _, err = base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{ SetFormData(map[string]string{
"task": "5", "task": "5",
"folder_id": folderId, "folder_id": folderId,
@ -108,7 +105,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
if err != nil { if err != nil {
return nil, err return nil, err
} }
res, err := lanzouClient.R().Get(shareUrl) res, err := base.RestyClient.R().Get(shareUrl)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -129,7 +126,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
pg := 1 pg := 1
for { for {
var resp LanZouFilesResp var resp LanZouFilesResp
res, err = lanzouClient.R().SetResult(&resp).SetFormData(map[string]string{ res, err = base.RestyClient.R().SetResult(&resp).SetFormData(map[string]string{
"lx": lx, "lx": lx,
"fid": fid, "fid": fid,
"uid": uid, "uid": uid,
@ -166,7 +163,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
// GetDownPageId 获取下载页面的ID // GetDownPageId 获取下载页面的ID
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, error) { func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, error) {
var resp LanZouFilesResp var resp LanZouFilesResp
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken). res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{ SetFormData(map[string]string{
"task": "22", "task": "22",
"file_id": fileId, "file_id": fileId,
@ -201,7 +198,7 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
if err != nil { if err != nil {
return "", err return "", err
} }
res, err := lanzouClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId)) res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -210,7 +207,7 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
return "", fmt.Errorf("get down empty page") return "", fmt.Errorf("get down empty page")
} }
iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1]) iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1])
res, err = lanzouClient.R().Get(iframeUrl) res, err = base.RestyClient.R().Get(iframeUrl)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -237,7 +234,7 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
"websignkey": websignkey, "websignkey": websignkey,
} }
log.Debugf("form: %+v", form) log.Debugf("form: %+v", form)
res, err = lanzouClient.R().SetResult(&resp). res, err = base.RestyClient.R().SetResult(&resp).
SetHeader("origin", "https://"+u.Host). SetHeader("origin", "https://"+u.Host).
SetHeader("referer", iframeUrl). SetHeader("referer", iframeUrl).
SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host)) SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
@ -253,7 +250,4 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
func init() { func init() {
base.RegisterDriver(&Lanzou{}) base.RegisterDriver(&Lanzou{})
lanzouClient.
SetRetryCount(3).
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
} }

View File

@ -50,6 +50,7 @@ func (driver MediaTrack) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "updated_at,title,size", Values: "updated_at,title,size",
Required: true, Required: true,
Description: "title",
}, },
{ {
Name: "order_direction", Name: "order_direction",
@ -57,6 +58,7 @@ func (driver MediaTrack) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "true,false", Values: "true,false",
Required: true, Required: true,
Default: "false",
}, },
} }
} }
@ -265,7 +267,7 @@ func (driver MediaTrack) Upload(file *model.FileStream, account *model.Account)
if err != nil { if err != nil {
return err return err
} }
tempFile, err := ioutil.TempFile("data/temp", "file-*") tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil { if err != nil {
return err return err
} }

View File

@ -132,7 +132,7 @@ func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, e
return nil, base.ErrNotFile return nil, base.ErrNotFile
} }
link := base.Link{ link := base.Link{
Url: fullPath, FilePath: fullPath,
} }
return &link, nil return &link, nil
} }

View File

@ -45,7 +45,7 @@ var onedriveHostMap = map[string]Host{
func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string { func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string {
path = filepath.Join(account.RootFolder, path) path = filepath.Join(account.RootFolder, path)
log.Debugf(path) //log.Debugf(path)
host, _ := onedriveHostMap[account.Zone] host, _ := onedriveHostMap[account.Zone]
if auth { if auth {
return host.Oauth return host.Oauth
@ -253,7 +253,7 @@ func (driver Onedrive) Request(url string, method int, headers, query, form map[
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug(res.String()) //log.Debug(res.String())
if e.Error.Code != "" { if e.Error.Code != "" {
if e.Error.Code == "InvalidAuthenticationToken" { if e.Error.Code == "InvalidAuthenticationToken" {
err = driver.RefreshToken(account) err = driver.RefreshToken(account)

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

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

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

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

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

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

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

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

View File

@ -79,6 +79,18 @@ func (driver S3) Items() []base.Item {
Type: base.TypeString, Type: base.TypeString,
Description: "default empty string", Description: "default empty string",
}, },
{
Name: "bool_1",
Label: "S3ForcePathStyle",
Type: base.TypeBool,
},
{
Name: "internal_type",
Label: "ListObject Version",
Type: base.TypeSelect,
Values: "v1,v2",
Default: "v1",
},
} }
} }
@ -131,8 +143,12 @@ func (driver S3) Files(path string, account *model.Account) ([]model.File, error
cache, err := base.GetCache(path, account) cache, err := base.GetCache(path, account)
if err == nil { if err == nil {
files, _ = cache.([]model.File) files, _ = cache.([]model.File)
} else {
if account.InternalType == "v2" {
files, err = driver.ListV2(path, account)
} else { } else {
files, err = driver.List(path, account) files, err = driver.List(path, account)
}
if err == nil && len(files) > 0 { if err == nil && len(files) > 0 {
_ = base.SetCache(path, files, account) _ = base.SetCache(path, files, account)
} }

View File

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

View File

@ -51,6 +51,7 @@ func (driver Teambition) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "fileName,fileSize,updated,created", Values: "fileName,fileSize,updated,created",
Required: true, Required: true,
Default: "fileName",
}, },
{ {
Name: "order_direction", Name: "order_direction",
@ -58,6 +59,7 @@ func (driver Teambition) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "Asc,Desc", Values: "Asc,Desc",
Required: true, Required: true,
Default: "Asc",
}, },
} }
} }

View File

@ -157,8 +157,10 @@ func (driver Teambition) upload(file *model.FileStream, token string, account *m
func (driver Teambition) chunkUpload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) { func (driver Teambition) chunkUpload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
prefix := "tcs" prefix := "tcs"
referer := "https://www.teambition.com/"
if account.InternalType == "International" { if account.InternalType == "International" {
prefix = "us-tcs" prefix = "us-tcs"
referer = "https://us.teambition.com/"
} }
var newChunk ChunkUpload var newChunk ChunkUpload
_, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token). _, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token).
@ -187,7 +189,7 @@ func (driver Teambition) chunkUpload(file *model.FileStream, token string, accou
res, err := base.RestyClient.R().SetHeaders(map[string]string{ res, err := base.RestyClient.R().SetHeaders(map[string]string{
"Authorization": token, "Authorization": token,
"Content-Type": "application/octet-stream", "Content-Type": "application/octet-stream",
"Referer": "https://www.teambition.com/", "Referer": referer,
}).SetBody(chunkData).Post(u) }).SetBody(chunkData).Post(u)
if err != nil { if err != nil {
return nil, err return nil, err

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@ -15,6 +15,8 @@ func InitApiRouter(r *gin.Engine) {
Cors(r) Cors(r)
r.GET("/d/*path", middlewares.DownCheck, controllers.Down) r.GET("/d/*path", middlewares.DownCheck, controllers.Down)
r.GET("/p/*path", middlewares.DownCheck, controllers.Proxy) r.GET("/p/*path", middlewares.DownCheck, controllers.Proxy)
r.GET("/favicon.ico", controllers.Favicon)
r.GET("/i/:data/ipa.plist", controllers.Plist)
api := r.Group("/api") api := r.Group("/api")
public := api.Group("/public") public := api.Group("/public")

View File

@ -48,8 +48,8 @@ func Static(r *gin.Engine) {
r.StaticFS("/assets/", http.FS(assets)) r.StaticFS("/assets/", http.FS(assets))
r.StaticFS("/public/", http.FS(pub)) r.StaticFS("/public/", http.FS(pub))
r.NoRoute(func(c *gin.Context) { r.NoRoute(func(c *gin.Context) {
c.Status(200)
c.Header("Content-Type", "text/html") c.Header("Content-Type", "text/html")
c.Status(200)
if strings.HasPrefix(c.Request.URL.Path, "/@manage") { if strings.HasPrefix(c.Request.URL.Path, "/@manage") {
_, _ = c.Writer.WriteString(conf.ManageHtml) _, _ = c.Writer.WriteString(conf.ManageHtml)
} else { } else {

View File

@ -50,18 +50,17 @@ func (fs *FileSystem) File(rawPath string) (*model.File, error) {
func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) { func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) {
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
var files []model.File
var err error
if model.AccountsCount() > 1 && rawPath == "/" { if model.AccountsCount() > 1 && rawPath == "/" {
files, err := model.GetAccountFiles() files, err = model.GetAccountFiles()
if err != nil { } else {
return nil, err
}
return files, nil
}
account, path_, driver, err := common.ParsePath(rawPath) account, path_, driver, err := common.ParsePath(rawPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
files, err := operate.Files(driver, account, path_) files, err = operate.Files(driver, account, path_)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -96,7 +95,7 @@ func ClientIP(r *http.Request) string {
return "" return ""
} }
func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) { func (fs *FileSystem) Link(w http.ResponseWriter, r *http.Request, rawPath string) (string, error) {
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
log.Debugf("get link path: %s", rawPath) log.Debugf("get link path: %s", rawPath)
if model.AccountsCount() > 1 && rawPath == "/" { if model.AccountsCount() > 1 && rawPath == "/" {
@ -111,6 +110,19 @@ func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) {
if r.TLS != nil { if r.TLS != nil {
protocol = "https" protocol = "https"
} }
// 直接返回
if account.WebdavDirect {
file, err := fs.File(rawPath)
if err != nil {
return "", err
}
link_, err := driver.Link(base.Args{Path: path_}, account)
if err != nil {
return "", err
}
err = common.Proxy(w, r, link_, file)
return "", err
}
if driver.Config().OnlyProxy || account.WebdavProxy { if driver.Config().OnlyProxy || account.WebdavProxy {
link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath) link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath)
if conf.GetBool("check down link") { if conf.GetBool("check down link") {

View File

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

View File

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