mirror of https://github.com/Xhofe/alist
283 lines
5.9 KiB
Go
283 lines
5.9 KiB
Go
package xunlei
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"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"
|
|
)
|
|
|
|
// 缓存登录状态
|
|
var userClients sync.Map
|
|
|
|
func GetClient(account *model.Account) *Client {
|
|
if v, ok := userClients.Load(account.Username); ok {
|
|
return v.(*Client)
|
|
}
|
|
|
|
client := &Client{
|
|
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
|
|
}
|
|
|
|
type Client struct {
|
|
*resty.Client
|
|
sync.Mutex
|
|
|
|
clientID string
|
|
clientSecret string
|
|
clientVersion string
|
|
packageName string
|
|
algorithms []string
|
|
userAgent string
|
|
deviceID string
|
|
|
|
captchaToken string
|
|
|
|
token string
|
|
refreshToken string
|
|
userID string
|
|
}
|
|
|
|
// 请求验证码token
|
|
func (c *Client) requestCaptchaToken(action string, meta map[string]string) error {
|
|
param := CaptchaTokenRequest{
|
|
Action: action,
|
|
CaptchaToken: c.captchaToken,
|
|
ClientID: c.clientID,
|
|
DeviceID: c.deviceID,
|
|
Meta: meta,
|
|
RedirectUri: "xlaccsdk01://xunlei.com/callback?state=harbor",
|
|
}
|
|
|
|
var e Erron
|
|
var resp CaptchaTokenResponse
|
|
_, err := c.Client.R().
|
|
SetBody(¶m).
|
|
SetError(&e).
|
|
SetResult(&resp).
|
|
SetHeader("X-Device-Id", c.deviceID).
|
|
SetQueryParam("client_id", c.clientID).
|
|
Post(XLUSER_API_URL + "/shield/captcha/init")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if e.HasError() {
|
|
return &e
|
|
}
|
|
|
|
if resp.Url != "" {
|
|
return fmt.Errorf("need verify:%s", resp.Url)
|
|
}
|
|
|
|
if resp.CaptchaToken == "" {
|
|
return fmt.Errorf("empty captchaToken")
|
|
}
|
|
c.captchaToken = resp.CaptchaToken
|
|
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()
|
|
defer c.Unlock()
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
account.Status = err.Error()
|
|
} else {
|
|
account.Status = "work"
|
|
}
|
|
model.SaveAccount(account)
|
|
}()
|
|
|
|
meta := make(map[string]string)
|
|
if strings.Contains(account.Username, "@") {
|
|
meta["email"] = account.Username
|
|
} else if len(account.Username) >= 11 {
|
|
if !strings.Contains(account.Username, "+") {
|
|
account.Username = "+86 " + account.Username
|
|
}
|
|
meta["phone_number"] = account.Username
|
|
} else {
|
|
meta["username"] = account.Username
|
|
}
|
|
|
|
url := XLUSER_API_URL + "/auth/signin"
|
|
err = c.requestCaptchaToken(getAction(http.MethodPost, url), meta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var e Erron
|
|
var resp TokenResponse
|
|
_, err = c.Client.R().
|
|
SetResult(&resp).
|
|
SetError(&e).
|
|
SetBody(&SignInRequest{
|
|
CaptchaToken: c.captchaToken,
|
|
ClientID: c.clientID,
|
|
ClientSecret: c.clientSecret,
|
|
Username: account.Username,
|
|
Password: account.Password,
|
|
}).
|
|
SetHeader("X-Device-Id", c.deviceID).
|
|
SetQueryParam("client_id", c.clientID).
|
|
Post(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if e.HasError() {
|
|
return &e
|
|
}
|
|
|
|
if resp.RefreshToken == "" {
|
|
return base.ErrEmptyToken
|
|
}
|
|
|
|
c.token = resp.Token()
|
|
c.refreshToken = resp.RefreshToken
|
|
c.userID = resp.UserID
|
|
return nil
|
|
}
|
|
|
|
// 刷新验证码token
|
|
func (c *Client) RefreshCaptchaToken(action string) error {
|
|
c.Lock()
|
|
defer c.Unlock()
|
|
|
|
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
|
|
func (c *Client) RefreshToken() error {
|
|
c.Lock()
|
|
defer c.Unlock()
|
|
|
|
var e Erron
|
|
var resp TokenResponse
|
|
_, err := c.Client.R().
|
|
SetError(&e).
|
|
SetResult(&resp).
|
|
SetBody(&base.Json{
|
|
"grant_type": "refresh_token",
|
|
"refresh_token": c.refreshToken,
|
|
"client_id": c.clientID,
|
|
"client_secret": c.clientSecret,
|
|
}).
|
|
SetHeader("X-Device-Id", c.deviceID).
|
|
SetQueryParam("client_id", c.clientID).
|
|
Post(XLUSER_API_URL + "/auth/token")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) Request(method string, url string, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
|
|
c.Lock()
|
|
req := c.Client.R().
|
|
SetHeaders(map[string]string{
|
|
"X-Device-Id": c.deviceID,
|
|
"Authorization": c.token,
|
|
"X-Captcha-Token": c.captchaToken,
|
|
"User-Agent": c.userAgent,
|
|
"client_id": c.clientID,
|
|
}).
|
|
SetQueryParam("client_id", c.clientID)
|
|
if callback != nil {
|
|
callback(req)
|
|
}
|
|
c.Unlock()
|
|
|
|
res, err := req.Execute(method, url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Debug(res.String())
|
|
|
|
var e Erron
|
|
if err = utils.Json.Unmarshal(res.Body(), &e); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 处理错误
|
|
switch e.ErrorCode {
|
|
case 0:
|
|
return res, nil
|
|
case 4122, 4121, 10: // token过期
|
|
if err = c.RefreshToken(); err == nil {
|
|
break
|
|
}
|
|
fallthrough
|
|
case 16: // 登录失效
|
|
if err = c.Login(account); err != nil {
|
|
return nil, err
|
|
}
|
|
case 9: // 验证码token过期
|
|
if err = c.RefreshCaptchaToken(getAction(method, url)); err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, &e
|
|
}
|
|
return c.Request(method, url, callback, account)
|
|
}
|
|
|
|
func (c *Client) UpdateCaptchaToken(captchaToken string) bool {
|
|
c.Lock()
|
|
defer c.Unlock()
|
|
|
|
if captchaToken != "" {
|
|
c.captchaToken = captchaToken
|
|
return true
|
|
}
|
|
return false
|
|
}
|