chore: Merge pull request #1020 from foxxorcat/dev

fix(xunlei):download link speed limit
This commit is contained in:
Xhofe 2022-05-01 13:25:54 +08:00 committed by GitHub
commit 427ae56333
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 151 additions and 101 deletions

View File

@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/Xhofe/alist/conf"
@ -15,6 +16,7 @@ import (
"github.com/Xhofe/alist/utils"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
)
type XunLeiCloud struct{}
@ -46,11 +48,65 @@ func (driver XunLeiCloud) Items() []base.Item {
Required: true,
Description: "account password",
},
{
Name: "captcha_token",
Label: "verified captcha token",
Type: base.TypeString,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
},
{
Name: "client_version",
Label: "client version",
Default: "7.43.0.7998",
Type: base.TypeString,
Required: true,
},
{
Name: "client_id",
Label: "client id",
Default: "Xp6vsxz_7IYVw2BB",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Default: "Xp6vsy4tN9toTVdMSpomVdXpRmES",
Type: base.TypeString,
Required: true,
},
{
Name: "algorithms",
Label: "algorithms",
Default: "hrVPGbeqYPs+CIscj05VpAtjalzY5yjpvlMS8bEo,DrI0uTP,HHK0VXyMgY0xk2K0o,BBaXsExvL3GadmIacjWv7ISUJp3ifAwqbJumu,5toJ7ejB+bh1,5LsZTFAFjgvFvIl1URBgOAJ,QcJ5Ry+,hYgZVz8r7REROaCYfd9,zw6gXgkk/8TtGrmx6EGfekPESLnbZfDFwqR,gtSwLnMBa8h12nF3DU6+LwEQPHxd,fMG8TvtAYbCkxuEbIm0Xi/Lb7Z",
Type: base.TypeString,
Required: true,
},
{
Name: "package_name",
Label: "package name",
Default: "com.xunlei.downloadprovider",
Type: base.TypeString,
Required: true,
},
{
Name: "user_agent",
Label: "user agent",
Default: "ANDROID-com.xunlei.downloadprovider/7.43.0.7998 netWorkType/WIFI appid/40 deviceName/Samsung_Sm-g9810 deviceModel/SM-G9810 OSVersion/7.1.2 protocolVersion/301 platformVersion/10 sdkVersion/220200 Oauth2Client/0.9 (Linux 4_0_9+) (JAVA 0)",
Type: base.TypeString,
Required: false,
},
{
Name: "device_id",
Label: "device id",
Default: utils.GetMD5Encode(uuid.NewString()),
Type: base.TypeString,
Required: false,
},
}
}
@ -58,10 +114,18 @@ func (driver XunLeiCloud) Save(account *model.Account, old *model.Account) error
if account == nil {
return nil
}
client := GetClient(account)
// 指定验证通过的captchaToken
if client.captchaToken != "" {
client.captchaToken = account.CaptchaToken
account.CaptchaToken = ""
}
if client.token == "" {
return client.Login(account)
}
account.Status = "work"
model.SaveAccount(account)
return nil
@ -105,6 +169,7 @@ func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.Fi
return nil, err
}
time.Sleep(time.Millisecond * 400)
files := make([]model.File, 0)
for {
var fileList FileList
@ -161,7 +226,9 @@ func (driver XunLeiCloud) Link(args base.Args, account *model.Account) (*base.Li
return nil, base.ErrNotFile
}
var lFile Files
_, err = GetClient(account).Request("GET", FILE_API_URL+"/"+file.Id, func(r *resty.Request) {
clinet := GetClient(account)
_, err = clinet.Request("GET", FILE_API_URL+"/{fileID}", func(r *resty.Request) {
r.SetPathParam("fileID", file.Id)
r.SetQueryParam("with_audit", "true")
r.SetResult(&lFile)
}, account)
@ -170,7 +237,7 @@ func (driver XunLeiCloud) Link(args base.Args, account *model.Account) (*base.Li
}
return &base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
{Name: "User-Agent", Value: clinet.userAgent},
},
Url: lFile.WebContentLink,
}, nil
@ -201,7 +268,8 @@ func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account)
if err != nil {
return err
}
_, err = GetClient(account).Request("PATCH", FILE_API_URL+"/"+srcFile.Id, func(r *resty.Request) {
_, err = GetClient(account).Request("PATCH", FILE_API_URL+"/{fileID}", func(r *resty.Request) {
r.SetPathParam("fileID", srcFile.Id)
r.SetBody(&base.Json{"name": filepath.Base(dst)})
}, account)
return err
@ -270,7 +338,8 @@ func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
if err != nil {
return err
}
_, err = GetClient(account).Request("PATCH", FILE_API_URL+"/"+srcFile.Id+"/trash", func(r *resty.Request) {
_, err = GetClient(account).Request("PATCH", FILE_API_URL+"/{fileID}/trash", func(r *resty.Request) {
r.SetPathParam("fileID", srcFile.Id)
r.SetBody(&base.Json{})
}, account)
return err
@ -318,6 +387,7 @@ func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account)
param := resp.Resumable.Params
if resp.UploadType == UPLOAD_TYPE_RESUMABLE {
param.Endpoint = strings.TrimLeft(param.Endpoint, param.Bucket+".")
client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true))
if err != nil {
return err
@ -331,7 +401,6 @@ func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account)
return err
}
}
time.Sleep(time.Millisecond * 200)
return nil
}

