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("")
if err != nil {
// getRefreshTokenFromWebServer 从自定义的web服务获取token fmt.Println("二维码登录错误:", err)
func getRefreshTokenFromWebServer(url string) (string, error) { return "", "", aliyunpan.WebLoginToken{}, err
type tokenResult struct { }
Code string `json:"code"` fmt.Printf("请在浏览器打开以下链接进行扫码登录链接有效时间为5分钟\n%s\n\n", qrCodeUrlResult.TokenUrl)
Msg string `json:"msg"`
Data string `json:"data"` // handler waiting
} line := cmdliner.NewLiner()
var qrCodeLoginResult *panlogin.QRCodeLoginResult
if url == "" { defer line.Close()
return "", fmt.Errorf("url is empty")
} go func() {
for {
logger.Verboseln("do request url: " + url) time.Sleep(3 * time.Second)
header := map[string]string { qr, er := h.GetQRCodeLoginResult(qrCodeUrlResult.TokenId)
"accept": "application/json, text/plain, */*", if er != nil {
"content-type": "application/json;charset=UTF-8", continue
"user-agent": "aliyunpan/" + config.AppVersion, }
} logger.Verboseln(qr)
// request if qr.QrCodeStatus == "CONFIRMED" {
client := requester.NewHTTPClient() // login successfully
client.SetTimeout(10 * time.Second) qrCodeLoginResult = qr
client.SetKeepAlive(false) break
body, err := client.Fetch("GET", url, nil, header) } else if qr.QrCodeStatus == "EXPIRED" {
if err != nil { break
logger.Verboseln("get token error ", err) }
return "", err }
} }()
// parse result line.State.Prompt("请在浏览器里面完成扫码登录然后再按Enter键继续...")
r := &tokenResult{} if qrCodeLoginResult == nil {
if err2 := json.Unmarshal(body, r); err2 != nil { return "", "", aliyunpan.WebLoginToken{}, fmt.Errorf("二维码登录失败")
logger.Verboseln("parse token info result json error ", err2) }
return "", apierror.NewFailedApiError(err2.Error())
} tokenStr, er := h.ParseSecureRefreshToken("", qrCodeLoginResult.SecureRefreshToken)
return r.Data, nil if er != nil {
fmt.Println("解析Token错误", er)
return "", "", aliyunpan.WebLoginToken{}, er
}
refreshToken = tokenStr
tokenId = qrCodeUrlResult.TokenId
}
refreshTokenStr, webToken, error = cmder.DoLoginHelper(refreshToken)
return
} }

View File

@ -26,12 +26,12 @@ import (
type DriveInfo struct { type DriveInfo struct {
DriveId string `json:"driveId"` DriveId string `json:"driveId"`
DriveName string `json:"driveName"` DriveName string `json:"driveName"`
DriveTag string `json:"driveTag"` DriveTag string `json:"driveTag"`
} }
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
} }
@ -41,22 +41,23 @@ func (d DriveInfoList) GetFileDriveId() string {
type PanUser struct { type PanUser struct {
UserId string `json:"userId"` UserId string `json:"userId"`
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
AccountName string `json:"accountName"` AccountName string `json:"accountName"`
Workdir string `json:"workdir"` Workdir string `json:"workdir"`
WorkdirFileEntity aliyunpan.FileEntity `json:"workdirFileEntity"` WorkdirFileEntity aliyunpan.FileEntity `json:"workdirFileEntity"`
AlbumWorkdir string `json:"albumWorkdir"` AlbumWorkdir string `json:"albumWorkdir"`
AlbumWorkdirFileEntity aliyunpan.FileEntity `json:"albumWorkdirFileEntity"` AlbumWorkdirFileEntity aliyunpan.FileEntity `json:"albumWorkdirFileEntity"`
ActiveDriveId string `json:"activeDriveId"` ActiveDriveId string `json:"activeDriveId"`
DriveList DriveInfoList `json:"driveList"` DriveList DriveInfoList `json:"driveList"`
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
} }
@ -72,9 +73,9 @@ func SetupUserByCookie(webToken *aliyunpan.WebLoginToken) (user *PanUser, err *a
doLoginAct: doLoginAct:
panClient := aliyunpan.NewPanClient(*webToken, aliyunpan.AppLoginToken{}) panClient := aliyunpan.NewPanClient(*webToken, aliyunpan.AppLoginToken{})
u := &PanUser{ u := &PanUser{
WebToken: *webToken, WebToken: *webToken,
panClient: panClient, panClient: panClient,
Workdir: "/", Workdir: "/",
WorkdirFileEntity: *aliyunpan.NewFileEntityForRootDir(), WorkdirFileEntity: *aliyunpan.NewFileEntityForRootDir(),
} }
@ -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
} }
@ -213,4 +214,4 @@ func (di *DriveInfo) IsFileDrive() bool {
func (di *DriveInfo) IsAlbumDrive() bool { func (di *DriveInfo) IsAlbumDrive() bool {
return di.DriveTag == "Album" return di.DriveTag == "Album"
} }

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
} }