forked from AlistGo/alist
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add aliyundrive open driver (AlistGo#3437)
close AlistGo#3533 close AlistGo#3521 close AlistGo#3459 close AlistGo#3375 * feat: add aliyundrive open driver * feat: adapt alist api * fix: trailing spaces * feat(aliyundrive_open): video preview api
- Loading branch information
Showing
7 changed files
with
440 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
package aliyundrive_open | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"math" | ||
"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/internal/op" | ||
"github.com/alist-org/alist/v3/pkg/cron" | ||
"github.com/alist-org/alist/v3/pkg/utils" | ||
"github.com/go-resty/resty/v2" | ||
log "github.com/sirupsen/logrus" | ||
) | ||
|
||
type AliyundriveOpen struct { | ||
model.Storage | ||
Addition | ||
base string | ||
cron *cron.Cron | ||
|
||
AccessToken string | ||
DriveId string | ||
} | ||
|
||
func (d *AliyundriveOpen) Config() driver.Config { | ||
return config | ||
} | ||
|
||
func (d *AliyundriveOpen) GetAddition() driver.Additional { | ||
return &d.Addition | ||
} | ||
|
||
func (d *AliyundriveOpen) Init(ctx context.Context) error { | ||
err := d.refreshToken() | ||
if err != nil { | ||
return err | ||
} | ||
res, err := d.request("/adrive/v1.0/user/getDriveInfo", http.MethodPost, nil) | ||
if err != nil { | ||
return err | ||
} | ||
d.DriveId = utils.Json.Get(res, "default_drive_id").ToString() | ||
d.cron = cron.NewCron(time.Hour * 2) | ||
d.cron.Do(func() { | ||
err := d.refreshToken() | ||
d.Status = err.Error() | ||
op.MustSaveDriverStorage(d) | ||
if err != nil { | ||
log.Errorf("%+v", err) | ||
} | ||
}) | ||
return nil | ||
} | ||
|
||
func (d *AliyundriveOpen) Drop(ctx context.Context) error { | ||
if d.cron != nil { | ||
d.cron.Stop() | ||
} | ||
return nil | ||
} | ||
|
||
func (d *AliyundriveOpen) 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 *AliyundriveOpen) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { | ||
res, err := d.request("/adrive/v1.0/openFile/getDownloadUrl", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"drive_id": d.DriveId, | ||
"file_id": file.GetID(), | ||
}) | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
url := utils.Json.Get(res, "url").ToString() | ||
return &model.Link{ | ||
URL: url, | ||
}, nil | ||
} | ||
|
||
func (d *AliyundriveOpen) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { | ||
_, err := d.request("/adrive/v1.0/openFile/create", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"drive_id": d.DriveId, | ||
"parent_file_id": parentDir.GetID(), | ||
"name": dirName, | ||
"type": "folder", | ||
"check_name_mode": "refuse", | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *AliyundriveOpen) Move(ctx context.Context, srcObj, dstDir model.Obj) error { | ||
_, err := d.request("/adrive/v1.0/openFile/move", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"drive_id": d.DriveId, | ||
"file_id": srcObj.GetID(), | ||
"to_parent_file_id": dstDir.GetID(), | ||
"check_name_mode": "refuse", // optional:ignore,auto_rename,refuse | ||
//"new_name": "newName", // The new name to use when a file of the same name exists | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *AliyundriveOpen) Rename(ctx context.Context, srcObj model.Obj, newName string) error { | ||
_, err := d.request("/adrive/v1.0/openFile/update", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"drive_id": d.DriveId, | ||
"file_id": srcObj.GetID(), | ||
"name": newName, | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *AliyundriveOpen) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { | ||
_, err := d.request("/adrive/v1.0/openFile/copy", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"drive_id": d.DriveId, | ||
"file_id": srcObj.GetID(), | ||
"to_parent_file_id": dstDir.GetID(), | ||
"auto_rename": true, | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *AliyundriveOpen) Remove(ctx context.Context, obj model.Obj) error { | ||
_, err := d.request("/adrive/v1.0/openFile/recyclebin/trash", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"drive_id": d.DriveId, | ||
"file_id": obj.GetID(), | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *AliyundriveOpen) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { | ||
// rapid_upload is not currently supported | ||
// 1. create | ||
const DEFAULT int64 = 20971520 | ||
createData := base.Json{ | ||
"drive_id": d.DriveId, | ||
"parent_file_id": dstDir.GetID(), | ||
"name": stream.GetName(), | ||
"type": "file", | ||
"check_name_mode": "ignore", | ||
} | ||
count := 1 | ||
if stream.GetSize() > DEFAULT { | ||
count = int(math.Ceil(float64(stream.GetSize()) / float64(DEFAULT))) | ||
partInfoList := make([]base.Json, 0, count) | ||
for i := 1; i <= count; i++ { | ||
partInfoList = append(partInfoList, base.Json{"part_number": i}) | ||
} | ||
createData["part_info_list"] = partInfoList | ||
} | ||
var createResp CreateResp | ||
_, err := d.request("/adrive/v1.0/openFile/create", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(createData).SetResult(&createResp) | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
// 2. upload | ||
for i, partInfo := range createResp.PartInfoList { | ||
if utils.IsCanceled(ctx) { | ||
return ctx.Err() | ||
} | ||
req, err := http.NewRequest("PUT", partInfo.UploadUrl, io.LimitReader(stream, DEFAULT)) | ||
if err != nil { | ||
return err | ||
} | ||
req = req.WithContext(ctx) | ||
res, err := base.HttpClient.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
res.Body.Close() | ||
if count > 0 { | ||
up(i * 100 / count) | ||
} | ||
} | ||
// 3. complete | ||
_, err = d.request("/adrive/v1.0/openFile/complete", http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(base.Json{ | ||
"drive_id": d.DriveId, | ||
"file_id": createResp.FileId, | ||
"upload_id": createResp.UploadId, | ||
}) | ||
}) | ||
return err | ||
} | ||
|
||
func (d *AliyundriveOpen) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { | ||
var resp base.Json | ||
var uri string | ||
data := base.Json{ | ||
"drive_id": d.DriveId, | ||
"file_id": args.Obj.GetID(), | ||
} | ||
switch args.Method { | ||
case "video_preview": | ||
uri = "/adrive/v1.0/openFile/getVideoPreviewPlayInfo" | ||
data["category"] = "live_transcoding" | ||
data["url_expire_sec"] = 14400 | ||
default: | ||
return nil, errs.NotSupport | ||
} | ||
_, err := d.request(uri, http.MethodPost, func(req *resty.Request) { | ||
req.SetBody(data).SetResult(&resp) | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return resp, nil | ||
} | ||
|
||
var _ driver.Driver = (*AliyundriveOpen)(nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package aliyundrive_open | ||
|
||
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"` | ||
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"` | ||
OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"` | ||
OauthTokenURL string `json:"oauth_token_url" default:"https://api.nn.ci/alist/ali_open/token"` | ||
ClientID string `json:"client_id" required:"false"` | ||
ClientSecret string `json:"client_secret" required:"false"` | ||
} | ||
|
||
var config = driver.Config{ | ||
Name: "AliyundriveOpen", | ||
LocalSort: false, | ||
OnlyLocal: false, | ||
OnlyProxy: false, | ||
NoCache: false, | ||
NoUpload: false, | ||
NeedMs: false, | ||
DefaultRoot: "root", | ||
} | ||
|
||
func init() { | ||
op.RegisterDriver(func() driver.Driver { | ||
return &AliyundriveOpen{ | ||
base: "https://open.aliyundrive.com", | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package aliyundrive_open | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/alist-org/alist/v3/internal/model" | ||
) | ||
|
||
type ErrResp struct { | ||
Code string `json:"code"` | ||
Message string `json:"message"` | ||
} | ||
|
||
type Files struct { | ||
Items []File `json:"items"` | ||
NextMarker string `json:"next_marker"` | ||
} | ||
|
||
type File struct { | ||
DriveId string `json:"drive_id"` | ||
FileId string `json:"file_id"` | ||
ParentFileId string `json:"parent_file_id"` | ||
Name string `json:"name"` | ||
Size int64 `json:"size"` | ||
FileExtension string `json:"file_extension"` | ||
ContentHash string `json:"content_hash"` | ||
Category string `json:"category"` | ||
Type string `json:"type"` | ||
Thumbnail string `json:"thumbnail"` | ||
Url string `json:"url"` | ||
CreatedAt *time.Time `json:"created_at"` | ||
UpdatedAt time.Time `json:"updated_at"` | ||
} | ||
|
||
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 CreateResp struct { | ||
//Type string `json:"type"` | ||
//ParentFileId string `json:"parent_file_id"` | ||
//DriveId string `json:"drive_id"` | ||
FileId string `json:"file_id"` | ||
//RevisionId string `json:"revision_id"` | ||
//EncryptMode string `json:"encrypt_mode"` | ||
//DomainId string `json:"domain_id"` | ||
//FileName string `json:"file_name"` | ||
UploadId string `json:"upload_id"` | ||
//Location string `json:"location"` | ||
RapidUpload bool `json:"rapid_upload"` | ||
PartInfoList []struct { | ||
Etag interface{} `json:"etag"` | ||
PartNumber int `json:"part_number"` | ||
PartSize interface{} `json:"part_size"` | ||
UploadUrl string `json:"upload_url"` | ||
ContentType string `json:"content_type"` | ||
} `json:"part_info_list"` | ||
} |
Oops, something went wrong.