View File

@ -12,6 +12,10 @@ type Erron struct {
// ErrorDetails interface{} `json:"error_details"`
}
func (e *Erron) HasError() bool {
return e.ErrorCode != 0 || e.ErrorMsg != "" || e.ErrorDescription != ""
}
func (e *Erron) Error() string {
return fmt.Sprintf("ErrorCode: %d ,Error: %s ,ErrorDescription: %s ", e.ErrorCode, e.ErrorMsg, e.ErrorDescription)
}
@ -25,7 +29,7 @@ type CaptchaTokenRequest struct {
ClientID string `json:"client_id"`
DeviceID string `json:"device_id"`
Meta map[string]string `json:"meta"`
//RedirectUri string `json:"redirect_uri"`
RedirectUri string `json:"redirect_uri"`
}
type CaptchaTokenResponse struct {
@ -173,9 +177,3 @@ type UploadTaskResponse struct {
File Files `json:"file"`
}
type Tasks struct {
Tasks []interface{}
NextPageToken string `json:"next_page_token"`
//ExpiresIn int64 `json:"expires_in"`
}

View File

@ -3,41 +3,10 @@ package xunlei
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"net"
"net/url"
"github.com/Xhofe/alist/utils"
)
const (
// 小米浏览器
CLIENT_ID = "X7MtiU0Gb5YqWv-6"
CLIENT_SECRET = "84MYEih3Eeu2HF4RrGce3Q"
CLIENT_VERSION = "5.1.0.51045"
ALG_VERSION = "1"
PACKAGE_NAME = "com.xunlei.xcloud.lib"
)
var Algorithms = []string{
"",
"BXza40wm+P4zw8rEFpHA",
"UfZLfKfYRmKTA0",
"OMBGVt/9Wcaln1XaBz",
"Jn217F4rk5FPPWyhoeV",
"w5OwkGo0pGpb0Xe/XZ5T3",
"5guM3DNiY4F78x49zQ97q75",
"QXwn4D2j884wJgrYXjGClM/IVrJX",
"NXBRosYvbHIm6w8vEB",
"2kZ8Ie1yW2ib4O2iAkNpJobP",
"11CoVJJQEc",
"xf3QWysVwnVsNv5DCxU+cgNT1rK",
"9eEfKkrqkfw",
"T78dnANexYRbiZy",
}
const (
API_URL = "https://api-pan.xunlei.com/drive/v1"
FILE_API_URL = API_URL + "/files"
@ -45,9 +14,8 @@ const (
)
const (
FOLDER = "drive#folder"
FILE = "drive#file"
FOLDER = "drive#folder"
FILE = "drive#file"
RESUMABLE = "drive#resumable"
)
@ -58,18 +26,9 @@ const (
UPLOAD_TYPE_URL = "UPLOAD_TYPE_URL"
)
// 验证码签名
func captchaSign(driverID string, time int64) string {
str := fmt.Sprint(CLIENT_ID, CLIENT_VERSION, PACKAGE_NAME, driverID, time)
for _, algorithm := range Algorithms {
str = utils.GetMD5Encode(str + algorithm)
}
return ALG_VERSION + "." + str
}
func getAction(method string, u string) string {
c, _ := url.Parse(u)
return fmt.Sprint(method, ":", c.Path)
return method + ":" + c.Path
}
// 计算文件Gcid
@ -102,13 +61,3 @@ func getGcid(r io.Reader, size int64) (string, error) {
}
return hex.EncodeToString(hash1.Sum(nil)), nil
}
// 获取driverID
func getDriverID(username string) string {
interfaces, _ := net.Interfaces()
str := username
for _, inter := range interfaces {
str += inter.HardwareAddr.String()
}
return utils.GetMD5Encode(str)
}

