Skip to content

Commit

Permalink
添加免扫码登录功能 (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
eatmoreapple authored Jan 5, 2023
1 parent 17fbbd3 commit fc438ab
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 98 deletions.
134 changes: 46 additions & 88 deletions bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,102 +66,43 @@ func (b *Bot) GetCurrentUser() (*Self, error) {
return b.self, nil
}

// HotLogin 热登录,可实现重复登录,
// retry设置为true可在热登录失效后进行普通登录行为
//
// Storage := NewJsonFileHotReloadStorage("Storage.json")
// err := bot.HotLogin(Storage, true)
// fmt.Println(err)
func (b *Bot) HotLogin(storage HotReloadStorage, retries ...bool) error {
err := b.hotLogin(storage)
// 判断是否为需要重新登录
if errors.Is(err, ErrInvalidStorage) {
return b.Login()
}
if err != nil {
if len(retries) > 0 && retries[0] {
retErr, ok := err.(Ret)
if !ok {
return err
}
// TODO add more error code handle here
switch retErr {
case cookieInvalid:
return b.Login()
}
return err
}
}
return err
}

func (b *Bot) hotLogin(storage HotReloadStorage) error {
b.hotReloadStorage = storage
var item HotReloadStorageItem
err := json.NewDecoder(storage).Decode(&item)
if err != nil {
return err
}
if err = b.hotLoginInit(&item); err != nil {
return err
}
return b.WebInit()
}

// 热登陆初始化
func (b *Bot) hotLoginInit(item *HotReloadStorageItem) error {
b.Caller.Client.Jar = item.Jar.AsCookieJar()
b.Storage.LoginInfo = item.LoginInfo
b.Storage.Request = item.BaseRequest
b.Caller.Client.Domain = item.WechatDomain
b.uuid = item.UUID
return nil
func (b *Bot) login(login BotLogin) error {
return login.Login(b)
}

// Login 用户登录
func (b *Bot) Login() error {
uuid, err := b.Caller.GetLoginUUID()
if err != nil {
return err
}
return b.LoginWithUUID(uuid)
scanLogin := &SacnLogin{}
return b.login(scanLogin)
}

// LoginWithUUID 用户登录
// 该方法会一直阻塞,直到用户扫码登录,或者二维码过期
func (b *Bot) LoginWithUUID(uuid string) error {
b.uuid = uuid
// 二维码获取回调
if b.UUIDCallback != nil {
b.UUIDCallback(uuid)
// HotLogin 热登录,可实现重复登录,
// retry设置为true可在热登录失效后进行普通登录行为
//
// Storage := NewJsonFileHotReloadStorage("Storage.json")
// err := bot.HotLogin(Storage, true)
// fmt.Println(err)
func (b *Bot) HotLogin(storage HotReloadStorage, opts ...HotLoginOptionFunc) error {
hotLogin := &HotLogin{storage: storage}
for _, opt := range opts {
opt(&hotLogin.opt)
}
for {
// 长轮询检查是否扫码登录
resp, err := b.Caller.CheckLogin(uuid)
if err != nil {
return err
}
switch resp.Code {
case StatusSuccess:
// 判断是否有登录回调,如果有执行它
if err = b.HandleLogin(resp.Raw); err != nil {
return err
}
if b.LoginCallBack != nil {
b.LoginCallBack(resp.Raw)
}
return nil
case StatusScanned:
// 执行扫码回调
if b.ScanCallBack != nil {
b.ScanCallBack(resp.Raw)
}
case StatusTimeout:
return ErrLoginTimeout
case StatusWait:
continue
}
return b.login(hotLogin)
}

// PushLogin 免扫码登录
// 免扫码登录需要先扫码登录一次才可以进行扫码登录
// 扫码登录成功后需要利用微信号发送一条消息,然后在手机上进行主动退出。
// 这时候在进行一次 PushLogin 即可。
func (b *Bot) PushLogin(storage HotReloadStorage, opts ...PushLoginOptionFunc) error {
pushLogin := &PushLogin{storage: storage}
// 进行相关设置。
// 如果相对默认的行为进行修改,在opts里面进行追加即可。
opts = append(defaultPushLoginOpts[:], opts...)
for _, opt := range opts {
opt(&pushLogin.opt)
}
return b.login(pushLogin)
}

// Logout 用户退出
Expand Down Expand Up @@ -464,3 +405,20 @@ func (b *Bot) IsHot() bool {
func (b *Bot) UUID() string {
return b.uuid
}

func (b *Bot) reload() error {
if b.hotReloadStorage == nil {
return errors.New("hotReloadStorage is nil")
}
var item HotReloadStorageItem
err := json.NewDecoder(b.hotReloadStorage).Decode(&item)
if err != nil {
return err
}
b.Caller.Client.Jar = item.Jar.AsCookieJar()
b.Storage.LoginInfo = item.LoginInfo
b.Storage.Request = item.BaseRequest
b.Caller.Client.Domain = item.WechatDomain
b.uuid = item.UUID
return nil
}
209 changes: 209 additions & 0 deletions bot_login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package openwechat

// BotLogin 定义了一个Login的接口
type BotLogin interface {
Login(bot *Bot) error
}

// SacnLogin 扫码登录
type SacnLogin struct{}

// Login 实现了 BotLogin 接口
func (s *SacnLogin) Login(bot *Bot) error {
uuid, err := bot.Caller.GetLoginUUID()
if err != nil {
return err
}
return s.checkLogin(bot, uuid)
}

// checkLogin 该方法会一直阻塞,直到用户扫码登录,或者二维码过期
func (s *SacnLogin) checkLogin(bot *Bot, uuid string) error {
bot.uuid = uuid
// 二维码获取回调
if bot.UUIDCallback != nil {
bot.UUIDCallback(uuid)
}
for {
// 长轮询检查是否扫码登录
resp, err := bot.Caller.CheckLogin(uuid, "0")
if err != nil {
return err
}
switch resp.Code {
case StatusSuccess:
// 判断是否有登录回调,如果有执行它
if err = bot.HandleLogin(resp.Raw); err != nil {
return err
}
if bot.LoginCallBack != nil {
bot.LoginCallBack(resp.Raw)
}
return nil
case StatusScanned:
// 执行扫码回调
if bot.ScanCallBack != nil {
bot.ScanCallBack(resp.Raw)
}
case StatusTimeout:
return ErrLoginTimeout
case StatusWait:
continue
}
}
}

type hotLoginOption struct {
withRetry bool
_ struct{}
}

type HotLoginOptionFunc func(o *hotLoginOption)

func HotLoginWithRetry(flag bool) HotLoginOptionFunc {
return func(o *hotLoginOption) {
o.withRetry = flag
}
}

// HotLogin 热登录模式
type HotLogin struct {
storage HotReloadStorage
opt hotLoginOption
}

// Login 实现了 BotLogin 接口
func (h *HotLogin) Login(bot *Bot) error {
err := h.login(bot)
if err != nil && h.opt.withRetry {
scanLogin := SacnLogin{}
return scanLogin.Login(bot)
}
return err
}

func (h *HotLogin) login(bot *Bot) error {
if err := h.hotLoginInit(bot); err != nil {
return err
}
return bot.WebInit()
}

func (h *HotLogin) hotLoginInit(bot *Bot) error {
bot.hotReloadStorage = h.storage
return bot.reload()
}

type pushLoginOption struct {
withoutUUIDCallback bool
withoutScanCallback bool
withoutLoginCallback bool
withRetry bool
}

type PushLoginOptionFunc func(o *pushLoginOption)

// PushLoginWithoutUUIDCallback 设置 PushLogin 不执行二维码回调
func PushLoginWithoutUUIDCallback(flag bool) PushLoginOptionFunc {
return func(o *pushLoginOption) {
o.withoutUUIDCallback = flag
}
}

// PushLoginWithoutScanCallback 设置 PushLogin 不执行扫码回调
func PushLoginWithoutScanCallback(flag bool) PushLoginOptionFunc {
return func(o *pushLoginOption) {
o.withoutScanCallback = flag
}
}

// PushLoginWithoutLoginCallback 设置 PushLogin 不执行登录回调
func PushLoginWithoutLoginCallback(flag bool) PushLoginOptionFunc {
return func(o *pushLoginOption) {
o.withoutLoginCallback = flag
}
}

// PushLoginWithRetry 设置 PushLogin 失败后执行扫码登录
func PushLoginWithRetry(flag bool) PushLoginOptionFunc {
return func(o *pushLoginOption) {
o.withRetry = flag
}
}

// defaultPushLoginOpts 默认的 PushLogin
var defaultPushLoginOpts = [...]PushLoginOptionFunc{
PushLoginWithoutUUIDCallback(true),
PushLoginWithoutScanCallback(true),
}

// PushLogin 免扫码登录模式
type PushLogin struct {
storage HotReloadStorage
opt pushLoginOption
}

// Login 实现了 BotLogin 接口
func (p PushLogin) Login(bot *Bot) error {
if err := p.pushLoginInit(bot); err != nil {
return err
}
resp, err := bot.Caller.WebWxPushLogin(bot.Storage.LoginInfo.WxUin)
if err != nil {
return err
}
if err = resp.Err(); err != nil {
return err
}
err = p.checkLogin(bot, resp.UUID, "1")
if err != nil && p.opt.withRetry {
scanLogin := SacnLogin{}
return scanLogin.Login(bot)
}
return err
}

func (p PushLogin) pushLoginInit(bot *Bot) error {
bot.hotReloadStorage = p.storage
return bot.reload()
}

// checkLogin 登录检查
func (p PushLogin) checkLogin(bot *Bot, uuid, tip string) error {
// todo 将checkLogin剥离出来
bot.uuid = uuid
// 二维码获取回调
if bot.UUIDCallback != nil && !p.opt.withoutUUIDCallback {
bot.UUIDCallback(uuid)
}
for {
// 长轮询检查是否扫码登录
resp, err := bot.Caller.CheckLogin(uuid, tip)
if err != nil {
return err
}
if tip == "1" {
tip = "0"
}
switch resp.Code {
case StatusSuccess:
// 判断是否有登录回调,如果有执行它
if err = bot.HandleLogin(resp.Raw); err != nil {
return err
}
if bot.LoginCallBack != nil && !p.opt.withoutLoginCallback {
bot.LoginCallBack(resp.Raw)
}
return nil
case StatusScanned:
// 执行扫码回调
if bot.ScanCallBack != nil && !p.opt.withoutScanCallback {
bot.ScanCallBack(resp.Raw)
}
case StatusTimeout:
return ErrLoginTimeout
case StatusWait:
continue
}
}
}
4 changes: 2 additions & 2 deletions caller.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ func (c *Caller) GetLoginUUID() (string, error) {
}

// CheckLogin 检查是否登录成功
func (c *Caller) CheckLogin(uuid string) (*CheckLoginResponse, error) {
resp, err := c.Client.CheckLogin(uuid)
func (c *Caller) CheckLogin(uuid, tip string) (*CheckLoginResponse, error) {
resp, err := c.Client.CheckLogin(uuid, tip)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit fc438ab

Please sign in to comment.