mirror of https://github.com/Xhofe/alist
289 lines
6.8 KiB
Go
289 lines
6.8 KiB
Go
package xunlei
|
|
|
|
import (
|
|
"fmt"
|
|
"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 xunleiClient = resty.New().SetTimeout(120 * time.Second)
|
|
|
|
// 一个账户只允许登陆一次
|
|
var userStateCache = struct {
|
|
sync.Mutex
|
|
States map[string]*State
|
|
}{States: make(map[string]*State)}
|
|
|
|
func GetState(account *model.Account) *State {
|
|
userStateCache.Lock()
|
|
defer userStateCache.Unlock()
|
|
if v, ok := userStateCache.States[account.Username]; ok && v != nil {
|
|
return v
|
|
}
|
|
state := new(State).Init()
|
|
userStateCache.States[account.Username] = state
|
|
return state
|
|
}
|
|
|
|
type State struct {
|
|
sync.Mutex
|
|
captchaToken string
|
|
captchaTokenExpiresTime int64
|
|
|
|
tokenType string
|
|
accessToken string
|
|
refreshToken string
|
|
tokenExpiresTime int64 //Milli
|
|
|
|
userID string
|
|
}
|
|
|
|
func (s *State) init() *State {
|
|
s.captchaToken = ""
|
|
s.captchaTokenExpiresTime = 0
|
|
s.tokenType = ""
|
|
s.accessToken = ""
|
|
s.refreshToken = ""
|
|
s.tokenExpiresTime = 0
|
|
s.userID = "0"
|
|
return s
|
|
}
|
|
|
|
func (s *State) getToken(account *model.Account) (string, error) {
|
|
if s.isTokensExpires() {
|
|
if err := s.refreshToken_(account); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
return fmt.Sprint(s.tokenType, " ", s.accessToken), nil
|
|
}
|
|
|
|
func (s *State) getCaptchaToken(action string, account *model.Account) (string, error) {
|
|
if s.isCaptchaTokenExpires() {
|
|
return s.newCaptchaToken(action, nil, account)
|
|
}
|
|
return s.captchaToken, nil
|
|
}
|
|
|
|
func (s *State) isCaptchaTokenExpires() bool {
|
|
return time.Now().UnixMilli() >= s.captchaTokenExpiresTime || s.captchaToken == "" || s.tokenType == ""
|
|
}
|
|
|
|
func (s *State) isTokensExpires() bool {
|
|
return time.Now().UnixMilli() >= s.tokenExpiresTime || s.accessToken == ""
|
|
}
|
|
|
|
func (s *State) newCaptchaToken(action string, meta map[string]string, account *model.Account) (string, error) {
|
|
ctime := time.Now().UnixMilli()
|
|
driverID := utils.GetMD5Encode(account.Username)
|
|
creq := CaptchaTokenRequest{
|
|
Action: action,
|
|
CaptchaToken: s.captchaToken,
|
|
ClientID: CLIENT_ID,
|
|
DeviceID: driverID,
|
|
Meta: map[string]string{
|
|
"captcha_sign": captchaSign(driverID, ctime),
|
|
"client_version": CLIENT_VERSION,
|
|
"package_name": PACKAGE_NAME,
|
|
"timestamp": fmt.Sprint(ctime),
|
|
"user_id": s.userID,
|
|
},
|
|
}
|
|
for k, v := range meta {
|
|
creq.Meta[k] = v
|
|
}
|
|
|
|
var e Erron
|
|
var resp CaptchaTokenResponse
|
|
_, err := xunleiClient.R().
|
|
SetHeader("X-Device-Id", driverID).
|
|
SetBody(&creq).
|
|
SetError(&e).
|
|
SetResult(&resp).
|
|
Post("https://xluser-ssl.xunlei.com/v1/shield/captcha/init?client_id=" + CLIENT_ID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if e.ErrorCode != 0 {
|
|
log.Debugf("%+v\n %+v", e, account)
|
|
return "", fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
|
}
|
|
if resp.Url != "" {
|
|
return "", fmt.Errorf("需要验证验证码")
|
|
}
|
|
|
|
s.captchaTokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000
|
|
s.captchaToken = resp.CaptchaToken
|
|
log.Debugf("%+v\n %+v", s.captchaToken, account)
|
|
return s.captchaToken, nil
|
|
}
|
|
|
|
func (s *State) refreshToken_(account *model.Account) error {
|
|
var e Erron
|
|
var resp TokenResponse
|
|
_, err := xunleiClient.R().
|
|
SetResult(&resp).SetError(&e).
|
|
SetBody(&base.Json{
|
|
"grant_type": "refresh_token",
|
|
"refresh_token": s.refreshToken,
|
|
"client_id": CLIENT_ID,
|
|
"client_secret": CLIENT_SECRET,
|
|
}).
|
|
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).SetQueryParam("client_id", CLIENT_ID).
|
|
Post("https://xluser-ssl.xunlei.com/v1/auth/token")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch e.ErrorCode {
|
|
case 4122, 4121:
|
|
return s.login(account)
|
|
case 0:
|
|
s.tokenExpiresTime = (time.Now().UnixMilli() + resp.ExpiresIn*1000) - 30000
|
|
s.tokenType = resp.TokenType
|
|
s.accessToken = resp.AccessToken
|
|
s.refreshToken = resp.RefreshToken
|
|
s.userID = resp.UserID
|
|
return nil
|
|
default:
|
|
log.Debugf("%+v\n %+v", e, account)
|
|
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
|
}
|
|
}
|
|
|
|
func (s *State) login(account *model.Account) error {
|
|
s.init()
|
|
ctime := time.Now().UnixMilli()
|
|
url := "https://xluser-ssl.xunlei.com/v1/auth/signin"
|
|
captchaToken, err := s.newCaptchaToken(getAction("POST", url), map[string]string{"username": account.Username}, account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
signReq := SignInRequest{
|
|
CaptchaToken: captchaToken,
|
|
ClientID: CLIENT_ID,
|
|
ClientSecret: CLIENT_SECRET,
|
|
Username: account.Username,
|
|
Password: account.Password,
|
|
}
|
|
|
|
var e Erron
|
|
var resp TokenResponse
|
|
_, err = xunleiClient.R().
|
|
SetResult(&resp).
|
|
SetError(&e).
|
|
SetBody(&signReq).
|
|
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).
|
|
SetQueryParam("client_id", CLIENT_ID).
|
|
Post(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer model.SaveAccount(account)
|
|
if e.ErrorCode != 0 {
|
|
account.Status = e.Error
|
|
log.Debugf("%+v\n %+v", e, account)
|
|
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
|
}
|
|
account.Status = "work"
|
|
s.tokenExpiresTime = (ctime + resp.ExpiresIn*1000) - 30000
|
|
s.tokenType = resp.TokenType
|
|
s.accessToken = resp.AccessToken
|
|
s.refreshToken = resp.RefreshToken
|
|
s.userID = resp.UserID
|
|
log.Debugf("%+v\n %+v", resp, account)
|
|
return nil
|
|
}
|
|
|
|
func (s *State) Request(method string, url string, body interface{}, resp interface{}, account *model.Account) error {
|
|
s.Lock()
|
|
token, err := s.getToken(account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
captchaToken, err := s.getCaptchaToken(getAction(method, url), account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.Unlock()
|
|
var e Erron
|
|
req := xunleiClient.R().
|
|
SetError(&e).
|
|
SetHeader("X-Device-Id", utils.GetMD5Encode(account.Username)).
|
|
SetHeader("Authorization", token).
|
|
SetHeader("X-Captcha-Token", captchaToken).
|
|
SetQueryParam("client_id", CLIENT_ID)
|
|
|
|
if body != nil {
|
|
req.SetBody(body)
|
|
}
|
|
if resp != nil {
|
|
req.SetResult(resp)
|
|
}
|
|
|
|
switch method {
|
|
case "GET":
|
|
_, err = req.Get(url)
|
|
case "POST":
|
|
_, err = req.Post(url)
|
|
case "DELETE":
|
|
_, err = req.Delete(url)
|
|
case "PATCH":
|
|
_, err = req.Patch(url)
|
|
case "PUT":
|
|
_, err = req.Put(url)
|
|
default:
|
|
return base.ErrNotSupport
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch e.ErrorCode {
|
|
case 0:
|
|
return nil
|
|
case 9:
|
|
s.newCaptchaToken(getAction(method, url), nil, account)
|
|
fallthrough
|
|
case 4122, 4121:
|
|
return s.Request(method, url, body, resp, account)
|
|
default:
|
|
log.Debugf("%+v\n %+v", e, account)
|
|
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
|
}
|
|
}
|
|
|
|
func (s *State) Init() *State {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
return s.init()
|
|
}
|
|
|
|
func (s *State) GetCaptchaToken(action string, account *model.Account) (string, error) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
return s.getCaptchaToken(action, account)
|
|
}
|
|
|
|
func (s *State) GetToken(account *model.Account) (string, error) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
return s.getToken(account)
|
|
}
|
|
|
|
func (s *State) Login(account *model.Account) error {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
return s.login(account)
|
|
}
|