添加免扫码登录功能 (#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
|
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 热登录,可实现重复登录,
|
// HotLogin 热登录,可实现重复登录,
|
||||||
// retry设置为true可在热登录失效后进行普通登录行为
|
// retry设置为true可在热登录失效后进行普通登录行为
|
||||||
//
|
//
|
||||||
// Storage := NewJsonFileHotReloadStorage("Storage.json")
|
// Storage := NewJsonFileHotReloadStorage("Storage.json")
|
||||||
// err := bot.HotLogin(Storage, true)
|
// err := bot.HotLogin(Storage, true)
|
||||||
// fmt.Println(err)
|
// fmt.Println(err)
|
||||||
func (b *Bot) HotLogin(storage HotReloadStorage, retries ...bool) error {
|
func (b *Bot) HotLogin(storage HotReloadStorage, opts ...HotLoginOptionFunc) error {
|
||||||
err := b.hotLogin(storage)
|
hotLogin := &HotLogin{storage: storage}
|
||||||
// 判断是否为需要重新登录
|
for _, opt := range opts {
|
||||||
if errors.Is(err, ErrInvalidStorage) {
|
opt(&hotLogin.opt)
|
||||||
return b.Login()
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
return b.login(hotLogin)
|
||||||
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 {
|
// PushLogin 免扫码登录
|
||||||
b.hotReloadStorage = storage
|
// 免扫码登录需要先扫码登录一次才可以进行扫码登录
|
||||||
var item HotReloadStorageItem
|
// 扫码登录成功后需要利用微信号发送一条消息,然后在手机上进行主动退出。
|
||||||
err := json.NewDecoder(storage).Decode(&item)
|
// 这时候在进行一次 PushLogin 即可。
|
||||||
if err != nil {
|
func (b *Bot) PushLogin(storage HotReloadStorage, opts ...PushLoginOptionFunc) error {
|
||||||
return err
|
pushLogin := &PushLogin{storage: storage}
|
||||||
}
|
// 进行相关设置。
|
||||||
if err = b.hotLoginInit(&item); err != nil {
|
// 如果相对默认的行为进行修改,在opts里面进行追加即可。
|
||||||
return err
|
opts = append(defaultPushLoginOpts[:], opts...)
|
||||||
}
|
for _, opt := range opts {
|
||||||
return b.WebInit()
|
opt(&pushLogin.opt)
|
||||||
}
|
|
||||||
|
|
||||||
// 热登陆初始化
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return b.login(pushLogin)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logout 用户退出
|
// Logout 用户退出
|
||||||
@ -464,3 +405,20 @@ func (b *Bot) IsHot() bool {
|
|||||||
func (b *Bot) UUID() string {
|
func (b *Bot) UUID() string {
|
||||||
return b.uuid
|
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 检查是否登录成功
|
// CheckLogin 检查是否登录成功
|
||||||
func (c *Caller) CheckLogin(uuid string) (*CheckLoginResponse, error) {
|
func (c *Caller) CheckLogin(uuid, tip string) (*CheckLoginResponse, error) {
|
||||||
resp, err := c.Client.CheckLogin(uuid)
|
resp, err := c.Client.CheckLogin(uuid, tip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
11
client.go
11
client.go
@ -116,7 +116,7 @@ func (c *Client) GetLoginQrcode(uuid string) (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckLogin 检查是否登录
|
// CheckLogin 检查是否登录
|
||||||
func (c *Client) CheckLogin(uuid string) (*http.Response, error) {
|
func (c *Client) CheckLogin(uuid, tip string) (*http.Response, error) {
|
||||||
path, _ := url.Parse(login)
|
path, _ := url.Parse(login)
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
@ -124,7 +124,7 @@ func (c *Client) CheckLogin(uuid string) (*http.Response, error) {
|
|||||||
params.Add("_", strconv.FormatInt(now, 10))
|
params.Add("_", strconv.FormatInt(now, 10))
|
||||||
params.Add("loginicon", "true")
|
params.Add("loginicon", "true")
|
||||||
params.Add("uuid", uuid)
|
params.Add("uuid", uuid)
|
||||||
params.Add("tip", "0")
|
params.Add("tip", tip)
|
||||||
path.RawQuery = params.Encode()
|
path.RawQuery = params.Encode()
|
||||||
req, _ := http.NewRequest(http.MethodGet, path.String(), nil)
|
req, _ := http.NewRequest(http.MethodGet, path.String(), nil)
|
||||||
return c.Do(req)
|
return c.Do(req)
|
||||||
@ -699,12 +699,7 @@ func (c *Client) WebWxRelationPin(request *BaseRequest, op uint8, user *User) (*
|
|||||||
|
|
||||||
// WebWxPushLogin 免扫码登陆接口
|
// WebWxPushLogin 免扫码登陆接口
|
||||||
func (c *Client) WebWxPushLogin(uin int64) (*http.Response, error) {
|
func (c *Client) WebWxPushLogin(uin int64) (*http.Response, error) {
|
||||||
path, _ := url.Parse(c.Domain.BaseHost() + webwxpushloginurl)
|
return c.mode.PushLogin(c, uin)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebWxSendVideoMsg 发送视频消息接口
|
// WebWxSendVideoMsg 发送视频消息接口
|
||||||
|
7
items.go
7
items.go
@ -167,3 +167,10 @@ type PushLoginResponse struct {
|
|||||||
func (p PushLoginResponse) Ok() bool {
|
func (p PushLoginResponse) Ok() bool {
|
||||||
return p.Ret == "0" && p.UUID != ""
|
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 {
|
type Mode interface {
|
||||||
GetLoginUUID(client *Client) (*http.Response, error)
|
GetLoginUUID(client *Client) (*http.Response, error)
|
||||||
GetLoginInfo(client *Client, path string) (*http.Response, error)
|
GetLoginInfo(client *Client, path string) (*http.Response, error)
|
||||||
|
PushLogin(client *Client, uin int64) (*http.Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// normal 网页版模式
|
||||||
normal Mode = normalMode{}
|
normal Mode = normalMode{}
|
||||||
|
|
||||||
|
// desktop 桌面模式,uos electron套壳
|
||||||
desktop Mode = desktopMode{}
|
desktop Mode = desktopMode{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type normalMode struct{}
|
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) {
|
func (n normalMode) GetLoginUUID(client *Client) (*http.Response, error) {
|
||||||
path, _ := url.Parse(jslogin)
|
path, _ := url.Parse(jslogin)
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
@ -65,3 +77,13 @@ func (n desktopMode) GetLoginInfo(client *Client, path string) (*http.Response,
|
|||||||
req.Header.Add("extspam", uosPatchExtspam)
|
req.Header.Add("extspam", uosPatchExtspam)
|
||||||
return client.Do(req)
|
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