From f127c959a17c4f30d9dc5136b1c4d29f30ad6580 Mon Sep 17 00:00:00 2001 From: Noah Hsu Date: Tue, 6 Sep 2022 17:24:05 +0800 Subject: [PATCH] feat: add `MediaTrack` driver --- drivers/all.go | 1 + drivers/mediatrack/driver.go | 237 +++++++++++++++++++++++++++++++++++ drivers/mediatrack/meta.go | 24 ++++ drivers/mediatrack/types.go | 62 +++++++++ drivers/mediatrack/util.go | 69 ++++++++++ 5 files changed, 393 insertions(+) create mode 100644 drivers/mediatrack/driver.go create mode 100644 drivers/mediatrack/meta.go create mode 100644 drivers/mediatrack/types.go create mode 100644 drivers/mediatrack/util.go diff --git a/drivers/all.go b/drivers/all.go index 36a23e5eb0b..c02a9765c4f 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -9,6 +9,7 @@ import ( _ "github.com/alist-org/alist/v3/drivers/ftp" _ "github.com/alist-org/alist/v3/drivers/google_drive" _ "github.com/alist-org/alist/v3/drivers/local" + _ "github.com/alist-org/alist/v3/drivers/mediatrack" _ "github.com/alist-org/alist/v3/drivers/onedrive" _ "github.com/alist-org/alist/v3/drivers/pikpak" _ "github.com/alist-org/alist/v3/drivers/quark" diff --git a/drivers/mediatrack/driver.go b/drivers/mediatrack/driver.go new file mode 100644 index 00000000000..432a8f35cab --- /dev/null +++ b/drivers/mediatrack/driver.go @@ -0,0 +1,237 @@ +package mediatrack + +import ( + "context" + "crypto/md5" + "encoding/hex" + "fmt" + "io" + "net/http" + "os" + "path" + "strconv" + + "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/internal/op" + "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" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +type MediaTrack struct { + model.Storage + Addition +} + +func (d *MediaTrack) Config() driver.Config { + return config +} + +func (d *MediaTrack) GetAddition() driver.Additional { + return d.Addition +} + +func (d *MediaTrack) 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.request("https://kayle.api.mediatrack.cn/users", http.MethodGet, nil, nil) + return err +} + +func (d *MediaTrack) Drop(ctx context.Context) error { + return nil +} + +func (d *MediaTrack) 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(f File) (model.Obj, error) { + size, _ := strconv.ParseInt(f.Size, 10, 64) + thumb := "" + if f.File != nil && f.File.Cover != "" { + thumb = "https://nano.mtres.cn/" + f.File.Cover + } + return &model.ObjThumb{ + Object: model.Object{ + ID: f.ID, + Name: f.Title, + Modified: f.UpdatedAt, + IsFolder: f.File == nil, + Size: size, + }, + Thumbnail: model.Thumbnail{Thumbnail: thumb}, + }, nil + }) +} + +//func (d *MediaTrack) Get(ctx context.Context, path string) (model.Obj, error) { +// // this is optional +// return nil, errs.NotImplement +//} + +func (d *MediaTrack) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + url := fmt.Sprintf("https://kayn.api.mediatrack.cn/v1/download_token/asset?asset_id=%s&source_type=project&password=&source_id=%s", + file.GetID(), d.ProjectID) + log.Debugf("media track url: %s", url) + body, err := d.request(url, http.MethodGet, nil, nil) + if err != nil { + return nil, err + } + token := utils.Json.Get(body, "data", "token").ToString() + url = "https://kayn.api.mediatrack.cn/v1/download/redirect?token=" + token + return &model.Link{URL: url}, nil +} + +func (d *MediaTrack) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { + url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentDir.GetID()) + _, err := d.request(url, http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "type": 1, + "title": dirName, + }) + }, nil) + return err +} + +func (d *MediaTrack) Move(ctx context.Context, srcObj, dstDir model.Obj) error { + data := base.Json{ + "parent_id": dstDir.GetID(), + "ids": []string{srcObj.GetID()}, + } + url := "https://jayce.api.mediatrack.cn/v4/assets/batch/move" + _, err := d.request(url, http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, nil) + return err +} + +func (d *MediaTrack) Rename(ctx context.Context, srcObj model.Obj, newName string) error { + url := "https://jayce.api.mediatrack.cn/v3/assets/" + srcObj.GetID() + data := base.Json{ + "title": newName, + } + _, err := d.request(url, http.MethodPut, func(req *resty.Request) { + req.SetBody(data) + }, nil) + return err +} + +func (d *MediaTrack) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { + data := base.Json{ + "parent_id": dstDir.GetID(), + "ids": []string{srcObj.GetID()}, + } + url := "https://jayce.api.mediatrack.cn/v4/assets/batch/clone" + _, err := d.request(url, http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, nil) + return err +} + +func (d *MediaTrack) Remove(ctx context.Context, obj model.Obj) error { + dir, err := op.Get(ctx, d, path.Dir(obj.GetPath())) + if err != nil { + return err + } + data := base.Json{ + "origin_id": dir.GetID(), + "ids": []string{obj.GetID()}, + } + url := "https://jayce.api.mediatrack.cn/v4/assets/batch/delete" + _, err = d.request(url, http.MethodDelete, func(req *resty.Request) { + req.SetBody(data) + }, nil) + return err +} + +func (d *MediaTrack) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { + src := "assets/" + uuid.New().String() + var resp UploadResp + _, err := d.request("https://jayce.api.mediatrack.cn/v3/storage/tokens/asset", http.MethodGet, func(req *resty.Request) { + req.SetQueryParam("src", src) + }, &resp) + if err != nil { + return err + } + credential := resp.Data.Credentials + cfg := &aws.Config{ + Credentials: credentials.NewStaticCredentials(credential.TmpSecretID, credential.TmpSecretKey, credential.Token), + Region: &resp.Data.Region, + Endpoint: aws.String("cos.accelerate.myqcloud.com"), + } + s, err := session.NewSession(cfg) + if err != nil { + return err + } + tempFile, err := os.CreateTemp(conf.Conf.TempDir, "file-*") + if err != nil { + return err + } + defer func() { + _ = tempFile.Close() + _ = os.Remove(tempFile.Name()) + }() + _, err = io.Copy(tempFile, stream) + if err != nil { + return err + } + _, err = tempFile.Seek(0, io.SeekStart) + if err != nil { + return err + } + uploader := s3manager.NewUploader(s) + input := &s3manager.UploadInput{ + Bucket: &resp.Data.Bucket, + Key: &resp.Data.Object, + Body: tempFile, + } + _, err = uploader.Upload(input) + if err != nil { + return err + } + url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", dstDir.GetID()) + _, err = tempFile.Seek(0, io.SeekStart) + if err != nil { + return err + } + h := md5.New() + _, err = io.Copy(h, tempFile) + if err != nil { + return err + } + hash := hex.EncodeToString(h.Sum(nil)) + data := base.Json{ + "category": 0, + "description": stream.GetName(), + "hash": hash, + "mime": stream.GetMimetype(), + "size": stream.GetSize(), + "src": src, + "title": stream.GetName(), + "type": 0, + } + _, err = d.request(url, http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, nil) + return err +} + +func (d *MediaTrack) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { + return nil, errs.NotSupport +} + +var _ driver.Driver = (*MediaTrack)(nil) diff --git a/drivers/mediatrack/meta.go b/drivers/mediatrack/meta.go new file mode 100644 index 00000000000..c8e9b229990 --- /dev/null +++ b/drivers/mediatrack/meta.go @@ -0,0 +1,24 @@ +package mediatrack + +import ( + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/op" +) + +type Addition struct { + AccessToken string `json:"access_token" required:"true"` + ProjectID string `json:"project_id"` + driver.RootID + OrderBy string `json:"order_by" type:"select" options:"updated_at,title,size" default:"title"` + OrderDesc bool `json:"order_desc"` +} + +var config = driver.Config{ + Name: "MediaTrack", +} + +func init() { + op.RegisterDriver(config, func() driver.Driver { + return &MediaTrack{} + }) +} diff --git a/drivers/mediatrack/types.go b/drivers/mediatrack/types.go new file mode 100644 index 00000000000..1adbfed9804 --- /dev/null +++ b/drivers/mediatrack/types.go @@ -0,0 +1,62 @@ +package mediatrack + +import "time" + +type BaseResp struct { + Status string `json:"status"` + Message string `json:"message"` +} +type File struct { + Category int `json:"category"` + ChildAssets []interface{} `json:"childAssets"` + CommentCount int `json:"comment_count"` + CoverAsset interface{} `json:"cover_asset"` + CoverAssetID string `json:"cover_asset_id"` + CreatedAt time.Time `json:"created_at"` + DeletedAt string `json:"deleted_at"` + Description string `json:"description"` + File *struct { + Cover string `json:"cover"` + Src string `json:"src"` + } `json:"file"` + //FileID string `json:"file_id"` + ID string `json:"id"` + + Size string `json:"size"` + Thumbnails []interface{} `json:"thumbnails"` + Title string `json:"title"` + UpdatedAt time.Time `json:"updated_at"` +} + +type ChildrenResp struct { + Status string `json:"status"` + Data struct { + Total int `json:"total"` + Assets []File `json:"assets"` + } `json:"data"` + Path string `json:"path"` + TraceID string `json:"trace_id"` + RequestID string `json:"requestId"` +} + +type UploadResp struct { + Status string `json:"status"` + Data struct { + Credentials struct { + TmpSecretID string `json:"TmpSecretId"` + TmpSecretKey string `json:"TmpSecretKey"` + Token string `json:"Token"` + ExpiredTime int `json:"ExpiredTime"` + Expiration time.Time `json:"Expiration"` + StartTime int `json:"StartTime"` + } `json:"credentials"` + Object string `json:"object"` + Bucket string `json:"bucket"` + Region string `json:"region"` + URL string `json:"url"` + Size string `json:"size"` + } `json:"data"` + Path string `json:"path"` + TraceID string `json:"trace_id"` + RequestID string `json:"requestId"` +} diff --git a/drivers/mediatrack/util.go b/drivers/mediatrack/util.go new file mode 100644 index 00000000000..37ca0b3d09c --- /dev/null +++ b/drivers/mediatrack/util.go @@ -0,0 +1,69 @@ +package mediatrack + +import ( + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/alist-org/alist/v3/drivers/base" + "github.com/alist-org/alist/v3/pkg/utils" + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" +) + +// do others that not defined in Driver interface + +func (d *MediaTrack) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { + req := base.RestyClient.R() + req.SetHeader("Authorization", "Bearer "+d.AccessToken) + if callback != nil { + callback(req) + } + var e BaseResp + req.SetResult(&e) + res, err := req.Execute(method, url) + if err != nil { + return nil, err + } + log.Debugln(res.String()) + if e.Status != "SUCCESS" { + return nil, errors.New(e.Message) + } + if resp != nil { + err = utils.Json.Unmarshal(res.Body(), resp) + } + return res.Body(), err +} + +func (d *MediaTrack) getFiles(parentId string) ([]File, error) { + files := make([]File, 0) + url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v4/assets/%s/children", parentId) + sort := "" + if d.OrderBy != "" { + if d.OrderDesc { + sort = "-" + } + sort += d.OrderBy + } + page := 1 + for { + var resp ChildrenResp + _, err := d.request(url, http.MethodGet, func(req *resty.Request) { + req.SetQueryParams(map[string]string{ + "page": strconv.Itoa(page), + "size": "50", + "sort": sort, + }) + }, &resp) + if err != nil { + return nil, err + } + if len(resp.Data.Assets) == 0 { + break + } + page++ + files = append(files, resp.Data.Assets...) + } + return files, nil +}