添加免扫码登录功能 (#178)
This commit is contained in:
parent
17fbbd350e
commit
fc438ab83f
130
bot.go
130
bot.go
@ -66,102 +66,43 @@ func (b *Bot) GetCurrentUser() (*Self, error) {
|
||||
return b.self, nil
|
||||
}
|
||||
|
||||
func (b *Bot) login(login BotLogin) error {
|
||||
return login.Login(b)
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (b *Bot) Login() error {
|
||||
scanLogin := &SacnLogin{}
|
||||
return b.login(scanLogin)
|
||||
}
|
||||
|
||||
// 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()
|
||||
func (b *Bot) HotLogin(storage HotReloadStorage, opts ...HotLoginOptionFunc) error {
|
||||
hotLogin := &HotLogin{storage: storage}
|
||||
for _, opt := range opts {
|
||||
opt(&hotLogin.opt)
|
||||
}
|
||||
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
|
||||
return b.login(hotLogin)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (b *Bot) Login() error {
|
||||
uuid, err := b.Caller.GetLoginUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.LoginWithUUID(uuid)
|
||||
}
|
||||
|
||||
// LoginWithUUID 用户登录
|
||||
// 该方法会一直阻塞,直到用户扫码登录,或者二维码过期
|
||||
func (b *Bot) LoginWithUUID(uuid string) error {
|
||||
b.uuid = uuid
|
||||
// 二维码获取回调
|
||||
if b.UUIDCallback != nil {
|
||||
b.UUIDCallback(uuid)
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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 用户退出
|
||||
@ -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
bot_login.go
Normal file
209
bot_login.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
11
client.go
11
client.go
@ -116,7 +116,7 @@ func (c *Client) GetLoginQrcode(uuid string) (*http.Response, error) {
|
||||
}
|
||||
|
||||
// CheckLogin 检查是否登录
|
||||
func (c *Client) CheckLogin(uuid string) (*http.Response, error) {
|
||||
func (c *Client) CheckLogin(uuid, tip string) (*http.Response, error) {
|
||||
path, _ := url.Parse(login)
|
||||
now := time.Now().Unix()
|
||||
params := url.Values{}
|
||||
@ -124,7 +124,7 @@ func (c *Client) CheckLogin(uuid string) (*http.Response, error) {
|
||||
params.Add("_", strconv.FormatInt(now, 10))
|
||||
params.Add("loginicon", "true")
|
||||
params.Add("uuid", uuid)
|
||||
params.Add("tip", "0")
|
||||
params.Add("tip", tip)
|
||||
path.RawQuery = params.Encode()
|
||||
req, _ := http.NewRequest(http.MethodGet, path.String(), nil)
|
||||
return c.Do(req)
|
||||
@ -699,12 +699,7 @@ func (c *Client) WebWxRelationPin(request *BaseRequest, op uint8, user *User) (*
|
||||
|
||||
// WebWxPushLogin 免扫码登陆接口
|
||||
func (c *Client) WebWxPushLogin(uin int64) (*http.Response, error) {
|
||||
path, _ := url.Parse(c.Domain.BaseHost() + webwxpushloginurl)
|
||||
params := url.Values{}
|
||||
params.Add("uin", strconv.FormatInt(uin, 10))
|
||||
path.RawQuery = params.Encode()
|
||||
req, _ := http.NewRequest(http.MethodGet, path.String(), nil)
|
||||
return c.Do(req)
|
||||
return c.mode.PushLogin(c, uin)
|
||||
}
|
||||
|
||||
// WebWxSendVideoMsg 发送视频消息接口
|
||||
|
7
items.go
7
items.go
@ -167,3 +167,10 @@ type PushLoginResponse struct {
|
||||
func (p PushLoginResponse) Ok() bool {
|
||||
return p.Ret == "0" && p.UUID != ""
|
||||
}
|
||||
|
||||
func (p PushLoginResponse) Err() error {
|
||||
if p.Ok() {
|
||||
return nil
|
||||
}
|
||||
return errors.New(p.Msg)
|
||||
}
|
||||
|
22
mode.go
22
mode.go
@ -10,16 +10,28 @@ import (
|
||||
type Mode interface {
|
||||
GetLoginUUID(client *Client) (*http.Response, error)
|
||||
GetLoginInfo(client *Client, path string) (*http.Response, error)
|
||||
PushLogin(client *Client, uin int64) (*http.Response, error)
|
||||
}
|
||||
|
||||
var (
|
||||
// normal 网页版模式
|
||||
normal Mode = normalMode{}
|
||||
|
||||
// desktop 桌面模式,uos electron套壳
|
||||
desktop Mode = desktopMode{}
|
||||
)
|
||||
|
||||
type normalMode struct{}
|
||||
|
||||
func (n normalMode) PushLogin(client *Client, uin int64) (*http.Response, error) {
|
||||
path, _ := url.Parse(client.Domain.BaseHost() + webwxpushloginurl)
|
||||
params := url.Values{}
|
||||
params.Add("uin", strconv.FormatInt(uin, 10))
|
||||
path.RawQuery = params.Encode()
|
||||
req, _ := http.NewRequest(http.MethodGet, path.String(), nil)
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
func (n normalMode) GetLoginUUID(client *Client) (*http.Response, error) {
|
||||
path, _ := url.Parse(jslogin)
|
||||
params := url.Values{}
|
||||
@ -65,3 +77,13 @@ func (n desktopMode) GetLoginInfo(client *Client, path string) (*http.Response,
|
||||
req.Header.Add("extspam", uosPatchExtspam)
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
func (n desktopMode) PushLogin(client *Client, uin int64) (*http.Response, error) {
|
||||
path, _ := url.Parse(client.Domain.BaseHost() + webwxpushloginurl)
|
||||
params := url.Values{}
|
||||
params.Add("uin", strconv.FormatInt(uin, 10))
|
||||
params.Add("mod", "desktop")
|
||||
path.RawQuery = params.Encode()
|
||||
req, _ := http.NewRequest(http.MethodGet, path.String(), nil)
|
||||
return client.Do(req)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user