View File

@ -3,6 +3,7 @@ package xunlei
import (
"fmt"
"net/http"
"strings"
"sync"
"time"
@ -13,12 +14,7 @@ import (
log "github.com/sirupsen/logrus"
)
var xunleiClient = resty.New().
SetHeaders(map[string]string{
"Accept": "application/json;charset=UTF-8",
}).
SetTimeout(base.DefaultTimeout)
// 缓存登录状态
var userClients sync.Map
func GetClient(account *model.Account) *Client {
@ -27,8 +23,15 @@ func GetClient(account *model.Account) *Client {
}
client := &Client{
Client: xunleiClient,
driverID: getDriverID(account.Username),
Client: base.RestyClient,
clientID: account.ClientId,
clientSecret: account.ClientSecret,
clientVersion: account.ClientVersion,
packageName: account.PackageName,
algorithms: strings.Split(account.Algorithms, ","),
userAgent: account.UserAgent,
deviceID: account.DeviceId,
}
userClients.Store(account.Username, client)
return client
@ -38,7 +41,14 @@ type Client struct {
*resty.Client
sync.Mutex
driverID string
clientID string
clientSecret string
clientVersion string
packageName string
algorithms []string
userAgent string
deviceID string
captchaToken string
token string
@ -48,25 +58,26 @@ type Client struct {
// 请求验证码token
func (c *Client) requestCaptchaToken(action string, meta map[string]string) error {
req := CaptchaTokenRequest{
param := CaptchaTokenRequest{
Action: action,
CaptchaToken: c.captchaToken,
ClientID: CLIENT_ID,
DeviceID: c.driverID,
ClientID: c.clientID,
DeviceID: c.deviceID,
Meta: meta,
RedirectUri: "xlaccsdk01://xunlei.com/callback?state=harbor",
}
var e Erron
var resp CaptchaTokenResponse
_, err := xunleiClient.R().
SetBody(&req).
_, err := c.Client.R().
SetBody(&param).
SetError(&e).
SetResult(&resp).
Post(XLUSER_API_URL + "/shield/captcha/init")
if err != nil {
return err
}
if e.ErrorCode != 0 || e.ErrorMsg != "" {
if e.HasError() {
return &e
}
@ -81,6 +92,15 @@ func (c *Client) requestCaptchaToken(action string, meta map[string]string) erro
return nil
}
// 验证码签名
func (c *Client) captchaSign(time string) string {
str := fmt.Sprint(c.clientID, c.clientVersion, c.packageName, c.deviceID, time)
for _, algorithm := range c.algorithms {
str = utils.GetMD5Encode(str + algorithm)
}
return "1." + str
}
// 登录
func (c *Client) Login(account *model.Account) (err error) {
c.Lock()
@ -103,13 +123,13 @@ func (c *Client) Login(account *model.Account) (err error) {
var e Erron
var resp TokenResponse
_, err = xunleiClient.R().
_, err = c.Client.R().
SetResult(&resp).
SetError(&e).
SetBody(&SignInRequest{
CaptchaToken: c.captchaToken,
ClientID: CLIENT_ID,
ClientSecret: CLIENT_SECRET,
ClientID: c.clientID,
ClientSecret: c.clientSecret,
Username: account.Username,
Password: account.Password,
}).
@ -118,7 +138,7 @@ func (c *Client) Login(account *model.Account) (err error) {
return err
}
if e.ErrorCode != 0 || e.ErrorMsg != "" {
if e.HasError() {
return &e
}
@ -137,14 +157,15 @@ func (c *Client) RefreshCaptchaToken(action string) error {
c.Lock()
defer c.Unlock()
ctime := time.Now().UnixMilli()
return c.requestCaptchaToken(action, map[string]string{
"captcha_sign": captchaSign(c.driverID, ctime),
"client_version": CLIENT_VERSION,
"package_name": PACKAGE_NAME,
"timestamp": fmt.Sprint(ctime),
timestamp := fmt.Sprint(time.Now().UnixMilli())
param := map[string]string{
"client_version": c.clientVersion,
"package_name": c.packageName,
"user_id": c.userID,
})
"captcha_sign": c.captchaSign(timestamp),
"timestamp": timestamp,
}
return c.requestCaptchaToken(action, param)
}
// 刷新token
@ -154,22 +175,27 @@ func (c *Client) RefreshToken() error {
var e Erron
var resp TokenResponse
_, err := xunleiClient.R().
_, err := c.Client.R().
SetError(&e).
SetResult(&resp).
SetBody(&base.Json{
"grant_type": "refresh_token",
"refresh_token": c.refreshToken,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"client_id": c.clientID,
"client_secret": c.clientSecret,
}).
Post(XLUSER_API_URL + "/auth/token")
if err != nil {
return err
}
if e.ErrorCode != 0 || e.ErrorMsg != "" {
if e.HasError() {
return &e
}
if resp.RefreshToken == "" {
return base.ErrEmptyToken
}
c.token = resp.TokenType + " " + resp.AccessToken
c.refreshToken = resp.RefreshToken
c.userID = resp.UserID
@ -178,13 +204,14 @@ func (c *Client) RefreshToken() error {
func (c *Client) Request(method string, url string, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
c.Lock()
req := xunleiClient.R().
req := c.Client.R().
SetHeaders(map[string]string{
"X-Device-Id": c.driverID,
"X-Device-Id": c.deviceID,
"Authorization": c.token,
"X-Captcha-Token": c.captchaToken,
}).
SetQueryParam("client_id", CLIENT_ID)
"User-Agent": c.userAgent,
"client_id": c.clientID,
})
if callback != nil {
callback(req)
}
@ -205,7 +232,7 @@ func (c *Client) Request(method string, url string, callback func(*resty.Request
switch e.ErrorCode {
case 0:
return res, nil
case 4122, 4121: // token过期
case 4122, 4121, 10: // token过期
if err = c.RefreshToken(); err == nil {
break
}

View File

@ -50,6 +50,13 @@ type Account struct {
CustomHost string `json:"custom_host"`
ExtractFolder string `json:"extract_folder"`
Bool1 bool `json:"bool_1"`
// for xunlei
Algorithms string `json:"algorithms"`
ClientVersion string `json:"client_version"`
PackageName string `json:"package_name"`
UserAgent string `json:"user_agent"`
CaptchaToken string `json:"captcha_token"`
DeviceId string `json:"device_id"`
}
var accountsMap = make(map[string]Account)