fix(pikpak_share): add captcha_token generation function (#7045)

pull/7061/head
YangXu 2024-08-22 00:35:14 +08:00 committed by GitHub
parent 18176c659c
commit 489b28bdf7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 316 additions and 46 deletions

View File

@ -2,20 +2,20 @@ package pikpak_share
import ( import (
"context" "context"
"github.com/alist-org/alist/v3/internal/op"
"net/http" "net/http"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils" "github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"golang.org/x/oauth2"
) )
type PikPakShare struct { type PikPakShare struct {
model.Storage model.Storage
Addition Addition
oauth2Token oauth2.TokenSource *Common
PassCodeToken string PassCodeToken string
} }
@ -28,28 +28,45 @@ func (d *PikPakShare) GetAddition() driver.Additional {
} }
func (d *PikPakShare) Init(ctx context.Context) error { func (d *PikPakShare) Init(ctx context.Context) error {
if d.ClientID == "" || d.ClientSecret == "" { if d.Common == nil {
d.ClientID = "YNxT9w7GMdWvEOKa" d.Common = &Common{
d.ClientSecret = "dbw2OtmVEeuUvIptb1Coyg" DeviceID: utils.GetMD5EncodeStr(d.Addition.ShareId + d.Addition.SharePwd + time.Now().String()),
UserAgent: "",
RefreshCTokenCk: func(token string) {
d.Common.CaptchaToken = token
op.MustSaveDriverStorage(d)
},
}
} }
oauth2Config := &oauth2.Config{ if d.Addition.DeviceID != "" {
ClientID: d.ClientID, d.SetDeviceID(d.Addition.DeviceID)
ClientSecret: d.ClientSecret, } else {
Endpoint: oauth2.Endpoint{ d.Addition.DeviceID = d.Common.DeviceID
AuthURL: "https://user.mypikpak.com/v1/auth/signin", op.MustSaveDriverStorage(d)
TokenURL: "https://user.mypikpak.com/v1/auth/token",
AuthStyle: oauth2.AuthStyleInParams,
},
} }
d.oauth2Token = oauth2.ReuseTokenSource(nil, utils.TokenSource(func() (*oauth2.Token, error) { if d.Platform == "android" {
return oauth2Config.PasswordCredentialsToken( d.ClientID = AndroidClientID
context.WithValue(context.Background(), oauth2.HTTPClient, base.HttpClient), d.ClientSecret = AndroidClientSecret
d.Username, d.ClientVersion = AndroidClientVersion
d.Password, d.PackageName = AndroidPackageName
) d.Algorithms = AndroidAlgorithms
})) d.UserAgent = BuildCustomUserAgent(d.GetDeviceID(), AndroidClientID, AndroidPackageName, AndroidSdkVersion, AndroidClientVersion, AndroidPackageName, "")
} else if d.Platform == "web" {
d.ClientID = WebClientID
d.ClientSecret = WebClientSecret
d.ClientVersion = WebClientVersion
d.PackageName = WebPackageName
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"
}
// 获取CaptchaToken
err := d.RefreshCaptchaToken(GetAction(http.MethodGet, "https://api-drive.mypikpak.com/drive/v1/share:batch_file_info"), "")
if err != nil {
return err
}
if d.SharePwd != "" { if d.SharePwd != "" {
return d.getSharePassToken() return d.getSharePassToken()
@ -87,9 +104,14 @@ func (d *PikPakShare) Link(ctx context.Context, file model.Obj, args model.LinkA
downloadUrl := resp.FileInfo.WebContentLink downloadUrl := resp.FileInfo.WebContentLink
if downloadUrl == "" && len(resp.FileInfo.Medias) > 0 { if downloadUrl == "" && len(resp.FileInfo.Medias) > 0 {
downloadUrl = resp.FileInfo.Medias[0].Link.Url // 使用转码后的链接
} if d.Addition.UseTransCodingAddress && len(resp.FileInfo.Medias) > 1 {
downloadUrl = resp.FileInfo.Medias[1].Link.Url
} else {
downloadUrl = resp.FileInfo.Medias[0].Link.Url
}
}
link := model.Link{ link := model.Link{
URL: downloadUrl, URL: downloadUrl,
} }

View File

@ -7,12 +7,11 @@ import (
type Addition struct { type Addition struct {
driver.RootID driver.RootID
Username string `json:"username" required:"true"` ShareId string `json:"share_id" required:"true"`
Password string `json:"password" required:"true"` SharePwd string `json:"share_pwd"`
ShareId string `json:"share_id" required:"true"` Platform string `json:"platform" required:"true" type:"select" options:"android,web"`
SharePwd string `json:"share_pwd"` DeviceID string `json:"device_id" required:"false" default:""`
ClientID string `json:"client_id" required:"true" default:"YNxT9w7GMdWvEOKa"` UseTransCodingAddress bool `json:"use_transcoding_address" required:"true" default:"false"`
ClientSecret string `json:"client_secret" required:"true" default:"dbw2OtmVEeuUvIptb1Coyg"`
} }
var config = driver.Config{ var config = driver.Config{

View File

@ -1,20 +1,16 @@
package pikpak_share package pikpak_share
import ( import (
"fmt"
"strconv" "strconv"
"time" "time"
"github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/internal/model"
) )
type RespErr struct {
ErrorCode int `json:"error_code"`
Error string `json:"error"`
}
type ShareResp struct { type ShareResp struct {
ShareStatus string `json:"share_status"` ShareStatus string `json:"share_status"`
ShareStatusText string `json:"share_status_text"` ShareStatusText string `json:"share_status_text"`
FileInfo File `json:"file_info"` FileInfo File `json:"file_info"`
Files []File `json:"files"` Files []File `json:"files"`
NextPageToken string `json:"next_page_token"` NextPageToken string `json:"next_page_token"`
@ -78,3 +74,32 @@ type Media struct {
IsVisible bool `json:"is_visible"` IsVisible bool `json:"is_visible"`
Category string `json:"category"` Category string `json:"category"`
} }
type CaptchaTokenRequest struct {
Action string `json:"action"`
CaptchaToken string `json:"captcha_token"`
ClientID string `json:"client_id"`
DeviceID string `json:"device_id"`
Meta map[string]string `json:"meta"`
RedirectUri string `json:"redirect_uri"`
}
type CaptchaTokenResponse struct {
CaptchaToken string `json:"captcha_token"`
ExpiresIn int64 `json:"expires_in"`
Url string `json:"url"`
}
type ErrResp struct {
ErrorCode int64 `json:"error_code"`
ErrorMsg string `json:"error"`
ErrorDescription string `json:"error_description"`
}
func (e *ErrResp) IsError() bool {
return e.ErrorCode != 0 || e.ErrorMsg != "" || e.ErrorDescription != ""
}
func (e *ErrResp) Error() string {
return fmt.Sprintf("ErrorCode: %d ,Error: %s ,ErrorDescription: %s ", e.ErrorCode, e.ErrorMsg, e.ErrorDescription)
}

View File

@ -1,21 +1,78 @@
package pikpak_share package pikpak_share
import ( import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"errors" "errors"
"fmt"
"github.com/alist-org/alist/v3/pkg/utils"
"net/http" "net/http"
"regexp"
"strings"
"time"
"github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/drivers/base"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
) )
var AndroidAlgorithms = []string{
"Gez0T9ijiI9WCeTsKSg3SMlx",
"zQdbalsolyb1R/",
"ftOjr52zt51JD68C3s",
"yeOBMH0JkbQdEFNNwQ0RI9T3wU/v",
"BRJrQZiTQ65WtMvwO",
"je8fqxKPdQVJiy1DM6Bc9Nb1",
"niV",
"9hFCW2R1",
"sHKHpe2i96",
"p7c5E6AcXQ/IJUuAEC9W6",
"",
"aRv9hjc9P+Pbn+u3krN6",
"BzStcgE8qVdqjEH16l4",
"SqgeZvL5j9zoHP95xWHt",
"zVof5yaJkPe3VFpadPof",
}
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",
}
const (
AndroidClientID = "YNxT9w7GMdWvEOKa"
AndroidClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
AndroidClientVersion = "1.47.1"
AndroidPackageName = "com.pikcloud.pikpak"
AndroidSdkVersion = "2.0.4.204000"
WebClientID = "YUMx5nI8ZU8Ap8pm"
WebClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
WebClientVersion = "2.0.0"
WebPackageName = "mypikpak.com"
WebSdkVersion = "8.0.3"
)
func (d *PikPakShare) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { func (d *PikPakShare) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R() req := base.RestyClient.R()
req.SetHeaders(map[string]string{
token, err := d.oauth2Token.Token() "User-Agent": d.GetUserAgent(),
if err != nil { "X-Client-ID": d.GetClientID(),
return nil, err "X-Device-ID": d.GetDeviceID(),
} "X-Captcha-Token": d.GetCaptchaToken(),
req.SetAuthScheme(token.TokenType).SetAuthToken(token.AccessToken) })
if callback != nil { if callback != nil {
callback(req) callback(req)
@ -23,16 +80,25 @@ func (d *PikPakShare) request(url string, method string, callback base.ReqCallba
if resp != nil { if resp != nil {
req.SetResult(resp) req.SetResult(resp)
} }
var e RespErr var e ErrResp
req.SetError(&e) req.SetError(&e)
res, err := req.Execute(method, url) res, err := req.Execute(method, url)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if e.ErrorCode != 0 { switch e.ErrorCode {
return nil, errors.New(e.Error) 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())
} }
return res.Body(), nil
} }
func (d *PikPakShare) getSharePassToken() error { func (d *PikPakShare) getSharePassToken() error {
@ -92,3 +158,161 @@ func (d *PikPakShare) getFiles(id string) ([]File, error) {
} }
return res, nil 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)
}
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
}