package quark import ( "context" "crypto/md5" "encoding/base64" "errors" "fmt" "net/http" "strconv" "strings" "time" "github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/internal/op" "github.com/alist-org/alist/v3/pkg/cookie" "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 *QuarkOrUC) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { u := d.conf.api + pathname req := base.RestyClient.R() req.SetHeaders(map[string]string{ "Cookie": d.Cookie, "Accept": "application/json, text/plain, */*", "Referer": d.conf.referer, }) req.SetQueryParam("pr", d.conf.pr) req.SetQueryParam("fr", "pc") if callback != nil { callback(req) } if resp != nil { req.SetResult(resp) } var e Resp req.SetError(&e) res, err := req.Execute(method, u) if err != nil { return nil, err } __puus := cookie.GetCookie(res.Cookies(), "__puus") if __puus != nil { d.Cookie = cookie.SetStr(d.Cookie, "__puus", __puus.Value) op.MustSaveDriverStorage(d) } if e.Status >= 400 || e.Code != 0 { return nil, errors.New(e.Message) } return res.Body(), nil } func (d *QuarkOrUC) GetFiles(parent string) ([]File, error) { files := make([]File, 0) page := 1 size := 100 query := map[string]string{ "pdir_fid": parent, "_size": strconv.Itoa(size), "_fetch_total": "1", } if d.OrderBy != "none" { query["_sort"] = "file_type:asc," + d.OrderBy + ":" + d.OrderDirection } for { query["_page"] = strconv.Itoa(page) var resp SortResp _, err := d.request("/file/sort", http.MethodGet, func(req *resty.Request) { req.SetQueryParams(query) }, &resp) if err != nil { return nil, err } files = append(files, resp.Data.List...) if page*size >= resp.Metadata.Total { break } page++ } return files, nil } func (d *QuarkOrUC) upPre(file model.FileStreamer, parentId string) (UpPreResp, error) { now := time.Now() data := base.Json{ "ccp_hash_update": true, "dir_name": "", "file_name": file.GetName(), "format_type": file.GetMimetype(), "l_created_at": now.UnixMilli(), "l_updated_at": now.UnixMilli(), "pdir_fid": parentId, "size": file.GetSize(), //"same_path_reuse": true, } var resp UpPreResp _, err := d.request("/file/upload/pre", http.MethodPost, func(req *resty.Request) { req.SetBody(data) }, &resp) return resp, err } func (d *QuarkOrUC) upHash(md5, sha1, taskId string) (bool, error) { data := base.Json{ "md5": md5, "sha1": sha1, "task_id": taskId, } log.Debugf("hash: %+v", data) var resp HashResp _, err := d.request("/file/update/hash", http.MethodPost, func(req *resty.Request) { req.SetBody(data) }, &resp) return resp.Data.Finish, err } func (d *QuarkOrUC) upPart(ctx context.Context, pre UpPreResp, mineType string, partNumber int, bytes []byte) (string, error) { //func (driver QuarkOrUC) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account, md5Str, sha1Str string) (string, error) { timeStr := time.Now().UTC().Format(http.TimeFormat) data := base.Json{ "auth_info": pre.Data.AuthInfo, "auth_meta": fmt.Sprintf(`PUT %s %s x-oss-date:%s x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit /%s/%s?partNumber=%d&uploadId=%s`, mineType, timeStr, timeStr, pre.Data.Bucket, pre.Data.ObjKey, partNumber, pre.Data.UploadId), "task_id": pre.Data.TaskId, } var resp UpAuthResp _, err := d.request("/file/upload/auth", http.MethodPost, func(req *resty.Request) { req.SetBody(data).SetContext(ctx) }, &resp) if err != nil { return "", err } //if partNumber == 1 { // finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account) // if err != nil { // return "", err // } // if finish { // return "finish", nil // } //} u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey) res, err := base.RestyClient.R().SetContext(ctx). SetHeaders(map[string]string{ "Authorization": resp.Data.AuthKey, "Content-Type": mineType, "Referer": "https://pan.quark.cn/", "x-oss-date": timeStr, "x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit", }). SetQueryParams(map[string]string{ "partNumber": strconv.Itoa(partNumber), "uploadId": pre.Data.UploadId, }).SetBody(bytes).Put(u) if res.StatusCode() != 200 { return "", fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String()) } return res.Header().Get("ETag"), nil } func (d *QuarkOrUC) upCommit(pre UpPreResp, md5s []string) error { timeStr := time.Now().UTC().Format(http.TimeFormat) log.Debugf("md5s: %+v", md5s) bodyBuilder := strings.Builder{} bodyBuilder.WriteString(` `) for i, m := range md5s { bodyBuilder.WriteString(fmt.Sprintf(` %d %s `, i+1, m)) } bodyBuilder.WriteString("") body := bodyBuilder.String() m := md5.New() m.Write([]byte(body)) contentMd5 := base64.StdEncoding.EncodeToString(m.Sum(nil)) callbackBytes, err := utils.Json.Marshal(pre.Data.Callback) if err != nil { return err } callbackBase64 := base64.StdEncoding.EncodeToString(callbackBytes) data := base.Json{ "auth_info": pre.Data.AuthInfo, "auth_meta": fmt.Sprintf(`POST %s application/xml %s x-oss-callback:%s x-oss-date:%s x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit /%s/%s?uploadId=%s`, contentMd5, timeStr, callbackBase64, timeStr, pre.Data.Bucket, pre.Data.ObjKey, pre.Data.UploadId), "task_id": pre.Data.TaskId, } log.Debugf("xml: %s", body) log.Debugf("auth data: %+v", data) var resp UpAuthResp _, err = d.request("/file/upload/auth", http.MethodPost, func(req *resty.Request) { req.SetBody(data) }, &resp) if err != nil { return err } u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey) res, err := base.RestyClient.R(). SetHeaders(map[string]string{ "Authorization": resp.Data.AuthKey, "Content-MD5": contentMd5, "Content-Type": "application/xml", "Referer": "https://pan.quark.cn/", "x-oss-callback": callbackBase64, "x-oss-date": timeStr, "x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit", }). SetQueryParams(map[string]string{ "uploadId": pre.Data.UploadId, }).SetBody(body).Post(u) if res.StatusCode() != 200 { return fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String()) } return nil } func (d *QuarkOrUC) upFinish(pre UpPreResp) error { data := base.Json{ "obj_key": pre.Data.ObjKey, "task_id": pre.Data.TaskId, } _, err := d.request("/file/upload/finish", http.MethodPost, func(req *resty.Request) { req.SetBody(data) }, nil) if err != nil { return err } time.Sleep(time.Second) return nil }