refactor: init v3

This commit is contained in:
Noah Hsu
2022-06-06 16:28:37 +08:00
parent eb15bce24b
commit b76060570e
185 changed files with 14 additions and 30438 deletions

View File

@ -1,318 +0,0 @@
package _189
import (
"bytes"
"errors"
"fmt"
"net/http"
"net/http/cookiejar"
"net/url"
"regexp"
"sync"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
)
var userStateCache = struct {
sync.Mutex
States map[string]*State
}{States: make(map[string]*State)}
func GetState(account *model.Account) *State {
userStateCache.Lock()
defer userStateCache.Unlock()
if v, ok := userStateCache.States[account.Username]; ok && v != nil {
return v
}
state := &State{client: resty.New().
SetHeaders(map[string]string{
"Accept": "application/json;charset=UTF-8",
"User-Agent": base.UserAgent,
}).SetTimeout(base.DefaultTimeout),
}
userStateCache.States[account.Username] = state
return state
}
type State struct {
sync.Mutex
client *resty.Client
RsaPublicKey string
SessionKey string
SessionSecret string
FamilySessionKey string
FamilySessionSecret string
AccessToken string
//怎么刷新的???
RefreshToken string
}
func (s *State) login(account *model.Account) error {
// 清除cookie
jar, _ := cookiejar.New(nil)
s.client.SetCookieJar(jar)
var err error
var res *resty.Response
defer func() {
account.Status = "work"
if err != nil {
account.Status = err.Error()
}
model.SaveAccount(account)
if res != nil {
log.Debug(res.String())
}
}()
var param *LoginParam
param, err = s.getLoginParam()
if err != nil {
return err
}
// 提交登录
s.RsaPublicKey = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", param.jRsaKey)
res, err = s.client.R().
SetHeaders(map[string]string{
"Referer": AUTH_URL,
"REQID": param.ReqId,
"lt": param.Lt,
}).
SetFormData(map[string]string{
"appKey": APP_ID,
"accountType": "02",
"userName": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Username),
"password": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Password),
"validateCode": param.vCodeRS,
"captchaToken": param.CaptchaToken,
"returnUrl": RETURN_URL,
"mailSuffix": "@189.cn",
"dynamicCheck": "FALSE",
"clientType": CLIENT_TYPE,
"cb_SaveName": "1",
"isOauth2": "false",
"state": "",
"paramId": param.ParamId,
}).
Post(AUTH_URL + "/api/logbox/oauth2/loginSubmit.do")
if err != nil {
return err
}
toUrl := utils.Json.Get(res.Body(), "toUrl").ToString()
if toUrl == "" {
log.Error(res.String())
return fmt.Errorf(res.String())
}
// 获取Session
var erron Erron
var sessionResp appSessionResp
res, err = s.client.R().
SetResult(&sessionResp).SetError(&erron).
SetQueryParams(clientSuffix()).
SetQueryParam("redirectURL", url.QueryEscape(toUrl)).
Post(API_URL + "/getSessionForPC.action")
if err != nil {
return err
}
if erron.ResCode != "" {
err = fmt.Errorf(erron.ResMessage)
return err
}
if sessionResp.ResCode != 0 {
err = fmt.Errorf(sessionResp.ResMessage)
return err
}
s.SessionKey = sessionResp.SessionKey
s.SessionSecret = sessionResp.SessionSecret
s.FamilySessionKey = sessionResp.FamilySessionKey
s.FamilySessionSecret = sessionResp.FamilySessionSecret
s.AccessToken = sessionResp.AccessToken
s.RefreshToken = sessionResp.RefreshToken
return err
}
func (s *State) getLoginParam() (*LoginParam, error) {
res, err := s.client.R().
SetQueryParams(map[string]string{
"appId": APP_ID,
"clientType": CLIENT_TYPE,
"returnURL": RETURN_URL,
"timeStamp": fmt.Sprint(timestamp()),
}).
Get(WEB_URL + "/api/portal/unifyLoginForPC.action")
if err != nil {
return nil, err
}
log.Debug(res.String())
param := &LoginParam{
CaptchaToken: regexp.MustCompile(`'captchaToken' value='(.+?)'`).FindStringSubmatch(res.String())[1],
Lt: regexp.MustCompile(`lt = "(.+?)"`).FindStringSubmatch(res.String())[1],
ParamId: regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(res.String())[1],
ReqId: regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(res.String())[1],
jRsaKey: regexp.MustCompile(`"j_rsaKey" value="(.+?)"`).FindStringSubmatch(res.String())[1],
vCodeID: regexp.MustCompile(`token=([A-Za-z0-9&=]+)`).FindStringSubmatch(res.String())[1],
}
imgRes, err := s.client.R().Get(fmt.Sprint(AUTH_URL, "/api/logbox/oauth2/picCaptcha.do?token=", param.vCodeID, timestamp()))
if err != nil {
return nil, err
}
if len(imgRes.Body()) > 0 {
vRes, err := resty.New().R().
SetMultipartField("image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
Post(conf.GetStr("ocr api"))
if err != nil {
return nil, err
}
if utils.Json.Get(vRes.Body(), "status").ToInt() != 200 {
return nil, errors.New("ocr error:" + utils.Json.Get(vRes.Body(), "msg").ToString())
}
param.vCodeRS = utils.Json.Get(vRes.Body(), "result").ToString()
log.Debugln("code: ", param.vCodeRS)
}
return param, nil
}
func (s *State) refreshSession(account *model.Account) error {
var erron Erron
var userSessionResp UserSessionResp
res, err := s.client.R().
SetResult(&userSessionResp).SetError(&erron).
SetQueryParams(clientSuffix()).
SetQueryParams(map[string]string{
"appId": APP_ID,
"accessToken": s.AccessToken,
}).
SetHeader("X-Request-ID", uuid.NewString()).
Get(API_URL + "/getSessionForPC.action")
if err != nil {
return err
}
log.Debug(res.String())
if erron.ResCode != "" {
return fmt.Errorf(erron.ResMessage)
}
switch userSessionResp.ResCode {
case 0:
s.SessionKey = userSessionResp.SessionKey
s.SessionSecret = userSessionResp.SessionSecret
s.FamilySessionKey = userSessionResp.FamilySessionKey
s.FamilySessionSecret = userSessionResp.FamilySessionSecret
case 11, 18:
return s.login(account)
default:
account.Status = userSessionResp.ResMessage
_ = model.SaveAccount(account)
return fmt.Errorf(userSessionResp.ResMessage)
}
return nil
}
func (s *State) IsLogin(account *model.Account) bool {
_, err := s.Request(http.MethodGet, API_URL+"/getUserInfo.action", nil, func(r *resty.Request) { r.SetQueryParams(clientSuffix()) }, account)
return err == nil
}
func (s *State) Login(account *model.Account) error {
s.Lock()
defer s.Unlock()
return s.login(account)
}
func (s *State) RefreshSession(account *model.Account) error {
s.Lock()
defer s.Unlock()
return s.refreshSession(account)
}
func (s *State) Request(method string, fullUrl string, params Params, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
s.Lock()
dateOfGmt := getHttpDateStr()
sessionKey := s.SessionKey
sessionSecret := s.SessionSecret
if isFamily(account) {
sessionKey = s.FamilySessionKey
sessionSecret = s.FamilySessionSecret
}
req := s.client.R()
req.SetHeaders(map[string]string{
"Date": dateOfGmt,
"SessionKey": sessionKey,
"X-Request-ID": uuid.NewString(),
})
// 设置params
var paramsData string
if params != nil {
paramsData = AesECBEncrypt(params.Encode(), s.SessionSecret[:16])
req.SetQueryParam("params", paramsData)
}
req.SetHeader("Signature", signatureOfHmac(sessionSecret, sessionKey, method, fullUrl, dateOfGmt, paramsData))
if callback != nil {
callback(req)
}
s.Unlock()
res, err := req.Execute(method, fullUrl)
if err != nil {
return nil, err
}
log.Debug(res.String())
var erron Erron
utils.Json.Unmarshal(res.Body(), &erron)
if erron.ResCode != "" {
return nil, fmt.Errorf(erron.ResMessage)
}
if erron.Code != "" && erron.Code != "SUCCESS" {
if erron.Msg == "" {
if erron.Message == "" {
return nil, fmt.Errorf(res.String())
}
return nil, fmt.Errorf(erron.Message)
}
return nil, fmt.Errorf(erron.Msg)
}
if erron.ErrorCode != "" {
switch erron.ErrorCode {
case "InvalidSessionKey":
if err := s.RefreshSession(account); err != nil {
return nil, err
}
return s.Request(method, fullUrl, params, callback, account)
}
return nil, fmt.Errorf(erron.ErrorMsg)
}
switch utils.Json.Get(res.Body(), "res_code").ToInt64() {
case 11, 18:
if err := s.RefreshSession(account); err != nil {
return nil, err
}
return s.Request(method, fullUrl, params, callback, account)
case 0:
if res.StatusCode() == http.StatusOK {
return res, nil
}
return nil, fmt.Errorf(res.String())
default:
return nil, fmt.Errorf(utils.Json.Get(res.Body(), "res_message").ToString())
}
}

View File

@ -1,923 +0,0 @@
package _189
import (
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"math"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
func init() {
base.RegisterDriver(new(Cloud189))
}
type Cloud189 struct {
}
func (driver Cloud189) Config() base.DriverConfig {
return base.DriverConfig{
Name: "189CloudPC",
}
}
func (driver Cloud189) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "username",
Type: base.TypeString,
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: base.TypeString,
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
},
{
Name: "internal_type",
Label: "189cloud type",
Type: base.TypeSelect,
Required: true,
Values: "Personal,Family",
},
{
Name: "site_id",
Label: "family id",
Type: base.TypeString,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "filename,filesize,lastOpTime",
Required: true,
},
{
Name: "order_direction",
Label: "desc",
Type: base.TypeSelect,
Values: "true,false",
Required: true,
},
{
Name: "bool_1",
Label: "fast upload",
Type: base.TypeBool,
},
}
}
func (driver Cloud189) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
if !isFamily(account) && account.RootFolder == "" {
account.RootFolder = "-11"
account.SiteId = ""
}
if isFamily(account) && account.RootFolder == "-11" {
account.RootFolder = ""
}
state := GetState(account)
if !state.IsLogin(account) {
if err := state.Login(account); err != nil {
return err
}
}
if isFamily(account) {
list, err := driver.getFamilyInfoList(account)
if err != nil {
return err
}
for _, l := range list {
if account.SiteId == "" {
account.SiteId = fmt.Sprint(l.FamilyID)
}
log.Infof("天翼家庭云 用户名:%s FamilyID %d\n", l.RemarkName, l.FamilyID)
}
}
account.Status = "work"
model.SaveAccount(account)
return nil
}
func (driver Cloud189) getFamilyInfoList(account *model.Account) ([]FamilyInfoResp, error) {
var resp FamilyInfoListResp
_, err := GetState(account).Request(http.MethodGet, API_URL+"/family/manage/getFamilyList.action", nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetResult(&resp)
}, account)
if err != nil {
return nil, err
}
return resp.FamilyInfoResp, nil
}
func (driver Cloud189) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
fullUrl := API_URL
if isFamily(account) {
fullUrl += "/family/file"
}
fullUrl += "/listFiles.action"
files := make([]model.File, 0)
client := GetState(account)
for pageNum := 1; ; pageNum++ {
var resp Cloud189FilesResp
_, err = client.Request(http.MethodGet, fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix()).
SetQueryParams(map[string]string{
"folderId": file.Id,
"fileType": "0",
"mediaAttr": "0",
"iconOption": "5",
"pageNum": fmt.Sprint(pageNum),
"pageSize": "130",
})
if isFamily(account) {
r.SetQueryParams(map[string]string{
"familyId": account.SiteId,
"orderBy": toFamilyOrderBy(account.OrderBy),
"descending": account.OrderDirection,
})
} else {
r.SetQueryParams(map[string]string{
"recursive": "0",
"orderBy": account.OrderBy,
"descending": account.OrderDirection,
})
}
r.SetResult(&resp)
}, account)
if err != nil {
return nil, err
}
// 获取完毕跳出
if resp.FileListAO.Count == 0 {
break
}
for _, folder := range resp.FileListAO.FolderList {
files = append(files, model.File{
Id: fmt.Sprint(folder.ID),
Name: folder.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: MustParseTime(folder.LastOpTime),
})
}
for _, file := range resp.FileListAO.FileList {
files = append(files, model.File{
Id: fmt.Sprint(file.ID),
Name: file.Name,
Size: file.Size,
Type: utils.GetFileType(filepath.Ext(file.Name)),
Driver: driver.Config().Name,
UpdatedAt: MustParseTime(file.LastOpTime),
Thumbnail: file.Icon.SmallUrl,
})
}
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("189PC path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(utils.ParsePath(args.Path), account)
if err != nil {
return nil, err
}
if file.Type == conf.FOLDER {
return nil, base.ErrNotFile
}
fullUrl := API_URL
if isFamily(account) {
fullUrl += "/family/file"
}
fullUrl += "/getFileDownloadUrl.action"
var downloadUrl struct {
URL string `json:"fileDownloadUrl"`
}
_, err = GetState(account).Request(http.MethodGet, fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix()).SetQueryParam("fileId", file.Id)
if isFamily(account) {
r.SetQueryParams(map[string]string{
"familyId": account.SiteId,
})
} else {
r.SetQueryParams(map[string]string{
"dt": "3",
"flag": "1",
})
}
r.SetResult(&downloadUrl)
}, account)
if err != nil {
return nil, err
}
return &base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
},
Url: strings.ReplaceAll(downloadUrl.URL, "&", "&"),
}, nil
}
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Cloud189) MakeDir(path string, account *model.Account) error {
dir, name := filepath.Split(path)
parentFile, err := driver.File(dir, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
fullUrl := API_URL
if isFamily(account) {
fullUrl += "/family/file"
}
fullUrl += "/createFolder.action"
_, err = GetState(account).Request(http.MethodPost, fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix()).SetQueryParams(map[string]string{
"folderName": name,
"relativePath": "",
})
if isFamily(account) {
r.SetQueryParams(map[string]string{
"familyId": account.SiteId,
"parentId": parentFile.Id,
})
} else {
r.SetQueryParams(map[string]string{
"parentFolderId": parentFile.Id,
})
}
}, account)
return err
}
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(filepath.Dir(dst), account)
if err != nil {
return err
}
_, err = GetState(account).Request(http.MethodPost, API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
"type": "MOVE",
"taskInfos": string(MustToBytes(utils.Json.Marshal(
[]*BatchTaskInfo{
{
FileId: srcFile.Id,
FileName: srcFile.Name,
IsFolder: BoolToNumber(srcFile.IsDir()),
},
}))),
"targetFolderId": dstDirFile.Id,
})
if isFamily(account) {
r.SetFormData(map[string]string{
"familyId": account.SiteId,
})
}
}, account)
return err
}
/*
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(filepath.Dir(dst), account)
if err != nil {
return err
}
var queryParam map[string]string
fullUrl := API_URL
method := http.MethodPost
if isFamily(account) {
fullUrl += "/family/file"
method = http.MethodGet
}
if srcFile.IsDir() {
fullUrl += "/moveFolder.action"
queryParam = map[string]string{
"folderId": srcFile.Id,
"destFolderName": srcFile.Name,
}
} else {
fullUrl += "/moveFile.action"
queryParam = map[string]string{
"fileId": srcFile.Id,
"destFileName": srcFile.Name,
}
}
_, err = GetState(account).Request(method, fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(queryParam).SetQueryParams(clientSuffix())
if isFamily(account) {
r.SetQueryParams(map[string]string{
"familyId": account.SiteId,
"destParentId": dstDirFile.Id,
})
} else {
r.SetQueryParam("destParentFolderId", dstDirFile.Id)
}
}, account)
return err
}*/
func (driver Cloud189) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
var queryParam map[string]string
fullUrl := API_URL
method := http.MethodPost
if isFamily(account) {
fullUrl += "/family/file"
method = http.MethodGet
}
if srcFile.IsDir() {
fullUrl += "/renameFolder.action"
queryParam = map[string]string{
"folderId": srcFile.Id,
"destFolderName": filepath.Base(dst),
}
} else {
fullUrl += "/renameFile.action"
queryParam = map[string]string{
"fileId": srcFile.Id,
"destFileName": filepath.Base(dst),
}
}
_, err = GetState(account).Request(method, fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(queryParam).SetQueryParams(clientSuffix())
if isFamily(account) {
r.SetQueryParam("familyId", account.SiteId)
}
}, account)
return err
}
func (driver Cloud189) Copy(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(filepath.Dir(dst), account)
if err != nil {
return err
}
_, err = GetState(account).Request(http.MethodPost, API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
"type": "COPY",
"taskInfos": string(MustToBytes(utils.Json.Marshal(
[]*BatchTaskInfo{
{
FileId: srcFile.Id,
FileName: srcFile.Name,
IsFolder: BoolToNumber(srcFile.IsDir()),
},
}))),
"targetFolderId": dstDirFile.Id,
"targetFileName": filepath.Base(dst),
})
if isFamily(account) {
r.SetFormData(map[string]string{
"familyId": account.SiteId,
})
}
}, account)
return err
}
func (driver Cloud189) Delete(path string, account *model.Account) error {
path = utils.ParsePath(path)
srcFile, err := driver.File(path, account)
if err != nil {
return err
}
_, err = GetState(account).Request(http.MethodPost, API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
"type": "DELETE",
"taskInfos": string(MustToBytes(utils.Json.Marshal(
[]*BatchTaskInfo{
{
FileId: srcFile.Id,
FileName: srcFile.Name,
IsFolder: BoolToNumber(srcFile.IsDir()),
},
}))),
})
if isFamily(account) {
r.SetFormData(map[string]string{
"familyId": account.SiteId,
})
}
}, account)
return err
}
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
if account.Bool1 {
return driver.FastUpload(file, parentFile, account)
}
return driver.CommonUpload(file, parentFile, account)
/*
if isFamily(account) {
return driver.uploadFamily(file, parentFile, account)
}
return driver.uploadPerson(file, parentFile, account)
*/
}
func (driver Cloud189) CommonUpload(file *model.FileStream, parentFile *model.File, account *model.Account) error {
// 初始化上传
state := GetState(account)
const DEFAULT int64 = 10485760
count := int(math.Ceil(float64(file.Size) / float64(DEFAULT)))
params := Params{
"parentFolderId": parentFile.Id,
"fileName": url.PathEscape(file.Name),
"fileSize": fmt.Sprint(file.Size),
"sliceSize": fmt.Sprint(DEFAULT),
"lazyCheck": "1",
}
fullUrl := UPLOAD_URL
if isFamily(account) {
params.Set("familyId", account.SiteId)
fullUrl += "/family"
} else {
//params.Set("extend", `{"opScene":"1","relativepath":"","rootfolderid":""}`)
fullUrl += "/person"
}
var initMultiUpload InitMultiUploadResp
_, err := state.Request(http.MethodGet, fullUrl+"/initMultiUpload", params, func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&initMultiUpload) }, account)
if err != nil {
return err
}
fileMd5 := md5.New()
silceMd5 := md5.New()
silceMd5Hexs := make([]string, 0, count)
byteData := bytes.NewBuffer(make([]byte, DEFAULT))
for i := 1; i <= count; i++ {
byteData.Reset()
silceMd5.Reset()
if n, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5, byteData), file, DEFAULT); err != io.EOF && n == 0 {
return err
}
md5Bytes := silceMd5.Sum(nil)
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Bytes)))
silceMd5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
var uploadUrl UploadUrlsResp
_, err = state.Request(http.MethodGet, fullUrl+"/getMultiUploadUrls",
Params{"partInfo": fmt.Sprintf("%d-%s", i, silceMd5Base64), "uploadFileId": initMultiUpload.Data.UploadFileID},
func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&uploadUrl) },
account)
if err != nil {
return err
}
uploadData := uploadUrl.UploadUrls[fmt.Sprint("partNumber_", i)]
req, _ := http.NewRequest(http.MethodPut, uploadData.RequestURL, byteData)
req.Header.Set("User-Agent", "")
for k, v := range ParseHttpHeader(uploadData.RequestHeader) {
req.Header.Set(k, v)
}
for k, v := range clientSuffix() {
req.URL.RawQuery += fmt.Sprintf("&%s=%s", k, v)
}
r, err := base.HttpClient.Do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusOK {
data, _ := io.ReadAll(r.Body)
r.Body.Close()
return fmt.Errorf(string(data))
}
r.Body.Close()
}
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
sliceMd5Hex := fileMd5Hex
if int64(file.Size) > DEFAULT {
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
}
_, err = state.Request(http.MethodGet, fullUrl+"/commitMultiUploadFile",
Params{
"uploadFileId": initMultiUpload.Data.UploadFileID,
"fileMd5": fileMd5Hex,
"sliceMd5": sliceMd5Hex,
"lazyCheck": "1",
"isLog": "0",
"opertype": "3",
},
func(r *resty.Request) { r.SetQueryParams(clientSuffix()) }, account)
return err
}
func (driver Cloud189) FastUpload(file *model.FileStream, parentFile *model.File, account *model.Account) error {
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
// 初始化上传
state := GetState(account)
const DEFAULT int64 = 10485760
count := int(math.Ceil(float64(file.Size) / float64(DEFAULT)))
// 优先计算所需信息
fileMd5 := md5.New()
silceMd5 := md5.New()
silceMd5Hexs := make([]string, 0, count)
silceMd5Base64s := make([]string, 0, count)
for i := 1; i <= count; i++ {
silceMd5.Reset()
if n, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5, tempFile), file, DEFAULT); err != nil && n == 0 {
return err
}
md5Byte := silceMd5.Sum(nil)
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Byte)))
silceMd5Base64s = append(silceMd5Base64s, fmt.Sprint(i, "-", base64.StdEncoding.EncodeToString(md5Byte)))
}
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
sliceMd5Hex := fileMd5Hex
if int64(file.Size) > DEFAULT {
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
}
params := Params{
"parentFolderId": parentFile.Id,
"fileName": url.PathEscape(file.Name),
"fileSize": fmt.Sprint(file.Size),
"fileMd5": fileMd5Hex,
"sliceSize": fmt.Sprint(DEFAULT),
"sliceMd5": sliceMd5Hex,
}
fullUrl := UPLOAD_URL
if isFamily(account) {
params.Set("familyId", account.SiteId)
fullUrl += "/family"
} else {
//params.Set("extend", `{"opScene":"1","relativepath":"","rootfolderid":""}`)
fullUrl += "/person"
}
var uploadInfo InitMultiUploadResp
_, err = state.Request(http.MethodGet, fullUrl+"/initMultiUpload", params, func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&uploadInfo) }, account)
if err != nil {
return err
}
if uploadInfo.Data.FileDataExists != 1 {
var uploadUrls UploadUrlsResp
_, err := state.Request(http.MethodGet, fullUrl+"/getMultiUploadUrls",
Params{
"uploadFileId": uploadInfo.Data.UploadFileID,
"partInfo": strings.Join(silceMd5Base64s, ","),
},
func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&uploadUrls) },
account)
if err != nil {
return err
}
for i := 1; i <= count; i++ {
uploadData := uploadUrls.UploadUrls[fmt.Sprint("partNumber_", i)]
req, _ := http.NewRequest(http.MethodPut, uploadData.RequestURL, io.NewSectionReader(tempFile, int64(i-1)*DEFAULT, DEFAULT))
req.Header.Set("User-Agent", "")
for k, v := range ParseHttpHeader(uploadData.RequestHeader) {
req.Header.Set(k, v)
}
for k, v := range clientSuffix() {
req.URL.RawQuery += fmt.Sprintf("&%s=%s", k, v)
}
r, err := base.HttpClient.Do(req)
if err != nil {
return err
}
if r.StatusCode != http.StatusOK {
data, _ := io.ReadAll(r.Body)
r.Body.Close()
return fmt.Errorf(string(data))
}
r.Body.Close()
}
}
_, err = state.Request(http.MethodGet, fullUrl+"/commitMultiUploadFile",
Params{
"uploadFileId": uploadInfo.Data.UploadFileID,
"isLog": "0",
"opertype": "3",
},
func(r *resty.Request) { r.SetQueryParams(clientSuffix()) },
account)
return err
}
/*
func (driver Cloud189) uploadFamily(file *model.FileStream, parentFile *model.File, account *model.Account) error {
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
fileMd5 := md5.New()
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
return err
}
client := GetState(account)
var createUpload CreateUploadFileResult
_, err = client.Request(http.MethodGet, API_URL+"/family/file/createFamilyFile.action", nil, func(r *resty.Request) {
r.SetQueryParams(map[string]string{
"fileMd5": hex.EncodeToString(fileMd5.Sum(nil)),
"fileName": file.Name,
"familyId": account.SiteId,
"parentId": parentFile.Id,
"resumePolicy": "1",
"fileSize": fmt.Sprint(file.Size),
})
r.SetQueryParams(clientSuffix())
r.SetResult(&createUpload)
}, account)
if err != nil {
return err
}
if createUpload.FileDataExists != 1 {
if createUpload.UploadFileId, err = driver.uploadFileData(file, tempFile, createUpload, account); err != nil {
return err
}
}
_, err = client.Request(http.MethodGet, createUpload.FileCommitUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetHeaders(map[string]string{
"FamilyId": account.SiteId,
"uploadFileId": fmt.Sprint(createUpload.UploadFileId),
"ResumePolicy": "1",
})
}, account)
return err
}
func (driver Cloud189) uploadPerson(file *model.FileStream, parentFile *model.File, account *model.Account) error {
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
fileMd5 := md5.New()
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
return err
}
client := GetState(account)
var createUpload CreateUploadFileResult
_, err = client.Request(http.MethodPost, API_URL+"/createUploadFile.action", nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
"parentFolderId": parentFile.Id,
"baseFileId": "",
"fileName": file.Name,
"size": fmt.Sprint(file.Size),
"md5": hex.EncodeToString(fileMd5.Sum(nil)),
// "lastWrite": param.LastWrite,
// "localPath": strings.ReplaceAll(file.ParentPath, "\\", "/"),
"opertype": "1",
"flag": "1",
"resumePolicy": "1",
"isLog": "0",
"fileExt": "",
})
r.SetResult(&createUpload)
}, account)
if err != nil {
return err
}
if createUpload.FileDataExists != 1 {
if createUpload.UploadFileId, err = driver.uploadFileData(file, tempFile, createUpload, account); err != nil {
return err
}
}
_, err = client.Request(http.MethodPost, createUpload.FileCommitUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetFormData(map[string]string{
"uploadFileId": fmt.Sprint(createUpload.UploadFileId),
"opertype": "5", //5 覆盖 1 重命名
"ResumePolicy": "1",
"isLog": "0",
})
}, account)
return err
}
func (driver Cloud189) uploadFileData(file *model.FileStream, tempFile *os.File, createUpload CreateUploadFileResult, account *model.Account) (int64, error) {
uploadFileState, err := driver.getUploadFileState(createUpload.UploadFileId, account)
if err != nil {
return 0, err
}
if uploadFileState.FileDataExists == 1 || uploadFileState.DataSize == int64(file.Size) {
return uploadFileState.UploadFileId, nil
}
if _, err = tempFile.Seek(uploadFileState.DataSize, io.SeekStart); err != nil {
return 0, err
}
_, err = GetState(account).Request("PUT", uploadFileState.FileUploadUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetHeaders(map[string]string{
"Content-Type": "application/octet-stream",
"ResumePolicy": "1",
"Edrive-UploadFileRange": fmt.Sprintf("bytes=%d-%d", uploadFileState.DataSize, file.Size),
"Expect": "100-continue",
})
if isFamily(account) {
r.SetHeaders(map[string]string{
"familyId": account.SiteId,
"UploadFileId": fmt.Sprint(uploadFileState.UploadFileId),
})
} else {
r.SetHeader("Edrive-UploadFileId", fmt.Sprint(uploadFileState.UploadFileId))
}
r.SetBody(tempFile)
}, account)
return uploadFileState.UploadFileId, err
}
func (driver Cloud189) getUploadFileState(uploadFileId int64, account *model.Account) (*UploadFileStatusResult, error) {
fullUrl := API_URL
if isFamily(account) {
fullUrl += "/family/file/getFamilyFileStatus.action"
} else {
fullUrl += "/getUploadFileStatus.action"
}
var uploadFileState UploadFileStatusResult
_, err := GetState(account).Request(http.MethodGet, fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetQueryParams(map[string]string{
"uploadFileId": fmt.Sprint(uploadFileId),
"resumePolicy": "1",
})
if isFamily(account) {
r.SetQueryParam("familyId", account.SiteId)
}
r.SetResult(&uploadFileState)
}, account)
if err != nil {
return nil, err
}
return &uploadFileState, nil
}*/
var _ base.Driver = (*Cloud189)(nil)

