mirror of
https://github.com/nianzhibai/91.git
synced 2026-06-15 00:44:30 +08:00
3506328441
Add PikPak backend driver, fixed tag matching, cached transcode playback, fast cover handling, and LF normalization.
284 lines
8.2 KiB
Go
284 lines
8.2 KiB
Go
package pikpak
|
|
|
|
import (
|
|
"context"
|
|
"crypto/md5"
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var androidAlgorithms = []string{
|
|
"SOP04dGzk0TNO7t7t9ekDbAmx+eq0OI1ovEx",
|
|
"nVBjhYiND4hZ2NCGyV5beamIr7k6ifAsAbl",
|
|
"Ddjpt5B/Cit6EDq2a6cXgxY9lkEIOw4yC1GDF28KrA",
|
|
"VVCogcmSNIVvgV6U+AochorydiSymi68YVNGiz",
|
|
"u5ujk5sM62gpJOsB/1Gu/zsfgfZO",
|
|
"dXYIiBOAHZgzSruaQ2Nhrqc2im",
|
|
"z5jUTBSIpBN9g4qSJGlidNAutX6",
|
|
"KJE2oveZ34du/g1tiimm",
|
|
}
|
|
|
|
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",
|
|
}
|
|
|
|
func (d *Driver) applyPlatformDefaults() {
|
|
switch d.platform {
|
|
case "android":
|
|
d.clientID = "YNxT9w7GMdWvEOKa"
|
|
d.clientSecret = "dbw2OtmVEeuUvIptb1Coyg"
|
|
d.clientVersion = "1.53.2"
|
|
d.packageName = "com.pikcloud.pikpak"
|
|
d.algorithms = androidAlgorithms
|
|
d.userAgent = buildAndroidUserAgent(d.deviceID, d.clientID, d.packageName, "2.0.6.206003", d.clientVersion, d.packageName, d.userID)
|
|
case "pc":
|
|
d.clientID = "YvtoWO6GNHiuCl7x"
|
|
d.clientSecret = "1NIH5R1IEe2pAxZE3hv3uA"
|
|
d.clientVersion = "undefined"
|
|
d.packageName = "mypikpak.com"
|
|
d.algorithms = pcAlgorithms
|
|
d.userAgent = "MainWindow Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) PikPak/2.6.11.4955 Chrome/100.0.4896.160 Electron/18.3.15 Safari/537.36"
|
|
default:
|
|
d.platform = "web"
|
|
d.clientID = "YUMx5nI8ZU8Ap8pm"
|
|
d.clientSecret = "dbw2OtmVEeuUvIptb1Coyg"
|
|
d.clientVersion = "2.0.0"
|
|
d.packageName = "mypikpak.com"
|
|
d.algorithms = webAlgorithms
|
|
d.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
|
|
}
|
|
}
|
|
|
|
func (d *Driver) login(ctx context.Context) error {
|
|
if d.username == "" || d.password == "" {
|
|
return fmt.Errorf("pikpak username or password is empty")
|
|
}
|
|
if d.captchaToken == "" {
|
|
if err := d.refreshCaptchaTokenInLogin(ctx, getAction(http.MethodPost, signinURL), d.username); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var out authResp
|
|
var e errResp
|
|
res, err := d.client.R().
|
|
SetContext(ctx).
|
|
SetError(&e).
|
|
SetResult(&out).
|
|
SetQueryParam("client_id", d.clientID).
|
|
SetBody(map[string]any{
|
|
"captcha_token": d.captchaToken,
|
|
"client_id": d.clientID,
|
|
"client_secret": d.clientSecret,
|
|
"username": d.username,
|
|
"password": d.password,
|
|
}).
|
|
Post(signinURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if e.isError() {
|
|
return &e
|
|
}
|
|
if res.IsError() {
|
|
return fmt.Errorf("pikpak signin http %d: %s", res.StatusCode(), string(res.Body()))
|
|
}
|
|
d.applyAuth(out)
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) refresh(ctx context.Context, refreshToken string) error {
|
|
if refreshToken == "" {
|
|
return fmt.Errorf("pikpak refresh_token is empty")
|
|
}
|
|
var out authResp
|
|
var e errResp
|
|
res, err := d.client.R().
|
|
SetContext(ctx).
|
|
SetHeader("User-Agent", "").
|
|
SetError(&e).
|
|
SetResult(&out).
|
|
SetQueryParam("client_id", d.clientID).
|
|
SetBody(map[string]any{
|
|
"client_id": d.clientID,
|
|
"client_secret": d.clientSecret,
|
|
"grant_type": "refresh_token",
|
|
"refresh_token": refreshToken,
|
|
}).
|
|
Post(tokenURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if e.isError() {
|
|
if e.ErrorCode == 4126 && d.username != "" && d.password != "" {
|
|
return d.login(ctx)
|
|
}
|
|
return &e
|
|
}
|
|
if res.IsError() {
|
|
return fmt.Errorf("pikpak refresh http %d: %s", res.StatusCode(), string(res.Body()))
|
|
}
|
|
d.applyAuth(out)
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) applyAuth(out authResp) {
|
|
d.accessToken = out.AccessToken
|
|
d.refreshToken = out.RefreshToken
|
|
d.userID = out.Sub
|
|
if d.platform == "android" {
|
|
d.userAgent = buildAndroidUserAgent(d.deviceID, d.clientID, d.packageName, "2.0.6.206003", d.clientVersion, d.packageName, d.userID)
|
|
}
|
|
d.persistTokens()
|
|
}
|
|
|
|
func (d *Driver) persistTokens() {
|
|
if d.onTokenUpdate != nil {
|
|
d.onTokenUpdate(d.accessToken, d.refreshToken, d.captchaToken, d.deviceID)
|
|
}
|
|
}
|
|
|
|
func (d *Driver) refreshCaptchaTokenAtLogin(ctx context.Context, action, userID string) error {
|
|
timestamp, sign := d.captchaSign()
|
|
return d.refreshCaptchaToken(ctx, action, map[string]string{
|
|
"client_version": d.clientVersion,
|
|
"package_name": d.packageName,
|
|
"user_id": userID,
|
|
"timestamp": timestamp,
|
|
"captcha_sign": sign,
|
|
})
|
|
}
|
|
|
|
func (d *Driver) refreshCaptchaTokenInLogin(ctx context.Context, action, username string) error {
|
|
meta := make(map[string]string)
|
|
if ok, _ := regexp.MatchString(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`, username); ok {
|
|
meta["email"] = username
|
|
} else if len(username) >= 11 && len(username) <= 18 {
|
|
meta["phone_number"] = username
|
|
} else {
|
|
meta["username"] = username
|
|
}
|
|
return d.refreshCaptchaToken(ctx, action, meta)
|
|
}
|
|
|
|
func (d *Driver) refreshCaptchaToken(ctx context.Context, action string, meta map[string]string) error {
|
|
var e errResp
|
|
var out captchaTokenResponse
|
|
req := d.client.R().
|
|
SetContext(ctx).
|
|
SetHeader("User-Agent", d.userAgent).
|
|
SetHeader("X-Device-ID", d.deviceID).
|
|
SetError(&e).
|
|
SetResult(&out).
|
|
SetQueryParam("client_id", d.clientID).
|
|
SetBody(captchaTokenRequest{
|
|
Action: action,
|
|
CaptchaToken: d.captchaToken,
|
|
ClientID: d.clientID,
|
|
DeviceID: d.deviceID,
|
|
Meta: meta,
|
|
RedirectURI: "xlaccsdk01://xbase.cloud/callback?state=harbor",
|
|
})
|
|
if d.accessToken != "" {
|
|
req.SetHeader("Authorization", "Bearer "+d.accessToken)
|
|
}
|
|
res, err := req.Post(captchaInitURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if e.isError() {
|
|
return &e
|
|
}
|
|
if res.IsError() {
|
|
return fmt.Errorf("pikpak captcha http %d: %s", res.StatusCode(), string(res.Body()))
|
|
}
|
|
if out.URL != "" {
|
|
return fmt.Errorf("pikpak captcha verification required: %s", out.URL)
|
|
}
|
|
d.captchaToken = out.CaptchaToken
|
|
d.persistTokens()
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) captchaSign() (timestamp, sign string) {
|
|
timestamp = fmt.Sprint(time.Now().UnixMilli())
|
|
raw := fmt.Sprint(d.clientID, d.clientVersion, d.packageName, d.deviceID, timestamp)
|
|
for _, algorithm := range d.algorithms {
|
|
raw = md5Hex(raw + algorithm)
|
|
}
|
|
return timestamp, "1." + raw
|
|
}
|
|
|
|
func getAction(method, rawURL string) string {
|
|
match := regexp.MustCompile(`://[^/]+((/[^/\s?#]+)*)`).FindStringSubmatch(rawURL)
|
|
if len(match) < 2 {
|
|
return method + ":" + rawURL
|
|
}
|
|
return method + ":" + match[1]
|
|
}
|
|
|
|
func generateDeviceSign(deviceID, packageName string) string {
|
|
signatureBase := fmt.Sprintf("%s%s%s%s", deviceID, packageName, "1", "appkey")
|
|
sha1Hash := sha1.Sum([]byte(signatureBase))
|
|
md5Hash := md5.Sum([]byte(hex.EncodeToString(sha1Hash[:])))
|
|
return fmt.Sprintf("div101.%s%s", deviceID, hex.EncodeToString(md5Hash[:]))
|
|
}
|
|
|
|
func buildAndroidUserAgent(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 accesstype/ ")
|
|
sb.WriteString(fmt.Sprintf("clientid/%s ", clientID))
|
|
sb.WriteString(fmt.Sprintf("clientversion/%s ", clientVersion))
|
|
sb.WriteString("action_type/ networktype/WIFI 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("session_origin/ grant_type/ appid/ clientip/ ")
|
|
sb.WriteString("devicename/Xiaomi_M2004j7ac osversion/13 platformversion/10 accessmode/ devicemodel/M2004J7AC ")
|
|
return sb.String()
|
|
}
|
|
|
|
func md5Hex(raw string) string {
|
|
sum := md5.Sum([]byte(raw))
|
|
return hex.EncodeToString(sum[:])
|
|
}
|