mirror of https://github.com/Xhofe/alist
				
				
				
			
		
			
				
	
	
		
			312 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			312 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
package thunder_browser
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/md5"
 | 
						|
	"crypto/sha1"
 | 
						|
	"encoding/hex"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"net/http"
 | 
						|
	"regexp"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/alist-org/alist/v3/drivers/base"
 | 
						|
	"github.com/alist-org/alist/v3/pkg/utils"
 | 
						|
	"github.com/go-resty/resty/v2"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	API_URL        = "https://x-api-pan.xunlei.com/drive/v1"
 | 
						|
	FILE_API_URL   = API_URL + "/files"
 | 
						|
	XLUSER_API_URL = "https://xluser-ssl.xunlei.com/v1"
 | 
						|
)
 | 
						|
 | 
						|
var Algorithms = []string{
 | 
						|
	"uWRwO7gPfdPB/0NfPtfQO+71",
 | 
						|
	"F93x+qPluYy6jdgNpq+lwdH1ap6WOM+nfz8/V",
 | 
						|
	"0HbpxvpXFsBK5CoTKam",
 | 
						|
	"dQhzbhzFRcawnsZqRETT9AuPAJ+wTQso82mRv",
 | 
						|
	"SAH98AmLZLRa6DB2u68sGhyiDh15guJpXhBzI",
 | 
						|
	"unqfo7Z64Rie9RNHMOB",
 | 
						|
	"7yxUdFADp3DOBvXdz0DPuKNVT35wqa5z0DEyEvf",
 | 
						|
	"RBG",
 | 
						|
	"ThTWPG5eC0UBqlbQ+04nZAptqGCdpv9o55A",
 | 
						|
}
 | 
						|
 | 
						|
const (
 | 
						|
	ClientID          = "ZUBzD9J_XPXfn7f7"
 | 
						|
	ClientSecret      = "yESVmHecEe6F0aou69vl-g"
 | 
						|
	ClientVersion     = "1.10.0.2633"
 | 
						|
	PackageName       = "com.xunlei.browser"
 | 
						|
	DownloadUserAgent = "AndroidDownloadManager/13 (Linux; U; Android 13; M2004J7AC Build/SP1A.210812.016)"
 | 
						|
	SdkVersion        = "233100"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	FOLDER    = "drive#folder"
 | 
						|
	FILE      = "drive#file"
 | 
						|
	RESUMABLE = "drive#resumable"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	UPLOAD_TYPE_UNKNOWN = "UPLOAD_TYPE_UNKNOWN"
 | 
						|
	//UPLOAD_TYPE_FORM      = "UPLOAD_TYPE_FORM"
 | 
						|
	UPLOAD_TYPE_RESUMABLE = "UPLOAD_TYPE_RESUMABLE"
 | 
						|
	UPLOAD_TYPE_URL       = "UPLOAD_TYPE_URL"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	ThunderDriveSpace                 = ""
 | 
						|
	ThunderDriveSafeSpace             = "SPACE_SAFE"
 | 
						|
	ThunderBrowserDriveSpace          = "SPACE_BROWSER"
 | 
						|
	ThunderBrowserDriveSafeSpace      = "SPACE_BROWSER_SAFE"
 | 
						|
	ThunderDriveFolderType            = "DEFAULT_ROOT"
 | 
						|
	ThunderBrowserDriveSafeFolderType = "BROWSER_SAFE"
 | 
						|
)
 | 
						|
 | 
						|
func GetAction(method string, url string) string {
 | 
						|
	urlpath := regexp.MustCompile(`://[^/]+((/[^/\s?#]+)*)`).FindStringSubmatch(url)[1]
 | 
						|
	return method + ":" + urlpath
 | 
						|
}
 | 
						|
 | 
						|
type Common struct {
 | 
						|
	client *resty.Client
 | 
						|
 | 
						|
	captchaToken string
 | 
						|
 | 
						|
	// 签名相关,二选一
 | 
						|
	Algorithms             []string
 | 
						|
	Timestamp, CaptchaSign string
 | 
						|
 | 
						|
	// 必要值,签名相关
 | 
						|
	DeviceID          string
 | 
						|
	ClientID          string
 | 
						|
	ClientSecret      string
 | 
						|
	ClientVersion     string
 | 
						|
	PackageName       string
 | 
						|
	UserAgent         string
 | 
						|
	DownloadUserAgent string
 | 
						|
	UseVideoUrl       bool
 | 
						|
	RemoveWay         string
 | 
						|
 | 
						|
	// 验证码token刷新成功回调
 | 
						|
	refreshCTokenCk func(token string)
 | 
						|
}
 | 
						|
 | 
						|
func (c *Common) SetDeviceID(deviceID string) {
 | 
						|
	c.DeviceID = deviceID
 | 
						|
}
 | 
						|
 | 
						|
func (c *Common) SetCaptchaToken(captchaToken string) {
 | 
						|
	c.captchaToken = captchaToken
 | 
						|
}
 | 
						|