View File

@ -1,180 +0,0 @@
package _189
import "encoding/xml"
type LoginParam struct {
CaptchaToken string
Lt string
ParamId string
ReqId string
jRsaKey string
vCodeID string
vCodeRS string
}
// 居然有四种返回方式
type Erron struct {
ResCode string `json:"res_code"`
ResMessage string `json:"res_message"`
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"`
ErrorCode string `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
// 刷新session返回
type UserSessionResp struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
LoginName string `json:"loginName"`
KeepAlive int `json:"keepAlive"`
GetFileDiffSpan int `json:"getFileDiffSpan"`
GetUserInfoSpan int `json:"getUserInfoSpan"`
// 个人云
SessionKey string `json:"sessionKey"`
SessionSecret string `json:"sessionSecret"`
// 家庭云
FamilySessionKey string `json:"familySessionKey"`
FamilySessionSecret string `json:"familySessionSecret"`
}
//登录返回
type appSessionResp struct {
UserSessionResp
IsSaveName string `json:"isSaveName"`
// 会话刷新Token
AccessToken string `json:"accessToken"`
//Token刷新
RefreshToken string `json:"refreshToken"`
}
type FamilyInfoListResp struct {
FamilyInfoResp []FamilyInfoResp `json:"familyInfoResp"`
}
type FamilyInfoResp struct {
Count int `json:"count"`
CreateTime string `json:"createTime"`
FamilyID int `json:"familyId"`
RemarkName string `json:"remarkName"`
Type int `json:"type"`
UseFlag int `json:"useFlag"`
UserRole int `json:"userRole"`
}
/*文件部分*/
// 文件
type Cloud189File struct {
CreateDate string `json:"createDate"`
FileCata int64 `json:"fileCata"`
Icon struct {
//iconOption 5
SmallUrl string `json:"smallUrl"`
LargeUrl string `json:"largeUrl"`
// iconOption 10
Max600 string `json:"max600"`
MediumURL string `json:"mediumUrl"`
} `json:"icon"`
ID int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Md5 string `json:"md5"`
MediaType int `json:"mediaType"`
Name string `json:"name"`
Orientation int64 `json:"orientation"`
Rev string `json:"rev"`
Size int64 `json:"size"`
StarLabel int64 `json:"starLabel"`
}
// 文件夹
type Cloud189Folder struct {
ID int64 `json:"id"`
ParentID int64 `json:"parentId"`
Name string `json:"name"`
FileCata int64 `json:"fileCata"`
FileCount int64 `json:"fileCount"`
LastOpTime string `json:"lastOpTime"`
CreateDate string `json:"createDate"`
FileListSize int64 `json:"fileListSize"`
Rev string `json:"rev"`
StarLabel int64 `json:"starLabel"`
}
type Cloud189FilesResp struct {
//ResCode int `json:"res_code"`
//ResMessage string `json:"res_message"`
FileListAO struct {
Count int `json:"count"`
FileList []Cloud189File `json:"fileList"`
FolderList []Cloud189Folder `json:"folderList"`
} `json:"fileListAO"`
}
// TaskInfo 任务信息
type BatchTaskInfo struct {
// FileId 文件ID
FileId string `json:"fileId"`
// FileName 文件名
FileName string `json:"fileName"`
// IsFolder 是否是文件夹0-否1-是
IsFolder int `json:"isFolder"`
// SrcParentId 文件所在父目录ID
//SrcParentId string `json:"srcParentId"`
}
/*
type CreateUploadFileResult struct {
// UploadFileId 上传文件请求ID
UploadFileId int64 `json:"uploadFileId"`
// FileUploadUrl 上传文件数据的URL路径
FileUploadUrl string `json:"fileUploadUrl"`
// FileCommitUrl 上传文件完成后确认路径
FileCommitUrl string `json:"fileCommitUrl"`
// FileDataExists 文件是否已存在云盘中0-未存在1-已存在
FileDataExists int `json:"fileDataExists"`
}
type UploadFileStatusResult struct {
// 上传文件的ID
UploadFileId int64 `json:"uploadFileId"`
// 已上传的大小
DataSize int64 `json:"dataSize"`
FileUploadUrl string `json:"fileUploadUrl"`
FileCommitUrl string `json:"fileCommitUrl"`
FileDataExists int `json:"fileDataExists"`
}
*/
type InitMultiUploadResp struct {
//Code string `json:"code"`
Data struct {
UploadType int `json:"uploadType"`
UploadHost string `json:"uploadHost"`
UploadFileID string `json:"uploadFileId"`
FileDataExists int `json:"fileDataExists"`
} `json:"data"`
}
type UploadUrlsResp struct {
Code string `json:"code"`
UploadUrls map[string]Part `json:"uploadUrls"`
}
type Part struct {
RequestURL string `json:"requestURL"`
RequestHeader string `json:"requestHeader"`
}

View File

@ -1,177 +0,0 @@
package _189
import (
"bytes"
"crypto/aes"
"crypto/hmac"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
rand2 "math/rand"
"net/http"
"net/url"
"sort"
"strings"
"time"
"github.com/Xhofe/alist/model"
)
const (
APP_ID = "8025431004"
CLIENT_TYPE = "10020"
VERSION = "6.2"
WEB_URL = "https://cloud.189.cn"
AUTH_URL = "https://open.e.189.cn"
API_URL = "https://api.cloud.189.cn"
UPLOAD_URL = "https://upload.cloud.189.cn"
RETURN_URL = "https://m.cloud.189.cn/zhuanti/2020/loginErrorPc/index.html"
PC = "TELEPC"
MAC = "TELEMAC"
CHANNEL_ID = "web_cloud.189.cn"
)
func clientSuffix() map[string]string {
return map[string]string{
"clientType": PC,
"version": VERSION,
"channelId": CHANNEL_ID,
"rand": fmt.Sprintf("%d_%d", rand2.Int63n(1e5), rand2.Int63n(1e10)),
}
}
// 带params的SignatureOfHmac HMAC签名
func signatureOfHmac(sessionSecret, sessionKey, operate, fullUrl, dateOfGmt, param string) string {
u, _ := url.Parse(fullUrl)
mac := hmac.New(sha1.New, []byte(sessionSecret))
data := fmt.Sprintf("SessionKey=%s&Operate=%s&RequestURI=%s&Date=%s", sessionKey, operate, u.Path, dateOfGmt)
if param != "" {
data += fmt.Sprintf("&params=%s", param)
}
mac.Write([]byte(data))
return strings.ToUpper(hex.EncodeToString(mac.Sum(nil)))
}
// 获取http规范的时间
func getHttpDateStr() string {
return time.Now().UTC().Format(http.TimeFormat)
}
// RAS 加密用户名密码
func rsaEncrypt(publicKey, origData string) string {
block, _ := pem.Decode([]byte(publicKey))
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
data, _ := rsa.EncryptPKCS1v15(rand.Reader, pubInterface.(*rsa.PublicKey), []byte(origData))
return base64ToHex(base64.StdEncoding.EncodeToString(data))
}
// aes 加密params
func AesECBEncrypt(data, key string) string {
block, _ := aes.NewCipher([]byte(key))
paddingData := PKCS7Padding([]byte(data), block.BlockSize())
decrypted := make([]byte, len(paddingData))
size := block.BlockSize()
for src, dst := paddingData, decrypted; len(src) > 0; src, dst = src[size:], dst[size:] {
block.Encrypt(dst[:size], src[:size])
}
return strings.ToUpper(hex.EncodeToString(decrypted))
}
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// 时间戳
func timestamp() int64 {
return time.Now().UTC().UnixNano() / 1e6
}
func base64ToHex(a string) string {
v, _ := base64.StdEncoding.DecodeString(a)
return strings.ToUpper(hex.EncodeToString(v))
}
func isFamily(account *model.Account) bool {
return account.InternalType == "Family"
}
func toFamilyOrderBy(o string) string {
switch o {
case "filename":
return "1"
case "filesize":
return "2"
case "lastOpTime":
return "3"
default:
return "1"
}
}
func ParseHttpHeader(str string) map[string]string {
header := make(map[string]string)
for _, value := range strings.Split(str, "&") {
i := strings.Index(value, "=")
header[strings.TrimSpace(value[0:i])] = strings.TrimSpace(value[i+1:])
}
return header
}
func MustString(str string, err error) string {
return str
}
func MustToBytes(b []byte, err error) []byte {
return b
}
func BoolToNumber(b bool) int {
if b {
return 1
}
return 0
}
func MustParseTime(str string) *time.Time {
loc, _ := time.LoadLocation("Local")
lastOpTime, _ := time.ParseInLocation("2006-01-02 15:04:05", str, loc)
return &lastOpTime
}
type Params map[string]string
func (p Params) Set(k, v string) {
p[k] = v
}
func (p Params) Encode() string {
if p == nil {
return ""
}
var buf strings.Builder
keys := make([]string, 0, len(p))
for k := range p {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(p[k])
}
return buf.String()
}