support QR Code login way

This commit is contained in:
tickstep 2022-01-30 22:10:46 +08:00
parent 7e0d2155a8
commit 04e414b65d
4 changed files with 94 additions and 96 deletions

View File

@ -14,20 +14,18 @@
package command package command
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/tickstep/aliyunpan-api/aliyunpan" "github.com/tickstep/aliyunpan-api/aliyunpan"
"github.com/tickstep/aliyunpan-api/aliyunpan/apierror"
"github.com/tickstep/aliyunpan/cmder" "github.com/tickstep/aliyunpan/cmder"
"github.com/tickstep/aliyunpan/cmder/cmdliner"
"github.com/tickstep/aliyunpan/internal/config" "github.com/tickstep/aliyunpan/internal/config"
"github.com/tickstep/aliyunpan/internal/functions/panlogin"
"github.com/tickstep/library-go/logger" "github.com/tickstep/library-go/logger"
"github.com/tickstep/library-go/requester"
_ "github.com/tickstep/library-go/requester" _ "github.com/tickstep/library-go/requester"
"github.com/urfave/cli" "github.com/urfave/cli"
"time" "time"
) )
func CmdLogin() cli.Command { func CmdLogin() cli.Command {
return cli.Command{ return cli.Command{
Name: "login", Name: "login",
@ -40,41 +38,24 @@ func CmdLogin() cli.Command {
2.直接指定RefreshToken进行登录 2.直接指定RefreshToken进行登录
aliyunpan login -RefreshToken=8B12CBBCE89CA8DFC3445985B63B511B5E7EC7... aliyunpan login -RefreshToken=8B12CBBCE89CA8DFC3445985B63B511B5E7EC7...
3.指定自行搭建的web服务从指定的URL获取Token进行登录 3.使用二维码方式进行登录
aliyunpan login --RefreshTokenUrl "http://your.host.com/aliyunpan/token/refresh" aliyunpan login -QrCode
web服务为GET请求返回的响应体必须是JSON格式要求如下所示data内容即为token
{
"code": "0",
"msg": "ok",
"data": "88771cd41a111521b4471a552bf633ba"
}
`, `,
Category: "阿里云盘账号", Category: "阿里云盘账号",
Before: cmder.ReloadConfigFunc, // 每次进行登录动作的时候需要调用刷新配置 Before: cmder.ReloadConfigFunc, // 每次进行登录动作的时候需要调用刷新配置
After: cmder.SaveConfigFunc, // 登录完成需要调用保存配置 After: cmder.SaveConfigFunc, // 登录完成需要调用保存配置
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
// 优先从web服务获取token
refreshTokenStr := "" refreshTokenStr := ""
if c.String("RefreshTokenUrl") != "" {
refreshTokenUrl := c.String("RefreshTokenUrl")
ts, e := getRefreshTokenFromWebServer(refreshTokenUrl)
if e != nil {
fmt.Println("从web服务获取Token失败")
} else if ts != "" {
fmt.Println("成功从web服务获取Token" + ts)
refreshTokenStr = ts
}
}
if refreshTokenStr == "" { if refreshTokenStr == "" {
refreshTokenStr = c.String("RefreshToken") refreshTokenStr = c.String("RefreshToken")
} }
useQrCode := c.Bool("QrCode")
tokenId := ""
webToken := aliyunpan.WebLoginToken{} webToken := aliyunpan.WebLoginToken{}
refreshToken := "" refreshToken := ""
var err error var err error
refreshToken, webToken, err = RunLogin(refreshTokenStr) tokenId, refreshToken, webToken, err = RunLogin(useQrCode, refreshTokenStr)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return err return err
@ -86,20 +67,21 @@ func CmdLogin() cli.Command {
return nil return nil
} }
cloudUser.RefreshToken = refreshToken cloudUser.RefreshToken = refreshToken
cloudUser.TokenId = tokenId
config.Config.SetActiveUser(cloudUser) config.Config.SetActiveUser(cloudUser)
fmt.Println("阿里云盘登录成功: ", cloudUser.Nickname) fmt.Println("阿里云盘登录成功: ", cloudUser.Nickname)
return nil return nil
}, },
// 命令的附加options参数说明使用 help login 命令即可查看 // 命令的附加options参数说明使用 help panlogin 命令即可查看
Flags: []cli.Flag{ Flags: []cli.Flag{
// aliyunpan login -RefreshToken=8B12CBBCE89CA8DFC3445985B63B511B5E7EC7... // aliyunpan panlogin -RefreshToken=8B12CBBCE89CA8DFC3445985B63B511B5E7EC7...
cli.StringFlag{ cli.StringFlag{
Name: "RefreshToken", Name: "RefreshToken",
Usage: "使用RefreshToken Cookie来登录帐号", Usage: "使用RefreshToken Cookie来登录帐号",
}, },
cli.StringFlag{ cli.BoolFlag{
Name: "RefreshTokenUrl", Name: "QrCode",
Usage: "使用自行搭建的web服务获取RefreshToken来进行登录", Usage: "使用二维码登录",
}, },
}, },
} }
@ -149,44 +131,53 @@ func CmdLogout() cli.Command {
} }
} }
func RunLogin(refreshToken string) (refreshTokenStr string, webToken aliyunpan.WebLoginToken, error error) { func RunLogin(useQrCodeLogin bool, refreshToken string) (tokenId, refreshTokenStr string, webToken aliyunpan.WebLoginToken, error error) {
return cmder.DoLoginHelper(refreshToken) if useQrCodeLogin {
} h := panlogin.NewLoginHelper("http://localhost:8977")
qrCodeUrlResult, err := h.GetQRCodeLoginUrl("")
// getRefreshTokenFromWebServer 从自定义的web服务获取token
func getRefreshTokenFromWebServer(url string) (string, error) {
type tokenResult struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data string `json:"data"`
}
if url == "" {
return "", fmt.Errorf("url is empty")
}
logger.Verboseln("do request url: " + url)
header := map[string]string {
"accept": "application/json, text/plain, */*",
"content-type": "application/json;charset=UTF-8",
"user-agent": "aliyunpan/" + config.AppVersion,
}
// request
client := requester.NewHTTPClient()
client.SetTimeout(10 * time.Second)
client.SetKeepAlive(false)
body, err := client.Fetch("GET", url, nil, header)
if err != nil { if err != nil {
logger.Verboseln("get token error ", err) fmt.Println("二维码登录错误:", err)
return "", err return "", "", aliyunpan.WebLoginToken{}, err
}
fmt.Printf("请在浏览器打开以下链接进行扫码登录链接有效时间为5分钟\n%s\n\n", qrCodeUrlResult.TokenUrl)
// handler waiting
line := cmdliner.NewLiner()
var qrCodeLoginResult *panlogin.QRCodeLoginResult
defer line.Close()
go func() {
for {
time.Sleep(3 * time.Second)
qr, er := h.GetQRCodeLoginResult(qrCodeUrlResult.TokenId)
if er != nil {
continue
}
logger.Verboseln(qr)
if qr.QrCodeStatus == "CONFIRMED" {
// login successfully
qrCodeLoginResult = qr
break
} else if qr.QrCodeStatus == "EXPIRED" {
break
}
}
}()
line.State.Prompt("请在浏览器里面完成扫码登录然后再按Enter键继续...")
if qrCodeLoginResult == nil {
return "", "", aliyunpan.WebLoginToken{}, fmt.Errorf("二维码登录失败")
} }
// parse result tokenStr, er := h.ParseSecureRefreshToken("", qrCodeLoginResult.SecureRefreshToken)
r := &tokenResult{} if er != nil {
if err2 := json.Unmarshal(body, r); err2 != nil { fmt.Println("解析Token错误", er)
logger.Verboseln("parse token info result json error ", err2) return "", "", aliyunpan.WebLoginToken{}, er
return "", apierror.NewFailedApiError(err2.Error())
} }
return r.Data, nil refreshToken = tokenStr
tokenId = qrCodeUrlResult.TokenId
}
refreshTokenStr, webToken, error = cmder.DoLoginHelper(refreshToken)
return
} }

