| | package homecloud |
| |
|
| | import ( |
| | "bytes" |
| | "context" |
| | "crypto/md5" |
| | "crypto/sha1" |
| | "encoding/hex" |
| | "fmt" |
| | "io" |
| | "mime/multipart" |
| | "net/http" |
| | "strconv" |
| | "strings" |
| | "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" |
| | "github.com/alist-org/alist/v3/pkg/utils/random" |
| | ) |
| |
|
| | type HomeCloud struct { |
| | model.Storage |
| | Addition |
| | AccessToken string |
| | UserID string |
| | cron *cron.Cron |
| | Account string |
| | } |
| |
|
| | func (d *HomeCloud) Config() driver.Config { |
| | return config |
| | } |
| |
|
| | func (d *HomeCloud) GetAddition() driver.Additional { |
| | return &d.Addition |
| | } |
| |
|
| | func (d *HomeCloud) Init(ctx context.Context) error { |
| | if d.RefreshToken == "" { |
| | return fmt.Errorf("RefreshToken is empty") |
| | } |
| |
|
| | if len(d.Addition.RootFolderID) == 0 { |
| | d.RootFolderID = "0" |
| | } |
| |
|
| | err := d.refreshToken() |
| | if err != nil { |
| | return err |
| | } |
| |
|
| | d.cron = cron.NewCron(time.Hour * 10) |
| | d.cron.Do(func() { |
| | err := d.refreshToken() |
| | if err != nil { |
| | return |
| | } |
| | }) |
| |
|
| | return nil |
| | } |
| |
|
| | func (d *HomeCloud) Drop(ctx context.Context) error { |
| | if d.cron != nil { |
| | d.cron.Stop() |
| | } |
| | return nil |
| | } |
| |
|
| | func (d *HomeCloud) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { |
| | return d.familyGetFiles(dir.GetID()) |
| | } |
| |
|
| | func (d *HomeCloud) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { |
| | var url string |
| | var err error |
| | url, err = d.getLink(file.GetID()) |
| | if err != nil { |
| | return nil, err |
| | } |
| |
|
| | link := &model.Link{ |
| | URL: url, |
| | } |
| |
|
| | |
| | header := make(http.Header) |
| | header.Add("Cookie", "H_TOKEN="+d.AccessToken) |
| | header.Add("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1") |
| | link.Header = header |
| |
|
| | return link, nil |
| | } |
| |
|
| | func (d *HomeCloud) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { |
| | var err error |
| | data := base.Json{ |
| | "parentDirId": parentDir.GetID(), |
| | "dirName": dirName, |
| | "category": 0, |
| | "userId": d.UserID, |
| | "groupId": d.GroupID, |
| | } |
| | pathname := "/storage/addDirectory/v1" |
| | _, err = d.post(pathname, data, nil) |
| | return err |
| | } |
| |
|
| | func (d *HomeCloud) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) { |
| | data := base.Json{ |
| | "fileIds": []string{srcObj.GetID()}, |
| | "targetDirId": dstDir.GetID(), |
| | "userId": d.UserID, |
| | "groupId": d.GroupID, |
| | } |
| | pathname := "/storage/batchMoveFile/v1" |
| | _, err := d.post(pathname, data, nil) |
| | if err != nil { |
| | return nil, err |
| | } |
| | return srcObj, nil |
| | } |
| |
|
| | func (d *HomeCloud) Rename(ctx context.Context, srcObj model.Obj, newName string) error { |
| | var err error |
| | data := base.Json{ |
| | "fileId": srcObj.GetID(), |
| | "fileName": newName, |
| | "userId": d.UserID, |
| | "groupId": d.GroupID, |
| | } |
| | pathname := "/storage/updateFileName/v1" |
| | _, err = d.post(pathname, data, nil) |
| |
|
| | return err |
| | } |
| |
|
| | func (d *HomeCloud) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | return errs.NotImplement |
| | } |
| |
|
| | func (d *HomeCloud) Remove(ctx context.Context, obj model.Obj) error { |
| | data := base.Json{ |
| | "fileIds": []string{obj.GetID()}, |
| | "userId": d.UserID, |
| | "groupId": d.GroupID, |
| | } |
| | pathname := "/storage/batchDeleteFile/v1" |
| | if obj.IsDir() { |
| | data = base.Json{ |
| | "fileId": obj.GetID(), |
| | "userId": d.UserID, |
| | "groupId": d.GroupID, |
| | } |
| | pathname = "/storage/deleteDirectory/v1" |
| | } |
| | _, err := d.post(pathname, data, nil) |
| | return err |
| | } |
| |
|
| | const ( |
| | _ = iota |
| | KB = 1 << (10 * iota) |
| | MB |
| | GB |
| | TB |
| | ) |
| |
|
| | func getPartSize(size int64) int64 { |
| | |
| | if size/GB > 30 { |
| | return 512 * MB |
| | } |
| | return 100 * MB |
| | } |
| |
|
| | func (d *HomeCloud) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { |
| | var err error |
| |
|
| | h := md5.New() |
| | |
| | tempFile, err := stream.CacheFullInTempFile() |
| | if err != nil { |
| | return err |
| | } |
| | defer func() { |
| | _ = tempFile.Close() |
| | }() |
| | if _, err = io.Copy(h, tempFile); err != nil { |
| | return err |
| | } |
| | _, err = tempFile.Seek(0, io.SeekStart) |
| | if err != nil { |
| | return err |
| | } |
| | etag := hex.EncodeToString(h.Sum(nil)) |
| |
|
| | |
| | data := base.Json{ |
| | "userId": d.UserID, |
| | "groupId": d.GroupID, |
| | "dirId": dstDir.GetID(), |
| | "fileName": stream.GetName(), |
| | "fileMd5": etag, |
| | "fileSize": stream.GetSize(), |
| | "fileCategory": 99, |
| | } |
| |
|
| | pathname := "/storage/addFileUploadTask/v1" |
| | var resp PersonalUploadResp |
| | _, err = d.post(pathname, data, &resp) |
| | if err != nil { |
| | return err |
| | } |
| |
|
| | |
| | p := driver.NewProgress(stream.GetSize(), up) |
| |
|
| | var partSize = getPartSize(stream.GetSize()) |
| | part := (stream.GetSize() + partSize - 1) / partSize |
| | if part == 0 { |
| | part = 1 |
| | } |
| | for i := int64(0); i < part; i++ { |
| | if utils.IsCanceled(ctx) { |
| | return ctx.Err() |
| | } |
| |
|
| | start := i * partSize |
| | byteSize := stream.GetSize() - start |
| | if byteSize > partSize { |
| | byteSize = partSize |
| | } |
| |
|
| | limitReader := io.LimitReader(stream, byteSize) |
| | |
| | r := io.TeeReader(limitReader, p) |
| | |
| | |
| |
|
| | body := &bytes.Buffer{} |
| | writer := multipart.NewWriter(body) |
| | filePart, err := writer.CreateFormFile("partFile", stream.GetName()) |
| | if err != nil { |
| | return err |
| | } |
| | _, err = io.Copy(filePart, r) |
| | if err != nil { |
| | return err |
| | } |
| |
|
| | isDone := false |
| |
|
| | if i == (part - 1) { |
| | isDone = true |
| | } |
| |
|
| | _ = writer.WriteField("uploadId", resp.Data.UploadId) |
| | _ = writer.WriteField("isComplete", strconv.FormatBool(isDone)) |
| | _ = writer.WriteField("rangeStart", strconv.Itoa(int(start))) |
| |
|
| | err = writer.Close() |
| | if err != nil { |
| | return err |
| | } |
| |
|
| | req, err := http.NewRequest("POST", resp.Data.UploadUrl, body) |
| | if err != nil { |
| | return err |
| | } |
| | requestID := random.String(12) |
| | pbody, err := utils.Json.Marshal(body) |
| |
|
| | if err != nil { |
| | return err |
| | } |
| |
|
| | timestamp := fmt.Sprintf("%.3f", float64(time.Now().UnixNano())/1e6) |
| | h := sha1.New() |
| | var sha1Hash string |
| |
|
| | if pbody == nil { |
| | h.Write([]byte("{}")) |
| | sha1Hash = strings.ToUpper(hex.EncodeToString(h.Sum(nil))) |
| | } else { |
| | h.Write(pbody) |
| | sha1Hash = strings.ToUpper(hex.EncodeToString(h.Sum(nil))) |
| | } |
| |
|
| | uppathname := "/upload/upload/uploadFilePart/v1" |
| | encStr := fmt.Sprintf("%s;%s;%s;Bearer %s;%s", uppathname, sha1Hash, requestID, d.AccessToken, timestamp) |
| | signature := strings.ToUpper(fmt.Sprintf("%x", md5.Sum([]byte(encStr)))) |
| |
|
| | req = req.WithContext(ctx) |
| | req.Header.Add("Accept", "*/*") |
| | req.Header.Add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") |
| | req.Header.Add("Authorization", "Bearer "+d.AccessToken) |
| | req.Header.Add("Origin", "https://homecloud.komect.com") |
| | req.Header.Add("Referer", "https://homecloud.komect.com/disk/main/familyspace") |
| | req.Header.Add("Request-Id", requestID) |
| | req.Header.Add("Signature", signature) |
| | req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36") |
| | req.Header.Add("X-Requested-With", "XMLHttpRequest") |
| | req.Header.Add("X-User-Agent", "Web|Chrome 127.0.0.0||OS X|homecloudWebDisk_1.1.1||yunpan 1.1.1|unknown") |
| | req.Header.Add("sec-ch-ua", "\"Not)A;Brand\";v=\"99\", \"Google Chrome\";v=\"127\", \"Chromium\";v=\"127\"") |
| | req.Header.Add("sec-ch-ua-mobile", "?0") |
| | req.Header.Add("sec-ch-ua-platform", "\"macOS\"") |
| | req.Header.Add("userId", d.UserID) |
| | req.Header.Set("Content-Type", writer.FormDataContentType()) |
| |
|
| | res, err := base.HttpClient.Do(req) |
| | if err != nil { |
| | return err |
| | } |
| | _ = res.Body.Close() |
| | |
| | if res.StatusCode != http.StatusOK { |
| | return fmt.Errorf("unexpected status code: %d", res.StatusCode) |
| | } |
| |
|
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | return nil |
| | } |
| |
|
| | func (d *HomeCloud) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { |
| | return nil, errs.NotImplement |
| | } |
| |
|
| | var _ driver.Driver = (*HomeCloud)(nil) |
| |
|