From dc1bfe53011034dc3bf7401b79bd8ce3594c4cdc Mon Sep 17 00:00:00 2001 From: YangXu <2945065490@qq.com> Date: Mon, 7 Oct 2024 11:05:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(123&123share):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=89=AB=E7=A0=81=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/123/meta.go | 6 +- drivers/123/types.go | 18 ++++ drivers/123/util.go | 78 +++++++++++++++- drivers/123_share/driver.go | 9 +- drivers/123_share/meta.go | 8 +- drivers/123_share/types.go | 18 ++++ drivers/123_share/util.go | 176 ++++++++++++++++++++++++++++++++++-- 7 files changed, 297 insertions(+), 16 deletions(-) diff --git a/drivers/123/meta.go b/drivers/123/meta.go index 961497d42922..4694f5857764 100644 --- a/drivers/123/meta.go +++ b/drivers/123/meta.go @@ -6,8 +6,10 @@ import ( ) type Addition struct { - Username string `json:"username" required:"true"` - Password string `json:"password" required:"true"` + Username string `json:"username"` + Password string `json:"password"` + UseQrCodeLogin bool `json:"use_qr_code_login"` + UniID string `json:"uni_id"` driver.RootID //OrderBy string `json:"order_by" type:"select" options:"file_id,file_name,size,update_at" default:"file_name"` //OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` diff --git a/drivers/123/types.go b/drivers/123/types.go index a8682c52fc95..81738676973e 100644 --- a/drivers/123/types.go +++ b/drivers/123/types.go @@ -121,3 +121,21 @@ type S3PreSignedURLs struct { PreSignedUrls map[string]string `json:"presignedUrls"` } `json:"data"` } + +type QrCodeGenerateResp struct { + Data struct { + UniID string `json:"uniID"` + Url string `json:"url"` + } `json:"data"` +} + +type QrCodeResultResp struct { + Data struct { + Expire time.Time `json:"expire"` + LoginType int `json:"login_type"` + RefreshTokenExpireTime int `json:"refresh_token_expire_time"` + Token string `json:"token"` + LoginStatus int `json:"loginStatus"` + ScanPlatform int `json:"scanPlatform"` + } `json:"data"` +} diff --git a/drivers/123/util.go b/drivers/123/util.go index c04dddbe5904..13c3c7de0c37 100644 --- a/drivers/123/util.go +++ b/drivers/123/util.go @@ -2,9 +2,12 @@ package _123 import ( "context" + "encoding/base64" "errors" "fmt" + "github.com/alist-org/alist/v3/internal/op" "github.com/google/uuid" + "github.com/skip2/go-qrcode" "hash/crc32" "math" "math/rand" @@ -45,6 +48,8 @@ const ( UploadCompleteV2 = MainApi + "/file/upload_complete/v2" S3Complete = MainApi + "/file/s3_complete_multipart_upload" //AuthKeySalt = "8-8D$sL8gPjom7bk#cY" + QrcodeGenerate = MainApi + "/user/qr-code/generate" + QrcodeResult = MainApi + "/user/qr-code/result" ) const ( @@ -171,6 +176,69 @@ func (d *Pan123) login() error { // return &authKey, nil //} +func (d *Pan123) loginByQrCode() error { + if d.Addition.UniID == "" { + uniID, err := d.generateQrCode() + if uniID == "" && err != nil { + return err + } else { + // 保存 uniID 用于 二维码登录 + d.Addition.UniID = uniID + op.MustSaveDriverStorage(d) + return err + } + } else { + token, err := d.getTokenByUniID() + if token == "" && err != nil { + return err + } else { + d.Addition.AccessToken = token + op.MustSaveDriverStorage(d) + return err + } + } +} + +func (d *Pan123) generateQrCode() (string, error) { + var resp QrCodeGenerateResp + _, err := d.request(QrcodeGenerate, http.MethodGet, nil, &resp) + if err != nil { + return "", err + } + // 拼接二维码链接 + qrUrl := fmt.Sprintf(resp.Data.Url+"?uniID=%s", resp.Data.UniID+"&source=123pan&type=login") + // 生成二维码 + qrBytes, _ := qrcode.Encode(qrUrl, qrcode.Medium, 256) + base64Bytes := base64.StdEncoding.EncodeToString(qrBytes) + // 展示二维码 + qrTemplate := ` +
+ + Or Click Here + ` + qrPage := fmt.Sprintf(qrTemplate, base64Bytes, qrUrl) + return resp.Data.UniID, fmt.Errorf("need verify: \n%s", qrPage) +} + +func (d *Pan123) getTokenByUniID() (string, error) { + var resp QrCodeResultResp + _, err := d.request(QrcodeResult, http.MethodGet, func(req *resty.Request) { + req.SetQueryParam("uniID", d.Addition.UniID) + }, &resp) + if err != nil { + return "", err + } + + if resp.Data.LoginStatus == 4 { + return "", errors.New("uniID expired") + } else if resp.Data.Token == "" && resp.Data.LoginStatus == 0 { + return "", errors.New("wait for scan qrcode") + } + + return resp.Data.Token, nil + +} + func (d *Pan123) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { req := base.RestyClient.R() req.SetHeaders(map[string]string{ @@ -213,13 +281,19 @@ func (d *Pan123) request(url string, method string, callback base.ReqCallback, r } body := res.Body() code := utils.Json.Get(body, "code").ToInt() - if code != 0 { - if code == 401 { + if code != 0 && code != 200 { + if code == 401 && d.Addition.UseQrCodeLogin == false { err := d.login() if err != nil { return nil, err } return d.request(url, method, callback, resp) + } else if code == 401 && d.Addition.UseQrCodeLogin == true { + err := d.loginByQrCode() + if err != nil { + return nil, err + } + return d.request(url, method, callback, resp) } return nil, errors.New(jsoniter.Get(body, "message").ToString()) } diff --git a/drivers/123_share/driver.go b/drivers/123_share/driver.go index 17f210937389..7f8b2541fca4 100644 --- a/drivers/123_share/driver.go +++ b/drivers/123_share/driver.go @@ -35,8 +35,7 @@ func (d *Pan123Share) GetAddition() driver.Additional { } func (d *Pan123Share) Init(ctx context.Context) error { - // TODO login / refresh token - //op.MustSaveDriverStorage(d) + // TODO refresh token // 拼接UserAgent if d.PlatformType == "android" { d.params.UserAgent = AndroidUserAgentPrefix + "(" + d.OsVersion + ";" + d.DeviceName + " " + d.DeiveType + ")" @@ -56,10 +55,14 @@ func (d *Pan123Share) Init(ctx context.Context) error { d.params.DeviceName = d.DeviceName d.params.DeviceType = d.DeiveType - return nil + _, err := d.request(UserInfo, http.MethodGet, nil, nil) + return err } func (d *Pan123Share) Drop(ctx context.Context) error { + _, _ = d.request(Logout, http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{}) + }, nil) return nil } diff --git a/drivers/123_share/meta.go b/drivers/123_share/meta.go index bb7f259f631c..7da542395ddd 100644 --- a/drivers/123_share/meta.go +++ b/drivers/123_share/meta.go @@ -6,8 +6,12 @@ import ( ) type Addition struct { - ShareKey string `json:"sharekey" required:"true"` - SharePwd string `json:"sharepassword"` + Username string `json:"username"` + Password string `json:"password"` + UseQrCodeLogin bool `json:"use_qr_code_login"` + UniID string `json:"uni_id"` + ShareKey string `json:"sharekey" required:"true"` + SharePwd string `json:"sharepassword"` driver.RootID //OrderBy string `json:"order_by" type:"select" options:"file_name,size,update_at" default:"file_name"` //OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` diff --git a/drivers/123_share/types.go b/drivers/123_share/types.go index e8ca9e774406..af26c5d034f2 100644 --- a/drivers/123_share/types.go +++ b/drivers/123_share/types.go @@ -97,3 +97,21 @@ type Files struct { // DownloadUrl string `json:"DownloadUrl"` // } `json:"data"` //} + +type QrCodeGenerateResp struct { + Data struct { + UniID string `json:"uniID"` + Url string `json:"url"` + } `json:"data"` +} + +type QrCodeResultResp struct { + Data struct { + Expire time.Time `json:"expire"` + LoginType int `json:"login_type"` + RefreshTokenExpireTime int `json:"refresh_token_expire_time"` + Token string `json:"token"` + LoginStatus int `json:"loginStatus"` + ScanPlatform int `json:"scanPlatform"` + } `json:"data"` +} diff --git a/drivers/123_share/util.go b/drivers/123_share/util.go index a7e3e5045be4..87a6a89d8460 100644 --- a/drivers/123_share/util.go +++ b/drivers/123_share/util.go @@ -2,9 +2,12 @@ package _123Share import ( "context" + "encoding/base64" "errors" "fmt" + "github.com/alist-org/alist/v3/internal/op" "github.com/google/uuid" + "github.com/skip2/go-qrcode" "hash/crc32" "math" "math/rand" @@ -21,12 +24,17 @@ import ( ) const ( - Api = "https://www.123pan.com/api" - AApi = "https://www.123pan.com/a/api" - BApi = "https://www.123pan.com/b/api" - MainApi = Api - FileList = MainApi + "/share/get" - DownloadInfo = MainApi + "/share/download/info" + Api = "https://www.123pan.com/api" + AApi = "https://www.123pan.com/a/api" + BApi = "https://www.123pan.com/b/api" + MainApi = Api + SignIn = MainApi + "/user/sign_in" + Logout = MainApi + "/user/logout" + FileList = MainApi + "/share/get" + DownloadInfo = MainApi + "/share/download/info" + UserInfo = MainApi + "/user/info" + QrcodeGenerate = MainApi + "/user/qr-code/generate" + QrcodeResult = MainApi + "/user/qr-code/result" //AuthKeySalt = "8-8D$sL8gPjom7bk#cY" ) @@ -53,6 +61,147 @@ type Params struct { XAppVersion string } +func (d *Pan123Share) login() error { + var body base.Json + if utils.IsEmailFormat(d.Username) { + body = base.Json{ + "mail": d.Username, + "password": d.Password, + "type": 2, + } + } else { + body = base.Json{ + "passport": d.Username, + "password": d.Password, + "type": 1, + } + } + + req := base.RestyClient.R() + + req.SetHeaders(map[string]string{ + /* "origin": "https://www.123pan.com", + "referer": "https://www.123pan.com/",*/ + "user-agent": d.params.UserAgent, + "platform": d.params.Platform, + "app-version": d.params.AppVersion, + "osversion": d.params.OsVersion, + "devicetype": d.params.DeviceType, + "devicename": d.params.DeviceName, + "loginuuid": d.params.LoginUuid, + }) + + if d.params.XChannel != "" && d.params.XAppVersion != "" { + req.SetHeaders(map[string]string{ + "x-channel": d.params.XChannel, + "x-app-version": d.params.XAppVersion, + }) + } + + req.SetQueryParam("auth-key", generateAuthKey()) + + res, err := req.SetBody(body).Post(SignIn) + //res, err := base.RestyClient.R(). + // SetHeaders(map[string]string{ + // /* "origin": "https://www.123pan.com", + // "referer": "https://www.123pan.com/",*/ + // "user-agent": d.params.UserAgent, + // "platform": d.params.Platform, + // "app-version": d.params.AppVersion, + // "osversion": d.params.OsVersion, + // "devicetype": d.params.DeviceType, + // "devicename": d.params.DeviceName, + // //"user-agent": base.UserAgent, + // }). + // SetBody(body).Post(SignIn) + if err != nil { + return err + } + if utils.Json.Get(res.Body(), "code").ToInt() != 200 { + err = fmt.Errorf(utils.Json.Get(res.Body(), "message").ToString()) + } else { + d.AccessToken = utils.Json.Get(res.Body(), "data", "token").ToString() + } + return err +} + +//func authKey(reqUrl string) (*string, error) { +// reqURL, err := url.Parse(reqUrl) +// if err != nil { +// return nil, err +// } +// +// nowUnix := time.Now().Unix() +// random := rand.Intn(0x989680) +// +// p4 := fmt.Sprintf("%d|%d|%s|%s|%s|%s", nowUnix, random, reqURL.Path, "web", "3", AuthKeySalt) +// authKey := fmt.Sprintf("%d-%d-%x", nowUnix, random, md5.Sum([]byte(p4))) +// return &authKey, nil +//} + +func (d *Pan123Share) loginByQrCode() error { + if d.Addition.UniID == "" { + uniID, err := d.generateQrCode() + if uniID == "" && err != nil { + return err + } else { + // 保存 uniID 用于 二维码登录 + d.Addition.UniID = uniID + op.MustSaveDriverStorage(d) + return err + } + } else { + token, err := d.getTokenByUniID() + if token == "" && err != nil { + return err + } else { + d.Addition.AccessToken = token + op.MustSaveDriverStorage(d) + return err + } + } +} + +func (d *Pan123Share) generateQrCode() (string, error) { + var resp QrCodeGenerateResp + _, err := d.request(QrcodeGenerate, http.MethodGet, nil, &resp) + if err != nil { + return "", err + } + // 拼接二维码链接 + qrUrl := fmt.Sprintf(resp.Data.Url+"?uniID=%s", resp.Data.UniID+"&source=123pan&type=login") + // 生成二维码 + qrBytes, _ := qrcode.Encode(qrUrl, qrcode.Medium, 256) + base64Bytes := base64.StdEncoding.EncodeToString(qrBytes) + // 展示二维码 + qrTemplate := ` + + + Or Click Here + ` + qrPage := fmt.Sprintf(qrTemplate, base64Bytes, qrUrl) + return resp.Data.UniID, fmt.Errorf("need verify: \n%s", qrPage) +} + +func (d *Pan123Share) getTokenByUniID() (string, error) { + var resp QrCodeResultResp + _, err := d.request(QrcodeResult, http.MethodGet, func(req *resty.Request) { + req.SetQueryParam("uniID", d.Addition.UniID) + }, &resp) + if err != nil { + return "", err + } + + if resp.Data.LoginStatus == 4 { + return "", errors.New("uniID expired") + } else if resp.Data.Token == "" && resp.Data.LoginStatus == 0 { + return "", errors.New("wait for scan qrcode") + } + + return resp.Data.Token, nil + +} + func signPath(path string, os string, version string) (k string, v string) { table := []byte{'a', 'd', 'e', 'f', 'g', 'h', 'l', 'm', 'y', 'i', 'j', 'n', 'o', 'p', 'k', 'q', 'r', 's', 't', 'u', 'b', 'c', 'v', 'w', 's', 'z'} random := fmt.Sprintf("%.f", math.Round(1e7*rand.Float64())) @@ -113,7 +262,20 @@ func (d *Pan123Share) request(url string, method string, callback base.ReqCallba } body := res.Body() code := utils.Json.Get(body, "code").ToInt() - if code != 0 { + if code != 0 && code != 200 { + if code == 401 && d.Addition.UseQrCodeLogin == false { + err := d.login() + if err != nil { + return nil, err + } + return d.request(url, method, callback, resp) + } else if code == 401 && d.Addition.UseQrCodeLogin == true { + err := d.loginByQrCode() + if err != nil { + return nil, err + } + return d.request(url, method, callback, resp) + } return nil, errors.New(jsoniter.Get(body, "message").ToString()) } return body, nil