func (c *Common) GetCaptchaToken() string {
 | 
						|
	return c.captchaToken
 | 
						|
}
 | 
						|
 | 
						|
// RefreshCaptchaTokenAtLogin 刷新验证码token(登录后)
 | 
						|
func (c *Common) RefreshCaptchaTokenAtLogin(action, userID string) error {
 | 
						|
	metas := map[string]string{
 | 
						|
		"client_version": c.ClientVersion,
 | 
						|
		"package_name":   c.PackageName,
 | 
						|
		"user_id":        userID,
 | 
						|
	}
 | 
						|
	metas["timestamp"], metas["captcha_sign"] = c.GetCaptchaSign()
 | 
						|
	return c.refreshCaptchaToken(action, metas)
 | 
						|
}
 | 
						|
 | 
						|
// RefreshCaptchaTokenInLogin 刷新验证码token(登录时)
 | 
						|
func (c *Common) RefreshCaptchaTokenInLogin(action, username string) error {
 | 
						|
	metas := make(map[string]string)
 | 
						|
	if ok, _ := regexp.MatchString(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`, username); ok {
 | 
						|
		metas["email"] = username
 | 
						|
	} else if len(username) >= 11 && len(username) <= 18 {
 | 
						|
		metas["phone_number"] = username
 | 
						|
	} else {
 | 
						|
		metas["username"] = username
 | 
						|
	}
 | 
						|
	return c.refreshCaptchaToken(action, metas)
 | 
						|
}
 | 
						|
 | 
						|
// GetCaptchaSign 获取验证码签名
 | 
						|
func (c *Common) GetCaptchaSign() (timestamp, sign string) {
 | 
						|
	if len(c.Algorithms) == 0 {
 | 
						|
		return c.Timestamp, c.CaptchaSign
 | 
						|
	}
 | 
						|
	timestamp = fmt.Sprint(time.Now().UnixMilli())
 | 
						|
	str := fmt.Sprint(c.ClientID, c.ClientVersion, c.PackageName, c.DeviceID, timestamp)
 | 
						|
	for _, algorithm := range c.Algorithms {
 | 
						|
		str = utils.GetMD5EncodeStr(str + algorithm)
 | 
						|
	}
 | 
						|
	sign = "1." + str
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// 刷新验证码token
 | 
						|
func (c *Common) refreshCaptchaToken(action string, metas map[string]string) error {
 | 
						|
	param := CaptchaTokenRequest{
 | 
						|
		Action:       action,
 | 
						|
		CaptchaToken: c.captchaToken,
 | 
						|
		ClientID:     c.ClientID,
 | 
						|
		DeviceID:     c.DeviceID,
 | 
						|
		Meta:         metas,
 | 
						|
		RedirectUri:  "xlaccsdk01://xunlei.com/callback?state=harbor",
 | 
						|
	}
 | 
						|
	var e ErrResp
 | 
						|
	var resp CaptchaTokenResponse
 | 
						|
	_, err := c.Request(XLUSER_API_URL+"/shield/captcha/init", http.MethodPost, func(req *resty.Request) {
 | 
						|
		req.SetError(&e).SetBody(param)
 | 
						|
	}, &resp)
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if e.IsError() {
 | 
						|
		return &e
 | 
						|
	}
 | 
						|
 | 
						|
	if resp.Url != "" {
 | 
						|
		return fmt.Errorf(`need verify: <a target="_blank" href="%s">Click Here</a>`, resp.Url)
 | 
						|
	}
 | 
						|
 | 
						|
	if resp.CaptchaToken == "" {
 | 
						|
		return fmt.Errorf("empty captchaToken")
 | 
						|
	}
 | 
						|
 | 
						|
	if c.refreshCTokenCk != nil {
 | 
						|
		c.refreshCTokenCk(resp.CaptchaToken)
 | 
						|
	}
 | 
						|
	c.SetCaptchaToken(resp.CaptchaToken)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Request 只有基础信息的请求
 | 
						|
func (c *Common) Request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
 | 
						|
	req := c.client.R().SetHeaders(map[string]string{
 | 
						|
		"user-agent":       c.UserAgent,
 | 
						|
		"accept":           "application/json;charset=UTF-8",
 | 
						|
		"x-device-id":      c.DeviceID,
 | 
						|
		"x-client-id":      c.ClientID,
 | 
						|
		"x-client-version": c.ClientVersion,
 | 
						|
	})
 | 
						|
 | 
						|
	if callback != nil {
 | 
						|
		callback(req)
 | 
						|
	}
 | 
						|
	if resp != nil {
 | 
						|
		req.SetResult(resp)
 | 
						|
	}
 | 
						|
	res, err := req.Execute(method, url)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	var erron ErrResp
 | 
						|
	utils.Json.Unmarshal(res.Body(), &erron)
 | 
						|
	if erron.IsError() {
 | 
						|
		return nil, &erron
 | 
						|
	}
 | 
						|
 | 
						|
	return res.Body(), nil
 | 
						|
}
 | 
						|
 | 
						|
// 计算文件Gcid
 | 
						|
func getGcid(r io.Reader, size int64) (string, error) {
 | 
						|
	calcBlockSize := func(j int64) int64 {
 | 
						|
		var psize int64 = 0x40000
 | 
						|
		for float64(j)/float64(psize) > 0x200 && psize < 0x200000 {
 | 
						|
			psize = psize << 1
 | 
						|
		}
 | 
						|
		return psize
 | 
						|
	}
 | 
						|
 | 
						|
	hash1 := sha1.New()
 | 
						|
	hash2 := sha1.New()
 | 
						|
	readSize := calcBlockSize(size)
 | 
						|
	for {
 | 
						|
		hash2.Reset()
 | 
						|
		if n, err := utils.CopyWithBufferN(hash2, r, readSize); err != nil && n == 0 {
 | 
						|
			if err != io.EOF {
 | 
						|
				return "", err
 | 
						|
			}
 | 
						|
			break
 | 
						|
		}
 | 
						|
		hash1.Write(hash2.Sum(nil))
 | 
						|
	}
 | 
						|
	return hex.EncodeToString(hash1.Sum(nil)), nil
 | 
						|
}
 | 
						|
 | 
						|
type CustomTime struct {
 | 
						|
	time.Time
 | 
						|
}
 | 
						|
 | 
						|
const timeFormat = time.RFC3339
 | 
						|
 | 
						|
func (ct *CustomTime) UnmarshalJSON(b []byte) error {
 | 
						|
	str := string(b)
 | 
						|
	if str == `""` {
 | 
						|
		*ct = CustomTime{Time: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	t, err := time.Parse(`"`+timeFormat+`"`, str)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	*ct = CustomTime{Time: t}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// EncryptPassword 超级保险箱 加密
 | 
						|
func EncryptPassword(password string) string {
 | 
						|
	if password == "" {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	// 将字符串转换为字节数组
 | 
						|
	byteData := []byte(password)
 | 
						|
	// 计算MD5哈希值
 | 
						|
	hash := md5.Sum(byteData)
 | 
						|
	// 将哈希值转换为十六进制字符串
 | 
						|
	return hex.EncodeToString(hash[:])
 | 
						|
}
 | 
						|
 | 
						|
func generateDeviceSign(deviceID, packageName string) string {
 | 
						|
 | 
						|
	signatureBase := fmt.Sprintf("%s%s%s%s", deviceID, packageName, "22062", "a5d7416858147a4ab99573872ffccef8")
 | 
						|
 | 
						|
	sha1Hash := sha1.New()
 | 
						|
	sha1Hash.Write([]byte(signatureBase))
 | 
						|
	sha1Result := sha1Hash.Sum(nil)
 | 
						|
 | 
						|
	sha1String := hex.EncodeToString(sha1Result)
 | 
						|
 | 
						|
	md5Hash := md5.New()
 | 
						|
	md5Hash.Write([]byte(sha1String))
 | 
						|
	md5Result := md5Hash.Sum(nil)
 | 
						|
 | 
						|
	md5String := hex.EncodeToString(md5Result)
 | 
						|
 | 
						|
	deviceSign := fmt.Sprintf("div101.%s%s", deviceID, md5String)
 | 
						|
 | 
						|
	return deviceSign
 | 
						|
}
 | 
						|
 | 
						|
func BuildCustomUserAgent(deviceID, appName, sdkVersion, clientVersion, packageName string) string {
 | 
						|
	//deviceSign := generateDeviceSign(deviceID, packageName)
 | 
						|
	var sb strings.Builder
 | 
						|
 | 
						|
	sb.WriteString(fmt.Sprintf("ANDROID-%s/%s ", appName, clientVersion))
 | 
						|
	sb.WriteString("networkType/WIFI ")
 | 
						|
	sb.WriteString(fmt.Sprintf("appid/%s ", "22062"))
 | 
						|
	sb.WriteString(fmt.Sprintf("deviceName/Xiaomi_M2004j7ac "))
 | 
						|
	sb.WriteString(fmt.Sprintf("deviceModel/M2004J7AC "))
 | 
						|
	sb.WriteString(fmt.Sprintf("OSVersion/13 "))
 | 
						|
	sb.WriteString(fmt.Sprintf("protocolVersion/301 "))
 | 
						|
	sb.WriteString(fmt.Sprintf("platformversion/10 "))
 | 
						|
	sb.WriteString(fmt.Sprintf("sdkVersion/%s ", sdkVersion))
 | 
						|
	sb.WriteString(fmt.Sprintf("Oauth2Client/0.9 (Linux 4_9_337-perf-sn-uotan-gd9d488809c3d) (JAVA 0) "))
 | 
						|
	return sb.String()
 | 
						|
}
 |