Compare commits
14 Commits
v3.0.0-rc.
...
v3.0.1
Author | SHA1 | Date | |
---|---|---|---|
0039dc18e1 | |||
4d6ab53336 | |||
c7f6684eed | |||
b71ecc8e89 | |||
3537153b91 | |||
9382f66f87 | |||
656f5f112c | |||
9181861f47 | |||
1ab73e0742 | |||
57686d9df1 | |||
ca177cc3b9 | |||
d8dc8d8623 | |||
5548ab62ac | |||
d6d82c3138 |
@ -215,6 +215,7 @@ func (d *Cloud189) getFiles(fileId string) ([]model.Obj, error) {
|
||||
ID: strconv.FormatInt(file.Id, 10),
|
||||
Name: file.Name,
|
||||
Modified: lastOpTime,
|
||||
Size: file.Size,
|
||||
},
|
||||
Thumbnail: model.Thumbnail{Thumbnail: file.Icon.SmallUrl},
|
||||
})
|
||||
|
@ -40,6 +40,7 @@ func fileToObj(f File) *model.ObjThumb {
|
||||
Modified: f.UpdatedAt,
|
||||
IsFolder: f.Type == "folder",
|
||||
},
|
||||
Thumbnail: model.Thumbnail{Thumbnail: f.Thumbnail},
|
||||
}
|
||||
}
|
||||
|
||||
|
144
drivers/aliyundrive_share/driver.go
Normal file
144
drivers/aliyundrive_share/driver.go
Normal file
@ -0,0 +1,144 @@
|
||||
package aliyundrive_share
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"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/cron"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type AliyundriveShare struct {
|
||||
model.Storage
|
||||
Addition
|
||||
AccessToken string
|
||||
ShareToken string
|
||||
DriveId string
|
||||
cron *cron.Cron
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) GetAddition() driver.Additional {
|
||||
return d.Addition
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) 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
|
||||
}
|
||||
err = d.refreshToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.getShareToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.cron = cron.NewCron(time.Hour * 2)
|
||||
d.cron.Do(func() {
|
||||
err := d.refreshToken()
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) Drop(ctx context.Context) error {
|
||||
if d.cron != nil {
|
||||
d.cron.Stop()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) 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 File) (model.Obj, error) {
|
||||
return fileToObj(src), nil
|
||||
})
|
||||
}
|
||||
|
||||
//func (d *AliyundriveShare) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||
// // this is optional
|
||||
// return nil, errs.NotImplement
|
||||
//}
|
||||
|
||||
func (d *AliyundriveShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
data := base.Json{
|
||||
"drive_id": d.DriveId,
|
||||
"file_id": file.GetID(),
|
||||
"expire_sec": 14400,
|
||||
}
|
||||
var e ErrorResp
|
||||
res, err := base.RestyClient.R().
|
||||
SetError(&e).SetBody(data).
|
||||
SetHeader("content-type", "application/json").
|
||||
SetHeader("Authorization", "Bearer\t"+d.AccessToken).
|
||||
Post("https://api.aliyundrive.com/v2/file/get_download_url")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = d.refreshToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Link(ctx, file, args)
|
||||
}
|
||||
return nil, errors.New(e.Message)
|
||||
}
|
||||
return &model.Link{
|
||||
Header: http.Header{
|
||||
"Referer": []string{"https://www.aliyundrive.com/"},
|
||||
},
|
||||
URL: utils.Json.Get(res.Body(), "url").ToString(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
// TODO create folder
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO move obj
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
// TODO rename obj
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO copy obj
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) Remove(ctx context.Context, obj model.Obj) error {
|
||||
// TODO remove obj
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
// TODO upload file
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*AliyundriveShare)(nil)
|
29
drivers/aliyundrive_share/meta.go
Normal file
29
drivers/aliyundrive_share/meta.go
Normal file
@ -0,0 +1,29 @@
|
||||
package aliyundrive_share
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
RefreshToken string `json:"refresh_token" required:"true"`
|
||||
ShareId string `json:"share_id" required:"true"`
|
||||
SharePwd string `json:"share_pwd"`
|
||||
driver.RootID
|
||||
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "AliyundriveShare",
|
||||
LocalSort: false,
|
||||
OnlyProxy: false,
|
||||
NoUpload: true,
|
||||
DefaultRoot: "root",
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(config, func() driver.Driver {
|
||||
return &AliyundriveShare{}
|
||||
})
|
||||
}
|
57
drivers/aliyundrive_share/types.go
Normal file
57
drivers/aliyundrive_share/types.go
Normal file
@ -0,0 +1,57 @@
|
||||
package aliyundrive_share
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type ErrorResp struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type ShareTokenResp struct {
|
||||
ShareToken string `json:"share_token"`
|
||||
ExpireTime time.Time `json:"expire_time"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
type ListResp struct {
|
||||
Items []File `json:"items"`
|
||||
NextMarker string `json:"next_marker"`
|
||||
PunishedFileCount int `json:"punished_file_count"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
DriveId string `json:"drive_id"`
|
||||
DomainId string `json:"domain_id"`
|
||||
FileId string `json:"file_id"`
|
||||
ShareId string `json:"share_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ParentFileId string `json:"parent_file_id"`
|
||||
Size int64 `json:"size"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
}
|
||||
|
||||
func fileToObj(f File) *model.ObjThumb {
|
||||
return &model.ObjThumb{
|
||||
Object: model.Object{
|
||||
ID: f.FileId,
|
||||
Name: f.Name,
|
||||
Size: f.Size,
|
||||
Modified: f.UpdatedAt,
|
||||
IsFolder: f.Type == "folder",
|
||||
},
|
||||
Thumbnail: model.Thumbnail{Thumbnail: f.Thumbnail},
|
||||
}
|
||||
}
|
||||
|
||||
//type ShareLinkResp struct {
|
||||
// DownloadUrl string `json:"download_url"`
|
||||
// Url string `json:"url"`
|
||||
// Thumbnail string `json:"thumbnail"`
|
||||
//}
|
99
drivers/aliyundrive_share/util.go
Normal file
99
drivers/aliyundrive_share/util.go
Normal file
@ -0,0 +1,99 @@
|
||||
package aliyundrive_share
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (d *AliyundriveShare) refreshToken() error {
|
||||
url := "https://auth.aliyundrive.com/v2/account/token"
|
||||
var resp base.TokenResp
|
||||
var e ErrorResp
|
||||
_, err := base.RestyClient.R().
|
||||
SetBody(base.Json{"refresh_token": d.RefreshToken, "grant_type": "refresh_token"}).
|
||||
SetResult(&resp).
|
||||
SetError(&e).
|
||||
Post(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" {
|
||||
return fmt.Errorf("failed to refresh token: %s", e.Message)
|
||||
}
|
||||
d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
// do others that not defined in Driver interface
|
||||
func (d *AliyundriveShare) getShareToken() error {
|
||||
data := base.Json{
|
||||
"share_id": d.ShareId,
|
||||
}
|
||||
if d.SharePwd != "" {
|
||||
data["share_pwd"] = d.SharePwd
|
||||
}
|
||||
var e ErrorResp
|
||||
var resp ShareTokenResp
|
||||
_, err := base.RestyClient.R().
|
||||
SetResult(&resp).SetError(&e).SetBody(data).
|
||||
Post("https://api.aliyundrive.com/v2/share_link/get_share_token")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Code != "" {
|
||||
return errors.New(e.Message)
|
||||
}
|
||||
d.ShareToken = resp.ShareToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *AliyundriveShare) getFiles(fileId string) ([]File, error) {
|
||||
files := make([]File, 0)
|
||||
data := base.Json{
|
||||
"image_thumbnail_process": "image/resize,w_160/format,jpeg",
|
||||
"image_url_process": "image/resize,w_1920/format,jpeg",
|
||||
"limit": 100,
|
||||
"order_by": d.OrderBy,
|
||||
"order_direction": d.OrderDirection,
|
||||
"parent_file_id": fileId,
|
||||
"share_id": d.ShareId,
|
||||
"video_thumbnail_process": "video/snapshot,t_1000,f_jpg,ar_auto,w_300",
|
||||
"marker": "first",
|
||||
}
|
||||
for data["marker"] != "" {
|
||||
if data["marker"] == "first" {
|
||||
data["marker"] = ""
|
||||
}
|
||||
var e ErrorResp
|
||||
var resp ListResp
|
||||
res, err := base.RestyClient.R().
|
||||
SetHeader("x-share-token", d.ShareToken).
|
||||
SetResult(&resp).SetError(&e).SetBody(data).
|
||||
Post("https://api.aliyundrive.com/adrive/v3/file/list")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("aliyundrive share get files: %s", res.String())
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = d.getShareToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.getFiles(fileId)
|
||||
}
|
||||
return nil, errors.New(e.Message)
|
||||
}
|
||||
data["marker"] = resp.NextMarker
|
||||
files = append(files, resp.Items...)
|
||||
}
|
||||
if len(files) > 0 && d.DriveId == "" {
|
||||
d.DriveId = files[0].DriveId
|
||||
}
|
||||
return files, nil
|
||||
}
|
@ -6,10 +6,12 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/189"
|
||||
_ "github.com/alist-org/alist/v3/drivers/189pc"
|
||||
_ "github.com/alist-org/alist/v3/drivers/aliyundrive"
|
||||
_ "github.com/alist-org/alist/v3/drivers/aliyundrive_share"
|
||||
_ "github.com/alist-org/alist/v3/drivers/baidu_netdisk"
|
||||
_ "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/lanzou"
|
||||
_ "github.com/alist-org/alist/v3/drivers/local"
|
||||
_ "github.com/alist-org/alist/v3/drivers/mediatrack"
|
||||
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
||||
|
@ -19,5 +19,6 @@ func (d *FTP) login() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.conn = conn
|
||||
return nil
|
||||
}
|
||||
|
171
drivers/lanzou/driver.go
Normal file
171
drivers/lanzou/driver.go
Normal file
@ -0,0 +1,171 @@
|
||||
package lanzou
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
var upClient = base.NewRestyClient().SetTimeout(120 * time.Second)
|
||||
|
||||
type LanZou struct {
|
||||
Addition
|
||||
model.Storage
|
||||
}
|
||||
|
||||
func (d *LanZou) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *LanZou) GetAddition() driver.Additional {
|
||||
return d.Addition
|
||||
}
|
||||
|
||||
func (d *LanZou) 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
|
||||
}
|
||||
if d.IsCookie() {
|
||||
if d.RootFolderID == "" {
|
||||
d.RootFolderID = "-1"
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *LanZou) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取的大小和时间不准确
|
||||
func (d *LanZou) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
if d.IsCookie() {
|
||||
return d.GetFiles(ctx, dir.GetID())
|
||||
} else {
|
||||
return d.GetFileOrFolderByShareUrl(ctx, dir.GetID(), d.SharePassword)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *LanZou) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
downID := file.GetID()
|
||||
pwd := d.SharePassword
|
||||
if d.IsCookie() {
|
||||
share, err := d.getFileShareUrlByID(ctx, file.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
downID = share.FID
|
||||
pwd = share.Pwd
|
||||
}
|
||||
fileInfo, err := d.getFilesByShareUrl(ctx, downID, pwd, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Link{
|
||||
URL: fileInfo.Url,
|
||||
Header: http.Header{
|
||||
"User-Agent": []string{base.UserAgent},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *LanZou) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
if d.IsCookie() {
|
||||
_, err := d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetFormData(map[string]string{
|
||||
"task": "2",
|
||||
"parent_id": parentDir.GetID(),
|
||||
"folder_name": dirName,
|
||||
"folder_description": "",
|
||||
})
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *LanZou) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
if d.IsCookie() {
|
||||
if !srcObj.IsDir() {
|
||||
_, err := d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetFormData(map[string]string{
|
||||
"task": "20",
|
||||
"folder_id": dstDir.GetID(),
|
||||
"file_id": srcObj.GetID(),
|
||||
})
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *LanZou) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
if d.IsCookie() {
|
||||
if !srcObj.IsDir() {
|
||||
_, err := d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetFormData(map[string]string{
|
||||
"task": "46",
|
||||
"file_id": srcObj.GetID(),
|
||||
"file_name": newName,
|
||||
"type": "2",
|
||||
})
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *LanZou) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *LanZou) Remove(ctx context.Context, obj model.Obj) error {
|
||||
if d.IsCookie() {
|
||||
_, err := d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
if obj.IsDir() {
|
||||
req.SetFormData(map[string]string{
|
||||
"task": "3",
|
||||
"folder_id": obj.GetID(),
|
||||
})
|
||||
} else {
|
||||
req.SetFormData(map[string]string{
|
||||
"task": "6",
|
||||
"file_id": obj.GetID(),
|
||||
})
|
||||
}
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *LanZou) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
if d.IsCookie() {
|
||||
_, err := d._post(d.BaseUrl+"/fileup.php", func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"task": "1",
|
||||
"id": "WU_FILE_0",
|
||||
"name": stream.GetName(),
|
||||
"folder_id": dstDir.GetID(),
|
||||
}).SetFileReader("upload_file", stream.GetName(), stream)
|
||||
}, nil, true)
|
||||
return err
|
||||
}
|
||||
return errs.NotImplement
|
||||
}
|
165
drivers/lanzou/help.go
Normal file
165
drivers/lanzou/help.go
Normal file
@ -0,0 +1,165 @@
|
||||
package lanzou
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const DAY time.Duration = 84600000000000
|
||||
|
||||
var timeSplitReg = regexp.MustCompile("([0-9.]*)\\s*([\u4e00-\u9fa5]+)")
|
||||
|
||||
func MustParseTime(str string) time.Time {
|
||||
lastOpTime, err := time.ParseInLocation("2006-01-02 -07", str+" +08", time.Local)
|
||||
if err != nil {
|
||||
strs := timeSplitReg.FindStringSubmatch(str)
|
||||
lastOpTime = time.Now()
|
||||
if len(strs) == 3 {
|
||||
i, _ := strconv.ParseInt(strs[1], 10, 64)
|
||||
ti := time.Duration(-i)
|
||||
switch strs[2] {
|
||||
case "秒前":
|
||||
lastOpTime = lastOpTime.Add(time.Second * ti)
|
||||
case "分钟前":
|
||||
lastOpTime = lastOpTime.Add(time.Minute * ti)
|
||||
case "小时前":
|
||||
lastOpTime = lastOpTime.Add(time.Hour * ti)
|
||||
case "天前":
|
||||
lastOpTime = lastOpTime.Add(DAY * ti)
|
||||
case "昨天":
|
||||
lastOpTime = lastOpTime.Add(-DAY)
|
||||
case "前天":
|
||||
lastOpTime = lastOpTime.Add(-DAY * 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
return lastOpTime
|
||||
}
|
||||
|
||||
var sizeSplitReg = regexp.MustCompile(`(?i)([0-9.]+)\s*([bkm]+)`)
|
||||
|
||||
func SizeStrToInt64(size string) int64 {
|
||||
strs := sizeSplitReg.FindStringSubmatch(size)
|
||||
if len(strs) < 3 {
|
||||
return 0
|
||||
}
|
||||
|
||||
s, _ := strconv.ParseFloat(strs[1], 64)
|
||||
switch strings.ToUpper(strs[2]) {
|
||||
case "B":
|
||||
return int64(s)
|
||||
case "K":
|
||||
return int64(s * (1 << 10))
|
||||
case "M":
|
||||
return int64(s * (1 << 20))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 移除注释
|
||||
func RemoveNotes(html []byte) []byte {
|
||||
return regexp.MustCompile(`<!--.*?-->|//.*|/\*.*?\*/`).ReplaceAll(html, []byte{})
|
||||
}
|
||||
|
||||
var findAcwScV2Reg = regexp.MustCompile(`arg1='([0-9A-Z]+)'`)
|
||||
|
||||
// 在页面被过多访问或其他情况下,有时候会先返回一个加密的页面,其执行计算出一个acw_sc__v2后放入页面后再重新访问页面才能获得正常页面
|
||||
// 若该页面进行了js加密,则进行解密,计算acw_sc__v2,并加入cookie
|
||||
func CalcAcwScV2(html string) (string, error) {
|
||||
acwScV2s := findAcwScV2Reg.FindStringSubmatch(html)
|
||||
if len(acwScV2s) != 2 {
|
||||
return "", fmt.Errorf("无法匹配acw_sc__v2")
|
||||
}
|
||||
return HexXor(Unbox(acwScV2s[1]), "3000176000856006061501533003690027800375"), nil
|
||||
}
|
||||
|
||||
func Unbox(hex string) string {
|
||||
var box = []int{6, 28, 34, 31, 33, 18, 30, 23, 9, 8, 19, 38, 17, 24, 0, 5, 32, 21, 10, 22, 25, 14, 15, 3, 16, 27, 13, 35, 2, 29, 11, 26, 4, 36, 1, 39, 37, 7, 20, 12}
|
||||
var newBox = make([]byte, len(hex))
|
||||
for i := 0; i < len(box); i++ {
|
||||
j := box[i]
|
||||
if len(newBox) > j {
|
||||
newBox[j] = hex[i]
|
||||
}
|
||||
}
|
||||
return string(newBox)
|
||||
}
|
||||
|
||||
func HexXor(hex1, hex2 string) string {
|
||||
out := bytes.NewBuffer(make([]byte, len(hex1)))
|
||||
for i := 0; i < len(hex1) && i < len(hex2); i += 2 {
|
||||
v1, _ := strconv.ParseInt(hex1[i:i+2], 16, 64)
|
||||
v2, _ := strconv.ParseInt(hex2[i:i+2], 16, 64)
|
||||
out.WriteString(strconv.FormatInt(v1^v2, 16))
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
var findDataReg = regexp.MustCompile(`data[:\s]+({[^}]+})`) // 查找json
|
||||
var findKVReg = regexp.MustCompile(`'(.+?)':('?([^' },]*)'?)`) // 拆分kv
|
||||
|
||||
// 根据key查询js变量
|
||||
func findJSVarFunc(key, data string) string {
|
||||
values := regexp.MustCompile(`var ` + key + ` = '(.+?)';`).FindStringSubmatch(data)
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
return values[1]
|
||||
}
|
||||
|
||||
// 解析html中的JSON
|
||||
func htmlJsonToMap(html string) (map[string]string, error) {
|
||||
datas := findDataReg.FindStringSubmatch(html)
|
||||
if len(datas) != 2 {
|
||||
return nil, fmt.Errorf("not find data")
|
||||
}
|
||||
return jsonToMap(datas[1], html), nil
|
||||
}
|
||||
|
||||
func jsonToMap(data, html string) map[string]string {
|
||||
var param = make(map[string]string)
|
||||
kvs := findKVReg.FindAllStringSubmatch(data, -1)
|
||||
for _, kv := range kvs {
|
||||
k, v := kv[1], kv[3]
|
||||
if v == "" || strings.Contains(kv[2], "'") || IsNumber(kv[2]) {
|
||||
param[k] = v
|
||||
} else {
|
||||
param[k] = findJSVarFunc(v, html)
|
||||
}
|
||||
}
|
||||
return param
|
||||
}
|
||||
|
||||
func IsNumber(str string) bool {
|
||||
for _, s := range str {
|
||||
if !unicode.IsDigit(s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var findFromReg = regexp.MustCompile(`data : '(.+?)'`) // 查找from字符串
|
||||
|
||||
// 解析html中的from
|
||||
func htmlFormToMap(html string) (map[string]string, error) {
|
||||
froms := findFromReg.FindStringSubmatch(html)
|
||||
if len(froms) != 2 {
|
||||
return nil, fmt.Errorf("not find file sgin")
|
||||
}
|
||||
return fromToMap(froms[1]), nil
|
||||
}
|
||||
|
||||
func fromToMap(from string) map[string]string {
|
||||
var param = make(map[string]string)
|
||||
for _, kv := range strings.Split(from, "&") {
|
||||
kv := strings.SplitN(kv, "=", 2)[:2]
|
||||
param[kv[0]] = kv[1]
|
||||
}
|
||||
return param
|
||||
}
|
31
drivers/lanzou/meta.go
Normal file
31
drivers/lanzou/meta.go
Normal file
@ -0,0 +1,31 @@
|
||||
package lanzou
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
Type string `json:"type" type:"select" options:"cookie,url" default:"cookie"`
|
||||
Cookie string `json:"cookie" required:"true" help:"about 15 days valid"`
|
||||
driver.RootID
|
||||
SharePassword string `json:"share_password"`
|
||||
BaseUrl string `json:"baseUrl" required:"true" default:"https://pc.woozooo.com"`
|
||||
ShareUrl string `json:"shareUrl" required:"true" default:"https://pan.lanzouo.com"`
|
||||
}
|
||||
|
||||
func (a *Addition) IsCookie() bool {
|
||||
return a.Type == "cookie"
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Lanzou",
|
||||
LocalSort: true,
|
||||
DefaultRoot: "-1",
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(config, func() driver.Driver {
|
||||
return &LanZou{}
|
||||
})
|
||||
}
|
131
drivers/lanzou/types.go
Normal file
131
drivers/lanzou/types.go
Normal file
@ -0,0 +1,131 @@
|
||||
package lanzou
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type FilesOrFoldersResp struct {
|
||||
Text []FileOrFolder `json:"text"`
|
||||
}
|
||||
|
||||
type FileOrFolder struct {
|
||||
Name string `json:"name"`
|
||||
//Onof string `json:"onof"` // 是否存在提取码
|
||||
//IsLock string `json:"is_lock"`
|
||||
//IsCopyright int `json:"is_copyright"`
|
||||
|
||||
// 文件通用
|
||||
ID string `json:"id"`
|
||||
NameAll string `json:"name_all"`
|
||||
Size string `json:"size"`
|
||||
Time string `json:"time"`
|
||||
//Icon string `json:"icon"`
|
||||
//Downs string `json:"downs"`
|
||||
//Filelock string `json:"filelock"`
|
||||
//IsBakdownload int `json:"is_bakdownload"`
|
||||
//Bakdownload string `json:"bakdownload"`
|
||||
//IsDes int `json:"is_des"` // 是否存在描述
|
||||
//IsIco int `json:"is_ico"`
|
||||
|
||||
// 文件夹
|
||||
FolID string `json:"fol_id"`
|
||||
//Folderlock string `json:"folderlock"`
|
||||
//FolderDes string `json:"folder_des"`
|
||||
}
|
||||
|
||||
func (f *FileOrFolder) isFloder() bool {
|
||||
return f.FolID != ""
|
||||
}
|
||||
func (f *FileOrFolder) ToObj() model.Obj {
|
||||
obj := &model.Object{}
|
||||
if f.isFloder() {
|
||||
obj.ID = f.FolID
|
||||
obj.Name = f.Name
|
||||
obj.Modified = time.Now()
|
||||
obj.IsFolder = true
|
||||
} else {
|
||||
obj.ID = f.ID
|
||||
obj.Name = f.NameAll
|
||||
obj.Modified = MustParseTime(f.Time)
|
||||
obj.Size = SizeStrToInt64(f.Size)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
type FileShareResp struct {
|
||||
Info FileShare `json:"info"`
|
||||
}
|
||||
type FileShare struct {
|
||||
Pwd string `json:"pwd"`
|
||||
Onof string `json:"onof"`
|
||||
Taoc string `json:"taoc"`
|
||||
IsNewd string `json:"is_newd"`
|
||||
|
||||
// 文件
|
||||
FID string `json:"f_id"`
|
||||
|
||||
// 文件夹
|
||||
NewUrl string `json:"new_url"`
|
||||
Name string `json:"name"`
|
||||
Des string `json:"des"`
|
||||
}
|
||||
|
||||
type FileOrFolderByShareUrlResp struct {
|
||||
Text []FileOrFolderByShareUrl `json:"text"`
|
||||
}
|
||||
type FileOrFolderByShareUrl struct {
|
||||
ID string `json:"id"`
|
||||
NameAll string `json:"name_all"`
|
||||
Size string `json:"size"`
|
||||
Time string `json:"time"`
|
||||
Duan string `json:"duan"`
|
||||
//Icon string `json:"icon"`
|
||||
//PIco int `json:"p_ico"`
|
||||
//T int `json:"t"`
|
||||
IsFloder bool
|
||||
}
|
||||
|
||||
func (f *FileOrFolderByShareUrl) ToObj() model.Obj {
|
||||
return &model.Object{
|
||||
ID: f.ID,
|
||||
Name: f.NameAll,
|
||||
Size: SizeStrToInt64(f.Size),
|
||||
Modified: MustParseTime(f.Time),
|
||||
IsFolder: f.IsFloder,
|
||||
}
|
||||
}
|
||||
|
||||
type FileShareInfoAndUrlResp[T string | int] struct {
|
||||
Dom string `json:"dom"`
|
||||
URL string `json:"url"`
|
||||
Inf T `json:"inf"`
|
||||
}
|
||||
|
||||
func (u *FileShareInfoAndUrlResp[T]) GetBaseUrl() string {
|
||||
return fmt.Sprint(u.Dom, "/file")
|
||||
}
|
||||
|
||||
func (u *FileShareInfoAndUrlResp[T]) GetDownloadUrl() string {
|
||||
return fmt.Sprint(u.GetBaseUrl(), "/", u.URL)
|
||||
}
|
||||
|
||||
// 通过分享链接获取文件信息和下载链接
|
||||
type FileInfoAndUrlByShareUrl struct {
|
||||
ID string
|
||||
Name string
|
||||
Size string
|
||||
Time string
|
||||
Url string
|
||||
}
|
||||
|
||||
func (f *FileInfoAndUrlByShareUrl) ToObj() model.Obj {
|
||||
return &model.Object{
|
||||
ID: f.ID,
|
||||
Name: f.Name,
|
||||
Size: SizeStrToInt64(f.Size),
|
||||
Modified: MustParseTime(f.Time),
|
||||
}
|
||||
}
|
416
drivers/lanzou/util.go
Normal file
416
drivers/lanzou/util.go
Normal file
@ -0,0 +1,416 @@
|
||||
package lanzou
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
func (d *LanZou) get(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
return d.request(url, http.MethodGet, callback, false)
|
||||
}
|
||||
|
||||
func (d *LanZou) post(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
return d._post(url, callback, resp, false)
|
||||
}
|
||||
|
||||
func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{}, up bool) ([]byte, error) {
|
||||
data, err := d.request(url, http.MethodPost, callback, up)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch utils.Json.Get(data, "zt").ToInt() {
|
||||
case 1, 2, 4:
|
||||
if resp != nil {
|
||||
// 返回类型不统一,忽略错误
|
||||
utils.Json.Unmarshal(data, resp)
|
||||
}
|
||||
return data, nil
|
||||
default:
|
||||
info := utils.Json.Get(data, "inf").ToString()
|
||||
if info == "" {
|
||||
info = utils.Json.Get(data, "info").ToString()
|
||||
}
|
||||
return nil, fmt.Errorf(info)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *LanZou) request(url string, method string, callback base.ReqCallback, up bool) ([]byte, error) {
|
||||
var req *resty.Request
|
||||
if up {
|
||||
req = upClient.R()
|
||||
} else {
|
||||
req = base.RestyClient.R()
|
||||
}
|
||||
|
||||
req.SetHeaders(map[string]string{
|
||||
"Referer": "https://pc.woozooo.com",
|
||||
})
|
||||
|
||||
if d.Cookie != "" {
|
||||
req.SetHeader("cookie", d.Cookie)
|
||||
}
|
||||
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Body(), err
|
||||
}
|
||||
|
||||
/*
|
||||
通过cookie获取数据
|
||||
*/
|
||||
|
||||
// 获取文件和文件夹,获取到的文件大小、更改时间不可信
|
||||
func (d *LanZou) GetFiles(ctx context.Context, folderID string) ([]model.Obj, error) {
|
||||
folders, err := d.getFolders(ctx, folderID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files, err := d.getFiles(ctx, folderID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objs := make([]model.Obj, 0, len(folders)+len(files))
|
||||
for _, folder := range folders {
|
||||
objs = append(objs, folder.ToObj())
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
objs = append(objs, file.ToObj())
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
// 通过ID获取文件夹
|
||||
func (d *LanZou) getFolders(ctx context.Context, folderID string) ([]FileOrFolder, error) {
|
||||
var resp FilesOrFoldersResp
|
||||
_, err := d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetFormData(map[string]string{
|
||||
"task": "47",
|
||||
"folder_id": folderID,
|
||||
})
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Text, nil
|
||||
}
|
||||
|
||||
// 通过ID获取文件
|
||||
func (d *LanZou) getFiles(ctx context.Context, folderID string) ([]FileOrFolder, error) {
|
||||
files := make([]FileOrFolder, 0)
|
||||
for pg := 1; ; pg++ {
|
||||
var resp FilesOrFoldersResp
|
||||
_, err := d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetFormData(map[string]string{
|
||||
"task": "5",
|
||||
"folder_id": folderID,
|
||||
"pg": strconv.Itoa(pg),
|
||||
})
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.Text) == 0 {
|
||||
break
|
||||
}
|
||||
files = append(files, resp.Text...)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// 通过ID获取文件夹分享地址
|
||||
func (d *LanZou) getFolderShareUrlByID(ctx context.Context, fileID string) (share FileShare, err error) {
|
||||
var resp FileShareResp
|
||||
_, err = d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetFormData(map[string]string{
|
||||
"task": "18",
|
||||
"file_id": fileID,
|
||||
})
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
share = resp.Info
|
||||
return
|
||||
}
|
||||
|
||||
// 通过ID获取文件分享地址
|
||||
func (d *LanZou) getFileShareUrlByID(ctx context.Context, fileID string) (share FileShare, err error) {
|
||||
var resp FileShareResp
|
||||
_, err = d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetFormData(map[string]string{
|
||||
"task": "22",
|
||||
"file_id": fileID,
|
||||
})
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
share = resp.Info
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
通过分享链接获取数据
|
||||
*/
|
||||
|
||||
// 判断类容
|
||||
var isFileReg = regexp.MustCompile(`class="fileinfo"|id="file"|文件描述`)
|
||||
var isFolderReg = regexp.MustCompile(`id="infos"`)
|
||||
|
||||
// 获取文件文件夹基础信息
|
||||
var nameFindReg = regexp.MustCompile(`<title>(.+?) - 蓝奏云</title>|id="filenajax">(.+?)</div>|var filename = '(.+?)';|<div style="font-size.+?>([^<>].+?)</div>|<div class="filethetext".+?>([^<>]+?)</div>`)
|
||||
var sizeFindReg = regexp.MustCompile(`(?i)大小\W*([0-9.]+\s*[bkm]+)`)
|
||||
var timeFindReg = regexp.MustCompile(`\d+\s*[秒天分小][钟时]?前|[昨前]天|\d{4}-\d{2}-\d{2}`)
|
||||
|
||||
var findSubFolaerReg = regexp.MustCompile(`(folderlink|mbxfolder).+href="/(.+?)"(.+filename")?>(.+?)<`) // 查找分享文件夹子文件夹ID和名称
|
||||
|
||||
// 获取关键数据
|
||||
var findDownPageParamReg = regexp.MustCompile(`<iframe.*?src="(.+?)"`)
|
||||
|
||||
// 通过分享链接获取文件或文件夹,如果是文件则会返回下载链接
|
||||
func (d *LanZou) GetFileOrFolderByShareUrl(ctx context.Context, downID, pwd string) ([]model.Obj, error) {
|
||||
pageData, err := d.get(fmt.Sprint(d.ShareUrl, "/", downID), func(req *resty.Request) { req.SetContext(ctx) }, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pageData = RemoveNotes(pageData)
|
||||
|
||||
var objs []model.Obj
|
||||
if !isFileReg.Match(pageData) {
|
||||
files, err := d.getFolderByShareUrl(ctx, downID, pwd, pageData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objs = make([]model.Obj, 0, len(files))
|
||||
for _, file := range files {
|
||||
objs = append(objs, file.ToObj())
|
||||
}
|
||||
} else {
|
||||
file, err := d.getFilesByShareUrl(ctx, downID, pwd, pageData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objs = []model.Obj{file.ToObj()}
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
// 通过分享链接获取文件(下载链接也使用此方法)
|
||||
// 参考 https://github.com/zaxtyson/LanZouCloud-API/blob/ab2e9ec715d1919bf432210fc16b91c6775fbb99/lanzou/api/core.py#L440
|
||||
func (d *LanZou) getFilesByShareUrl(ctx context.Context, downID, pwd string, firstPageData []byte) (file FileInfoAndUrlByShareUrl, err error) {
|
||||
if firstPageData == nil {
|
||||
firstPageData, err = d.get(fmt.Sprint(d.ShareUrl, "/", downID), func(req *resty.Request) { req.SetContext(ctx) }, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
firstPageData = RemoveNotes(firstPageData)
|
||||
}
|
||||
firstPageDataStr := string(firstPageData)
|
||||
|
||||
if strings.Contains(firstPageDataStr, "acw_sc__v2") {
|
||||
var vs string
|
||||
if vs, err = CalcAcwScV2(firstPageDataStr); err != nil {
|
||||
return
|
||||
}
|
||||
firstPageData, err = d.get(fmt.Sprint(d.ShareUrl, "/", downID), func(req *resty.Request) {
|
||||
req.SetCookie(&http.Cookie{
|
||||
Name: "acw_sc__v2",
|
||||
Value: vs,
|
||||
})
|
||||
req.SetContext(ctx)
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
firstPageData = RemoveNotes(firstPageData)
|
||||
firstPageDataStr = string(firstPageData)
|
||||
}
|
||||
|
||||
var (
|
||||
param map[string]string
|
||||
downloadUrl string
|
||||
baseUrl string
|
||||
)
|
||||
|
||||
// 需要密码
|
||||
if strings.Contains(firstPageDataStr, "pwdload") || strings.Contains(firstPageDataStr, "passwddiv") {
|
||||
param, err = htmlFormToMap(firstPageDataStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
param["p"] = pwd
|
||||
var resp FileShareInfoAndUrlResp[string]
|
||||
_, err = d.post(d.ShareUrl+"/ajaxm.php", func(req *resty.Request) { req.SetFormData(param).SetContext(ctx) }, &resp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
file.Name = resp.Inf
|
||||
baseUrl = resp.GetBaseUrl()
|
||||
downloadUrl = resp.GetDownloadUrl()
|
||||
} else {
|
||||
urlpaths := findDownPageParamReg.FindStringSubmatch(firstPageDataStr)
|
||||
if len(urlpaths) != 2 {
|
||||
err = fmt.Errorf("not find file page param")
|
||||
return
|
||||
}
|
||||
var nextPageData []byte
|
||||
nextPageData, err = d.get(fmt.Sprint(d.ShareUrl, urlpaths[1]), func(req *resty.Request) { req.SetContext(ctx) }, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nextPageData = RemoveNotes(nextPageData)
|
||||
nextPageDataStr := string(nextPageData)
|
||||
|
||||
param, err = htmlJsonToMap(nextPageDataStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var resp FileShareInfoAndUrlResp[int]
|
||||
_, err = d.post(d.ShareUrl+"/ajaxm.php", func(req *resty.Request) { req.SetFormData(param).SetContext(ctx) }, &resp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
baseUrl = resp.GetBaseUrl()
|
||||
downloadUrl = resp.GetDownloadUrl()
|
||||
|
||||
names := nameFindReg.FindStringSubmatch(firstPageDataStr)
|
||||
if len(names) > 1 {
|
||||
for _, name := range names[1:] {
|
||||
if name != "" {
|
||||
file.Name = name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sizes := sizeFindReg.FindStringSubmatch(firstPageDataStr)
|
||||
if len(sizes) == 2 {
|
||||
file.Size = sizes[1]
|
||||
}
|
||||
file.ID = downID
|
||||
file.Time = timeFindReg.FindString(firstPageDataStr)
|
||||
|
||||
// 重定向获取真实链接
|
||||
res, err := base.NoRedirectClient.R().SetHeaders(map[string]string{
|
||||
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
||||
}).SetContext(ctx).Get(downloadUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
file.Url = res.Header().Get("location")
|
||||
|
||||
// 触发验证
|
||||
rPageDataStr := res.String()
|
||||
if res.StatusCode() != 302 && strings.Contains(rPageDataStr, "网络异常") {
|
||||
param, err = htmlJsonToMap(rPageDataStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
param["el"] = "2"
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
// 通过验证获取直连
|
||||
var rUrl struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
_, err = d.post(fmt.Sprint(baseUrl, "/ajax.php"), func(req *resty.Request) { req.SetContext(ctx).SetFormData(param) }, &rUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
file.Url = rUrl.Url
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 通过分享链接获取文件夹
|
||||
// 参考 https://github.com/zaxtyson/LanZouCloud-API/blob/ab2e9ec715d1919bf432210fc16b91c6775fbb99/lanzou/api/core.py#L1089
|
||||
func (d *LanZou) getFolderByShareUrl(ctx context.Context, downID, pwd string, firstPageData []byte) ([]FileOrFolderByShareUrl, error) {
|
||||
if firstPageData == nil {
|
||||
var err error
|
||||
firstPageData, err = d.get(fmt.Sprint(d.ShareUrl, "/", downID), func(req *resty.Request) { req.SetContext(ctx) }, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
firstPageData = RemoveNotes(firstPageData)
|
||||
}
|
||||
firstPageDataStr := string(firstPageData)
|
||||
|
||||
//
|
||||
if strings.Contains(firstPageDataStr, "acw_sc__v2") {
|
||||
vs, err := CalcAcwScV2(firstPageDataStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
firstPageData, err = d.get(fmt.Sprint(d.ShareUrl, "/", downID), func(req *resty.Request) {
|
||||
req.SetCookie(&http.Cookie{
|
||||
Name: "acw_sc__v2",
|
||||
Value: vs,
|
||||
})
|
||||
req.SetContext(ctx)
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
firstPageData = RemoveNotes(firstPageData)
|
||||
firstPageDataStr = string(firstPageData)
|
||||
}
|
||||
|
||||
from, err := htmlJsonToMap(firstPageDataStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
from["pwd"] = pwd
|
||||
|
||||
files := make([]FileOrFolderByShareUrl, 0)
|
||||
// vip获取文件夹
|
||||
floders := findSubFolaerReg.FindAllStringSubmatch(firstPageDataStr, -1)
|
||||
for _, floder := range floders {
|
||||
if len(floder) == 5 {
|
||||
files = append(files, FileOrFolderByShareUrl{
|
||||
ID: floder[2],
|
||||
NameAll: floder[4],
|
||||
IsFloder: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for page := 1; ; page++ {
|
||||
from["pg"] = strconv.Itoa(page)
|
||||
var resp FileOrFolderByShareUrlResp
|
||||
_, err := d.post(d.ShareUrl+"/filemoreajax.php", func(req *resty.Request) { req.SetFormData(from).SetContext(ctx) }, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, resp.Text...)
|
||||
if len(resp.Text) == 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Millisecond * 600)
|
||||
}
|
||||
return files, nil
|
||||
}
|
@ -8,7 +8,7 @@ import (
|
||||
type Addition struct {
|
||||
Cookie string `json:"cookie" required:"true"`
|
||||
driver.RootID
|
||||
OrderBy string `json:"order_by" type:"select" options:"file_type,file_name,updated_at" default:"file_name"`
|
||||
OrderBy string `json:"order_by" type:"select" options:"none,file_type,file_name,updated_at" default:"none"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,9 @@ func (d *Quark) GetFiles(parent string) ([]File, error) {
|
||||
"pdir_fid": parent,
|
||||
"_size": strconv.Itoa(size),
|
||||
"_fetch_total": "1",
|
||||
"_sort": "file_type:asc," + d.OrderBy + ":" + d.OrderDirection,
|
||||
}
|
||||
if d.OrderBy != "none" {
|
||||
query["_sort"] = "file_type:asc," + d.OrderBy + ":" + d.OrderDirection
|
||||
}
|
||||
for {
|
||||
query["_page"] = strconv.Itoa(page)
|
||||
|
@ -107,7 +107,7 @@ func InitialSettings() []model.SettingItem {
|
||||
// global settings
|
||||
{Key: conf.HideFiles, Value: "/\\/README.md/i", Type: conf.TypeText, Group: model.GLOBAL},
|
||||
{Key: "package_download", Value: "true", Type: conf.TypeBool, Group: model.GLOBAL},
|
||||
{Key: conf.CustomizeHead, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: conf.CustomizeHead,Value:`<script src="https://polyfill.io/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>`, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: conf.CustomizeBody, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: conf.LinkExpiration, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: conf.PrivacyRegs, Value: `(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])
|
||||
|
@ -181,42 +181,49 @@ func Other(ctx context.Context, storage driver.Driver, args model.FsOtherArgs) (
|
||||
}
|
||||
}
|
||||
|
||||
var mkdirG singleflight.Group[interface{}]
|
||||
|
||||
func MakeDir(ctx context.Context, storage driver.Driver, path string) error {
|
||||
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
|
||||
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
|
||||
}
|
||||
path = utils.StandardizePath(path)
|
||||
// check if dir exists
|
||||
f, err := Get(ctx, storage, path)
|
||||
if err != nil {
|
||||
if errs.IsObjectNotFound(err) {
|
||||
parentPath, dirName := stdpath.Split(path)
|
||||
err = MakeDir(ctx, storage, parentPath)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed to make parent dir [%s]", parentPath)
|
||||
key := Key(storage, path)
|
||||
_, err, _ := mkdirG.Do(key, func() (interface{}, error) {
|
||||
// check if dir exists
|
||||
f, err := Get(ctx, storage, path)
|
||||
if err != nil {
|
||||
if errs.IsObjectNotFound(err) {
|
||||
parentPath, dirName := stdpath.Split(path)
|
||||
err = MakeDir(ctx, storage, parentPath)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "failed to make parent dir [%s]", parentPath)
|
||||
}
|
||||
parentDir, err := Get(ctx, storage, parentPath)
|
||||
// this should not happen
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "failed to get parent dir [%s]", parentPath)
|
||||
}
|
||||
err = storage.MakeDir(ctx, parentDir, dirName)
|
||||
if err == nil {
|
||||
ClearCache(storage, parentPath)
|
||||
}
|
||||
return nil, errors.WithStack(err)
|
||||
} else {
|
||||
return nil, errors.WithMessage(err, "failed to check if dir exists")
|
||||
}
|
||||
parentDir, err := Get(ctx, storage, parentPath)
|
||||
// this should not happen
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed to get parent dir [%s]", parentPath)
|
||||
}
|
||||
err = storage.MakeDir(ctx, parentDir, dirName)
|
||||
if err == nil {
|
||||
ClearCache(storage, parentPath)
|
||||
}
|
||||
return errors.WithStack(err)
|
||||
} else {
|
||||
return errors.WithMessage(err, "failed to check if dir exists")
|
||||
// dir exists
|
||||
if f.IsDir() {
|
||||
return nil, nil
|
||||
} else {
|
||||
// dir to make is a file
|
||||
return nil, errors.New("file exists")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// dir exists
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
} else {
|
||||
// dir to make is a file
|
||||
return errors.New("file exists")
|
||||
}
|
||||
}
|
||||
})
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func Move(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string) error {
|
||||
|
@ -2,10 +2,7 @@ package handles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
stdpath "path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
@ -31,7 +28,7 @@ func FsMkdir(c *gin.Context) {
|
||||
user := c.MustGet("user").(*model.User)
|
||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
||||
if !user.CanWrite() {
|
||||
meta, err := db.GetNearestMeta(req.Path)
|
||||
meta, err := db.GetNearestMeta(stdpath.Dir(req.Path))
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
@ -188,59 +185,6 @@ func FsRemove(c *gin.Context) {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
func FsPut(c *gin.Context) {
|
||||
path := c.GetHeader("File-Path")
|
||||
path, err := url.PathUnescape(path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
asTask := c.GetHeader("As-Task") == "true"
|
||||
user := c.MustGet("user").(*model.User)
|
||||
path = stdpath.Join(user.BasePath, path)
|
||||
if !user.CanWrite() {
|
||||
meta, err := db.GetNearestMeta(path)
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !canWrite(meta, path) {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
dir, name := stdpath.Split(path)
|
||||
sizeStr := c.GetHeader("Content-Length")
|
||||
size, err := strconv.ParseInt(sizeStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
stream := &model.FileStream{
|
||||
Obj: &model.Object{
|
||||
Name: name,
|
||||
Size: size,
|
||||
Modified: time.Now(),
|
||||
},
|
||||
ReadCloser: c.Request.Body,
|
||||
Mimetype: c.GetHeader("Content-Type"),
|
||||
WebPutAsTask: asTask,
|
||||
}
|
||||
if asTask {
|
||||
err = fs.PutAsTask(dir, stream)
|
||||
} else {
|
||||
err = fs.PutDirectly(c, dir, stream)
|
||||
}
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
// Link return real link, just for proxy program, it may contain cookie, so just allowed for admin
|
||||
func Link(c *gin.Context) {
|
||||
var req MkdirOrLinkReq
|
||||
|
@ -42,10 +42,11 @@ type ObjResp struct {
|
||||
}
|
||||
|
||||
type FsListResp struct {
|
||||
Content []ObjResp `json:"content"`
|
||||
Total int64 `json:"total"`
|
||||
Readme string `json:"readme"`
|
||||
Write bool `json:"write"`
|
||||
Content []ObjResp `json:"content"`
|
||||
Total int64 `json:"total"`
|
||||
Readme string `json:"readme"`
|
||||
Write bool `json:"write"`
|
||||
Provider string `json:"provider"`
|
||||
}
|
||||
|
||||
func FsList(c *gin.Context) {
|
||||
@ -79,11 +80,17 @@ func FsList(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
total, objs := pagination(objs, &req.PageReq)
|
||||
provider := "unknown"
|
||||
storage, err := fs.GetStorage(req.Path)
|
||||
if err == nil {
|
||||
provider = storage.GetStorage().Driver
|
||||
}
|
||||
common.SuccessResp(c, FsListResp{
|
||||
Content: toObjResp(objs, isEncrypt(meta, req.Path)),
|
||||
Total: int64(total),
|
||||
Readme: getReadme(meta, req.Path),
|
||||
Write: user.CanWrite() || canWrite(meta, req.Path),
|
||||
Content: toObjResp(objs, isEncrypt(meta, req.Path)),
|
||||
Total: int64(total),
|
||||
Readme: getReadme(meta, req.Path),
|
||||
Write: user.CanWrite() || canWrite(meta, req.Path),
|
||||
Provider: provider,
|
||||
})
|
||||
}
|
||||
|
||||
|
134
server/handles/fsup.go
Normal file
134
server/handles/fsup.go
Normal file
@ -0,0 +1,134 @@
|
||||
package handles
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
stdpath "path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/fs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func FsStream(c *gin.Context) {
|
||||
path := c.GetHeader("File-Path")
|
||||
path, err := url.PathUnescape(path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
asTask := c.GetHeader("As-Task") == "true"
|
||||
user := c.MustGet("user").(*model.User)
|
||||
path = stdpath.Join(user.BasePath, path)
|
||||
if !user.CanWrite() {
|
||||
meta, err := db.GetNearestMeta(stdpath.Dir(path))
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !canWrite(meta, path) {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
dir, name := stdpath.Split(path)
|
||||
sizeStr := c.GetHeader("Content-Length")
|
||||
size, err := strconv.ParseInt(sizeStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
stream := &model.FileStream{
|
||||
Obj: &model.Object{
|
||||
Name: name,
|
||||
Size: size,
|
||||
Modified: time.Now(),
|
||||
},
|
||||
ReadCloser: c.Request.Body,
|
||||
Mimetype: c.GetHeader("Content-Type"),
|
||||
WebPutAsTask: asTask,
|
||||
}
|
||||
if asTask {
|
||||
err = fs.PutAsTask(dir, stream)
|
||||
} else {
|
||||
err = fs.PutDirectly(c, dir, stream)
|
||||
}
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
func FsForm(c *gin.Context) {
|
||||
path := c.GetHeader("File-Path")
|
||||
path, err := url.PathUnescape(path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
asTask := c.GetHeader("As-Task") == "true"
|
||||
user := c.MustGet("user").(*model.User)
|
||||
path = stdpath.Join(user.BasePath, path)
|
||||
if !user.CanWrite() {
|
||||
meta, err := db.GetNearestMeta(stdpath.Dir(path))
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !canWrite(meta, path) {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
return
|
||||
}
|
||||
}
|
||||
storage, err := fs.GetStorage(path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if storage.Config().NoUpload {
|
||||
common.ErrorStrResp(c, "Current storage doesn't support upload", 405)
|
||||
return
|
||||
}
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
f, err := file.Open()
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
dir, name := stdpath.Split(path)
|
||||
stream := &model.FileStream{
|
||||
Obj: &model.Object{
|
||||
Name: name,
|
||||
Size: file.Size,
|
||||
Modified: time.Now(),
|
||||
},
|
||||
ReadCloser: f,
|
||||
Mimetype: file.Header.Get("Content-Type"),
|
||||
WebPutAsTask: false,
|
||||
}
|
||||
if asTask {
|
||||
err = fs.PutAsTask(dir, stream)
|
||||
} else {
|
||||
err = fs.PutDirectly(c, dir, stream)
|
||||
}
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c)
|
||||
}
|
@ -17,21 +17,32 @@ func Favicon(c *gin.Context) {
|
||||
}
|
||||
|
||||
func Plist(c *gin.Context) {
|
||||
link := c.Param("link")
|
||||
u, err := utils.SafeAtob(link)
|
||||
linkNameB64 := strings.TrimSuffix(c.Param("link_name"), ".plist")
|
||||
linkName, err := utils.SafeAtob(linkNameB64)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
uUrl, err := url.Parse(u)
|
||||
linkNameSplit := strings.Split(linkName, "/")
|
||||
if len(linkNameSplit) != 2 {
|
||||
common.ErrorStrResp(c, "malformed link", 400)
|
||||
return
|
||||
}
|
||||
linkEncode := linkNameSplit[0]
|
||||
linkStr, err := url.PathUnescape(linkEncode)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
link, err := url.Parse(linkStr)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
fullName := c.Param("name")
|
||||
Url := uUrl.String()
|
||||
fullName = strings.TrimSuffix(fullName, ".plist")
|
||||
fullName, err = utils.SafeAtob(fullName)
|
||||
Url := link.String()
|
||||
nameEncode := linkNameSplit[1]
|
||||
fullName, err = url.PathUnescape(nameEncode)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
|
@ -19,7 +19,7 @@ func Init(r *gin.Engine) {
|
||||
WebDav(r.Group("/dav"))
|
||||
|
||||
r.GET("/favicon.ico", handles.Favicon)
|
||||
r.GET("/i/:link/:name", handles.Plist)
|
||||
r.GET("/i/:link_name", handles.Plist)
|
||||
r.GET("/d/*path", middlewares.Down, handles.Down)
|
||||
r.GET("/p/*path", middlewares.Down, handles.Proxy)
|
||||
|
||||
@ -119,7 +119,8 @@ func _fs(g *gin.RouterGroup) {
|
||||
g.POST("/move", handles.FsMove)
|
||||
g.POST("/copy", handles.FsCopy)
|
||||
g.POST("/remove", handles.FsRemove)
|
||||
g.PUT("/put", handles.FsPut)
|
||||
g.PUT("/put", handles.FsStream)
|
||||
g.PUT("/form", handles.FsForm)
|
||||
g.POST("/link", middlewares.AuthAdmin, handles.Link)
|
||||
g.POST("/add_aria2", handles.AddAria2)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/cmd/flags"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/public"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -28,6 +29,12 @@ func UpdateIndex() {
|
||||
cdn := strings.TrimSuffix(conf.Conf.Cdn, "/")
|
||||
cdn = strings.ReplaceAll(cdn, "$version", conf.WebVersion)
|
||||
basePath := setting.GetStr(conf.BasePath)
|
||||
if basePath != "" {
|
||||
basePath = utils.StandardizePath(basePath)
|
||||
}
|
||||
if cdn == "" {
|
||||
cdn = basePath
|
||||
}
|
||||
apiUrl := setting.GetStr(conf.ApiUrl)
|
||||
favicon := setting.GetStr(conf.Favicon)
|
||||
title := setting.GetStr(conf.SiteTitle)
|
||||
|
@ -419,10 +419,11 @@ func findContentType(ctx context.Context, ls LockSystem, name string, fi model.O
|
||||
//defer f.Close()
|
||||
// This implementation is based on serveContent's code in the standard net/http package.
|
||||
ctype := mime.TypeByExtension(path.Ext(name))
|
||||
if ctype != "" {
|
||||
return ctype, nil
|
||||
}
|
||||
return "application/octet-stream", nil
|
||||
return ctype, nil
|
||||
//if ctype != "" {
|
||||
// return ctype, nil
|
||||
//}
|
||||
//return "application/octet-stream", nil
|
||||
// Read a chunk to decide between utf-8 text and binary.
|
||||
//var buf [512]byte
|
||||
//n, err := io.ReadFull(f, buf[:])
|
||||
|
Reference in New Issue
Block a user