Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
b8cf02ca68 | |||
3db798a82a | |||
45cc0cedbd | |||
2efade123e | |||
fc393f743f | |||
0e99e7e9b9 | |||
7a95850c1b | |||
549355bb29 | |||
55aa8ee3b1 | |||
1c22fc367e | |||
5ea8d62aa4 | |||
baebc2fbe9 | |||
8c69260972 | |||
30f992c6a8 | |||
dcaaae366b | |||
284035823f | |||
be8ff92414 | |||
a4c846a424 | |||
451e418b18 | |||
4e13b1a83c | |||
9d2e9887af | |||
dc73c2e97d | |||
a624121095 | |||
9d9c79179b | |||
b7479651e1 | |||
2fc0ccbfe0 | |||
f86ad1dce4 |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://alist.nn.ci/guide/sponsor.html']
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
go-version: [1.18]
|
||||
go-version: [1.19]
|
||||
name: Build
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
1
.github/workflows/issue_close_inactive.yml
vendored
1
.github/workflows/issue_close_inactive.yml
vendored
@ -3,6 +3,7 @@ name: Close inactive
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 */7 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
close-inactive:
|
||||
|
1
.github/workflows/issue_close_question.yml
vendored
1
.github/workflows/issue_close_question.yml
vendored
@ -3,6 +3,7 @@ name: Close need info
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 */7 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
close-need-info:
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
go-version: [1.18]
|
||||
go-version: [1.19]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
@ -7,7 +7,7 @@
|
||||
Prerequisites:
|
||||
|
||||
- [git](https://nodejs.org/zh-cn/)
|
||||
- [Go 1.18+](https://golang.org/doc/install)
|
||||
- [Go 1.19+](https://golang.org/doc/install)
|
||||
- [gcc](https://gcc.gnu.org/)
|
||||
- [nodejs](https://nodejs.org/)
|
||||
|
||||
|
33
README.md
33
README.md
@ -1,30 +1,40 @@
|
||||
<div align="center">
|
||||
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||
<p><em>🗂️A file list program that supports multiple storage, powered by Gin and Solidjs.</em></p>
|
||||
<div>
|
||||
<a href="https://goreportcard.com/report/github.com/alist-org/alist/v3">
|
||||
<img src="https://goreportcard.com/badge/github.com/alist-org/alist/v3" alt="latest version" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Xhofe/alist" alt="License" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/discussions">
|
||||
<img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936" 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" alt="Build status" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/releases">
|
||||
<img src="https://img.shields.io/github/release/Xhofe/alist" alt="latest version" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Xhofe/alist/total?color=%239F7AEA" alt="Downloads" />
|
||||
</a>
|
||||
<a title="Crowdin" target="_blank" href="https://crwd.in/alist">
|
||||
<img src="https://badges.crowdin.net/alist/localized.svg">
|
||||
</a>
|
||||
<a href="https://pay.xhofe.top">
|
||||
<img src="https://img.shields.io/badge/%24-sponsor-ff69b4.svg" alt="sponsor" />
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://github.com/Xhofe/alist/discussions">
|
||||
<img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936" alt="discussions" />
|
||||
</a>
|
||||
<a href="https://discord.gg/F4ymsH4xv2">
|
||||
<img src="https://img.shields.io/discord/1018870125102895134?logo=discord" alt="discussions" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Xhofe/alist/total?color=%239F7AEA&logo=github" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/releases">
|
||||
<img src="https://img.shields.io/docker/pulls/xhofe/alist?color=%2348BB78&logo=docker&label=pulls" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://alist.nn.ci/zh/guide/sponsor.html">
|
||||
<img src="https://img.shields.io/badge/%24-sponsor-F87171.svg" alt="sponsor" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
@ -52,6 +62,11 @@ English | [中文](./README_cn.md) | [Contributors](./CONTRIBUTORS.md) | [Contri
|
||||
- [x] [BaiduNetdisk](http://pan.baidu.com/)
|
||||
- [x] [Quark](https://pan.quark.cn)
|
||||
- [x] [Thunder](https://pan.xunlei.com)
|
||||
- [x] [Lanzou](https://www.lanzou.com/)
|
||||
- [x] [Aliyundrive share](https://www.aliyundrive.com/)
|
||||
- [x] [Google photo](https://photos.google.com/)
|
||||
- [x] [Mega.nz](https://mega.nz)
|
||||
- [x] [Baidu photo](https://photo.baidu.com/)
|
||||
- [x] Easy to deploy and out-of-the-box
|
||||
- [x] File preview (PDF, markdown, code, plain text, ...)
|
||||
- [x] Image preview in gallery mode
|
||||
@ -100,4 +115,4 @@ The `AList` is open-source software licensed under the AGPL-3.0 license.
|
||||
|
||||
---
|
||||
|
||||
> [@Blog](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=YJJj2Gwb)
|
||||
> [@Blog](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@Discord](https://discord.gg/F4ymsH4xv2)
|
33
README_cn.md
33
README_cn.md
@ -1,30 +1,40 @@
|
||||
<div align="center">
|
||||
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||
<p><em>🗂一个支持多存储的文件列表程序,使用 Gin 和 Solidjs。</em></p>
|
||||
<div>
|
||||
<a href="https://goreportcard.com/report/github.com/alist-org/alist/v3">
|
||||
<img src="https://goreportcard.com/badge/github.com/alist-org/alist/v3" alt="latest version" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/Xhofe/alist" alt="License" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/discussions">
|
||||
<img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936" 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" alt="Build status" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/releases">
|
||||
<img src="https://img.shields.io/github/release/Xhofe/alist" alt="latest version" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Xhofe/alist/total?color=%239F7AEA" alt="Downloads" />
|
||||
</a>
|
||||
<a title="Crowdin" target="_blank" href="https://crwd.in/alist">
|
||||
<img src="https://badges.crowdin.net/alist/localized.svg">
|
||||
</a>
|
||||
<a href="https://pay.xhofe.top">
|
||||
<img src="https://img.shields.io/badge/%24-sponsor-ff69b4.svg" alt="sponsor" />
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://github.com/Xhofe/alist/discussions">
|
||||
<img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936" alt="discussions" />
|
||||
</a>
|
||||
<a href="https://discord.gg/F4ymsH4xv2">
|
||||
<img src="https://img.shields.io/discord/1018870125102895134?logo=discord" alt="discussions" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/releases">
|
||||
<img src="https://img.shields.io/github/downloads/Xhofe/alist/total?color=%239F7AEA&logo=github" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://github.com/Xhofe/alist/releases">
|
||||
<img src="https://img.shields.io/docker/pulls/xhofe/alist?color=%2348BB78&logo=docker&label=pulls" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://alist.nn.ci/zh/guide/sponsor.html">
|
||||
<img src="https://img.shields.io/badge/%24-sponsor-F87171.svg" alt="sponsor" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
@ -52,6 +62,11 @@
|
||||
- [x] [百度网盘](http://pan.baidu.com/)
|
||||
- [x] [夸克网盘](https://pan.quark.cn)
|
||||
- [x] [迅雷网盘](https://pan.xunlei.com)
|
||||
- [x] [蓝奏云](https://www.lanzou.com/)
|
||||
- [x] [阿里云盘分享](https://www.aliyundrive.com/)
|
||||
- [x] [谷歌相册](https://photos.google.com/)
|
||||
- [x] [Mega.nz](https://mega.nz)
|
||||
- [x] [一刻相册](https://photo.baidu.com/)
|
||||
- [x] 部署方便,开箱即用
|
||||
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
||||
- [x] 画廊模式下的图像预览
|
||||
@ -100,4 +115,4 @@
|
||||
|
||||
---
|
||||
|
||||
> [@博客](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@Telegram群](https://t.me/alist_chat) · [@QQ群](https://jq.qq.com/?_wv=1027&k=YJJj2Gwb)
|
||||
> [@博客](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@Telegram群](https://t.me/alist_chat) · [@Discord](https://discord.gg/F4ymsH4xv2)
|
3
build.sh
3
build.sh
@ -12,7 +12,8 @@ else
|
||||
webVersion=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
||||
fi
|
||||
|
||||
echo "build version: $gitTag"
|
||||
echo "backend version: $version"
|
||||
echo "frontend version: $webVersion"
|
||||
|
||||
ldflags="\
|
||||
-w -s \
|
||||
|
@ -61,7 +61,7 @@ the address is defined in config file`,
|
||||
<-quit
|
||||
utils.Log.Println("Shutdown Server ...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
utils.Log.Fatal("Server Shutdown:", err)
|
||||
@ -69,7 +69,7 @@ the address is defined in config file`,
|
||||
// catching ctx.Done(). timeout of 3 seconds.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
utils.Log.Println("timeout of 3 seconds.")
|
||||
utils.Log.Println("timeout of 1 seconds.")
|
||||
}
|
||||
utils.Log.Println("Server exiting")
|
||||
},
|
||||
|
@ -110,7 +110,9 @@ func ParseHttpHeader(str string) map[string]string {
|
||||
header := make(map[string]string)
|
||||
for _, value := range strings.Split(str, "&") {
|
||||
i := strings.Index(value, "=")
|
||||
header[strings.TrimSpace(value[0:i])] = strings.TrimSpace(value[i+1:])
|
||||
if i > 0 {
|
||||
header[strings.TrimSpace(value[0:i])] = strings.TrimSpace(value[i+1:])
|
||||
}
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ type Addition struct {
|
||||
Type string `json:"type" type:"select" options:"personal,family" default:"personal"`
|
||||
FamilyID string `json:"family_id"`
|
||||
RapidUpload bool `json:"rapid_upload"`
|
||||
NonuseOrc bool `json:"nonuse_orc"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -186,20 +186,19 @@ func (y *Yun189PC) getFiles(ctx context.Context, fileId string) ([]model.Obj, er
|
||||
|
||||
func (y *Yun189PC) login() (err error) {
|
||||
// 初始化登陆所需参数
|
||||
if y.loginParam == nil {
|
||||
if y.loginParam == nil || !y.NonuseOrc {
|
||||
if err = y.initLoginParam(); err != nil {
|
||||
// 验证码也通过错误返回
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// 销毁验证码
|
||||
y.VCode = ""
|
||||
// 销毁登陆参数
|
||||
y.loginParam = nil
|
||||
// 遇到错误,重新加载登陆参数
|
||||
if err != nil {
|
||||
if err != nil && y.NonuseOrc {
|
||||
if err1 := y.initLoginParam(); err1 != nil {
|
||||
err = fmt.Errorf("err1: %s \nerr2: %s", err, err1)
|
||||
}
|
||||
@ -303,44 +302,34 @@ func (y *Yun189PC) initLoginParam() error {
|
||||
param.jRsaKey = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", encryptConf.Data.PubKey)
|
||||
param.RsaUsername = encryptConf.Data.Pre + RsaEncrypt(param.jRsaKey, y.Username)
|
||||
param.RsaPassword = encryptConf.Data.Pre + RsaEncrypt(param.jRsaKey, y.Password)
|
||||
|
||||
// 判断是否需要验证码
|
||||
res, err = y.client.R().
|
||||
SetFormData(map[string]string{
|
||||
"appKey": APP_ID,
|
||||
"accountType": ACCOUNT_TYPE,
|
||||
"userName": param.RsaUsername,
|
||||
}).
|
||||
Post(AUTH_URL + "/api/logbox/oauth2/needcaptcha.do")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
y.loginParam = ¶m
|
||||
if res.String() != "0" {
|
||||
imgRes, err := y.client.R().
|
||||
SetQueryParams(map[string]string{
|
||||
"token": param.CaptchaToken,
|
||||
"REQID": param.ReqId,
|
||||
"rnd": fmt.Sprint(timestamp()),
|
||||
}).
|
||||
Get(AUTH_URL + "/api/logbox/oauth2/picCaptcha.do")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to obtain verification code")
|
||||
|
||||
imgRes, err := y.client.R().
|
||||
SetQueryParams(map[string]string{
|
||||
"token": param.CaptchaToken,
|
||||
"REQID": param.ReqId,
|
||||
"rnd": fmt.Sprint(timestamp()),
|
||||
}).
|
||||
Get(AUTH_URL + "/api/logbox/oauth2/picCaptcha.do")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to obtain verification code")
|
||||
}
|
||||
if imgRes.Size() > 20 {
|
||||
if setting.GetStr(conf.OcrApi) != "" && !y.NonuseOrc {
|
||||
vRes, err := base.RestyClient.R().
|
||||
SetMultipartField("image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
|
||||
Post(setting.GetStr(conf.OcrApi))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if jsoniter.Get(vRes.Body(), "status").ToInt() == 200 {
|
||||
y.VCode = jsoniter.Get(vRes.Body(), "result").ToString()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试使用ocr
|
||||
vRes, err := base.RestyClient.R().
|
||||
SetMultipartField("image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
|
||||
Post(setting.GetStr(conf.OcrApi))
|
||||
if err == nil && jsoniter.Get(vRes.Body(), "status").ToInt() == 200 {
|
||||
y.VCode = jsoniter.Get(vRes.Body(), "result").ToString()
|
||||
}
|
||||
|
||||
// ocr无法处理,返回验证码图片给前端
|
||||
if len(y.VCode) != 4 {
|
||||
return fmt.Errorf("need validate code: data:image/png;base64,%s", base64.StdEncoding.EncodeToString(res.Body()))
|
||||
}
|
||||
// 返回验证码图片给前端
|
||||
return fmt.Errorf(`need img validate code: <img src="data:image/png;base64,%s"/>`, base64.StdEncoding.EncodeToString(imgRes.Body()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -396,6 +385,7 @@ func (y *Yun189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
const DEFAULT int64 = 10485760
|
||||
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||
|
||||
requestID := uuid.NewString()
|
||||
params := Params{
|
||||
"parentFolderId": dstDir.GetID(),
|
||||
"fileName": url.QueryEscape(file.GetName()),
|
||||
@ -417,6 +407,7 @@ func (y *Yun189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
var initMultiUpload InitMultiUploadResp
|
||||
_, err = y.request(fullUrl+"/initMultiUpload", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, params, &initMultiUpload)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -451,6 +442,7 @@ func (y *Yun189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
_, err = y.request(fullUrl+"/getMultiUploadUrls", http.MethodGet,
|
||||
func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, Params{
|
||||
"partInfo": fmt.Sprintf("%d-%s", i, silceMd5Base64),
|
||||
"uploadFileId": initMultiUpload.Data.UploadFileID,
|
||||
@ -486,6 +478,7 @@ func (y *Yun189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
_, err = y.request(fullUrl+"/commitMultiUploadFile", http.MethodGet,
|
||||
func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, Params{
|
||||
"uploadFileId": initMultiUpload.Data.UploadFileID,
|
||||
"fileMd5": fileMd5Hex,
|
||||
@ -542,6 +535,7 @@ func (y *Yun189PC) FastUpload(ctx context.Context, dstDir model.Obj, file model.
|
||||
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
|
||||
}
|
||||
|
||||
requestID := uuid.NewString()
|
||||
// 检测是否支持快传
|
||||
params := Params{
|
||||
"parentFolderId": dstDir.GetID(),
|
||||
@ -564,6 +558,7 @@ func (y *Yun189PC) FastUpload(ctx context.Context, dstDir model.Obj, file model.
|
||||
var uploadInfo InitMultiUploadResp
|
||||
_, err = y.request(fullUrl+"/initMultiUpload", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, params, &uploadInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -575,6 +570,7 @@ func (y *Yun189PC) FastUpload(ctx context.Context, dstDir model.Obj, file model.
|
||||
_, err = y.request(fullUrl+"/getMultiUploadUrls", http.MethodGet,
|
||||
func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, Params{
|
||||
"uploadFileId": uploadInfo.Data.UploadFileID,
|
||||
"partInfo": strings.Join(silceMd5Base64s, ","),
|
||||
@ -611,6 +607,7 @@ func (y *Yun189PC) FastUpload(ctx context.Context, dstDir model.Obj, file model.
|
||||
_, err = y.request(fullUrl+"/commitMultiUploadFile", http.MethodGet,
|
||||
func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, Params{
|
||||
"uploadFileId": uploadInfo.Data.UploadFileID,
|
||||
"isLog": "0",
|
||||
|
@ -11,9 +11,11 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/baidu_photo"
|
||||
_ "github.com/alist-org/alist/v3/drivers/ftp"
|
||||
_ "github.com/alist-org/alist/v3/drivers/google_drive"
|
||||
_ "github.com/alist-org/alist/v3/drivers/google_photo"
|
||||
_ "github.com/alist-org/alist/v3/drivers/lanzou"
|
||||
_ "github.com/alist-org/alist/v3/drivers/local"
|
||||
_ "github.com/alist-org/alist/v3/drivers/mediatrack"
|
||||
_ "github.com/alist-org/alist/v3/drivers/mega"
|
||||
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
||||
_ "github.com/alist-org/alist/v3/drivers/pikpak"
|
||||
_ "github.com/alist-org/alist/v3/drivers/quark"
|
||||
|
@ -33,7 +33,7 @@ func fileToObj(f File) *model.ObjThumb {
|
||||
ID: f.Id,
|
||||
Name: f.Name,
|
||||
Size: size,
|
||||
Modified: time.Time{},
|
||||
Modified: f.ModifiedTime,
|
||||
IsFolder: f.MimeType == "application/vnd.google-apps.folder",
|
||||
},
|
||||
Thumbnail: model.Thumbnail{},
|
||||
|
170
drivers/google_photo/driver.go
Normal file
170
drivers/google_photo/driver.go
Normal file
@ -0,0 +1,170 @@
|
||||
package google_photo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type GooglePhoto struct {
|
||||
model.Storage
|
||||
Addition
|
||||
AccessToken string
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) GetAddition() driver.Additional {
|
||||
return d.Addition
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) Init(ctx context.Context, storage model.Storage) error {
|
||||
d.Storage = storage
|
||||
err := utils.Json.UnmarshalFromString(d.Storage.Addition, &d.Addition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.refreshToken()
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
files, err := d.getFiles(dir.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SliceConvert(files, func(src MediaItem) (model.Obj, error) {
|
||||
return fileToObj(src), nil
|
||||
})
|
||||
}
|
||||
|
||||
//func (d *GooglePhoto) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||
// // this is optional
|
||||
// return nil, errs.NotImplement
|
||||
//}
|
||||
|
||||
func (d *GooglePhoto) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
f, err := d.getMedia(file.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.Contains(f.MimeType, "image/") {
|
||||
return &model.Link{
|
||||
URL: f.BaseURL + "=d",
|
||||
}, nil
|
||||
} else if strings.Contains(f.MimeType, "video/") {
|
||||
return &model.Link{
|
||||
URL: f.BaseURL + "=dv",
|
||||
}, nil
|
||||
}
|
||||
return &model.Link{}, nil
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) Remove(ctx context.Context, obj model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
var e Error
|
||||
// Create resumable upload url
|
||||
postHeaders := map[string]string{
|
||||
"Authorization": "Bearer " + d.AccessToken,
|
||||
"Content-type": "application/octet-stream",
|
||||
"X-Goog-Upload-Command": "start",
|
||||
"X-Goog-Upload-Content-Type": stream.GetMimetype(),
|
||||
"X-Goog-Upload-Protocol": "resumable",
|
||||
"X-Goog-Upload-Raw-Size": strconv.FormatInt(stream.GetSize(), 10),
|
||||
}
|
||||
url := "https://photoslibrary.googleapis.com/v1/uploads"
|
||||
res, err := base.NoRedirectClient.R().SetHeaders(postHeaders).
|
||||
SetError(&e).
|
||||
Post(url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Error.Code != 0 {
|
||||
if e.Error.Code == 401 {
|
||||
err = d.refreshToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.Put(ctx, dstDir, stream, up)
|
||||
}
|
||||
return fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
||||
}
|
||||
|
||||
//Upload to the Google Photo
|
||||
postUrl := res.Header().Get("X-Goog-Upload-URL")
|
||||
//chunkSize := res.Header().Get("X-Goog-Upload-Chunk-Granularity")
|
||||
postHeaders = map[string]string{
|
||||
"X-Goog-Upload-Command": "upload, finalize",
|
||||
"X-Goog-Upload-Offset": "0",
|
||||
}
|
||||
|
||||
resp, err := d.request(postUrl, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(stream.GetReadCloser())
|
||||
}, nil, postHeaders)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//Create MediaItem
|
||||
createItemUrl := "https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate"
|
||||
|
||||
postHeaders = map[string]string{
|
||||
"X-Goog-Upload-Command": "upload, finalize",
|
||||
"X-Goog-Upload-Offset": "0",
|
||||
}
|
||||
|
||||
data := base.Json{
|
||||
"newMediaItems": []base.Json{
|
||||
{
|
||||
"description": "item-description",
|
||||
"simpleMediaItem": base.Json{
|
||||
"fileName": stream.GetName(),
|
||||
"uploadToken": string(resp),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = d.request(createItemUrl, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(data)
|
||||
}, nil, postHeaders)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*GooglePhoto)(nil)
|
30
drivers/google_photo/meta.go
Normal file
30
drivers/google_photo/meta.go
Normal file
@ -0,0 +1,30 @@
|
||||
package google_photo
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootID
|
||||
RefreshToken string `json:"refresh_token" required:"true"`
|
||||
ClientID string `json:"client_id" required:"true" default:"202264815644.apps.googleusercontent.com"`
|
||||
ClientSecret string `json:"client_secret" required:"true" default:"X4Z3ca8xfWDb1Voo-F9a7ZxJ"`
|
||||
ShowArchive bool `json:"show_archive"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "GooglePhoto",
|
||||
OnlyProxy: true,
|
||||
DefaultRoot: "root",
|
||||
NoUpload: true,
|
||||
LocalSort: true,
|
||||
}
|
||||
|
||||
func New() driver.Driver {
|
||||
return &GooglePhoto{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(config, New)
|
||||
}
|
85
drivers/google_photo/types.go
Normal file
85
drivers/google_photo/types.go
Normal file
@ -0,0 +1,85 @@
|
||||
package google_photo
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type TokenError struct {
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
}
|
||||
|
||||
type Items struct {
|
||||
NextPageToken string `json:"nextPageToken"`
|
||||
MediaItems []MediaItem `json:"mediaItems,omitempty"`
|
||||
Albums []MediaItem `json:"albums,omitempty"`
|
||||
SharedAlbums []MediaItem `json:"sharedAlbums,omitempty"`
|
||||
}
|
||||
|
||||
type MediaItem struct {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title,omitempty"`
|
||||
BaseURL string `json:"baseUrl,omitempty"`
|
||||
CoverPhotoBaseUrl string `json:"coverPhotoBaseUrl,omitempty"`
|
||||
MimeType string `json:"mimeType,omitempty"`
|
||||
FileName string `json:"filename,omitempty"`
|
||||
MediaMetadata MediaMetadata `json:"mediaMetadata,omitempty"`
|
||||
}
|
||||
|
||||
type MediaMetadata struct {
|
||||
CreationTime time.Time `json:"creationTime"`
|
||||
Width string `json:"width"`
|
||||
Height string `json:"height"`
|
||||
Photo Photo `json:"photo,omitempty"`
|
||||
Video Video `json:"video,omitempty"`
|
||||
}
|
||||
|
||||
type Photo struct {
|
||||
}
|
||||
|
||||
type Video struct {
|
||||
}
|
||||
|
||||
func fileToObj(f MediaItem) *model.ObjThumb {
|
||||
if !reflect.DeepEqual(f.MediaMetadata, MediaMetadata{}){
|
||||
return &model.ObjThumb{
|
||||
Object: model.Object{
|
||||
ID: f.Id,
|
||||
Name: f.FileName,
|
||||
Size: 0,
|
||||
Modified: f.MediaMetadata.CreationTime,
|
||||
IsFolder: false,
|
||||
},
|
||||
Thumbnail: model.Thumbnail{
|
||||
Thumbnail: f.BaseURL + "=w100-h100-c",
|
||||
},
|
||||
}
|
||||
}
|
||||
return &model.ObjThumb{
|
||||
Object: model.Object{
|
||||
ID: f.Id,
|
||||
Name: f.Title,
|
||||
Size: 0,
|
||||
Modified: time.Time{},
|
||||
IsFolder: true,
|
||||
},
|
||||
Thumbnail: model.Thumbnail{},
|
||||
}
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Error struct {
|
||||
Errors []struct {
|
||||
Domain string `json:"domain"`
|
||||
Reason string `json:"reason"`
|
||||
Message string `json:"message"`
|
||||
LocationType string `json:"location_type"`
|
||||
Location string `json:"location"`
|
||||
}
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
186
drivers/google_photo/util.go
Normal file
186
drivers/google_photo/util.go
Normal file
@ -0,0 +1,186 @@
|
||||
package google_photo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
// do others that not defined in Driver interface
|
||||
|
||||
const (
|
||||
FETCH_ALL = "all"
|
||||
FETCH_ALBUMS = "albums"
|
||||
FETCH_ROOT = "root"
|
||||
FETCH_SHARE_ALBUMS = "share_albums"
|
||||
)
|
||||
|
||||
func (d *GooglePhoto) refreshToken() error {
|
||||
url := "https://www.googleapis.com/oauth2/v4/token"
|
||||
var resp base.TokenResp
|
||||
var e TokenError
|
||||
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
|
||||
SetFormData(map[string]string{
|
||||
"client_id": d.ClientID,
|
||||
"client_secret": d.ClientSecret,
|
||||
"refresh_token": d.RefreshToken,
|
||||
"grant_type": "refresh_token",
|
||||
}).Post(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Error != "" {
|
||||
return fmt.Errorf(e.Error)
|
||||
}
|
||||
d.AccessToken = resp.AccessToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) request(url string, method string, callback base.ReqCallback, resp interface{}, headers map[string]string) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
||||
req.SetHeader("Accept-Encoding", "gzip")
|
||||
if headers != nil {
|
||||
req.SetHeaders(headers)
|
||||
}
|
||||
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
var e Error
|
||||
req.SetError(&e)
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Error.Code != 0 {
|
||||
if e.Error.Code == 401 {
|
||||
err = d.refreshToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.request(url, method, callback, resp, headers)
|
||||
}
|
||||
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) getFiles(id string) ([]MediaItem, error) {
|
||||
switch id {
|
||||
case FETCH_ALL:
|
||||
return d.getAllMedias()
|
||||
case FETCH_ALBUMS:
|
||||
return d.getAlbums()
|
||||
case FETCH_SHARE_ALBUMS:
|
||||
return d.getShareAlbums()
|
||||
case FETCH_ROOT:
|
||||
return d.getFakeRoot()
|
||||
default:
|
||||
return d.getMedias(id)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) getFakeRoot() ([]MediaItem, error) {
|
||||
return []MediaItem{
|
||||
{
|
||||
Id: FETCH_ALL,
|
||||
Title: "全部媒体",
|
||||
},
|
||||
{
|
||||
Id: FETCH_ALBUMS,
|
||||
Title: "全部影集",
|
||||
},
|
||||
{
|
||||
Id: FETCH_SHARE_ALBUMS,
|
||||
Title: "共享影集",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) getAlbums() ([]MediaItem, error) {
|
||||
return d.fetchItems(
|
||||
"https://photoslibrary.googleapis.com/v1/albums",
|
||||
map[string]string{
|
||||
"fields": "albums(id,title,coverPhotoBaseUrl),nextPageToken",
|
||||
"pageSize": "50",
|
||||
"pageToken": "first",
|
||||
},
|
||||
http.MethodGet)
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) getShareAlbums() ([]MediaItem, error) {
|
||||
return d.fetchItems(
|
||||
"https://photoslibrary.googleapis.com/v1/sharedAlbums",
|
||||
map[string]string{
|
||||
"fields": "sharedAlbums(id,title,coverPhotoBaseUrl),nextPageToken",
|
||||
"pageSize": "50",
|
||||
"pageToken": "first",
|
||||
},
|
||||
http.MethodGet)
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) getMedias(albumId string) ([]MediaItem, error) {
|
||||
return d.fetchItems(
|
||||
"https://photoslibrary.googleapis.com/v1/mediaItems:search",
|
||||
map[string]string{
|
||||
"fields": "mediaItems(id,baseUrl,mimeType,mediaMetadata,filename),nextPageToken",
|
||||
"pageSize": "100",
|
||||
"albumId": albumId,
|
||||
"pageToken": "first",
|
||||
}, http.MethodPost)
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) getAllMedias() ([]MediaItem, error) {
|
||||
return d.fetchItems(
|
||||
"https://photoslibrary.googleapis.com/v1/mediaItems",
|
||||
map[string]string{
|
||||
"fields": "mediaItems(id,baseUrl,mimeType,mediaMetadata,filename),nextPageToken",
|
||||
"pageSize": "100",
|
||||
"pageToken": "first",
|
||||
},
|
||||
http.MethodGet)
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) getMedia(id string) (MediaItem, error) {
|
||||
var resp MediaItem
|
||||
|
||||
query := map[string]string{
|
||||
"fields": "baseUrl,mimeType",
|
||||
}
|
||||
_, err := d.request(fmt.Sprintf("https://photoslibrary.googleapis.com/v1/mediaItems/%s", id), http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (d *GooglePhoto) fetchItems(url string, query map[string]string, method string) ([]MediaItem, error){
|
||||
res := make([]MediaItem, 0)
|
||||
for query["pageToken"] != "" {
|
||||
if query["pageToken"] == "first" {
|
||||
query["pageToken"] = ""
|
||||
}
|
||||
var resp Items
|
||||
|
||||
_, err := d.request(url, method, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query["pageToken"] = resp.NextPageToken
|
||||
res = append(res, resp.MediaItems...)
|
||||
res = append(res, resp.Albums...)
|
||||
res = append(res, resp.SharedAlbums...)
|
||||
}
|
||||
return res, nil
|
||||
}
|
192
drivers/mega/driver.go
Normal file
192
drivers/mega/driver.go
Normal file
@ -0,0 +1,192 @@
|
||||
package mega
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/chanio"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/t3rm1n4l/go-mega"
|
||||
)
|
||||
|
||||
type Mega struct {
|
||||
model.Storage
|
||||
Addition
|
||||
c *mega.Mega
|
||||
}
|
||||
|
||||
func (d *Mega) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *Mega) GetAddition() driver.Additional {
|
||||
return d.Addition
|
||||
}
|
||||
|
||||
func (d *Mega) Init(ctx context.Context, storage model.Storage) error {
|
||||
d.Storage = storage
|
||||
err := utils.Json.UnmarshalFromString(d.Storage.Addition, &d.Addition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.c = mega.New()
|
||||
return d.c.Login(d.Email, d.Password)
|
||||
}
|
||||
|
||||
func (d *Mega) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Mega) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
if node, ok := dir.(*MegaNode); ok {
|
||||
nodes, err := d.c.FS.GetChildren(node.Node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]model.Obj, 0)
|
||||
for i := range nodes {
|
||||
n := nodes[i]
|
||||
if n.GetType() == mega.FILE || n.GetType() == mega.FOLDER {
|
||||
res = append(res, &MegaNode{n})
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
log.Errorf("can't convert: %+v", dir)
|
||||
return nil, fmt.Errorf("unable to convert dir to mega node")
|
||||
}
|
||||
|
||||
func (d *Mega) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||
if path == "/" {
|
||||
n := d.c.FS.GetRoot()
|
||||
log.Debugf("mega root: %+v", *n)
|
||||
return &MegaNode{n}, nil
|
||||
}
|
||||
return nil, errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Mega) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
if node, ok := file.(*MegaNode); ok {
|
||||
//link, err := d.c.Link(node.Node, true)
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
//return &model.Link{URL: link}, nil
|
||||
down, err := d.c.NewDownload(node.Node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//u := down.GetResourceUrl()
|
||||
//u = strings.Replace(u, "http", "https", 1)
|
||||
//return &model.Link{URL: u}, nil
|
||||
c := chanio.New()
|
||||
go func() {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
log.Debugf("chunk size: %d", down.Chunks())
|
||||
for id := 0; id < down.Chunks(); id++ {
|
||||
chunk, err := down.DownloadChunk(id)
|
||||
if err != nil {
|
||||
log.Errorf("mega down: %+v", err)
|
||||
return
|
||||
}
|
||||
log.Debugf("id: %d,len: %d", id, len(chunk))
|
||||
//_, _, err = down.ChunkLocation(id)
|
||||
//if err != nil {
|
||||
// log.Errorf("mega down: %+v", err)
|
||||
// return
|
||||
//}
|
||||
//_, err = c.Write(chunk)
|
||||
_, err = c.Write(chunk)
|
||||
}
|
||||
err := c.Close()
|
||||
if err != nil {
|
||||
log.Errorf("mega down: %+v", err)
|
||||
}
|
||||
}()
|
||||
return &model.Link{Data: c}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to convert dir to mega node")
|
||||
}
|
||||
|
||||
func (d *Mega) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
if parentNode, ok := parentDir.(*MegaNode); ok {
|
||||
_, err := d.c.CreateDir(dirName, parentNode.Node)
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("unable to convert dir to mega node")
|
||||
}
|
||||
|
||||
func (d *Mega) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
if srcNode, ok := srcObj.(*MegaNode); ok {
|
||||
if dstNode, ok := dstDir.(*MegaNode); ok {
|
||||
return d.c.Move(srcNode.Node, dstNode.Node)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unable to convert dir to mega node")
|
||||
}
|
||||
|
||||
func (d *Mega) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
if srcNode, ok := srcObj.(*MegaNode); ok {
|
||||
return d.c.Rename(srcNode.Node, newName)
|
||||
}
|
||||
return fmt.Errorf("unable to convert dir to mega node")
|
||||
}
|
||||
|
||||
func (d *Mega) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Mega) Remove(ctx context.Context, obj model.Obj) error {
|
||||
if node, ok := obj.(*MegaNode); ok {
|
||||
return d.c.Delete(node.Node, false)
|
||||
}
|
||||
return fmt.Errorf("unable to convert dir to mega node")
|
||||
}
|
||||
|
||||
func (d *Mega) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
if dstNode, ok := dstDir.(*MegaNode); ok {
|
||||
u, err := d.c.NewUpload(dstNode.Node, stream.GetName(), stream.GetSize())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for id := 0; id < u.Chunks(); id++ {
|
||||
_, chkSize, err := u.ChunkLocation(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chunk := make([]byte, chkSize)
|
||||
n, err := io.ReadFull(stream, chunk)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
if n != len(chunk) {
|
||||
return errors.New("chunk too short")
|
||||
}
|
||||
|
||||
err = u.UploadChunk(id, chunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
up(id * 100 / u.Chunks())
|
||||
}
|
||||
|
||||
_, err = u.Finish()
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("unable to convert dir to mega node")
|
||||
}
|
||||
|
||||
//func (d *Mega) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*Mega)(nil)
|
26
drivers/mega/meta.go
Normal file
26
drivers/mega/meta.go
Normal file
@ -0,0 +1,26 @@
|
||||
package mega
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
// Usually one of two
|
||||
//driver.RootPath
|
||||
//driver.RootID
|
||||
Email string `json:"email" required:"true"`
|
||||
Password string `json:"password" required:"true"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Mega_nz",
|
||||
LocalSort: true,
|
||||
OnlyLocal: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(config, func() driver.Driver {
|
||||
return &Mega{}
|
||||
})
|
||||
}
|
40
drivers/mega/types.go
Normal file
40
drivers/mega/types.go
Normal file
@ -0,0 +1,40 @@
|
||||
package mega
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/t3rm1n4l/go-mega"
|
||||
)
|
||||
|
||||
type MegaNode struct {
|
||||
*mega.Node
|
||||
}
|
||||
|
||||
//func (m *MegaNode) GetSize() int64 {
|
||||
// //TODO implement me
|
||||
// panic("implement me")
|
||||
//}
|
||||
//
|
||||
//func (m *MegaNode) GetName() string {
|
||||
// //TODO implement me
|
||||
// panic("implement me")
|
||||
//}
|
||||
|
||||
func (m *MegaNode) ModTime() time.Time {
|
||||
return m.GetTimeStamp()
|
||||
}
|
||||
|
||||
func (m *MegaNode) IsDir() bool {
|
||||
return m.GetType() == mega.FOLDER || m.GetType() == mega.ROOT
|
||||
}
|
||||
|
||||
func (m *MegaNode) GetID() string {
|
||||
return m.GetHash()
|
||||
}
|
||||
|
||||
func (m *MegaNode) GetPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
var _ model.Obj = (*MegaNode)(nil)
|
3
drivers/mega/util.go
Normal file
3
drivers/mega/util.go
Normal file
@ -0,0 +1,3 @@
|
||||
package mega
|
||||
|
||||
// do others that not defined in Driver interface
|
@ -34,6 +34,9 @@ func (d *Onedrive) Init(ctx context.Context, storage model.Storage) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.ChunkSize < 1 {
|
||||
d.ChunkSize = 5
|
||||
}
|
||||
return d.refreshToken()
|
||||
}
|
||||
|
||||
|
@ -7,13 +7,14 @@ import (
|
||||
|
||||
type Addition struct {
|
||||
driver.RootPath
|
||||
Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de"`
|
||||
Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"`
|
||||
IsSharepoint bool `json:"is_sharepoint"`
|
||||
ClientID string `json:"client_id" required:"true"`
|
||||
ClientSecret string `json:"client_secret" required:"true"`
|
||||
RedirectUri string `json:"redirect_uri" required:"true" default:"https://tool.nn.ci/onedrive/callback"`
|
||||
RefreshToken string `json:"refresh_token" required:"true"`
|
||||
SiteId string `json:"site_id"`
|
||||
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -42,6 +42,7 @@ var onedriveHostMap = map[string]Host{
|
||||
|
||||
func (d *Onedrive) GetMetaUrl(auth bool, path string) string {
|
||||
host, _ := onedriveHostMap[d.Region]
|
||||
path = utils.EncodePath(path, true)
|
||||
if auth {
|
||||
return host.Oauth
|
||||
}
|
||||
@ -166,7 +167,7 @@ func (d *Onedrive) upBig(ctx context.Context, dstDir model.Obj, stream model.Fil
|
||||
}
|
||||
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
|
||||
var finish int64 = 0
|
||||
const DEFAULT = 4 * 1024 * 1024
|
||||
DEFAULT := d.ChunkSize * 1024 * 1024
|
||||
for finish < stream.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
|
@ -52,11 +52,12 @@ func getKey(path string, dir bool) string {
|
||||
return path
|
||||
}
|
||||
|
||||
// var defaultPlaceholderName = ".placeholder"
|
||||
var defaultPlaceholderName = ".alist"
|
||||
|
||||
func getPlaceholderName(placeholder string) string {
|
||||
//if placeholder == "" {
|
||||
// return defaultPlaceholderName
|
||||
//}
|
||||
if placeholder == "" {
|
||||
return defaultPlaceholderName
|
||||
}
|
||||
return placeholder
|
||||
}
|
||||
|
||||
@ -88,7 +89,7 @@ func (d *S3) listV1(prefix string) ([]model.Obj, error) {
|
||||
}
|
||||
for _, object := range listObjectsResult.Contents {
|
||||
name := path.Base(*object.Key)
|
||||
if name == getPlaceholderName(d.Placeholder) {
|
||||
if name == getPlaceholderName(d.Placeholder) || name == d.Placeholder {
|
||||
continue
|
||||
}
|
||||
file := model.Object{
|
||||
@ -140,7 +141,7 @@ func (d *S3) listV2(prefix string) ([]model.Obj, error) {
|
||||
}
|
||||
for _, object := range listObjectsResult.Contents {
|
||||
name := path.Base(*object.Key)
|
||||
if name == getPlaceholderName(d.Placeholder) {
|
||||
if name == getPlaceholderName(d.Placeholder) || name == d.Placeholder {
|
||||
continue
|
||||
}
|
||||
file := model.Object{
|
||||
@ -222,11 +223,12 @@ func (d *S3) removeDir(ctx context.Context, src string) error {
|
||||
}
|
||||
}
|
||||
_ = d.removeFile(path.Join(src, getPlaceholderName(d.Placeholder)))
|
||||
_ = d.removeFile(path.Join(src, d.Placeholder))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *S3) removeFile(src string) error {
|
||||
key := getKey(src, true)
|
||||
key := getKey(src, false)
|
||||
input := &s3.DeleteObjectInput{
|
||||
Bucket: &d.Bucket,
|
||||
Key: &key,
|
||||
|
@ -59,13 +59,18 @@ func (x *Thunder) Init(ctx context.Context, storage model.Storage) (err error) {
|
||||
"j",
|
||||
"4scKJNdd7F27Hv7tbt",
|
||||
},
|
||||
DeviceID: "9aa5c268e7bcfc197a9ad88e2fb330e5",
|
||||
DeviceID: utils.GetMD5Encode(x.Username + x.Password),
|
||||
ClientID: "Xp6vsxz_7IYVw2BB",
|
||||
ClientSecret: "Xp6vsy4tN9toTVdMSpomVdXpRmES",
|
||||
ClientVersion: "7.51.0.8196",
|
||||
PackageName: "com.xunlei.downloadprovider",
|
||||
UserAgent: "ANDROID-com.xunlei.downloadprovider/7.51.0.8196 netWorkType/5G appid/40 deviceName/Xiaomi_M2004j7ac deviceModel/M2004J7AC OSVersion/12 protocolVersion/301 platformVersion/10 sdkVersion/220200 Oauth2Client/0.9 (Linux 4_14_186-perf-gddfs8vbb238b) (JAVA 0)",
|
||||
DownloadUserAgent: "Dalvik/2.1.0 (Linux; U; Android 12; M2004J7AC Build/SP1A.210812.016)",
|
||||
|
||||
refreshCTokenCk: func(token string) {
|
||||
x.CaptchaToken = token
|
||||
op.MustSaveDriverStorage(x)
|
||||
},
|
||||
},
|
||||
refreshTokenFunc: func() error {
|
||||
// 通过RefreshToken刷新
|
||||
@ -88,7 +93,6 @@ func (x *Thunder) Init(ctx context.Context, storage model.Storage) (err error) {
|
||||
ctoekn := strings.TrimSpace(x.CaptchaToken)
|
||||
if ctoekn != "" {
|
||||
x.SetCaptchaToken(ctoekn)
|
||||
x.CaptchaToken = ""
|
||||
}
|
||||
|
||||
// 防止重复登录
|
||||
@ -139,7 +143,12 @@ func (x *ThunderExpert) Init(ctx context.Context, storage model.Storage) (err er
|
||||
Common: &Common{
|
||||
client: base.NewRestyClient(),
|
||||
|
||||
DeviceID: x.DeviceID,
|
||||
DeviceID: func() string {
|
||||
if len(x.DeviceID) != 32 {
|
||||
return utils.GetMD5Encode(x.DeviceID)
|
||||
}
|
||||
return x.DeviceID
|
||||
}(),
|
||||
ClientID: x.ClientID,
|
||||
ClientSecret: x.ClientSecret,
|
||||
ClientVersion: x.ClientVersion,
|
||||
@ -147,12 +156,16 @@ func (x *ThunderExpert) Init(ctx context.Context, storage model.Storage) (err er
|
||||
UserAgent: x.UserAgent,
|
||||
DownloadUserAgent: x.DownloadUserAgent,
|
||||
UseVideoUrl: x.UseVideoUrl,
|
||||
|
||||
refreshCTokenCk: func(token string) {
|
||||
x.CaptchaToken = token
|
||||
op.MustSaveDriverStorage(x)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if x.CaptchaToken != "" {
|
||||
x.SetCaptchaToken(x.CaptchaToken)
|
||||
x.CaptchaToken = ""
|
||||
}
|
||||
|
||||
// 签名方法
|
||||
@ -206,7 +219,6 @@ func (x *ThunderExpert) Init(ctx context.Context, storage model.Storage) (err er
|
||||
// 仅修改验证码token
|
||||
if x.CaptchaToken != "" {
|
||||
x.SetCaptchaToken(x.CaptchaToken)
|
||||
x.CaptchaToken = ""
|
||||
}
|
||||
x.XunLeiCommon.UserAgent = x.UserAgent
|
||||
x.XunLeiCommon.DownloadUserAgent = x.DownloadUserAgent
|
||||
|
@ -53,6 +53,9 @@ type Common struct {
|
||||
UserAgent string
|
||||
DownloadUserAgent string
|
||||
UseVideoUrl bool
|
||||
|
||||
// 验证码token刷新成功回调
|
||||
refreshCTokenCk func(token string)
|
||||
}
|
||||
|
||||
func (c *Common) SetCaptchaToken(captchaToken string) {
|
||||
@ -125,13 +128,16 @@ func (c *Common) refreshCaptchaToken(action string, metas map[string]string) err
|
||||
}
|
||||
|
||||
if resp.Url != "" {
|
||||
return fmt.Errorf("need verify:%s", resp.Url)
|
||||
return fmt.Errorf(`need verify: <a target="_blank" href="%s">Click Here</a>`, resp.Url)
|
||||
}
|
||||
|
||||
if resp.CaptchaToken == "" {
|
||||
return fmt.Errorf("empty captchaToken")
|
||||
}
|
||||
|
||||
if c.refreshCTokenCk != nil {
|
||||
c.refreshCTokenCk(resp.CaptchaToken)
|
||||
}
|
||||
c.SetCaptchaToken(resp.CaptchaToken)
|
||||
return nil
|
||||
}
|
||||
|
17
go.mod
17
go.mod
@ -1,23 +1,31 @@
|
||||
module github.com/alist-org/alist/v3
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||
github.com/aws/aws-sdk-go v1.44.88
|
||||
github.com/caarlos0/env/v6 v6.9.3
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gin-contrib/cors v1.3.1
|
||||
github.com/gin-gonic/gin v1.8.0
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jlaffaye/ftp v0.0.0-20220829015825-b85cf1edccd4
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.5
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf
|
||||
github.com/upyun/go-sdk/v3 v3.0.3
|
||||
github.com/winfsp/cgofuse v1.5.0
|
||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b
|
||||
gorm.io/driver/mysql v1.3.4
|
||||
gorm.io/driver/postgres v1.3.7
|
||||
gorm.io/driver/sqlite v1.3.4
|
||||
@ -25,13 +33,11 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go v1.44.88 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.7.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/goccy/go-json v0.9.7 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
@ -47,7 +53,6 @@ require (
|
||||
github.com/jackc/pgx/v4 v4.16.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jlaffaye/ftp v0.0.0-20220829015825-b85cf1edccd4 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
@ -56,13 +61,9 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||
github.com/pkg/sftp v1.13.5 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
github.com/upyun/go-sdk/v3 v3.0.3 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 // indirect
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
|
19
go.sum
19
go.sum
@ -125,6 +125,7 @@ github.com/jlaffaye/ftp v0.0.0-20220829015825-b85cf1edccd4 h1:8bWaY08VCoFn17gezY
|
||||
github.com/jlaffaye/ftp v0.0.0-20220829015825-b85cf1edccd4/go.mod h1:hhq4G4crv+nW2qXtNYcuzLeOudG92Ps37HEKeg2e3lE=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
@ -211,9 +212,10 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf h1:Y43S3e9P1NPs/QF4R5/SdlXj2d31540hP4Gk8VKNvDg=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf/go.mod h1:c+cGNU1qi9bO7ZF4IRMYk+KaZTNiQ/gQrSbyMmGFq1Q=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
@ -235,6 +237,7 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -246,10 +249,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
|
||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
@ -264,8 +265,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -285,14 +284,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@ -334,7 +330,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gorm.io/driver/mysql v1.3.4 h1:/KoBMgsUHC3bExsekDcmNYaBnfH2WNeFuXqqrqMc98Q=
|
||||
|
@ -2,7 +2,6 @@ package aria2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
@ -13,6 +12,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/task"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -70,6 +70,8 @@ func (m *Monitor) Update() (bool, error) {
|
||||
info, err := client.TellStatus(m.tsk.ID)
|
||||
if err != nil {
|
||||
m.retried++
|
||||
log.Errorf("failed to get status of %s, retried %d times", m.tsk.ID, m.retried)
|
||||
return false, nil
|
||||
}
|
||||
if m.retried > 5 {
|
||||
return true, errors.Errorf("failed to get status of %s, retried %d times", m.tsk.ID, m.retried)
|
||||
@ -147,10 +149,7 @@ func (m *Monitor) Complete() error {
|
||||
Func: func(tsk *task.Task[uint64]) error {
|
||||
defer wg.Done()
|
||||
size, _ := strconv.ParseInt(file.Length, 10, 64)
|
||||
mimetype := mime.TypeByExtension(path.Ext(file.Path))
|
||||
if mimetype == "" {
|
||||
mimetype = "application/octet-stream"
|
||||
}
|
||||
mimetype := utils.GetMimeType(file.Path)
|
||||
f, err := os.Open(file.Path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open file %s", file.Path)
|
||||
|
@ -6,11 +6,9 @@ import (
|
||||
)
|
||||
|
||||
func InitAria2() {
|
||||
go func() {
|
||||
_, err := aria2.InitClient(2)
|
||||
if err != nil {
|
||||
//utils.Log.Errorf("failed to init aria2 client: %+v", err)
|
||||
utils.Log.Infof("Aria2 not ready.")
|
||||
}
|
||||
}()
|
||||
_, err := aria2.InitClient(2)
|
||||
if err != nil {
|
||||
//utils.Log.Errorf("failed to init aria2 client: %+v", err)
|
||||
utils.Log.Infof("Aria2 not ready.")
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@ -25,7 +24,7 @@ func InitConfig() {
|
||||
log.Fatalf("failed to create default config file")
|
||||
}
|
||||
} else {
|
||||
configBytes, err := ioutil.ReadFile(flags.Config)
|
||||
configBytes, err := os.ReadFile(flags.Config)
|
||||
if err != nil {
|
||||
log.Fatalf("reading config file error: %+v", err)
|
||||
}
|
||||
@ -39,7 +38,7 @@ func InitConfig() {
|
||||
if err != nil {
|
||||
log.Fatalf("marshal config error: %+v", err)
|
||||
}
|
||||
err = ioutil.WriteFile(flags.Config, confBody, 0777)
|
||||
err = os.WriteFile(flags.Config, confBody, 0777)
|
||||
if err != nil {
|
||||
log.Fatalf("update config struct error: %+v", err)
|
||||
}
|
||||
@ -59,7 +58,7 @@ func InitConfig() {
|
||||
if err != nil {
|
||||
log.Errorln("failed delete temp file:", err)
|
||||
}
|
||||
err = os.MkdirAll(conf.Conf.TempDir, 0700)
|
||||
err = os.MkdirAll(conf.Conf.TempDir, 0777)
|
||||
if err != nil {
|
||||
log.Fatalf("create temp dir error: %+v", err)
|
||||
}
|
||||
|
@ -32,32 +32,32 @@ type LogConfig struct {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Force bool `json:"force" env:"FORCE"`
|
||||
Address string `json:"address" env:"ADDR"`
|
||||
Port int `json:"port" env:"PORT"`
|
||||
SiteURL string `json:"site_url" env:"SITE_URL"`
|
||||
Cdn string `json:"cdn" env:"CDN"`
|
||||
JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`
|
||||
Database Database `json:"database"`
|
||||
Scheme Scheme `json:"scheme"`
|
||||
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
||||
Log LogConfig `json:"log"`
|
||||
Force bool `json:"force" env:"FORCE"`
|
||||
Address string `json:"address" env:"ADDR"`
|
||||
Port int `json:"port" env:"PORT"`
|
||||
SiteURL string `json:"site_url" env:"SITE_URL"`
|
||||
Cdn string `json:"cdn" env:"CDN"`
|
||||
JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`
|
||||
TokenExpiresIn int `json:"token_expires_in" env:"TOKEN_EXPIRES_IN"`
|
||||
Database Database `json:"database"`
|
||||
Scheme Scheme `json:"scheme"`
|
||||
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
||||
Log LogConfig `json:"log"`
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Address: "0.0.0.0",
|
||||
Port: 5244,
|
||||
JwtSecret: random.String(16),
|
||||
Cdn: "",
|
||||
TempDir: "data/temp",
|
||||
Address: "0.0.0.0",
|
||||
Port: 5244,
|
||||
JwtSecret: random.String(16),
|
||||
TokenExpiresIn: 48,
|
||||
TempDir: "data/temp",
|
||||
Database: Database{
|
||||
Type: "sqlite3",
|
||||
Port: 0,
|
||||
TablePrefix: "x_",
|
||||
DBFile: "data/data.db",
|
||||
},
|
||||
// CaCheExpiration: 30,
|
||||
Log: LogConfig{
|
||||
Enable: true,
|
||||
Name: "log/log.log",
|
||||
|
@ -3,7 +3,6 @@ package fs
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
stdpath "path"
|
||||
@ -38,7 +37,7 @@ var httpClient = &http.Client{}
|
||||
|
||||
func getFileStreamFromLink(file model.Obj, link *model.Link) (model.FileStreamer, error) {
|
||||
var rc io.ReadCloser
|
||||
mimetype := mime.TypeByExtension(stdpath.Ext(file.GetName()))
|
||||
mimetype := utils.GetMimeType(file.GetName())
|
||||
if link.Data != nil {
|
||||
rc = link.Data
|
||||
} else if link.FilePath != nil {
|
||||
|
@ -38,6 +38,13 @@ func List(ctx context.Context, storage driver.Driver, path string, args model.Li
|
||||
}
|
||||
path = utils.StandardizePath(path)
|
||||
log.Debugf("op.List %s", path)
|
||||
key := Key(storage, path)
|
||||
if len(refresh) == 0 || !refresh[0] {
|
||||
if files, ok := listCache.Get(key); ok {
|
||||
log.Debugf("use cache when list %s", path)
|
||||
return files, nil
|
||||
}
|
||||
}
|
||||
dir, err := Get(ctx, storage, path)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed get dir")
|
||||
@ -46,22 +53,14 @@ func List(ctx context.Context, storage driver.Driver, path string, args model.Li
|
||||
if !dir.IsDir() {
|
||||
return nil, errors.WithStack(errs.NotFolder)
|
||||
}
|
||||
if storage.Config().NoCache {
|
||||
objs, err := storage.List(ctx, dir, args)
|
||||
return objs, errors.WithStack(err)
|
||||
}
|
||||
key := Key(storage, path)
|
||||
if len(refresh) == 0 || !refresh[0] {
|
||||
if files, ok := listCache.Get(key); ok && len(files) > 0 {
|
||||
return files, nil
|
||||
}
|
||||
}
|
||||
objs, err, _ := listG.Do(key, func() ([]model.Obj, error) {
|
||||
files, err := storage.List(ctx, dir, args)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to list objs")
|
||||
}
|
||||
listCache.Set(key, files, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
|
||||
if !storage.Config().NoCache && len(files) > 0 {
|
||||
listCache.Set(key, files, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
|
||||
}
|
||||
return files, nil
|
||||
})
|
||||
return objs, err
|
||||
@ -128,6 +127,7 @@ func Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, er
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
log.Debugf("cant find obj with name: %s", name)
|
||||
return nil, errors.WithStack(errs.ObjectNotFound)
|
||||
}
|
||||
|
||||
|
@ -182,13 +182,15 @@ func DeleteStorageById(ctx context.Context, id uint) error {
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed get storage")
|
||||
}
|
||||
storageDriver, err := GetStorageByVirtualPath(storage.MountPath)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed get storage driver")
|
||||
}
|
||||
// drop the storage in the driver
|
||||
if err := storageDriver.Drop(ctx); err != nil {
|
||||
return errors.Wrapf(err, "failed drop storage")
|
||||
if !storage.Disabled {
|
||||
storageDriver, err := GetStorageByVirtualPath(storage.MountPath)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed get storage driver")
|
||||
}
|
||||
// drop the storage in the driver
|
||||
if err := storageDriver.Drop(ctx); err != nil {
|
||||
return errors.Wrapf(err, "failed drop storage")
|
||||
}
|
||||
}
|
||||
// delete the storage in the database
|
||||
if err := db.DeleteStorageById(id); err != nil {
|
||||
|
62
pkg/chanio/chanio.go
Normal file
62
pkg/chanio/chanio.go
Normal file
@ -0,0 +1,62 @@
|
||||
package chanio
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type ChanIO struct {
|
||||
cl atomic.Bool
|
||||
c chan []byte
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func New() *ChanIO {
|
||||
return &ChanIO{
|
||||
cl: atomic.Bool{},
|
||||
c: make(chan []byte),
|
||||
buf: make([]byte, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChanIO) Read(p []byte) (int, error) {
|
||||
if c.cl.Load() {
|
||||
if len(c.buf) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := copy(p, c.buf)
|
||||
if len(c.buf) > n {
|
||||
c.buf = c.buf[n:]
|
||||
} else {
|
||||
c.buf = make([]byte, 0)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
for len(c.buf) < len(p) && !c.cl.Load() {
|
||||
c.buf = append(c.buf, <-c.c...)
|
||||
}
|
||||
n := copy(p, c.buf)
|
||||
if len(c.buf) > n {
|
||||
c.buf = c.buf[n:]
|
||||
} else {
|
||||
c.buf = make([]byte, 0)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *ChanIO) Write(p []byte) (int, error) {
|
||||
if c.cl.Load() {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
c.c <- p
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *ChanIO) Close() error {
|
||||
if c.cl.Load() {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
c.cl.Store(true)
|
||||
close(c.c)
|
||||
return nil
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@ -136,3 +137,12 @@ func GetFileType(filename string) int {
|
||||
}
|
||||
return conf.UNKNOWN
|
||||
}
|
||||
|
||||
func GetMimeType(name string) string {
|
||||
ext := path.Ext(name)
|
||||
m := mime.TypeByExtension(ext)
|
||||
if m != "" {
|
||||
return m
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package common
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -18,7 +19,7 @@ func GenerateToken(username string) (tokenString string, err error) {
|
||||
claim := UserClaims{
|
||||
Username: username,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(12 * time.Hour)),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(conf.Conf.TokenExpiresIn) * time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
}}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
stdpath "path"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
@ -16,9 +16,13 @@ type SiteConfig struct {
|
||||
}
|
||||
|
||||
func getSiteConfig() SiteConfig {
|
||||
u, err := url.Parse(conf.Conf.SiteURL)
|
||||
if err != nil {
|
||||
utils.Log.Fatalf("can't parse site_url: %+v", err)
|
||||
}
|
||||
siteConfig := SiteConfig{
|
||||
ApiURL: conf.Conf.SiteURL,
|
||||
BasePath: stdpath.Base(conf.Conf.SiteURL),
|
||||
BasePath: u.Path,
|
||||
Cdn: strings.ReplaceAll(strings.TrimSuffix(conf.Conf.Cdn, "/"), "$version", conf.WebVersion),
|
||||
}
|
||||
// try to get old config
|
||||
|
@ -21,11 +21,19 @@ func InitIndex() {
|
||||
log.Fatalf("failed to read index.html: %v", err)
|
||||
}
|
||||
conf.RawIndexHtml = string(index)
|
||||
siteConfig := getSiteConfig()
|
||||
replaceMap := map[string]string{
|
||||
"cdn: undefined": fmt.Sprintf("cdn: '%s'", siteConfig.Cdn),
|
||||
"base_path: undefined": fmt.Sprintf("base_path: '%s'", siteConfig.BasePath),
|
||||
"api: undefined": fmt.Sprintf("api: '%s'", siteConfig.ApiURL),
|
||||
}
|
||||
for k, v := range replaceMap {
|
||||
conf.RawIndexHtml = strings.Replace(conf.RawIndexHtml, k, v, 1)
|
||||
}
|
||||
UpdateIndex()
|
||||
}
|
||||
|
||||
func UpdateIndex() {
|
||||
siteConfig := getSiteConfig()
|
||||
favicon := setting.GetStr(conf.Favicon)
|
||||
title := setting.GetStr(conf.SiteTitle)
|
||||
customizeHead := setting.GetStr(conf.CustomizeHead)
|
||||
@ -35,9 +43,6 @@ func UpdateIndex() {
|
||||
replaceMap1 := map[string]string{
|
||||
"https://jsd.nn.ci/gh/alist-org/logo@main/logo.svg": favicon,
|
||||
"Loading...": title,
|
||||
"cdn: undefined": fmt.Sprintf("cdn: '%s'", siteConfig.Cdn),
|
||||
"base_path: undefined": fmt.Sprintf("base_path: '%s'", siteConfig.BasePath),
|
||||
"api: undefined": fmt.Sprintf("api: '%s'", siteConfig.ApiURL),
|
||||
"main_color: undefined": fmt.Sprintf("main_color: '%s'", mainColor),
|
||||
}
|
||||
for k, v := range replaceMap1 {
|
||||
|
@ -300,6 +300,9 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
|
||||
ReadCloser: r.Body,
|
||||
Mimetype: r.Header.Get("Content-Type"),
|
||||
}
|
||||
if stream.Mimetype == "" {
|
||||
stream.Mimetype = utils.GetMimeType(reqPath)
|
||||
}
|
||||
err = fs.PutDirectly(ctx, path.Dir(reqPath), stream)
|
||||
|
||||
// TODO(rost): Returning 405 Method Not Allowed might not be appropriate.
|
||||
|
Reference in New Issue
Block a user