mirror of https://github.com/Xhofe/alist
				
				
				
			
		
			
				
	
	
		
			252 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			252 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
package quark
 | 
						|
 | 
						|
import (
 | 
						|
	"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 *Quark) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
 | 
						|
	u := "https://drive.quark.cn/1/clouddrive" + pathname
 | 
						|
	req := base.RestyClient.R()
 | 
						|
	req.SetHeaders(map[string]string{
 | 
						|
		"Cookie":  d.Cookie,
 | 
						|
		"Accept":  "application/json, text/plain, */*",
 | 
						|
		"Referer": "https://pan.quark.cn/",
 | 
						|
	})
 | 
						|
	req.SetQueryParam("pr", "ucpro")
 | 
						|
	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 *Quark) 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 *Quark) 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 *Quark) 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 *Quark) upPart(pre UpPreResp, mineType string, partNumber int, bytes []byte) (string, error) {
 | 
						|
	//func (driver Quark) 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)
 | 
						|
	}, &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().
 | 
						|
		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 *Quark) upCommit(pre UpPreResp, md5s []string) error {
 | 
						|
	timeStr := time.Now().UTC().Format(http.TimeFormat)
 | 
						|
	log.Debugf("md5s: %+v", md5s)
 | 
						|
	bodyBuilder := strings.Builder{}
 | 
						|
	bodyBuilder.WriteString(`<?xml version="1.0" encoding="UTF-8"?>
 | 
						|
<CompleteMultipartUpload>
 | 
						|
`)
 | 
						|
	for i, m := range md5s {
 | 
						|
		bodyBuilder.WriteString(fmt.Sprintf(`<Part>
 | 
						|
<PartNumber>%d</PartNumber>
 | 
						|
<ETag>%s</ETag>
 | 
						|
</Part>
 | 
						|
`, i+1, m))
 | 
						|
	}
 | 
						|
	bodyBuilder.WriteString("</CompleteMultipartUpload>")
 | 
						|
	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 *Quark) 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
 | 
						|
}
 |