diff --git a/drivers/123/driver.go b/drivers/123/driver.go new file mode 100644 index 00000000..00e412db --- /dev/null +++ b/drivers/123/driver.go @@ -0,0 +1,227 @@ +package _123 + +import ( + "bytes" + "context" + "crypto/md5" + "encoding/binary" + "encoding/hex" + "fmt" + "io" + "net/http" + "os" + + "github.com/alist-org/alist/v3/drivers/base" + "github.com/alist-org/alist/v3/internal/conf" + "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/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3/s3manager" + "github.com/go-resty/resty/v2" +) + +type Pan123 struct { + model.Storage + Addition + AccessToken string +} + +func (d *Pan123) Config() driver.Config { + return config +} + +func (d *Pan123) GetAddition() driver.Additional { + return d.Addition +} + +func (d *Pan123) 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.login() +} + +func (d *Pan123) Drop(ctx context.Context) error { + return nil +} + +func (d *Pan123) 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 src, nil + }) +} + +//func (d *Pan123) Get(ctx context.Context, path string) (model.Obj, error) { +// // TODO this is optional +// return nil, errs.NotImplement +//} + +func (d *Pan123) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + if f, ok := file.(File); ok { + return &model.Link{ + URL: f.DownloadUrl, + }, nil + } else { + return nil, fmt.Errorf("can't convert obj") + } +} + +func (d *Pan123) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { + data := base.Json{ + "driveId": 0, + "etag": "", + "fileName": dirName, + "parentFileId": parentDir.GetID(), + "size": 0, + "type": 1, + } + _, err := d.request("https://www.123pan.com/api/file/upload_request", http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, nil) + return err +} + +func (d *Pan123) Move(ctx context.Context, srcObj, dstDir model.Obj) error { + data := base.Json{ + "fileIdList": []base.Json{{"FileId": srcObj.GetID()}}, + "parentFileId": dstDir.GetID(), + } + _, err := d.request("https://www.123pan.com/api/file/mod_pid", http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, nil) + return err +} + +func (d *Pan123) Rename(ctx context.Context, srcObj model.Obj, newName string) error { + data := base.Json{ + "driveId": 0, + "fileId": srcObj.GetID(), + "fileName": newName, + } + _, err := d.request("https://www.123pan.com/api/file/rename", http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, nil) + return err +} + +func (d *Pan123) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { + return errs.NotSupport +} + +func (d *Pan123) Remove(ctx context.Context, obj model.Obj) error { + if f, ok := obj.(File); ok { + data := base.Json{ + "driveId": 0, + "operation": true, + "fileTrashInfoList": []File{f}, + } + _, err := d.request("https://www.123pan.com/a/api/file/trash", http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, nil) + return err + } else { + return fmt.Errorf("can't convert obj") + } +} + +func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { + const DEFAULT int64 = 10485760 + var uploadFile io.Reader + h := md5.New() + if d.StreamUpload && stream.GetSize() > DEFAULT { + // 只计算前10MIB + buf := bytes.NewBuffer(make([]byte, 0, DEFAULT)) + if n, err := io.CopyN(io.MultiWriter(buf, h), stream, DEFAULT); err != io.EOF && n == 0 { + return err + } + // 增加额外参数防止MD5碰撞 + h.Write([]byte(stream.GetName())) + num := make([]byte, 8) + binary.BigEndian.PutUint64(num, uint64(stream.GetSize())) + h.Write(num) + // 拼装 + uploadFile = io.MultiReader(buf, stream) + } else { + // 计算完整文件MD5 + tempFile, err := os.CreateTemp(conf.Conf.TempDir, "file-*") + if err != nil { + return err + } + defer func() { + _ = tempFile.Close() + _ = os.Remove(tempFile.Name()) + }() + + if _, err = io.Copy(io.MultiWriter(tempFile, h), stream); err != nil { + return err + } + + _, err = tempFile.Seek(0, io.SeekStart) + if err != nil { + return err + } + uploadFile = tempFile + } + etag := hex.EncodeToString(h.Sum(nil)) + data := base.Json{ + "driveId": 0, + "duplicate": 2, // 2->覆盖 1->重命名 0->默认 + "etag": etag, + "fileName": stream.GetName(), + "parentFileId": dstDir.GetID(), + "size": stream.GetSize(), + "type": 0, + } + var resp UploadResp + _, err := d.request("https://www.123pan.com/api/file/upload_request", http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, &resp) + if err != nil { + return err + } + if resp.Data.Key == "" { + return nil + } + cfg := &aws.Config{ + Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken), + Region: aws.String("123pan"), + Endpoint: aws.String("file.123pan.com"), + S3ForcePathStyle: aws.Bool(true), + } + s, err := session.NewSession(cfg) + if err != nil { + return err + } + uploader := s3manager.NewUploader(s) + input := &s3manager.UploadInput{ + Bucket: &resp.Data.Bucket, + Key: &resp.Data.Key, + Body: uploadFile, + } + _, err = uploader.Upload(input) + if err != nil { + return err + } + _, err = d.request("https://www.123pan.com/api/file/upload_complete", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "fileId": resp.Data.FileId, + }) + }, nil) + return err +} + +func (d *Pan123) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { + return nil, errs.NotSupport +} + +var _ driver.Driver = (*Pan123)(nil) diff --git a/drivers/123/meta.go b/drivers/123/meta.go new file mode 100644 index 00000000..a4933a1e --- /dev/null +++ b/drivers/123/meta.go @@ -0,0 +1,30 @@ +package _123 + +import ( + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/op" +) + +type Addition struct { + Username string `json:"username" required:"true"` + Password string `json:"password" required:"true"` + OrderBy string `json:"order_by" type:"select" options:"name,fileId,updateAt,createAt" default:"name"` + OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` + driver.RootFolderId + // define other + StreamUpload bool `json:"stream_upload"` + //Field string `json:"field" type:"select" required:"true" options:"a,b,c" default:"a"` +} + +var config = driver.Config{ + Name: "123Pan", + DefaultRoot: "0", +} + +func New() driver.Driver { + return &Pan123{} +} + +func init() { + op.RegisterDriver(config, New) +} diff --git a/drivers/123/types.go b/drivers/123/types.go new file mode 100644 index 00000000..6ef29c0f --- /dev/null +++ b/drivers/123/types.go @@ -0,0 +1,85 @@ +package _123 + +import ( + "strconv" + "time" + + "github.com/alist-org/alist/v3/internal/model" +) + +type BaseResp struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type TokenResp struct { + BaseResp + Data struct { + Token string `json:"token"` + } `json:"data"` +} + +type File struct { + FileName string `json:"FileName"` + Size int64 `json:"Size"` + UpdateAt time.Time `json:"UpdateAt"` + FileId int64 `json:"FileId"` + Type int `json:"Type"` + Etag string `json:"Etag"` + S3KeyFlag string `json:"S3KeyFlag"` + DownloadUrl string `json:"DownloadUrl"` +} + +func (f File) GetSize() int64 { + return f.Size +} + +func (f File) GetName() string { + return f.FileName +} + +func (f File) ModTime() time.Time { + return f.UpdateAt +} + +func (f File) IsDir() bool { + return f.Type == 1 +} + +func (f File) GetID() string { + return strconv.FormatInt(f.FileId, 10) +} + +var _ model.Obj = (*File)(nil) + +//func (f File) Thumb() string { +// +//} +//var _ model.Thumb = (*File)(nil) + +type Files struct { + BaseResp + Data struct { + InfoList []File `json:"InfoList"` + Next string `json:"Next"` + } `json:"data"` +} + +type DownResp struct { + BaseResp + Data struct { + DownloadUrl string `json:"DownloadUrl"` + } `json:"data"` +} + +type UploadResp struct { + BaseResp + Data struct { + AccessKeyId string `json:"AccessKeyId"` + Bucket string `json:"Bucket"` + Key string `json:"Key"` + SecretAccessKey string `json:"SecretAccessKey"` + SessionToken string `json:"SessionToken"` + FileId int64 `json:"FileId"` + } `json:"data"` +} diff --git a/drivers/123/util.go b/drivers/123/util.go new file mode 100644 index 00000000..cfe3ddd8 --- /dev/null +++ b/drivers/123/util.go @@ -0,0 +1,87 @@ +package _123 + +import ( + "errors" + "fmt" + "net/http" + + "github.com/alist-org/alist/v3/drivers/base" + "github.com/go-resty/resty/v2" + jsoniter "github.com/json-iterator/go" +) + +// do others that not defined in Driver interface + +func (d *Pan123) login() error { + url := "https://www.123pan.com/api/user/sign_in" + var resp TokenResp + _, err := base.RestyClient.R(). + SetResult(&resp). + SetBody(base.Json{ + "passport": d.Username, + "password": d.Password, + }).Post(url) + if err != nil { + return err + } + if resp.Code != 200 { + err = fmt.Errorf(resp.Message) + } else { + d.AccessToken = resp.Data.Token + } + return err +} + +func (d *Pan123) request(url string, method string, callback func(*resty.Request), resp interface{}) ([]byte, error) { + req := base.RestyClient.R() + req.SetHeader("Authorization", "Bearer "+d.AccessToken) + if callback != nil { + callback(req) + } + if resp != nil { + req.SetResult(resp) + } + res, err := req.Execute(method, url) + if err != nil { + return nil, err + } + body := res.Body() + code := jsoniter.Get(body, "code").ToInt() + if code != 0 { + if code == 401 { + err := d.login() + if err != nil { + return nil, err + } + return d.request(url, method, callback, resp) + } + return nil, errors.New(jsoniter.Get(body, "message").ToString()) + } + return body, nil +} + +func (d *Pan123) getFiles(parentId string) ([]File, error) { + next := "0" + res := make([]File, 0) + for next != "-1" { + var resp Files + query := map[string]string{ + "driveId": "0", + "limit": "100", + "next": next, + "orderBy": d.OrderBy, + "orderDirection": d.OrderDirection, + "parentFileId": parentId, + "trashed": "false", + } + _, err := d.request("https://www.123pan.com/api/file/list/new", http.MethodGet, func(req *resty.Request) { + req.SetQueryParams(query) + }, &resp) + if err != nil { + return nil, err + } + next = resp.Data.Next + res = append(res, resp.Data.InfoList...) + } + return res, nil +} diff --git a/drivers/aliyundrive/driver.go b/drivers/aliyundrive/driver.go index 96246d8c..9fde212a 100644 --- a/drivers/aliyundrive/driver.go +++ b/drivers/aliyundrive/driver.go @@ -1,4 +1,4 @@ -package local +package aliyundrive import ( "bytes" diff --git a/drivers/aliyundrive/meta.go b/drivers/aliyundrive/meta.go index 76997898..ed17de32 100644 --- a/drivers/aliyundrive/meta.go +++ b/drivers/aliyundrive/meta.go @@ -1,4 +1,4 @@ -package local +package aliyundrive import ( "github.com/alist-org/alist/v3/internal/driver" @@ -14,7 +14,7 @@ type Addition struct { } var config = driver.Config{ - Name: "aliyundrive", + Name: "Aliyundrive", DefaultRoot: "root", } diff --git a/drivers/aliyundrive/types.go b/drivers/aliyundrive/types.go index 6864dcee..dd1ceade 100644 --- a/drivers/aliyundrive/types.go +++ b/drivers/aliyundrive/types.go @@ -1,4 +1,4 @@ -package local +package aliyundrive import ( "time" diff --git a/drivers/aliyundrive/util.go b/drivers/aliyundrive/util.go index f85dd78f..42baa87c 100644 --- a/drivers/aliyundrive/util.go +++ b/drivers/aliyundrive/util.go @@ -1,4 +1,4 @@ -package local +package aliyundrive import ( "errors" diff --git a/drivers/all.go b/drivers/all.go index 7fef46ca..a779ae82 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -1,6 +1,7 @@ package drivers import ( + _ "github.com/alist-org/alist/v3/drivers/123" _ "github.com/alist-org/alist/v3/drivers/aliyundrive" _ "github.com/alist-org/alist/v3/drivers/local" _ "github.com/alist-org/alist/v3/drivers/onedrive" diff --git a/drivers/local/meta.go b/drivers/local/meta.go index e442929e..0d0482a1 100644 --- a/drivers/local/meta.go +++ b/drivers/local/meta.go @@ -11,7 +11,7 @@ type Addition struct { } var config = driver.Config{ - Name: "local", + Name: "Local", OnlyLocal: true, LocalSort: true, NoCache: true, diff --git a/drivers/onedrive/meta.go b/drivers/onedrive/meta.go index fc77c506..f68cb174 100644 --- a/drivers/onedrive/meta.go +++ b/drivers/onedrive/meta.go @@ -17,7 +17,7 @@ type Addition struct { } var config = driver.Config{ - Name: "onedrive", + Name: "Onedrive", LocalSort: true, DefaultRoot: "/", } diff --git a/drivers/pikpak/driver.go b/drivers/pikpak/driver.go index cfa04dfe..5aff3e7c 100644 --- a/drivers/pikpak/driver.go +++ b/drivers/pikpak/driver.go @@ -1,4 +1,4 @@ -package local +package pikpak import ( "context" diff --git a/drivers/pikpak/meta.go b/drivers/pikpak/meta.go index 07ec905a..88427b95 100644 --- a/drivers/pikpak/meta.go +++ b/drivers/pikpak/meta.go @@ -1,4 +1,4 @@ -package local +package pikpak import ( "github.com/alist-org/alist/v3/internal/driver" diff --git a/drivers/pikpak/types.go b/drivers/pikpak/types.go index 276d063d..d469f30a 100644 --- a/drivers/pikpak/types.go +++ b/drivers/pikpak/types.go @@ -1,4 +1,4 @@ -package local +package pikpak import ( "strconv" diff --git a/drivers/pikpak/util.go b/drivers/pikpak/util.go index 094435d5..02b86374 100644 --- a/drivers/pikpak/util.go +++ b/drivers/pikpak/util.go @@ -1,4 +1,4 @@ -package local +package pikpak import ( "errors" diff --git a/drivers/template/driver.go b/drivers/template/driver.go index 6dc9c480..6f605ea7 100644 --- a/drivers/template/driver.go +++ b/drivers/template/driver.go @@ -1,4 +1,4 @@ -package local +package template import ( "context" diff --git a/drivers/template/meta.go b/drivers/template/meta.go index e66ae7a5..f3a66184 100644 --- a/drivers/template/meta.go +++ b/drivers/template/meta.go @@ -1,4 +1,4 @@ -package local +package template import ( "github.com/alist-org/alist/v3/internal/driver" @@ -14,7 +14,7 @@ type Addition struct { } var config = driver.Config{ - Name: "template", + Name: "Template", LocalSort: false, OnlyLocal: false, OnlyProxy: false, diff --git a/drivers/template/types.go b/drivers/template/types.go index 469c3dc0..38cdfe44 100644 --- a/drivers/template/types.go +++ b/drivers/template/types.go @@ -1 +1 @@ -package local +package template diff --git a/drivers/template/util.go b/drivers/template/util.go index 01f11c23..9d967bdf 100644 --- a/drivers/template/util.go +++ b/drivers/template/util.go @@ -1,3 +1,3 @@ -package local +package template // do others that not defined in Driver interface diff --git a/drivers/virtual/meta.go b/drivers/virtual/meta.go index f93286f8..0322c540 100644 --- a/drivers/virtual/meta.go +++ b/drivers/virtual/meta.go @@ -14,7 +14,7 @@ type Addition struct { } var config = driver.Config{ - Name: "virtual", + Name: "Virtual", OnlyLocal: true, LocalSort: true, NeedMs: true, diff --git a/internal/aria2/aria2_test.go b/internal/aria2/aria2_test.go index eb52ba25..d55452b1 100644 --- a/internal/aria2/aria2_test.go +++ b/internal/aria2/aria2_test.go @@ -43,7 +43,7 @@ func TestDown(t *testing.T) { ID: 0, MountPath: "/", Index: 0, - Driver: "local", + Driver: "Local", Status: "", Addition: `{"root_folder":"../../data"}`, Remark: "", diff --git a/internal/bootstrap/data/dev.go b/internal/bootstrap/data/dev.go index 97a5fc37..65cd9717 100644 --- a/internal/bootstrap/data/dev.go +++ b/internal/bootstrap/data/dev.go @@ -15,7 +15,7 @@ func initDevData() { err := op.CreateStorage(context.Background(), model.Storage{ MountPath: "/", Index: 0, - Driver: "local", + Driver: "Local", Status: "", Addition: `{"root_folder":"."}`, }) diff --git a/internal/op/storage_test.go b/internal/op/storage_test.go index 66d815dd..79f44e57 100644 --- a/internal/op/storage_test.go +++ b/internal/op/storage_test.go @@ -25,8 +25,8 @@ func TestCreateStorage(t *testing.T) { storage model.Storage isErr bool }{ - {storage: model.Storage{Driver: "local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: false}, - {storage: model.Storage{Driver: "local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: true}, + {storage: model.Storage{Driver: "Local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: false}, + {storage: model.Storage{Driver: "Local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: true}, {storage: model.Storage{Driver: "None", MountPath: "/none", Addition: `{"root_folder":"."}`}, isErr: true}, } for _, storage := range storages { @@ -70,11 +70,11 @@ func TestGetBalancedStorage(t *testing.T) { func setupStorages(t *testing.T) { var storages = []model.Storage{ - {Driver: "local", MountPath: "/a/b", Index: 0, Addition: `{"root_folder":"."}`}, - {Driver: "local", MountPath: "/a/c", Index: 1, Addition: `{"root_folder":"."}`}, - {Driver: "local", MountPath: "/a/d", Index: 2, Addition: `{"root_folder":"."}`}, - {Driver: "local", MountPath: "/a/d/e", Index: 3, Addition: `{"root_folder":"."}`}, - {Driver: "local", MountPath: "/a/d/e.balance", Index: 4, Addition: `{"root_folder":"."}`}, + {Driver: "Local", MountPath: "/a/b", Index: 0, Addition: `{"root_folder":"."}`}, + {Driver: "Local", MountPath: "/a/c", Index: 1, Addition: `{"root_folder":"."}`}, + {Driver: "Local", MountPath: "/a/d", Index: 2, Addition: `{"root_folder":"."}`}, + {Driver: "Local", MountPath: "/a/d/e", Index: 3, Addition: `{"root_folder":"."}`}, + {Driver: "Local", MountPath: "/a/d/e.balance", Index: 4, Addition: `{"root_folder":"."}`}, } for _, storage := range storages { err := op.CreateStorage(context.Background(), storage)