alist/drivers/quark_uc_tv/util.go

212 lines
5.6 KiB
Go

package quark_uc_tv
import (
"context"
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"errors"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
"net/http"
"strconv"
"time"
)
const (
UserAgent = "Mozilla/5.0 (Linux; U; Android 13; zh-cn; M2004J7AC Build/UKQ1.231108.001) AppleWebKit/533.1 (KHTML, like Gecko) Mobile Safari/533.1"
DeviceBrand = "Xiaomi"
Platform = "tv"
DeviceName = "M2004J7AC"
DeviceModel = "M2004J7AC"
BuildDevice = "M2004J7AC"
BuildProduct = "M2004J7AC"
DeviceGpu = "Adreno (TM) 550"
ActivityRect = "{}"
)
func (d *QuarkUCTV) request(ctx context.Context, pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
u := d.conf.api + pathname
tm, token, reqID := d.generateReqSign(method, pathname, d.conf.signKey)
req := base.RestyClient.R()
req.SetContext(ctx)
req.SetHeaders(map[string]string{
"Accept": "application/json, text/plain, */*",
"User-Agent": UserAgent,
"x-pan-tm": tm,
"x-pan-token": token,
"x-pan-client-id": d.conf.clientID,
})
req.SetQueryParams(map[string]string{
"req_id": reqID,
"access_token": d.QuarkUCTVCommon.AccessToken,
"app_ver": d.conf.appVer,
"device_id": d.Addition.DeviceID,
"device_brand": DeviceBrand,
"platform": Platform,
"device_name": DeviceName,
"device_model": DeviceModel,
"build_device": BuildDevice,
"build_product": BuildProduct,
"device_gpu": DeviceGpu,
"activity_rect": ActivityRect,
"channel": d.conf.channel,
})
if callback != nil {
callback(req)
}
if resp != nil {
req.SetResult(resp)
}
var e Resp
req.SetError(&e)
res, err := req.Execute(method, u)
if err != nil {
return nil, err
}
// 判断 是否需要 刷新 access_token
if e.Status == -1 && e.Errno == 10001 {
// token 过期
err = d.getRefreshTokenByTV(ctx, d.Addition.RefreshToken, true)
if err != nil {
return nil, err
}
ctx1, cancelFunc := context.WithTimeout(ctx, 10*time.Second)
defer cancelFunc()
return d.request(ctx1, pathname, method, callback, resp)
}
if e.Status >= 400 || e.Errno != 0 {
return nil, errors.New(e.ErrorInfo)
}
return res.Body(), nil
}
func (d *QuarkUCTV) getLoginCode(ctx context.Context) (string, error) {
// 获取登录二维码
pathname := "/oauth/authorize"
var resp struct {
CommonRsp
QrData string `json:"qr_data"`
QueryToken string `json:"query_token"`
}
_, err := d.request(ctx, pathname, "GET", func(req *resty.Request) {
req.SetQueryParams(map[string]string{
"auth_type": "code",
"client_id": d.conf.clientID,
"scope": "netdisk",
"qrcode": "1",
"qr_width": "460",
"qr_height": "460",
})
}, &resp)
if err != nil {
return "", err
}
// 保存query_token 用于后续登录
if resp.QueryToken != "" {
d.Addition.QueryToken = resp.QueryToken
op.MustSaveDriverStorage(d)
}
return resp.QrData, nil
}
func (d *QuarkUCTV) getCode(ctx context.Context) (string, error) {
// 通过query token获取code
pathname := "/oauth/code"
var resp struct {
CommonRsp
Code string `json:"code"`
}
_, err := d.request(ctx, pathname, "GET", func(req *resty.Request) {
req.SetQueryParams(map[string]string{
"client_id": d.conf.clientID,
"scope": "netdisk",
"query_token": d.Addition.QueryToken,
})
}, &resp)
if err != nil {
return "", err
}
return resp.Code, nil
}
func (d *QuarkUCTV) getRefreshTokenByTV(ctx context.Context, code string, isRefresh bool) error {
pathname := "/token"
_, _, reqID := d.generateReqSign("POST", pathname, d.conf.signKey)
u := d.conf.codeApi + pathname
var resp RefreshTokenAuthResp
body := map[string]string{
"req_id": reqID,
"app_ver": d.conf.appVer,
"device_id": d.Addition.DeviceID,
"device_brand": DeviceBrand,
"platform": Platform,
"device_name": DeviceName,
"device_model": DeviceModel,
"build_device": BuildDevice,
"build_product": BuildProduct,
"device_gpu": DeviceGpu,
"activity_rect": ActivityRect,
"channel": d.conf.channel,
}
if isRefresh {
body["refresh_token"] = code
} else {
body["code"] = code
}
_, err := base.RestyClient.R().
SetHeader("Content-Type", "application/json").
SetBody(body).
SetResult(&resp).
SetContext(ctx).
Post(u)
if err != nil {
return err
}
if resp.Code != 200 {
return errors.New(resp.Message)
}
if resp.Data.RefreshToken != "" {
d.Addition.RefreshToken = resp.Data.RefreshToken
op.MustSaveDriverStorage(d)
d.QuarkUCTVCommon.AccessToken = resp.Data.AccessToken
} else {
return errors.New("refresh token is empty")
}
return nil
}
func (d *QuarkUCTV) isLogin(ctx context.Context) (bool, error) {
_, err := d.request(ctx, "/user", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(map[string]string{
"method": "user_info",
})
}, nil)
return err == nil, err
}
func (d *QuarkUCTV) generateReqSign(method string, pathname string, key string) (string, string, string) {
//timestamp 13位时间戳
timestamp := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
deviceID := d.Addition.DeviceID
if deviceID == "" {
deviceID = utils.GetMD5EncodeStr(timestamp)
d.Addition.DeviceID = deviceID
op.MustSaveDriverStorage(d)
}
// 生成req_id
reqID := md5.Sum([]byte(deviceID + timestamp))
reqIDHex := hex.EncodeToString(reqID[:])
// 生成x_pan_token
tokenData := method + "&" + pathname + "&" + timestamp + "&" + key
xPanToken := sha256.Sum256([]byte(tokenData))
xPanTokenHex := hex.EncodeToString(xPanToken[:])
return timestamp, xPanTokenHex, reqIDHex
}