mirror of https://github.com/Xhofe/alist
parent
1c65588b4a
commit
c64c003257
|
@ -27,6 +27,8 @@ English | [中文](./README_cn.md)
|
||||||
- [x] [123pan](https://www.123pan.com/)
|
- [x] [123pan](https://www.123pan.com/)
|
||||||
- [x] [lanzou](https://pc.woozooo.com/)
|
- [x] [lanzou](https://pc.woozooo.com/)
|
||||||
- [x] [Alist](https://github.com/Xhofe/alist)
|
- [x] [Alist](https://github.com/Xhofe/alist)
|
||||||
|
- [x] FTP
|
||||||
|
- [x] [PikPak](https://www.mypikpak.com/)
|
||||||
- [x] File preview (PDF, markdown, code, plain text, ...)
|
- [x] File preview (PDF, markdown, code, plain text, ...)
|
||||||
- [x] Image preview in gallery mode
|
- [x] Image preview in gallery mode
|
||||||
- [x] Video and audio preview (mp4, mp3, ...)
|
- [x] Video and audio preview (mp4, mp3, ...)
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
- [x] [123云盘](https://www.123pan.com/)
|
- [x] [123云盘](https://www.123pan.com/)
|
||||||
- [x] [蓝奏云](https://pc.woozooo.com/)
|
- [x] [蓝奏云](https://pc.woozooo.com/)
|
||||||
- [x] [Alist](https://github.com/Xhofe/alist)
|
- [x] [Alist](https://github.com/Xhofe/alist)
|
||||||
|
- [x] FTP
|
||||||
|
- [x] [PikPak](https://www.mypikpak.com/)
|
||||||
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
||||||
- [x] 画廊模式下的图像预览
|
- [x] 画廊模式下的图像预览
|
||||||
- [x] 视频和音频预览(mp4、mp3 等)
|
- [x] 视频和音频预览(mp4、mp3 等)
|
||||||
|
|
|
@ -10,4 +10,5 @@ import (
|
||||||
_ "github.com/Xhofe/alist/drivers/lanzou"
|
_ "github.com/Xhofe/alist/drivers/lanzou"
|
||||||
_ "github.com/Xhofe/alist/drivers/native"
|
_ "github.com/Xhofe/alist/drivers/native"
|
||||||
_ "github.com/Xhofe/alist/drivers/onedrive"
|
_ "github.com/Xhofe/alist/drivers/onedrive"
|
||||||
|
_ "github.com/Xhofe/alist/drivers/pikpak"
|
||||||
)
|
)
|
||||||
|
|
|
@ -106,4 +106,5 @@ func init() {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
NoRedirectClient.SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
|
NoRedirectClient.SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
|
||||||
|
RestyClient.SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,14 @@ const (
|
||||||
TypeNumber = "number"
|
TypeNumber = "number"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Get = iota
|
||||||
|
Post
|
||||||
|
Put
|
||||||
|
Delete
|
||||||
|
Patch
|
||||||
|
)
|
||||||
|
|
||||||
type Json map[string]interface{}
|
type Json map[string]interface{}
|
||||||
|
|
||||||
type TokenResp struct {
|
type TokenResp struct {
|
||||||
|
|
|
@ -0,0 +1,240 @@
|
||||||
|
package pikpak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PikPak struct{}
|
||||||
|
|
||||||
|
func (driver PikPak) Config() base.DriverConfig {
|
||||||
|
return base.DriverConfig{
|
||||||
|
Name: "PikPak",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Items() []base.Item {
|
||||||
|
return []base.Item{
|
||||||
|
{
|
||||||
|
Name: "username",
|
||||||
|
Label: "username",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "password",
|
||||||
|
Label: "password",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "root_folder",
|
||||||
|
Label: "root folder id",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Save(account *model.Account, old *model.Account) error {
|
||||||
|
err := driver.Login(account)
|
||||||
|
_ = model.SaveAccount(account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) File(path string, account *model.Account) (*model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
if path == "/" {
|
||||||
|
return &model.File{
|
||||||
|
Id: account.RootFolder,
|
||||||
|
Name: account.Name,
|
||||||
|
Size: 0,
|
||||||
|
Type: conf.FOLDER,
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: account.UpdatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
dir, name := filepath.Split(path)
|
||||||
|
files, err := driver.Files(dir, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if file.Name == name {
|
||||||
|
return &file, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, base.ErrPathNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Files(path string, account *model.Account) ([]model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
var files []model.File
|
||||||
|
cache, err := base.GetCache(path, account)
|
||||||
|
if err == nil {
|
||||||
|
files, _ = cache.([]model.File)
|
||||||
|
} else {
|
||||||
|
file, err := driver.File(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawFiles, err := driver.GetFiles(file.Id, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
files = make([]model.File, 0)
|
||||||
|
for _, file := range rawFiles {
|
||||||
|
files = append(files, *driver.FormatFile(&file))
|
||||||
|
}
|
||||||
|
if len(files) > 0 {
|
||||||
|
_ = base.SetCache(path, files, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Link(path string, account *model.Account) (*base.Link, error) {
|
||||||
|
file, err := driver.File(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var resp File
|
||||||
|
_, err = driver.Request(fmt.Sprintf("https://api-drive.mypikpak.com/drive/v1/files/%s?_magic=2021&thumbnail_size=SIZE_LARGE", file.Id),
|
||||||
|
base.Get, nil, nil, &resp, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &base.Link{
|
||||||
|
Url: resp.WebContentLink,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
log.Debugf("pikpak path: %s", path)
|
||||||
|
file, err := driver.File(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !file.IsDir() {
|
||||||
|
link, err := driver.Link(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
file.Url = link.Url
|
||||||
|
return file, nil, nil
|
||||||
|
}
|
||||||
|
files, err := driver.Files(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return nil, files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Proxy(c *gin.Context, account *model.Account) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Preview(path string, account *model.Account) (interface{}, error) {
|
||||||
|
return nil, base.ErrNotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) MakeDir(path string, account *model.Account) error {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
dir, name := filepath.Split(path)
|
||||||
|
parentFile, err := driver.File(dir, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !parentFile.IsDir() {
|
||||||
|
return base.ErrNotFolder
|
||||||
|
}
|
||||||
|
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Post, nil, &base.Json{
|
||||||
|
"kind": "drive#folder",
|
||||||
|
"parent_id": parentFile.Id,
|
||||||
|
"name": name,
|
||||||
|
}, nil, account)
|
||||||
|
if err == nil {
|
||||||
|
_ = base.DeleteCache(dir, account)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Move(src string, dst string, account *model.Account) error {
|
||||||
|
srcDir, _ := filepath.Split(src)
|
||||||
|
dstDir, dstName := filepath.Split(dst)
|
||||||
|
srcFile, err := driver.File(src, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// rename
|
||||||
|
if srcDir == dstDir {
|
||||||
|
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files/"+srcFile.Id, base.Patch, nil, &base.Json{
|
||||||
|
"name": dstName,
|
||||||
|
}, nil, account)
|
||||||
|
} else {
|
||||||
|
// move
|
||||||
|
dstDirFile, err := driver.File(dstDir, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchMove", base.Post, nil, &base.Json{
|
||||||
|
"ids": []string{srcFile.Id},
|
||||||
|
"to": base.Json{
|
||||||
|
"parent_id": dstDirFile.Id,
|
||||||
|
},
|
||||||
|
}, nil, account)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
_ = base.DeleteCache(srcDir, account)
|
||||||
|
_ = base.DeleteCache(dstDir, account)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Copy(src string, dst string, account *model.Account) error {
|
||||||
|
srcFile, err := driver.File(src, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dstDirFile, err := driver.File(utils.Dir(dst), account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchCopy", base.Post, nil, &base.Json{
|
||||||
|
"ids": []string{srcFile.Id},
|
||||||
|
"to": base.Json{
|
||||||
|
"parent_id": dstDirFile.Id,
|
||||||
|
},
|
||||||
|
}, nil, account)
|
||||||
|
if err == nil {
|
||||||
|
_ = base.DeleteCache(utils.Dir(dst), account)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Delete(path string, account *model.Account) error {
|
||||||
|
file, err := driver.File(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchTrash", base.Post, nil, &base.Json{
|
||||||
|
"ids": []string{file.Id},
|
||||||
|
}, nil, account)
|
||||||
|
if err == nil {
|
||||||
|
_ = base.DeleteCache(utils.Dir(path), account)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Upload(file *model.FileStream, account *model.Account) error {
|
||||||
|
return base.ErrNotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ base.Driver = (*PikPak)(nil)
|
|
@ -0,0 +1,178 @@
|
||||||
|
package pikpak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RespErr struct {
|
||||||
|
ErrorCode int `json:"error_code"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Login(account *model.Account) error {
|
||||||
|
var e RespErr
|
||||||
|
res, err := base.RestyClient.R().SetError(&e).SetBody(base.Json{
|
||||||
|
"captcha_token": "",
|
||||||
|
"client_id": "YNxT9w7GMdWvEOKa",
|
||||||
|
"client_secret": "dbw2OtmVEeuUvIptb1Coyg",
|
||||||
|
"username": account.Username,
|
||||||
|
"password": account.Password,
|
||||||
|
}).Post("https://user.mypikpak.com/v1/auth/signin")
|
||||||
|
if err != nil {
|
||||||
|
account.Status = err.Error()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.ErrorCode != 0 {
|
||||||
|
account.Status = e.Error
|
||||||
|
return errors.New(e.Error)
|
||||||
|
}
|
||||||
|
data := res.Body()
|
||||||
|
account.Status = "work"
|
||||||
|
account.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
|
||||||
|
account.AccessToken = jsoniter.Get(data, "access_token").ToString()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) RefreshToken(account *model.Account) error {
|
||||||
|
var e RespErr
|
||||||
|
res, err := base.RestyClient.R().SetError(&e).SetBody(base.Json{
|
||||||
|
"client_id": "YNxT9w7GMdWvEOKa",
|
||||||
|
"client_secret": "dbw2OtmVEeuUvIptb1Coyg",
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
"refresh_token": account.RefreshToken,
|
||||||
|
}).Post("https://user.mypikpak.com/v1/auth/token")
|
||||||
|
if err != nil {
|
||||||
|
account.Status = err.Error()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.ErrorCode != 0 {
|
||||||
|
if e.ErrorCode == 4126 {
|
||||||
|
// refresh_token 失效,重新登陆
|
||||||
|
return driver.Login(account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data := res.Body()
|
||||||
|
account.Status = "work"
|
||||||
|
account.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
|
||||||
|
account.AccessToken = jsoniter.Get(data, "access_token").ToString()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) Request(url string, method int, query map[string]string, data *base.Json, resp interface{}, account *model.Account) ([]byte, error) {
|
||||||
|
req := base.RestyClient.R()
|
||||||
|
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
|
||||||
|
if query != nil {
|
||||||
|
req.SetQueryParams(query)
|
||||||
|
}
|
||||||
|
if data != nil {
|
||||||
|
req.SetBody(data)
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
req.SetResult(resp)
|
||||||
|
}
|
||||||
|
var e RespErr
|
||||||
|
req.SetError(&e)
|
||||||
|
var res *resty.Response
|
||||||
|
var err error
|
||||||
|
switch method {
|
||||||
|
case base.Get:
|
||||||
|
res, err = req.Get(url)
|
||||||
|
case base.Post:
|
||||||
|
res, err = req.Post(url)
|
||||||
|
case base.Patch:
|
||||||
|
res, err = req.Patch(url)
|
||||||
|
default:
|
||||||
|
return nil, base.ErrNotSupport
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if e.ErrorCode != 0 {
|
||||||
|
if e.ErrorCode == 16 {
|
||||||
|
// login / refresh token
|
||||||
|
err = driver.RefreshToken(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_ = model.SaveAccount(account)
|
||||||
|
return driver.Request(url, method, query, data, resp, account)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New(e.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res.Body(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ModifiedTime *time.Time `json:"modified_time"`
|
||||||
|
Size string `json:"size"`
|
||||||
|
ThumbnailLink string `json:"thumbnail_link"`
|
||||||
|
WebContentLink string `json:"web_content_link"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) FormatFile(file *File) *model.File {
|
||||||
|
size, _ := strconv.ParseInt(file.Size, 10, 64)
|
||||||
|
f := &model.File{
|
||||||
|
Id: file.Id,
|
||||||
|
Name: file.Name,
|
||||||
|
Size: size,
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: file.ModifiedTime,
|
||||||
|
Thumbnail: file.ThumbnailLink,
|
||||||
|
}
|
||||||
|
if file.Kind == "drive#folder" {
|
||||||
|
f.Type = conf.FOLDER
|
||||||
|
} else {
|
||||||
|
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
type Files struct {
|
||||||
|
Files []File `json:"files"`
|
||||||
|
NextPageToken string `json:"next_page_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver PikPak) GetFiles(id string, account *model.Account) ([]File, error) {
|
||||||
|
res := make([]File, 0)
|
||||||
|
pageToken := "first"
|
||||||
|
for pageToken != "" {
|
||||||
|
if pageToken == "first" {
|
||||||
|
pageToken = ""
|
||||||
|
}
|
||||||
|
query := map[string]string{
|
||||||
|
"parent_id": id,
|
||||||
|
"thumbnail_size": "SIZE_LARGE",
|
||||||
|
"with_audit": "true",
|
||||||
|
"limit": "100",
|
||||||
|
"filters": `{"phase":{"eq":"PHASE_TYPE_COMPLETE"},"trashed":{"eq":false}}`,
|
||||||
|
"page_token": pageToken,
|
||||||
|
}
|
||||||
|
var resp Files
|
||||||
|
_, err := driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Get, query, nil, &resp, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debugf("%+v", resp)
|
||||||
|
pageToken = resp.NextPageToken
|
||||||
|
res = append(res, resp.Files...)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RegisterDriver(&PikPak{})
|
||||||
|
}
|
|
@ -220,6 +220,7 @@ func moveFiles(ctx context.Context, fs *FileSystem, src string, dst string, over
|
||||||
}
|
}
|
||||||
err = driver.Move(srcPath,dstPath,srcAccount)
|
err = driver.Move(srcPath,dstPath,srcAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Debug(err)
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
return http.StatusNoContent, nil
|
return http.StatusNoContent, nil
|
||||||
|
|
Loading…
Reference in New Issue