mirror of
https://github.com/AlistGo/alist.git
synced 2025-04-22 21:04:07 +08:00
chore: Merge pull request #1020 from foxxorcat/dev
fix(xunlei):download link speed limit
This commit is contained in:
commit
427ae56333
@ -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
|
||||
}
|
||||
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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(¶m).
|
||||
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
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user