alist/drivers/pikpak_share/util.go

413 lines
11 KiB
Go
Raw Normal View History

package pikpak_share
import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"errors"
"fmt"
"github.com/alist-org/alist/v3/pkg/utils"
"net/http"
"regexp"
"strings"
"sync"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/go-resty/resty/v2"
)
var AndroidAlgorithms = []string{
"aDhgaSE3MsjROCmpmsWqP1sJdFJ",
"+oaVkqdd8MJuKT+uMr2AYKcd9tdWge3XPEPR2hcePUknd",
"u/sd2GgT2fTytRcKzGicHodhvIltMntA3xKw2SRv7S48OdnaQIS5mn",
"2WZiae2QuqTOxBKaaqCNHCW3olu2UImelkDzBn",
"/vJ3upic39lgmrkX855Qx",
"yNc9ruCVMV7pGV7XvFeuLMOcy1",
"4FPq8mT3JQ1jzcVxMVfwFftLQm33M7i",
"xozoy5e3Ea",
}
var WebAlgorithms = []string{
"C9qPpZLN8ucRTaTiUMWYS9cQvWOE",
"+r6CQVxjzJV6LCV",
"F",
"pFJRC",
"9WXYIDGrwTCz2OiVlgZa90qpECPD6olt",
"/750aCr4lm/Sly/c",
"RB+DT/gZCrbV",
"",
"CyLsf7hdkIRxRm215hl",
"7xHvLi2tOYP0Y92b",
"ZGTXXxu8E/MIWaEDB+Sm/",
"1UI3",
"E7fP5Pfijd+7K+t6Tg/NhuLq0eEUVChpJSkrKxpO",
"ihtqpG6FMt65+Xk+tWUH2",
"NhXXU9rg4XXdzo7u5o",
}
var PCAlgorithms = []string{
"KHBJ07an7ROXDoK7Db",
"G6n399rSWkl7WcQmw5rpQInurc1DkLmLJqE",
"JZD1A3M4x+jBFN62hkr7VDhkkZxb9g3rWqRZqFAAb",
"fQnw/AmSlbbI91Ik15gpddGgyU7U",
"/Dv9JdPYSj3sHiWjouR95NTQff",
"yGx2zuTjbWENZqecNI+edrQgqmZKP",
"ljrbSzdHLwbqcRn",
"lSHAsqCkGDGxQqqwrVu",
"TsWXI81fD1",
"vk7hBjawK/rOSrSWajtbMk95nfgf3",
}
const (
AndroidClientID = "YNxT9w7GMdWvEOKa"
AndroidClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
AndroidClientVersion = "1.48.3"
AndroidPackageName = "com.pikcloud.pikpak"
AndroidSdkVersion = "2.0.4.204101"
WebClientID = "YUMx5nI8ZU8Ap8pm"
WebClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
WebClientVersion = "2.0.0"
WebPackageName = "mypikpak.com"
WebSdkVersion = "8.0.3"
PCClientID = "YvtoWO6GNHiuCl7x"
PCClientSecret = "1NIH5R1IEe2pAxZE3hv3uA"
PCClientVersion = "undefined" // 2.5.6.4831
PCPackageName = "mypikpak.com"
PCSdkVersion = "8.0.3"
)
var DlAddr = []string{
"dl-a10b-0621.mypikpak.com",
"dl-a10b-0622.mypikpak.com",
"dl-a10b-0623.mypikpak.com",
"dl-a10b-0624.mypikpak.com",
"dl-a10b-0625.mypikpak.com",
"dl-a10b-0858.mypikpak.com",
"dl-a10b-0859.mypikpak.com",
"dl-a10b-0860.mypikpak.com",
"dl-a10b-0861.mypikpak.com",
"dl-a10b-0862.mypikpak.com",
"dl-a10b-0863.mypikpak.com",
"dl-a10b-0864.mypikpak.com",
"dl-a10b-0865.mypikpak.com",
"dl-a10b-0866.mypikpak.com",
"dl-a10b-0867.mypikpak.com",
"dl-a10b-0868.mypikpak.com",
"dl-a10b-0869.mypikpak.com",
"dl-a10b-0870.mypikpak.com",
"dl-a10b-0871.mypikpak.com",
"dl-a10b-0872.mypikpak.com",
"dl-a10b-0873.mypikpak.com",
"dl-a10b-0874.mypikpak.com",
"dl-a10b-0875.mypikpak.com",
"dl-a10b-0876.mypikpak.com",
"dl-a10b-0877.mypikpak.com",
"dl-a10b-0878.mypikpak.com",
"dl-a10b-0879.mypikpak.com",
"dl-a10b-0880.mypikpak.com",
"dl-a10b-0881.mypikpak.com",
"dl-a10b-0882.mypikpak.com",
"dl-a10b-0883.mypikpak.com",
"dl-a10b-0884.mypikpak.com",
"dl-a10b-0885.mypikpak.com",
"dl-a10b-0886.mypikpak.com",
"dl-a10b-0887.mypikpak.com",
}
func (d *PikPakShare) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeaders(map[string]string{
"User-Agent": d.GetUserAgent(),
"X-Client-ID": d.GetClientID(),
"X-Device-ID": d.GetDeviceID(),
"X-Captcha-Token": d.GetCaptchaToken(),
})
if callback != nil {
callback(req)
}
if resp != nil {
req.SetResult(resp)
}
var e ErrResp
req.SetError(&e)
res, err := req.Execute(method, url)
if err != nil {
return nil, err
}
switch e.ErrorCode {
case 0:
return res.Body(), nil
case 9: // 验证码token过期
if err = d.RefreshCaptchaToken(GetAction(method, url), ""); err != nil {
return nil, err
}
return d.request(url, method, callback, resp)
case 10: // 操作频繁
return nil, errors.New(e.ErrorDescription)
default:
return nil, errors.New(e.Error())
}
}
func (d *PikPakShare) getSharePassToken() error {
query := map[string]string{
"share_id": d.ShareId,
"pass_code": d.SharePwd,
"thumbnail_size": "SIZE_LARGE",
"limit": "100",
}
var resp ShareResp
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/share", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
return err
}
d.PassCodeToken = resp.PassCodeToken
return nil
}
func (d *PikPakShare) getFiles(id string) ([]File, error) {
res := make([]File, 0)
pageToken := "first"
for pageToken != "" {
if pageToken == "first" {
pageToken = ""
}
query := map[string]string{
"parent_id": id,
"share_id": d.ShareId,
"thumbnail_size": "SIZE_LARGE",
"with_audit": "true",
"limit": "100",
"filters": `{"phase":{"eq":"PHASE_TYPE_COMPLETE"},"trashed":{"eq":false}}`,
"page_token": pageToken,
"pass_code_token": d.PassCodeToken,
}
var resp ShareResp
_, err := d.request("https://api-drive.mypikpak.com/drive/v1/share/detail", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
return nil, err
}
if resp.ShareStatus != "OK" {
if resp.ShareStatus == "PASS_CODE_EMPTY" || resp.ShareStatus == "PASS_CODE_ERROR" {
err = d.getSharePassToken()
if err != nil {
return nil, err
}
return d.getFiles(id)
}
return nil, errors.New(resp.ShareStatusText)
}
pageToken = resp.NextPageToken
res = append(res, resp.Files...)
}
return res, nil
}
func GetAction(method string, url string) string {
urlpath := regexp.MustCompile(`://[^/]+((/[^/\s?#]+)*)`).FindStringSubmatch(url)[1]
return method + ":" + urlpath
}
type Common struct {
client *resty.Client
CaptchaToken string
// 必要值,签名相关
ClientID string
ClientSecret string
ClientVersion string
PackageName string
Algorithms []string
DeviceID string
UserAgent string
// 验证码token刷新成功回调
RefreshCTokenCk func(token string)
LowLatencyAddr string
}
func (c *Common) SetUserAgent(userAgent string) {
c.UserAgent = userAgent
}
func (c *Common) SetCaptchaToken(captchaToken string) {
c.CaptchaToken = captchaToken
}
func (c *Common) SetDeviceID(deviceID string) {
c.DeviceID = deviceID
}
func (c *Common) GetCaptchaToken() string {
return c.CaptchaToken
}
func (c *Common) GetClientID() string {
return c.ClientID
}
func (c *Common) GetUserAgent() string {
return c.UserAgent
}
func (c *Common) GetDeviceID() string {
return c.DeviceID
}
func generateDeviceSign(deviceID, packageName string) string {
signatureBase := fmt.Sprintf("%s%s%s%s", deviceID, packageName, "1", "appkey")
sha1Hash := sha1.New()
sha1Hash.Write([]byte(signatureBase))
sha1Result := sha1Hash.Sum(nil)
sha1String := hex.EncodeToString(sha1Result)
md5Hash := md5.New()
md5Hash.Write([]byte(sha1String))
md5Result := md5Hash.Sum(nil)
md5String := hex.EncodeToString(md5Result)
deviceSign := fmt.Sprintf("div101.%s%s", deviceID, md5String)
return deviceSign
}
func BuildCustomUserAgent(deviceID, clientID, appName, sdkVersion, clientVersion, packageName, userID string) string {
deviceSign := generateDeviceSign(deviceID, packageName)
var sb strings.Builder
sb.WriteString(fmt.Sprintf("ANDROID-%s/%s ", appName, clientVersion))
sb.WriteString("protocolVersion/200 ")
sb.WriteString("accesstype/ ")
sb.WriteString(fmt.Sprintf("clientid/%s ", clientID))
sb.WriteString(fmt.Sprintf("clientversion/%s ", clientVersion))
sb.WriteString("action_type/ ")
sb.WriteString("networktype/WIFI ")
sb.WriteString("sessionid/ ")
sb.WriteString(fmt.Sprintf("deviceid/%s ", deviceID))
sb.WriteString("providername/NONE ")
sb.WriteString(fmt.Sprintf("devicesign/%s ", deviceSign))
sb.WriteString("refresh_token/ ")
sb.WriteString(fmt.Sprintf("sdkversion/%s ", sdkVersion))
sb.WriteString(fmt.Sprintf("datetime/%d ", time.Now().UnixMilli()))
sb.WriteString(fmt.Sprintf("usrno/%s ", userID))
sb.WriteString(fmt.Sprintf("appname/android-%s ", appName))
sb.WriteString(fmt.Sprintf("session_origin/ "))
sb.WriteString(fmt.Sprintf("grant_type/ "))
sb.WriteString(fmt.Sprintf("appid/ "))
sb.WriteString(fmt.Sprintf("clientip/ "))
sb.WriteString(fmt.Sprintf("devicename/Xiaomi_M2004j7ac "))
sb.WriteString(fmt.Sprintf("osversion/13 "))
sb.WriteString(fmt.Sprintf("platformversion/10 "))
sb.WriteString(fmt.Sprintf("accessmode/ "))
sb.WriteString(fmt.Sprintf("devicemodel/M2004J7AC "))
return sb.String()
}
// RefreshCaptchaToken 刷新验证码token
func (d *PikPakShare) RefreshCaptchaToken(action, userID string) error {
metas := map[string]string{
"client_version": d.ClientVersion,
"package_name": d.PackageName,
"user_id": userID,
}
metas["timestamp"], metas["captcha_sign"] = d.Common.GetCaptchaSign()
return d.refreshCaptchaToken(action, metas)
}
// GetCaptchaSign 获取验证码签名
func (c *Common) GetCaptchaSign() (timestamp, sign string) {
timestamp = fmt.Sprint(time.Now().UnixMilli())
str := fmt.Sprint(c.ClientID, c.ClientVersion, c.PackageName, c.DeviceID, timestamp)
for _, algorithm := range c.Algorithms {
str = utils.GetMD5EncodeStr(str + algorithm)
}
sign = "1." + str
return
}
// refreshCaptchaToken 刷新CaptchaToken
func (d *PikPakShare) refreshCaptchaToken(action string, metas map[string]string) error {
param := CaptchaTokenRequest{
Action: action,
CaptchaToken: d.GetCaptchaToken(),
ClientID: d.ClientID,
DeviceID: d.GetDeviceID(),
Meta: metas,
}
var e ErrResp
var resp CaptchaTokenResponse
_, err := d.request("https://user.mypikpak.com/v1/shield/captcha/init", http.MethodPost, func(req *resty.Request) {
req.SetError(&e).SetBody(param)
}, &resp)
if err != nil {
return err
}
if e.IsError() {
return errors.New(e.Error())
}
//if resp.Url != "" {
// return fmt.Errorf(`need verify: <a target="_blank" href="%s">Click Here</a>`, resp.Url)
//}
if d.Common.RefreshCTokenCk != nil {
d.Common.RefreshCTokenCk(resp.CaptchaToken)
}
d.Common.SetCaptchaToken(resp.CaptchaToken)
return nil
}
type AddressLatency struct {
Address string
Latency time.Duration
}
func checkLatency(address string, wg *sync.WaitGroup, ch chan<- AddressLatency) {
defer wg.Done()
start := time.Now()
resp, err := http.Get("https://" + address + "/generate_204")
if err != nil {
ch <- AddressLatency{Address: address, Latency: time.Hour} // Set high latency on error
return
}
defer resp.Body.Close()
latency := time.Since(start)
ch <- AddressLatency{Address: address, Latency: latency}
}
func findLowestLatencyAddress(addresses []string) string {
var wg sync.WaitGroup
ch := make(chan AddressLatency, len(addresses))
for _, address := range addresses {
wg.Add(1)
go checkLatency(address, &wg, ch)
}
wg.Wait()
close(ch)
var lowestLatencyAddress string
lowestLatency := time.Hour
for result := range ch {
if result.Latency < lowestLatency {
lowestLatency = result.Latency
lowestLatencyAddress = result.Address
}
}
return lowestLatencyAddress
}