mirror of https://github.com/Xhofe/alist
				
				
				
			
		
			
				
	
	
		
			433 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			433 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
package _139
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/base64"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"sort"
 | 
						|
	"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/pkg/utils"
 | 
						|
	"github.com/alist-org/alist/v3/pkg/utils/random"
 | 
						|
	"github.com/alist-org/alist/v3/internal/op"
 | 
						|
	"github.com/go-resty/resty/v2"
 | 
						|
	jsoniter "github.com/json-iterator/go"
 | 
						|
	log "github.com/sirupsen/logrus"
 | 
						|
)
 | 
						|
 | 
						|
// do others that not defined in Driver interface
 | 
						|
func (d *Yun139) isFamily() bool {
 | 
						|
	return d.Type == "family"
 | 
						|
}
 | 
						|
 | 
						|
func encodeURIComponent(str string) string {
 | 
						|
	r := url.QueryEscape(str)
 | 
						|
	r = strings.Replace(r, "+", "%20", -1)
 | 
						|
	r = strings.Replace(r, "%21", "!", -1)
 | 
						|
	r = strings.Replace(r, "%27", "'", -1)
 | 
						|
	r = strings.Replace(r, "%28", "(", -1)
 | 
						|
	r = strings.Replace(r, "%29", ")", -1)
 | 
						|
	r = strings.Replace(r, "%2A", "*", -1)
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
func calSign(body, ts, randStr string) string {
 | 
						|
	body = encodeURIComponent(body)
 | 
						|
	strs := strings.Split(body, "")
 | 
						|
	sort.Strings(strs)
 | 
						|
	body = strings.Join(strs, "")
 | 
						|
	body = base64.StdEncoding.EncodeToString([]byte(body))
 | 
						|
	res := utils.GetMD5EncodeStr(body) + utils.GetMD5EncodeStr(ts+":"+randStr)
 | 
						|
	res = strings.ToUpper(utils.GetMD5EncodeStr(res))
 | 
						|
	return res
 | 
						|
}
 | 
						|
 | 
						|
func getTime(t string) time.Time {
 | 
						|
	stamp, _ := time.ParseInLocation("20060102150405", t, utils.CNLoc)
 | 
						|
	return stamp
 | 
						|
}
 | 
						|
 | 
						|
func (d *Yun139) refreshToken() error {
 | 
						|
	url := "https://aas.caiyun.feixin.10086.cn:443/tellin/authTokenRefresh.do"
 | 
						|
	var resp RefreshTokenResp
 | 
						|
	decode, err := base64.StdEncoding.DecodeString(d.Authorization)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	decodeStr := string(decode)
 | 
						|
	splits := strings.Split(decodeStr, ":")
 | 
						|
	reqBody := "<root><token>" + splits[2] + "</token><account>" + splits[1] + "</account><clienttype>656</clienttype></root>"
 | 
						|
	_, err = base.RestyClient.R().
 | 
						|
		ForceContentType("application/xml").
 | 
						|
		SetBody(reqBody).
 | 
						|
		SetResult(&resp).
 | 
						|
		Post(url)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if resp.Return != "0" {
 | 
						|
		return fmt.Errorf("failed to refresh token: %s", resp.Desc)
 | 
						|
	}
 | 
						|
	d.Authorization = base64.StdEncoding.EncodeToString([]byte(splits[0] + ":" + splits[1] + ":" + resp.Token))
 | 
						|
	op.MustSaveDriverStorage(d)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (d *Yun139) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
 | 
						|
	url := "https://yun.139.com" + pathname
 | 
						|
	req := base.RestyClient.R()
 | 
						|
	randStr := random.String(16)
 | 
						|
	ts := time.Now().Format("2006-01-02 15:04:05")
 | 
						|
	if callback != nil {
 | 
						|
		callback(req)
 | 
						|
	}
 | 
						|
	body, err := utils.Json.Marshal(req.Body)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	sign := calSign(string(body), ts, randStr)
 | 
						|
	svcType := "1"
 | 
						|
	if d.isFamily() {
 | 
						|
		svcType = "2"
 | 
						|
	}
 | 
						|
	req.SetHeaders(map[string]string{
 | 
						|
		"Accept":         "application/json, text/plain, */*",
 | 
						|
		"CMS-DEVICE":     "default",
 | 
						|
		"Authorization":  "Basic " + d.Authorization,
 | 
						|
		"mcloud-channel": "1000101",
 | 
						|
		"mcloud-client":  "10701",
 | 
						|
		//"mcloud-route": "001",
 | 
						|
		"mcloud-sign": fmt.Sprintf("%s,%s,%s", ts, randStr, sign),
 | 
						|
		//"mcloud-skey":"",
 | 
						|
		"mcloud-version":      "6.6.0",
 | 
						|
		"Origin":              "https://yun.139.com",
 | 
						|
		"Referer":             "https://yun.139.com/w/",
 | 
						|
		"x-DeviceInfo":        "||9|6.6.0|chrome|95.0.4638.69|uwIy75obnsRPIwlJSd7D9GhUvFwG96ce||macos 10.15.2||zh-CN|||",
 | 
						|
		"x-huawei-channelSrc": "10000034",
 | 
						|
		"x-inner-ntwk":        "2",
 | 
						|
		"x-m4c-caller":        "PC",
 | 
						|
		"x-m4c-src":           "10002",
 | 
						|
		"x-SvcType":           svcType,
 | 
						|
	})
 | 
						|
 | 
						|
	var e BaseResp
 | 
						|
	req.SetResult(&e)
 | 
						|
	res, err := req.Execute(method, url)
 | 
						|
	log.Debugln(res.String())
 | 
						|
	if !e.Success {
 | 
						|
		return nil, errors.New(e.Message)
 | 
						|
	}
 | 
						|
	if resp != nil {
 | 
						|
		err = utils.Json.Unmarshal(res.Body(), resp)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return res.Body(), nil
 | 
						|
}
 | 
						|
func (d *Yun139) post(pathname string, data interface{}, resp interface{}) ([]byte, error) {
 | 
						|
	return d.request(pathname, http.MethodPost, func(req *resty.Request) {
 | 
						|
		req.SetBody(data)
 | 
						|
	}, resp)
 | 
						|
}
 | 
						|
 | 
						|
func (d *Yun139) getFiles(catalogID string) ([]model.Obj, error) {
 | 
						|
	start := 0
 | 
						|
	limit := 100
 | 
						|
	files := make([]model.Obj, 0)
 | 
						|
	for {
 | 
						|
		data := base.Json{
 | 
						|
			"catalogID":       catalogID,
 | 
						|
			"sortDirection":   1,
 | 
						|
			"startNumber":     start + 1,
 | 
						|
			"endNumber":       start + limit,
 | 
						|
			"filterType":      0,
 | 
						|
			"catalogSortType": 0,
 | 
						|
			"contentSortType": 0,
 | 
						|
			"commonAccountInfo": base.Json{
 | 
						|
				"account":     d.Account,
 | 
						|
				"accountType": 1,
 | 
						|
			},
 | 
						|
		}
 | 
						|
		var resp GetDiskResp
 | 
						|
		_, err := d.post("/orchestration/personalCloud/catalog/v1.0/getDisk", data, &resp)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		for _, catalog := range resp.Data.GetDiskResult.CatalogList {
 | 
						|
			f := model.Object{
 | 
						|
				ID:       catalog.CatalogID,
 | 
						|
				Name:     catalog.CatalogName,
 | 
						|
				Size:     0,
 | 
						|
				Modified: getTime(catalog.UpdateTime),
 | 
						|
				Ctime:    getTime(catalog.CreateTime),
 | 
						|
				IsFolder: true,
 | 
						|
			}
 | 
						|
			files = append(files, &f)
 | 
						|
		}
 | 
						|
		for _, content := range resp.Data.GetDiskResult.ContentList {
 | 
						|
			f := model.ObjThumb{
 | 
						|
				Object: model.Object{
 | 
						|
					ID:       content.ContentID,
 | 
						|
					Name:     content.ContentName,
 | 
						|
					Size:     content.ContentSize,
 | 
						|
					Modified: getTime(content.UpdateTime),
 | 
						|
					HashInfo: utils.NewHashInfo(utils.MD5, content.Digest),
 | 
						|
				},
 | 
						|
				Thumbnail: model.Thumbnail{Thumbnail: content.ThumbnailURL},
 | 
						|
				//Thumbnail: content.BigthumbnailURL,
 | 
						|
			}
 | 
						|
			files = append(files, &f)
 | 
						|
		}
 | 
						|
		if start+limit >= resp.Data.GetDiskResult.NodeCount {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		start += limit
 | 
						|
	}
 | 
						|
	return files, nil
 | 
						|
}
 | 
						|
 | 
						|
func (d *Yun139) newJson(data map[string]interface{}) base.Json {
 | 
						|
	common := map[string]interface{}{
 | 
						|
		"catalogType": 3,
 | 
						|
		"cloudID":     d.CloudID,
 | 
						|
		"cloudType":   1,
 | 
						|
		"commonAccountInfo": base.Json{
 | 
						|
			"account":     d.Account,
 | 
						|
			"accountType": 1,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	return utils.MergeMap(data, common)
 | 
						|
}
 | 
						|
 | 
						|
func (d *Yun139) familyGetFiles(catalogID string) ([]model.Obj, error) {
 | 
						|
	pageNum := 1
 | 
						|
	files := make([]model.Obj, 0)
 | 
						|
	for {
 | 
						|
		data := d.newJson(base.Json{
 | 
						|
			"catalogID":       catalogID,
 | 
						|
			"contentSortType": 0,
 | 
						|
			"pageInfo": base.Json{
 | 
						|
				"pageNum":  pageNum,
 | 
						|
				"pageSize": 100,
 | 
						|
			},
 | 
						|
			"sortDirection": 1,
 | 
						|
		})
 | 
						|
		var resp QueryContentListResp
 | 
						|
		_, err := d.post("/orchestration/familyCloud/content/v1.0/queryContentList", data, &resp)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		for _, catalog := range resp.Data.CloudCatalogList {
 | 
						|
			f := model.Object{
 | 
						|
				ID:       catalog.CatalogID,
 | 
						|
				Name:     catalog.CatalogName,
 | 
						|
				Size:     0,
 | 
						|
				IsFolder: true,
 | 
						|
				Modified: getTime(catalog.LastUpdateTime),
 | 
						|
				Ctime:    getTime(catalog.CreateTime),
 | 
						|
			}
 | 
						|
			files = append(files, &f)
 | 
						|
		}
 | 
						|
		for _, content := range resp.Data.CloudContentList {
 | 
						|
			f := model.ObjThumb{
 | 
						|
				Object: model.Object{
 | 
						|
					ID:       content.ContentID,
 | 
						|
					Name:     content.ContentName,
 | 
						|
					Size:     content.ContentSize,
 | 
						|
					Modified: getTime(content.LastUpdateTime),
 | 
						|
					Ctime:    getTime(content.CreateTime),
 | 
						|
				},
 | 
						|
				Thumbnail: model.Thumbnail{Thumbnail: content.ThumbnailURL},
 | 
						|
				//Thumbnail: content.BigthumbnailURL,
 | 
						|
			}
 | 
						|
			files = append(files, &f)
 | 
						|
		}
 | 
						|
		if 100*pageNum > resp.Data.TotalCount {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		pageNum++
 | 
						|
	}
 | 
						|
	return files, nil
 | 
						|
}
 | 
						|
 | 
						|
func (d *Yun139) getLink(contentId string) (string, error) {
 | 
						|
	data := base.Json{
 | 
						|
		"appName":   "",
 | 
						|
		"contentID": contentId,
 | 
						|
		"commonAccountInfo": base.Json{
 | 
						|
			"account":     d.Account,
 | 
						|
			"accountType": 1,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	res, err := d.post("/orchestration/personalCloud/uploadAndDownload/v1.0/downloadRequest",
 | 
						|
		data, nil)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
 | 
						|
}
 | 
						|
 | 
						|
func unicode(str string) string {
 | 
						|
	textQuoted := strconv.QuoteToASCII(str)
 | 
						|
	textUnquoted := textQuoted[1 : len(textQuoted)-1]
 | 
						|
	return textUnquoted
 | 
						|
}
 | 
						|
 | 
						|
func (d *Yun139) personalRequest(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
 | 
						|
	url := "https://personal-kd-njs.yun.139.com" + pathname
 | 
						|
	req := base.RestyClient.R()
 | 
						|
	randStr := random.String(16)
 | 
						|
	ts := time.Now().Format("2006-01-02 15:04:05")
 | 
						|
	if callback != nil {
 | 
						|
		callback(req)
 | 
						|
	}
 | 
						|
	body, err := utils.Json.Marshal(req.Body)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	sign := calSign(string(body), ts, randStr)
 | 
						|
	svcType := "1"
 | 
						|
	if d.isFamily() {
 | 
						|
		svcType = "2"
 | 
						|
	}
 | 
						|
	req.SetHeaders(map[string]string{
 | 
						|
		"Accept":               "application/json, text/plain, */*",
 | 
						|
		"Authorization":        "Basic " + d.Authorization,
 | 
						|
		"Caller":               "web",
 | 
						|
		"Cms-Device":           "default",
 | 
						|
		"Mcloud-Channel":       "1000101",
 | 
						|
		"Mcloud-Client":        "10701",
 | 
						|
		"Mcloud-Route":         "001",
 | 
						|
		"Mcloud-Sign":          fmt.Sprintf("%s,%s,%s", ts, randStr, sign),
 | 
						|
		"Mcloud-Version":       "7.13.0",
 | 
						|
		"Origin":               "https://yun.139.com",
 | 
						|
		"Referer":              "https://yun.139.com/w/",
 | 
						|
		"x-DeviceInfo":         "||9|7.13.0|chrome|120.0.0.0|||windows 10||zh-CN|||",
 | 
						|
		"x-huawei-channelSrc":  "10000034",
 | 
						|
		"x-inner-ntwk":         "2",
 | 
						|
		"x-m4c-caller":         "PC",
 | 
						|
		"x-m4c-src":            "10002",
 | 
						|
		"x-SvcType":            svcType,
 | 
						|
		"X-Yun-Api-Version":    "v1",
 | 
						|
		"X-Yun-App-Channel":    "10000034",
 | 
						|
		"X-Yun-Channel-Source": "10000034",
 | 
						|
		"X-Yun-Client-Info":    "||9|7.13.0|chrome|120.0.0.0|||windows 10||zh-CN|||dW5kZWZpbmVk||",
 | 
						|
		"X-Yun-Module-Type":    "100",
 | 
						|
		"X-Yun-Svc-Type":       "1",
 | 
						|
	})
 | 
						|
 | 
						|
	var e BaseResp
 | 
						|
	req.SetResult(&e)
 | 
						|
	res, err := req.Execute(method, url)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	log.Debugln(res.String())
 | 
						|
	if !e.Success {
 | 
						|
		return nil, errors.New(e.Message)
 | 
						|
	}
 | 
						|
	if resp != nil {
 | 
						|
		err = utils.Json.Unmarshal(res.Body(), resp)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return res.Body(), nil
 | 
						|
}
 | 
						|
func (d *Yun139) personalPost(pathname string, data interface{}, resp interface{}) ([]byte, error) {
 | 
						|
	return d.personalRequest(pathname, http.MethodPost, func(req *resty.Request) {
 | 
						|
		req.SetBody(data)
 | 
						|
	}, resp)
 | 
						|
}
 | 
						|
 | 
						|
func getPersonalTime(t string) time.Time {
 | 
						|
	stamp, err := time.ParseInLocation("2006-01-02T15:04:05.999-07:00", t, utils.CNLoc)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	return stamp
 | 
						|
}
 | 
						|
 | 
						|
func (d *Yun139) personalGetFiles(fileId string) ([]model.Obj, error) {
 | 
						|
	files := make([]model.Obj, 0)
 | 
						|
	nextPageCursor := ""
 | 
						|
	for {
 | 
						|
		data := base.Json{
 | 
						|
			"imageThumbnailStyleList": []string{"Small", "Large"},
 | 
						|
			"orderBy":                 "updated_at",
 | 
						|
			"orderDirection":          "DESC",
 | 
						|
			"pageInfo": base.Json{
 | 
						|
				"pageCursor": nextPageCursor,
 | 
						|
				"pageSize":   100,
 | 
						|
			},
 | 
						|
			"parentFileId": fileId,
 | 
						|
		}
 | 
						|
		var resp PersonalListResp
 | 
						|
		_, err := d.personalPost("/hcy/file/list", data, &resp)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		nextPageCursor = resp.Data.NextPageCursor
 | 
						|
		for _, item := range resp.Data.Items {
 | 
						|
			var isFolder = (item.Type == "folder")
 | 
						|
			var f model.Obj
 | 
						|
			if isFolder {
 | 
						|
				f = &model.Object{
 | 
						|
					ID:       item.FileId,
 | 
						|
					Name:     item.Name,
 | 
						|
					Size:     0,
 | 
						|
					Modified: getPersonalTime(item.UpdatedAt),
 | 
						|
					Ctime:    getPersonalTime(item.CreatedAt),
 | 
						|
					IsFolder: isFolder,
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				var Thumbnails = item.Thumbnails
 | 
						|
				var ThumbnailUrl string
 | 
						|
				if len(Thumbnails) > 0 {
 | 
						|
					ThumbnailUrl = Thumbnails[len(Thumbnails)-1].Url
 | 
						|
				}
 | 
						|
				f = &model.ObjThumb{
 | 
						|
					Object: model.Object{
 | 
						|
						ID:       item.FileId,
 | 
						|
						Name:     item.Name,
 | 
						|
						Size:     item.Size,
 | 
						|
						Modified: getPersonalTime(item.UpdatedAt),
 | 
						|
						Ctime:    getPersonalTime(item.CreatedAt),
 | 
						|
						IsFolder: isFolder,
 | 
						|
					},
 | 
						|
					Thumbnail: model.Thumbnail{Thumbnail: ThumbnailUrl},
 | 
						|
				}
 | 
						|
			}
 | 
						|
			files = append(files, f)
 | 
						|
		}
 | 
						|
		if len(nextPageCursor) == 0 {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return files, nil
 | 
						|
}
 | 
						|
 | 
						|
func (d *Yun139) personalGetLink(fileId string) (string, error) {
 | 
						|
	data := base.Json{
 | 
						|
		"fileId": fileId,
 | 
						|
	}
 | 
						|
	res, err := d.personalPost("/hcy/file/getDownloadUrl",
 | 
						|
		data, nil)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	var cdnUrl = jsoniter.Get(res, "data", "cdnUrl").ToString()
 | 
						|
	if cdnUrl != "" {
 | 
						|
		return cdnUrl, nil
 | 
						|
	} else {
 | 
						|
		return jsoniter.Get(res, "data", "url").ToString(), nil
 | 
						|
	}
 | 
						|
}
 |