package client import ( "encoding/json" "errors" "fmt" "io" "net/http" "os" "path" "strings" "github.com/go-resty/resty/v2" ) type aliClient struct { token string driveID string } func NewALIClient(vars map[string]interface{}) (*aliClient, error) { refresh_token := loadParamFromVars("refresh_token", vars) drive_id := loadParamFromVars("drive_id", vars) token, err := loadToken(refresh_token) if err != nil { return nil, err } return &aliClient{token: token, driveID: drive_id}, nil } func (a aliClient) ListBuckets() ([]interface{}, error) { return nil, nil } func (a aliClient) Exist(pathItem string) (bool, error) { pathItem = path.Join("root", pathItem) if _, err := a.loadFileWithName(pathItem); err != nil { return false, err } return true, nil } func (a aliClient) Size(pathItem string) (int64, error) { pathItem = path.Join("root", pathItem) fileInfo, err := a.loadFileWithName(pathItem) if err != nil { return 0, err } return int64(fileInfo.Size), nil } func (a aliClient) Delete(pathItem string) (bool, error) { pathItem = path.Join("root", pathItem) fileInfo, err := a.loadFileWithName(pathItem) if err != nil { return false, err } client := resty.New() data := map[string]interface{}{ "drive_id": a.driveID, "file_id": fileInfo.FileID, } url := "https://api.alipan.com/v2/file/delete" resp, err := client.R(). SetHeader("Authorization", a.token). SetBody(data). Post(url) if err != nil { return false, err } if resp.StatusCode() != 204 { return false, fmt.Errorf("delete file %s failed, err: %v", pathItem, string(resp.Body())) } return true, nil } func (a aliClient) Upload(src, target string) (bool, error) { target = path.Join("/root", target) parentID := "root" var err error if path.Dir(target) != "/root" { parentID, err = a.mkdirWithPath(path.Dir(target)) if err != nil { return false, err } } file, err := os.Open(src) if err != nil { return false, err } defer file.Close() fileInfo, err := file.Stat() if err != nil { return false, err } data := map[string]interface{}{ "drive_id": a.driveID, "part_info_list": makePartInfoList(fileInfo.Size()), "parent_file_id": parentID, "name": path.Base(src), "type": "file", "size": fileInfo.Size(), "check_name_mode": "auto_rename", } client := resty.New() url := "https://api.alipan.com/v2/file/create" resp, err := client.R(). SetHeader("Authorization", a.token). SetBody(data). Post(url) if err != nil { return false, err } var createResp createFileResp if err := json.Unmarshal(resp.Body(), &createResp); err != nil { return false, err } for _, part := range createResp.PartInfoList { err = a.uploadPart(part.UploadURL, io.LimitReader(file, 1024*1024*1024)) if err != nil { return false, err } } if err := a.completeUpload(createResp.UploadID, createResp.FileID); err != nil { return false, err } return true, nil } func (a aliClient) Download(src, target string) (bool, error) { src = path.Join("/root", src) fileInfo, err := a.loadFileWithName(src) if err != nil { return false, err } client := resty.New() if fileInfo.Size > 100*1024*1024 { return false, fmt.Errorf("The translation file %s exceeds 100MB, please download it through the client.", src) } data := map[string]interface{}{ "drive_id": a.driveID, "file_id": fileInfo.FileID, } url := "https://api.aliyundrive.com/v2/file/get_download_url" resp, err := client.R(). SetHeader("Authorization", a.token). SetBody(data). Post(url) if err != nil { return false, err } if resp.StatusCode() != 200 { return false, fmt.Errorf("download file %s failed, err: %v", src, string(resp.Body())) } var respItem downloadResp if err := json.Unmarshal(resp.Body(), &respItem); err != nil { return false, err } if err := a.handleDownload(respItem.URL, target); err != nil { return false, err } return true, nil } func (a *aliClient) ListObjects(src string) ([]string, error) { if len(src) == 0 || src == "root" || src == "/root" { src = "root" } else { src = path.Join("/root", src) } fileInfos, err := a.loadDirWithPath(src) if err != nil { return nil, err } var names []string for _, item := range fileInfos { names = append(names, item.Name) } return names, nil } func (a aliClient) loadFileWithName(pathItem string) (fileInfo, error) { pathItems := strings.Split(pathItem, "/") var ( fileInfos []fileInfo err error ) parentID := "root" for i := 0; i < len(pathItems); i++ { if len(pathItems[i]) == 0 { continue } fileInfos, err = a.loadFileWithParentID(parentID) if err != nil { return fileInfo{}, err } isEnd := false if i == len(pathItems)-2 { isEnd = true } exist := false for _, item := range fileInfos { if item.Name == pathItems[i+1] { if isEnd { return item, nil } else { parentID = item.FileID exist = true } } } if !exist { return fileInfo{}, errors.New("no such file or dir") } } return fileInfo{}, errors.New("no such file or dir") } func (a aliClient) loadDirWithPath(path string) ([]fileInfo, error) { pathItems := strings.Split(path, "/") var ( fileInfos []fileInfo err error ) parentID := "root" for i := 0; i < len(pathItems); i++ { if len(pathItems[i]) == 0 { continue } fileInfos, err = a.loadFileWithParentID(parentID) if err != nil { return fileInfos, err } if i == len(pathItems)-1 { return fileInfos, nil } exist := false for _, item := range fileInfos { if item.Name == pathItems[i+1] { parentID = item.FileID exist = true } } if !exist { return nil, errors.New("no such file or dir") } } return fileInfos, errors.New("no such file or dir") } func (a aliClient) loadFileWithParentID(parentID string) ([]fileInfo, error) { client := resty.New() data := map[string]interface{}{ "drive_id": a.driveID, "fields": "*", "limit": 100, "parent_file_id": parentID, } url := "https://api.aliyundrive.com/adrive/v3/file/list" resp, err := client.R(). SetHeader("Authorization", a.token). SetBody(data). Post(url) if err != nil { return nil, err } if resp.StatusCode() != 200 { return nil, fmt.Errorf("load file list failed, code: %v, err: %v", resp.StatusCode(), string(resp.Body())) } var fileResp fileResp if err := json.Unmarshal(resp.Body(), &fileResp); err != nil { return nil, err } return fileResp.Items, nil } func (a aliClient) mkdirWithPath(target string) (string, error) { pathItems := strings.Split(target, "/") var ( fileInfos []fileInfo err error ) parentID := "root" for i := 0; i < len(pathItems); i++ { if len(pathItems[i]) == 0 { continue } fileInfos, err = a.loadFileWithParentID(parentID) if err != nil { return "", err } isEnd := false if i == len(pathItems)-2 { isEnd = true } exist := false for _, item := range fileInfos { if item.Name == pathItems[i+1] { parentID = item.FileID if isEnd { return item.FileID, nil } else { exist = true } } } if !exist { parentID, err = a.mkdir(parentID, pathItems[i+1]) if err != nil { return parentID, err } if isEnd { return parentID, nil } } } return "", errors.New("mkdir failed.") } func (a aliClient) mkdir(parentID, name string) (string, error) { client := resty.New() data := map[string]interface{}{ "drive_id": a.driveID, "name": name, "type": "folder", "limit": 100, "parent_file_id": parentID, } url := "https://api.aliyundrive.com/adrive/v2/file/createWithFolders" resp, err := client.R(). SetHeader("Authorization", a.token). SetBody(data). Post(url) if err != nil { return "", err } if resp.StatusCode() != 201 { return "", fmt.Errorf("mkdir %s failed, code: %v, err: %v", name, resp.StatusCode(), string(resp.Body())) } var mkdirResp mkdirResp if err := json.Unmarshal(resp.Body(), &mkdirResp); err != nil { return "", err } return mkdirResp.FileID, nil } type fileResp struct { Items []fileInfo `json:"items"` } type fileInfo struct { FileID string `json:"file_id"` Name string `json:"name"` Size int `json:"size"` } type mkdirResp struct { FileID string `json:"file_id"` } type partInfo struct { PartNumber int `json:"part_number"` UploadURL string `json:"upload_url"` InternalUploadURL string `json:"internal_upload_url"` ContentType string `json:"content_type"` } func makePartInfoList(size int64) []*partInfo { var res []*partInfo maxPartSize := int64(1024 * 1024 * 1024) partInfoNum := int(size / maxPartSize) if size%maxPartSize > 0 { partInfoNum += 1 } for i := 0; i < partInfoNum; i++ { res = append(res, &partInfo{PartNumber: i + 1}) } return res } type createFileResp struct { Type string `json:"type"` RapidUpload bool `json:"rapid_upload"` DomainId string `json:"domain_id"` DriveId string `json:"drive_id"` FileName string `json:"file_name"` EncryptMode string `json:"encrypt_mode"` Location string `json:"location"` UploadID string `json:"upload_id"` FileID string `json:"file_id"` PartInfoList []*partInfo `json:"part_info_list,omitempty"` } func (a aliClient) uploadPart(uri string, reader io.Reader) error { req, err := http.NewRequest(http.MethodPut, uri, reader) if err != nil { return err } client := &http.Client{} response, err := client.Do(req) if err != nil { return err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return fmt.Errorf("handle upload park file with url failed, code: %v", response.StatusCode) } return nil } type downloadResp struct { URL string `json:"url"` } func (a aliClient) handleDownload(uri string, target string) error { req, err := http.NewRequest(http.MethodGet, uri, nil) if err != nil { return err } req.Header.Add("Authorization", a.token) req.Header.Add("origin", "https://www.aliyundrive.com") req.Header.Add("referer", "https://www.aliyundrive.com/") client := &http.Client{} response, err := client.Do(req) if err != nil { return err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return fmt.Errorf("handle download with url failed, code: %v", response.StatusCode) } if _, err := os.Stat(path.Dir(target)); err != nil { _ = os.MkdirAll(path.Dir(target), os.ModePerm) } out, err := os.Create(target) if err != nil { return err } defer out.Close() if _, err = io.Copy(out, response.Body); err != nil { return err } return nil } func (a *aliClient) completeUpload(uploadID, fileID string) error { client := resty.New() data := map[string]interface{}{ "drive_id": a.driveID, "upload_id": uploadID, "file_id": fileID, } url := "https://api.aliyundrive.com/v2/file/complete" resp, err := client.R(). SetHeader("Authorization", a.token). SetBody(data). Post(url) if err != nil { return err } if resp.StatusCode() != 200 { return fmt.Errorf("complete upload failed, err: %v", string(resp.Body())) } return nil } type tokenResp struct { AccessToken string `json:"access_token"` } func loadToken(refresh_token string) (string, error) { client := resty.New() data := map[string]interface{}{ "grant_type": "refresh_token", "refresh_token": refresh_token, } url := "https://api.aliyundrive.com/token/refresh" resp, err := client.R(). SetBody(data). Post(url) if err != nil { return "", fmt.Errorf("load account token failed, err: %v", err) } if resp.StatusCode() != 200 { return "", fmt.Errorf("load account token failed, code: %v", resp.StatusCode()) } var respItem tokenResp if err := json.Unmarshal(resp.Body(), &respItem); err != nil { return "", err } return respItem.AccessToken, nil }