diff --git a/drivers/189pc/driver.go b/drivers/189pc/driver.go index e757c200..fe4cc3e1 100644 --- a/drivers/189pc/driver.go +++ b/drivers/189pc/driver.go @@ -4,7 +4,6 @@ import ( "context" "net/http" "strings" - "time" "github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/internal/driver" @@ -19,8 +18,7 @@ type Cloud189PC struct { identity string - client *resty.Client - putClient *resty.Client + client *resty.Client loginParam *LoginParam tokenInfo *AppSessionResp @@ -51,9 +49,6 @@ func (y *Cloud189PC) Init(ctx context.Context) (err error) { "Referer": WEB_URL, }) } - if y.putClient == nil { - y.putClient = base.NewRestyClient().SetTimeout(120 * time.Second) - } // 避免重复登陆 identity := utils.GetMD5Encode(y.Username + y.Password) @@ -266,8 +261,14 @@ func (y *Cloud189PC) Remove(ctx context.Context, obj model.Obj) error { } func (y *Cloud189PC) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { - if y.RapidUpload { + switch y.UploadMethod { + case "stream": + return y.CommonUpload(ctx, dstDir, stream, up) + case "old": + return y.OldUpload(ctx, dstDir, stream, up) + case "rapid": return y.FastUpload(ctx, dstDir, stream, up) + default: + return y.CommonUpload(ctx, dstDir, stream, up) } - return y.CommonUpload(ctx, dstDir, stream, up) } diff --git a/drivers/189pc/help.go b/drivers/189pc/help.go index 53afb8df..6cc6facc 100644 --- a/drivers/189pc/help.go +++ b/drivers/189pc/help.go @@ -11,6 +11,7 @@ import ( "encoding/hex" "encoding/pem" "fmt" + "math" "net/http" "regexp" "strings" @@ -131,3 +132,18 @@ func BoolToNumber(b bool) int { } return 0 } + +// 计算分片大小 +// 对分片数量有限制 +// 10MIB 20 MIB 999片 +// 50MIB 60MIB 70MIB 80MIB ∞MIB 1999片 +func partSize(size int64) int64 { + const DEFAULT = 1024 * 1024 * 10 // 10MIB + if size > DEFAULT*2*999 { + return int64(math.Max(math.Ceil((float64(size)/1999) /*=单个切片大小*/ /float64(DEFAULT)) /*=倍率*/, 5) * DEFAULT) + } + if size > DEFAULT*999 { + return DEFAULT * 2 // 20MIB + } + return DEFAULT +} diff --git a/drivers/189pc/meta.go b/drivers/189pc/meta.go index 3cf535a7..dde17842 100644 --- a/drivers/189pc/meta.go +++ b/drivers/189pc/meta.go @@ -14,13 +14,14 @@ type Addition struct { OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` Type string `json:"type" type:"select" options:"personal,family" default:"personal"` FamilyID string `json:"family_id"` - RapidUpload bool `json:"rapid_upload"` + UploadMethod string `json:"upload_method" type:"select" options:"stream,rapid,old" default:"stream"` NoUseOcr bool `json:"no_use_ocr"` } var config = driver.Config{ Name: "189CloudPC", DefaultRoot: "-11", + CheckStatus: true, } func init() { diff --git a/drivers/189pc/types.go b/drivers/189pc/types.go index 0a6d4f9f..5459c0d4 100644 --- a/drivers/189pc/types.go +++ b/drivers/189pc/types.go @@ -10,20 +10,62 @@ import ( // 居然有四种返回方式 type RespErr struct { - ResCode string `json:"res_code"` + ResCode any `json:"res_code"` // int or string ResMessage string `json:"res_message"` + Error_ string `json:"error"` + XMLName xml.Name `xml:"error"` Code string `json:"code" xml:"code"` Message string `json:"message" xml:"message"` - - // Code string `json:"code"` - Msg string `json:"msg"` + Msg string `json:"msg"` ErrorCode string `json:"errorCode"` ErrorMsg string `json:"errorMsg"` } +func (e *RespErr) HasError() bool { + switch v := e.ResCode.(type) { + case int, int64, int32: + return v != 0 + case string: + return e.ResCode != "" + } + return (e.Code != "" && e.Code != "SUCCESS") || e.ErrorCode != "" || e.Error_ != "" +} + +func (e *RespErr) Error() string { + switch v := e.ResCode.(type) { + case int, int64, int32: + if v != 0 { + return fmt.Sprintf("res_code: %d ,res_msg: %s", v, e.ResMessage) + } + case string: + if e.ResCode != "" { + return fmt.Sprintf("res_code: %s ,res_msg: %s", e.ResCode, e.ResMessage) + } + } + + if e.Code != "" && e.Code != "SUCCESS" { + if e.Msg != "" { + return fmt.Sprintf("code: %s ,msg: %s", e.Code, e.Msg) + } + if e.Message != "" { + return fmt.Sprintf("code: %s ,msg: %s", e.Code, e.Message) + } + return "code: " + e.Code + } + + if e.ErrorCode != "" { + return fmt.Sprintf("err_code: %s ,err_msg: %s", e.ErrorCode, e.ErrorMsg) + } + + if e.Error_ != "" { + return fmt.Sprintf("error: %s ,message: %s", e.ErrorCode, e.Message) + } + return "" +} + // 登陆需要的参数 type LoginParam struct { // 加密后的用户名和密码 @@ -218,6 +260,42 @@ type Part struct { RequestHeader string `json:"requestHeader"` } +/* 第二种上传方式 */ +type CreateUploadFileResp struct { + // 上传文件请求ID + UploadFileId int64 `json:"uploadFileId"` + // 上传文件数据的URL路径 + FileUploadUrl string `json:"fileUploadUrl"` + // 上传文件完成后确认路径 + FileCommitUrl string `json:"fileCommitUrl"` + // 文件是否已存在云盘中,0-未存在,1-已存在 + FileDataExists int `json:"fileDataExists"` +} + +type GetUploadFileStatusResp struct { + CreateUploadFileResp + + // 已上传的大小 + DataSize int64 `json:"dataSize"` + Size int64 `json:"size"` +} + +func (r *GetUploadFileStatusResp) GetSize() int64 { + return r.DataSize + r.Size +} + +type CommitUploadFileResp struct { + XMLName xml.Name `xml:"file"` + Id string `xml:"id"` + Name string `xml:"name"` + Size string `xml:"size"` + Md5 string `xml:"md5"` + CreateDate string `xml:"createDate"` + Rev string `xml:"rev"` + UserId string `xml:"userId"` +} + +/* query 加密参数*/ type Params map[string]string func (p Params) Set(k, v string) { diff --git a/drivers/189pc/utils.go b/drivers/189pc/utils.go index eab497dd..3472f82d 100644 --- a/drivers/189pc/utils.go +++ b/drivers/189pc/utils.go @@ -6,6 +6,7 @@ import ( "crypto/md5" "encoding/base64" "encoding/hex" + "encoding/xml" "fmt" "io" "math" @@ -15,6 +16,7 @@ import ( "os" "regexp" "strings" + "time" "github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/internal/conf" @@ -23,9 +25,12 @@ import ( "github.com/alist-org/alist/v3/internal/op" "github.com/alist-org/alist/v3/internal/setting" "github.com/alist-org/alist/v3/pkg/utils" + + "github.com/avast/retry-go" "github.com/go-resty/resty/v2" "github.com/google/uuid" jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" ) const ( @@ -47,7 +52,7 @@ const ( CHANNEL_ID = "web_cloud.189.cn" ) -func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, params Params, resp interface{}) ([]byte, error) { +func (y *Cloud189PC) SignatureHeader(url, method, params string) map[string]string { dateOfGmt := getHttpDateStr() sessionKey := y.tokenInfo.SessionKey sessionSecret := y.tokenInfo.SessionSecret @@ -56,19 +61,40 @@ func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, para sessionSecret = y.tokenInfo.FamilySessionSecret } - req := y.client.R().SetQueryParams(clientSuffix()).SetHeaders(map[string]string{ + header := map[string]string{ "Date": dateOfGmt, "SessionKey": sessionKey, "X-Request-ID": uuid.NewString(), - }) + "Signature": signatureOfHmac(sessionSecret, sessionKey, method, url, dateOfGmt, params), + } + return header +} + +func (y *Cloud189PC) EncryptParams(params Params) string { + sessionSecret := y.tokenInfo.SessionSecret + if y.isFamily() { + sessionSecret = y.tokenInfo.FamilySessionSecret + } + if params != nil { + return AesECBEncrypt(params.Encode(), sessionSecret[:16]) + } + return "" +} + +func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, params Params, resp interface{}) ([]byte, error) { + req := y.client.R().SetQueryParams(clientSuffix()) // 设置params - var paramsData string - if params != nil { - paramsData = AesECBEncrypt(params.Encode(), sessionSecret[:16]) + paramsData := y.EncryptParams(params) + if paramsData != "" { req.SetQueryParam("params", paramsData) } - req.SetHeader("Signature", signatureOfHmac(sessionSecret, sessionKey, method, url, dateOfGmt, paramsData)) + + // Signature + req.SetHeaders(y.SignatureHeader(url, method, paramsData)) + + var erron RespErr + req.SetError(&erron) if callback != nil { callback(req) @@ -80,32 +106,6 @@ func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, para if err != nil { return nil, err } - var erron RespErr - utils.Json.Unmarshal(res.Body(), &erron) - - if erron.ResCode != "" { - return nil, fmt.Errorf("res_code: %s ,res_msg: %s", erron.ResCode, erron.ResMessage) - } - if erron.Code != "" && erron.Code != "SUCCESS" { - if erron.Msg != "" { - return nil, fmt.Errorf("code: %s ,msg: %s", erron.Code, erron.Msg) - } - if erron.Message != "" { - return nil, fmt.Errorf("code: %s ,msg: %s", erron.Code, erron.Message) - } - return nil, fmt.Errorf(res.String()) - } - switch erron.ErrorCode { - case "": - break - case "InvalidSessionKey": - if err = y.refreshSession(); err != nil { - return nil, err - } - return y.request(url, method, callback, params, resp) - default: - return nil, fmt.Errorf("err_code: %s ,err_msg: %s", erron.ErrorCode, erron.ErrorMsg) - } if strings.Contains(res.String(), "userSessionBO is null") { if err = y.refreshSession(); err != nil { @@ -114,14 +114,17 @@ func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, para return y.request(url, method, callback, params, resp) } - resCode := utils.Json.Get(res.Body(), "res_code").ToInt64() - message := utils.Json.Get(res.Body(), "res_message").ToString() - switch resCode { - case 0: - return res.Body(), nil - default: - return nil, fmt.Errorf("res_code: %d ,res_msg: %s", resCode, message) + // 处理错误 + if erron.HasError() { + if erron.ErrorCode == "InvalidSessionKey" { + if err = y.refreshSession(); err != nil { + return nil, err + } + return y.request(url, method, callback, params, resp) + } + return nil, &erron } + return res.Body(), nil } func (y *Cloud189PC) get(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) { @@ -132,6 +135,50 @@ func (y *Cloud189PC) post(url string, callback base.ReqCallback, resp interface{ return y.request(url, http.MethodPost, callback, nil, resp) } +func (y *Cloud189PC) put(ctx context.Context, url string, headers map[string]string, sign bool, file io.Reader) ([]byte, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, file) + if err != nil { + return nil, err + } + + query := req.URL.Query() + for key, value := range clientSuffix() { + query.Add(key, value) + } + req.URL.RawQuery = query.Encode() + + for key, value := range headers { + req.Header.Add(key, value) + } + + if sign { + for key, value := range y.SignatureHeader(url, http.MethodPut, "") { + req.Header.Add(key, value) + } + } + + resp, err := base.HttpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var erron RespErr + jsoniter.Unmarshal(body, &erron) + xml.Unmarshal(body, &erron) + if erron.HasError() { + return nil, &erron + } + if resp.StatusCode != http.StatusOK { + return nil, errors.Errorf("put fail,err:%s", string(body)) + } + return body, nil +} func (y *Cloud189PC) getFiles(ctx context.Context, fileId string) ([]model.Obj, error) { fullUrl := API_URL if y.isFamily() { @@ -186,7 +233,7 @@ func (y *Cloud189PC) getFiles(ctx context.Context, fileId string) ([]model.Obj, func (y *Cloud189PC) login() (err error) { // 初始化登陆所需参数 - if y.loginParam == nil || !y.NoUseOcr { + if y.loginParam == nil { if err = y.initLoginParam(); err != nil { // 验证码也通过错误返回 return err @@ -197,7 +244,7 @@ func (y *Cloud189PC) login() (err error) { y.VCode = "" // 销毁登陆参数 y.loginParam = nil - // 遇到错误,重新加载登陆参数 + // 遇到错误,重新加载登陆参数(刷新验证码) if err != nil && y.NoUseOcr { if err1 := y.initLoginParam(); err1 != nil { err = fmt.Errorf("err1: %s \nerr2: %s", err, err1) @@ -249,9 +296,8 @@ func (y *Cloud189PC) login() (err error) { return } - if erron.ResCode != "" { - err = fmt.Errorf(erron.ResMessage) - return + if erron.HasError() { + return &erron } if tokenInfo.ResCode != 0 { err = fmt.Errorf(tokenInfo.ResMessage) @@ -304,6 +350,21 @@ func (y *Cloud189PC) initLoginParam() error { param.RsaPassword = encryptConf.Data.Pre + RsaEncrypt(param.jRsaKey, y.Password) y.loginParam = ¶m + // 判断是否需要验证码 + resp, err := y.client.R(). + SetFormData(map[string]string{ + "appKey": APP_ID, + "accountType": ACCOUNT_TYPE, + "userName": param.RsaUsername, + }).Post(AUTH_URL + "/api/logbox/oauth2/needcaptcha.do") + if err != nil { + return err + } + if resp.String() == "0" { + return nil + } + + // 拉取验证码 imgRes, err := y.client.R(). SetQueryParams(map[string]string{ "token": param.CaptchaToken, @@ -359,33 +420,23 @@ func (y *Cloud189PC) refreshSession() (err error) { } }() - switch erron.ResCode { - case "": - break - case "UserInvalidOpenToken": - if err = y.login(); err != nil { - return err + if erron.HasError() { + if erron.ResCode == "UserInvalidOpenToken" { + if err = y.login(); err != nil { + return err + } } - default: - err = fmt.Errorf("res_code: %s ,res_msg: %s", erron.ResCode, erron.ResMessage) - return - } - - switch userSessionResp.ResCode { - case 0: - y.tokenInfo.UserSessionResp = userSessionResp - default: - err = fmt.Errorf("code: %d , msg: %s", userSessionResp.ResCode, userSessionResp.ResMessage) + return &erron } + y.tokenInfo.UserSessionResp = userSessionResp return } // 普通上传 func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (err error) { - const DEFAULT int64 = 10485760 - var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT))) + var DEFAULT = partSize(file.GetSize()) + var count = int(math.Ceil(float64(file.GetSize()) / float64(DEFAULT))) - requestID := uuid.NewString() params := Params{ "parentFolderId": dstDir.GetID(), "fileName": url.QueryEscape(file.GetName()), @@ -407,7 +458,6 @@ func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mo var initMultiUpload InitMultiUploadResp _, err = y.request(fullUrl+"/initMultiUpload", http.MethodGet, func(req *resty.Request) { req.SetContext(ctx) - req.SetHeader("X-Request-ID", requestID) }, params, &initMultiUpload) if err != nil { return err @@ -417,7 +467,7 @@ func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mo silceMd5 := md5.New() silceMd5Hexs := make([]string, 0, count) byteData := bytes.NewBuffer(make([]byte, DEFAULT)) - for i := int64(1); i <= count; i++ { + for i := 1; i <= count; i++ { if utils.IsCanceled(ctx) { return ctx.Err() } @@ -440,7 +490,6 @@ func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mo _, err = y.request(fullUrl+"/getMultiUploadUrls", http.MethodGet, func(req *resty.Request) { req.SetContext(ctx) - req.SetHeader("X-Request-ID", requestID) }, Params{ "partInfo": fmt.Sprintf("%d-%s", i, silceMd5Base64), "uploadFileId": initMultiUpload.Data.UploadFileID, @@ -451,18 +500,18 @@ func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mo // 开始上传 uploadData := uploadUrl.UploadUrls[fmt.Sprint("partNumber_", i)] - res, err := y.putClient.R(). - SetContext(ctx). - SetQueryParams(clientSuffix()). - SetHeaders(ParseHttpHeader(uploadData.RequestHeader)). - SetBody(byteData). - Put(uploadData.RequestURL) + + err = retry.Do(func() error { + _, err := y.put(ctx, uploadData.RequestURL, ParseHttpHeader(uploadData.RequestHeader), false, bytes.NewReader(byteData.Bytes())) + return err + }, + retry.Context(ctx), + retry.Attempts(3), + retry.Delay(time.Second), + retry.MaxDelay(5*time.Second)) if err != nil { return err } - if res.StatusCode() != http.StatusOK { - return fmt.Errorf("updload fail,msg: %s", res.String()) - } up(int(i * 100 / count)) } @@ -476,7 +525,6 @@ func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mo _, err = y.request(fullUrl+"/commitMultiUploadFile", http.MethodGet, func(req *resty.Request) { req.SetContext(ctx) - req.SetHeader("X-Request-ID", requestID) }, Params{ "uploadFileId": initMultiUpload.Data.UploadFileID, "fileMd5": fileMd5Hex, @@ -500,7 +548,7 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode _ = os.Remove(tempFile.Name()) }() - const DEFAULT int64 = 10485760 + var DEFAULT = partSize(file.GetSize()) count := int(math.Ceil(float64(file.GetSize()) / float64(DEFAULT))) // 优先计算所需信息 @@ -531,7 +579,6 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n"))) } - requestID := uuid.NewString() // 检测是否支持快传 params := Params{ "parentFolderId": dstDir.GetID(), @@ -554,7 +601,6 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode var uploadInfo InitMultiUploadResp _, err = y.request(fullUrl+"/initMultiUpload", http.MethodGet, func(req *resty.Request) { req.SetContext(ctx) - req.SetHeader("X-Request-ID", requestID) }, params, &uploadInfo) if err != nil { return err @@ -566,7 +612,6 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode _, err = y.request(fullUrl+"/getMultiUploadUrls", http.MethodGet, func(req *resty.Request) { req.SetContext(ctx) - req.SetHeader("X-Request-ID", requestID) }, Params{ "uploadFileId": uploadInfo.Data.UploadFileID, "partInfo": strings.Join(silceMd5Base64s, ","), @@ -575,26 +620,29 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode return err } + buf := make([]byte, DEFAULT) for i := 1; i <= count; i++ { - select { - case <-ctx.Done(): + if utils.IsCanceled(ctx) { return ctx.Err() - default: } + n, err := io.ReadFull(tempFile, buf) + if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { + return err + } uploadData := uploadUrls.UploadUrls[fmt.Sprint("partNumber_", i)] - res, err := y.putClient.R(). - SetContext(ctx). - SetQueryParams(clientSuffix()). - SetHeaders(ParseHttpHeader(uploadData.RequestHeader)). - SetBody(io.LimitReader(tempFile, DEFAULT)). - Put(uploadData.RequestURL) + err = retry.Do(func() error { + _, err := y.put(ctx, uploadData.RequestURL, ParseHttpHeader(uploadData.RequestHeader), false, bytes.NewReader(buf[:n])) + return err + }, + retry.Context(ctx), + retry.Attempts(3), + retry.Delay(time.Second), + retry.MaxDelay(5*time.Second)) if err != nil { return err } - if res.StatusCode() != http.StatusOK { - return fmt.Errorf("updload fail,msg: %s", res.String()) - } + up(int(i * 100 / count)) } } @@ -603,7 +651,6 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode _, err = y.request(fullUrl+"/commitMultiUploadFile", http.MethodGet, func(req *resty.Request) { req.SetContext(ctx) - req.SetHeader("X-Request-ID", requestID) }, Params{ "uploadFileId": uploadInfo.Data.UploadFileID, "isLog": "0", @@ -612,6 +659,137 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode return err } +func (y *Cloud189PC) OldUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (err error) { + // 需要获取完整文件md5,必须支持 io.Seek + tempFile, err := utils.CreateTempFile(file.GetReadCloser()) + if err != nil { + return err + } + defer func() { + _ = tempFile.Close() + _ = os.Remove(tempFile.Name()) + }() + + // 计算md5 + fileMd5 := md5.New() + if _, err := io.Copy(fileMd5, tempFile); err != nil { + return err + } + if _, err = tempFile.Seek(0, io.SeekStart); err != nil { + return err + } + fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil))) + + // 创建上传会话 + var uploadInfo CreateUploadFileResp + + fullUrl := API_URL + "/createUploadFile.action" + if y.isFamily() { + fullUrl = API_URL + "/family/file/createFamilyFile.action" + } + _, err = y.post(fullUrl, func(req *resty.Request) { + req.SetContext(ctx) + if y.isFamily() { + req.SetQueryParams(map[string]string{ + "familyId": y.FamilyID, + "fileMd5": fileMd5Hex, + "fileName": file.GetName(), + "fileSize": fmt.Sprint(file.GetSize()), + "parentId": dstDir.GetID(), + "resumePolicy": "1", + }) + } else { + req.SetFormData(map[string]string{ + "parentFolderId": dstDir.GetID(), + "fileName": file.GetName(), + "size": fmt.Sprint(file.GetSize()), + "md5": fileMd5Hex, + "opertype": "3", + "flag": "1", + "resumePolicy": "1", + "isLog": "0", + // "baseFileId": "", + // "lastWrite":"", + // "localPath": strings.ReplaceAll(param.LocalPath, "\\", "/"), + // "fileExt": "", + }) + } + }, &uploadInfo) + + if err != nil { + return err + } + + // 网盘中不存在该文件,开始上传 + status := GetUploadFileStatusResp{CreateUploadFileResp: uploadInfo} + for status.Size < file.GetSize() && status.FileDataExists != 1 { + if utils.IsCanceled(ctx) { + return ctx.Err() + } + + header := map[string]string{ + "ResumePolicy": "1", + "Expect": "100-continue", + } + + if y.isFamily() { + header["FamilyId"] = fmt.Sprint(y.FamilyID) + header["UploadFileId"] = fmt.Sprint(status.UploadFileId) + } else { + header["Edrive-UploadFileId"] = fmt.Sprint(status.UploadFileId) + } + + _, err := y.put(ctx, status.FileUploadUrl, header, true, io.NopCloser(tempFile)) + if err, ok := err.(*RespErr); ok && err.Code != "InputStreamReadError" { + return err + } + + // 获取断点状态 + fullUrl := API_URL + "/getUploadFileStatus.action" + if y.isFamily() { + fullUrl = API_URL + "/family/file/getFamilyFileStatus.action" + } + _, err = y.get(fullUrl, func(req *resty.Request) { + req.SetContext(ctx).SetQueryParams(map[string]string{ + "uploadFileId": fmt.Sprint(status.UploadFileId), + "resumePolicy": "1", + }) + if y.isFamily() { + req.SetQueryParam("familyId", fmt.Sprint(y.FamilyID)) + } + }, &status) + if err != nil { + return err + } + + if _, err := tempFile.Seek(status.GetSize(), io.SeekStart); err != nil { + return err + } + up(int(status.Size / file.GetSize())) + } + + // 提交 + var resp CommitUploadFileResp + _, err = y.post(status.FileCommitUrl, func(req *resty.Request) { + req.SetContext(ctx) + if y.isFamily() { + req.SetHeaders(map[string]string{ + "ResumePolicy": "1", + "UploadFileId": fmt.Sprint(status.UploadFileId), + "FamilyId": fmt.Sprint(y.FamilyID), + }) + } else { + req.SetFormData(map[string]string{ + "opertype": "3", + "resumePolicy": "1", + "uploadFileId": fmt.Sprint(status.UploadFileId), + "isLog": "0", + }) + } + }, &resp) + return err +} + func (y *Cloud189PC) isFamily() bool { return y.Type == "family" } diff --git a/drivers/lanzou/driver.go b/drivers/lanzou/driver.go index 8016c2d4..c2e60f5e 100644 --- a/drivers/lanzou/driver.go +++ b/drivers/lanzou/driver.go @@ -18,6 +18,7 @@ type LanZou struct { Addition model.Storage uid string + vei string } func (d *LanZou) Config() driver.Config { @@ -28,7 +29,7 @@ func (d *LanZou) GetAddition() driver.Additional { return &d.Addition } -func (d *LanZou) Init(ctx context.Context) error { +func (d *LanZou) Init(ctx context.Context) (err error) { if d.IsCookie() { if d.RootFolderID == "" { d.RootFolderID = "-1" @@ -38,8 +39,9 @@ func (d *LanZou) Init(ctx context.Context) error { return fmt.Errorf("cookie does not contain ylogin") } d.uid = ylogin[1] + d.vei, err = d.getVei() } - return nil + return } func (d *LanZou) Drop(ctx context.Context) error { diff --git a/drivers/lanzou/util.go b/drivers/lanzou/util.go index 859eed34..1fb17521 100644 --- a/drivers/lanzou/util.go +++ b/drivers/lanzou/util.go @@ -22,12 +22,17 @@ var once sync.Once func (d *LanZou) doupload(callback base.ReqCallback, resp interface{}) ([]byte, error) { return d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) { - req.SetQueryParam("uid", d.uid) - callback(req) + req.SetQueryParams(map[string]string{ + "uid": d.uid, + "vei": d.vei, + }) + if callback != nil { + callback(req) + } }, resp) } -func (d *LanZou) get(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) { +func (d *LanZou) get(url string, callback base.ReqCallback) ([]byte, error) { return d.request(url, http.MethodGet, callback, false) } @@ -224,7 +229,7 @@ func (d *LanZou) getShareUrlHtml(shareID string) (string, error) { Value: vs, }) } - }, nil) + }) if err != nil { return "", err } @@ -315,7 +320,7 @@ func (d *LanZou) getFilesByShareUrl(shareID, pwd string, sharePageData string) ( log.Errorf("lanzou: err => not find file page param ,data => %s\n", sharePageData) return nil, fmt.Errorf("not find file page param") } - data, err := d.get(fmt.Sprint(d.ShareUrl, urlpaths[1]), nil, nil) + data, err := d.get(fmt.Sprint(d.ShareUrl, urlpaths[1]), nil) if err != nil { return nil, err } @@ -445,3 +450,22 @@ func (d *LanZou) getFileRealInfo(downURL string) (*int64, *time.Time) { size, _ := strconv.ParseInt(res.Header().Get("Content-Length"), 10, 64) return &size, &time } + +func (d *LanZou) getVei() (string, error) { + resp, err := d.get("https://pc.woozooo.com/mydisk.php", func(req *resty.Request) { + req.SetQueryParams(map[string]string{ + "item": "files", + "action": "index", + "u": d.uid, + }) + }) + if err != nil { + return "", err + } + html := RemoveNotes(string(resp)) + data, err := htmlJsonToMap(html) + if err != nil { + return "", err + } + return data["vei"], nil +} diff --git a/go.mod b/go.mod index dc17d955..4757ec7b 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/aead/ecdh v0.2.0 // indirect github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible // indirect github.com/andreburgaud/crypt2go v1.1.0 // indirect + github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/bits-and-blooms/bitset v1.2.0 // indirect github.com/blevesearch/bleve_index_api v1.0.5 // indirect diff --git a/go.sum b/go.sum index a95c6158..f36980a8 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1e github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andreburgaud/crypt2go v1.1.0 h1:eitZxTPY1krUsxinsng3Qvt/Ud7q/aQmmYRh8p4hyPw= github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc= +github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.44.194 h1:1ZDK+QDcc5oRbZGgRZSz561eR8XVizXCeGpoZKo33NU= github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=