View File

@ -31,7 +31,7 @@ type DriveInfo struct {
type DriveInfoList []*DriveInfo type DriveInfoList []*DriveInfo
func (d DriveInfoList) GetFileDriveId() string { func (d DriveInfoList) GetFileDriveId() string {
for _,drive := range d { for _, drive := range d {
if drive.DriveTag == "File" { if drive.DriveTag == "File" {
return drive.DriveId return drive.DriveId
} }
@ -55,6 +55,7 @@ type PanUser struct {
RefreshToken string `json:"refreshToken"` RefreshToken string `json:"refreshToken"`
WebToken aliyunpan.WebLoginToken `json:"webToken"` WebToken aliyunpan.WebLoginToken `json:"webToken"`
TokenId string `json:"tokenId"`
panClient *aliyunpan.PanClient panClient *aliyunpan.PanClient
cacheOpMap cachemap.CacheOpMap cacheOpMap cachemap.CacheOpMap
@ -83,7 +84,7 @@ doLoginAct:
if err != nil { if err != nil {
if err.Code == apierror.ApiCodeTokenExpiredCode && tryRefreshWebToken { if err.Code == apierror.ApiCodeTokenExpiredCode && tryRefreshWebToken {
tryRefreshWebToken = false tryRefreshWebToken = false
webCookie,_ := aliyunpan.GetAccessTokenFromRefreshToken(webToken.RefreshToken) webCookie, _ := aliyunpan.GetAccessTokenFromRefreshToken(webToken.RefreshToken)
if webCookie != nil { if webCookie != nil {
webToken = webCookie webToken = webCookie
goto doLoginAct goto doLoginAct
@ -171,7 +172,7 @@ func (pu *PanUser) GetSavePath(filePanPath string) string {
} }
func (pu *PanUser) GetDriveByTag(tag string) *DriveInfo { func (pu *PanUser) GetDriveByTag(tag string) *DriveInfo {
for _,item := range pu.DriveList { for _, item := range pu.DriveList {
if item.DriveTag == tag { if item.DriveTag == tag {
return item return item
} }
@ -180,7 +181,7 @@ func (pu *PanUser) GetDriveByTag(tag string) *DriveInfo {
} }
func (pu *PanUser) GetDriveById(id string) *DriveInfo { func (pu *PanUser) GetDriveById(id string) *DriveInfo {
for _,item := range pu.DriveList { for _, item := range pu.DriveList {
if item.DriveId == id { if item.DriveId == id {
return item return item
} }
@ -189,7 +190,7 @@ func (pu *PanUser) GetDriveById(id string) *DriveInfo {
} }
func (pu *PanUser) GetActiveDriveInfo() *DriveInfo { func (pu *PanUser) GetActiveDriveInfo() *DriveInfo {
for _,item := range pu.DriveList { for _, item := range pu.DriveList {
if item.DriveId == pu.ActiveDriveId { if item.DriveId == pu.ActiveDriveId {
return item return item
} }

View File

@ -47,6 +47,9 @@ func NewLoginHelper(webHost string) *LoginHelper {
// GetQRCodeLoginUrl 获取登录二维码链接 // GetQRCodeLoginUrl 获取登录二维码链接
func (h *LoginHelper) GetQRCodeLoginUrl(keyStr string) (*QRCodeUrlResult, error) { func (h *LoginHelper) GetQRCodeLoginUrl(keyStr string) (*QRCodeUrlResult, error) {
if keyStr == "" {
keyStr = ids.GetUniqueId("", 32)
}
fullUrl := strings.Builder{} fullUrl := strings.Builder{}
ipAddr, err := getip.IPInfoFromTechainBaidu() ipAddr, err := getip.IPInfoFromTechainBaidu()
if err != nil { if err != nil {

View File

@ -104,11 +104,11 @@ func ParseVersionNum(versionStr string) int {
versionStr = strings.ReplaceAll(versionStr, "-dev", "") versionStr = strings.ReplaceAll(versionStr, "-dev", "")
versionStr = strings.ReplaceAll(versionStr, "v", "") versionStr = strings.ReplaceAll(versionStr, "v", "")
versionParts := strings.Split(versionStr, ".") versionParts := strings.Split(versionStr, ".")
verNum := parseInt(versionParts[0]) * 1e4 + parseInt(versionParts[1]) * 1e2 + parseInt(versionParts[2]) verNum := parseInt(versionParts[0])*1e4 + parseInt(versionParts[1])*1e2 + parseInt(versionParts[2])
return verNum return verNum
} }
func parseInt(numStr string) int { func parseInt(numStr string) int {
num,e := strconv.Atoi(numStr) num, e := strconv.Atoi(numStr)
if e != nil { if e != nil {
return 0 return 0
} }
@ -117,7 +117,10 @@ func parseInt(numStr string) int {
func ConvertTime(t time.Duration) string { func ConvertTime(t time.Duration) string {
seconds := int64(t.Seconds()) seconds := int64(t.Seconds())
return ConvertTimeSecond(seconds)
}
func ConvertTimeSecond(seconds int64) string {
MT := int64(1 * 60) MT := int64(1 * 60)
HT := int64(1 * 60 * 60) HT := int64(1 * 60 * 60)
@ -128,22 +131,22 @@ func ConvertTime(t time.Duration) string {
return fmt.Sprintf("%d秒", seconds) return fmt.Sprintf("%d秒", seconds)
} }
if seconds >= MT && seconds < HT { if seconds >= MT && seconds < HT {
return fmt.Sprintf("%d分%d秒", seconds / MT, seconds % MT) return fmt.Sprintf("%d分%d秒", seconds/MT, seconds%MT)
} }
if seconds >= HT { if seconds >= HT {
h := seconds / HT h := seconds / HT
tmp := seconds % HT tmp := seconds % HT
return fmt.Sprintf("%d小时%d分%d秒", h, tmp / MT, tmp % MT) return fmt.Sprintf("%d小时%d分%d秒", h, tmp/MT, tmp%MT)
} }
return "0秒" return "0秒"
} }
// HasSuffix 判断是否以某字符串作为结尾 // HasSuffix 判断是否以某字符串作为结尾
func HasSuffix(s ,suffix string) bool { func HasSuffix(s, suffix string) bool {
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
} }
// HasPrefix 判断是否以某字符串作为开始 // HasPrefix 判断是否以某字符串作为开始
func HasPrefix(s, prefix string) bool { func HasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[0:len(prefix)]== prefix return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
} }