From cc4d650796758893a0e8ea480b85d1d5e268c6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=9A=E5=90=83=E7=82=B9=E8=8B=B9=E6=9E=9C?= <73388495+eatmoreapple@users.noreply.github.com> Date: Thu, 12 Jan 2023 12:20:34 +0800 Subject: [PATCH] =?UTF-8?q?[style]:=20=E6=9B=B4=E6=96=B0=20BotLoginOption?= =?UTF-8?q?=20(#196)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.go | 43 +++--- bot_login.go | 380 +++++++++++++++++++++++++++++++++----------------- bot_option.go | 30 ---- 3 files changed, 274 insertions(+), 179 deletions(-) delete mode 100644 bot_option.go diff --git a/bot.go b/bot.go index ad6d3cd..e53a14f 100644 --- a/bot.go +++ b/bot.go @@ -66,41 +66,48 @@ func (b *Bot) GetCurrentUser() (*Self, error) { return b.self, nil } -func (b *Bot) login(login BotLogin) error { - return login.Login(b) +// login 这里对进行一些对登录前后的hook +func (b *Bot) login(login BotLogin, opts ...BotLoginOption) (err error) { + opt := BotOptionGroup(opts) + opt.Prepare(b) + if err = login.Login(b); err != nil { + err = opt.OnError(b, err) + } + if err != nil { + return err + } + return opt.OnSuccess(b) } // Login 用户登录 func (b *Bot) Login() error { - scanLogin := &SacnLogin{} + scanLogin := &SacnLogin{ + UUIDCallback: b.UUIDCallback, + ScanCallBack: b.ScanCallBack, + LoginCallBack: b.LoginCallBack, + } return b.login(scanLogin) } // HotLogin 热登录,可实现在单位时间内免重复扫码登录 -func (b *Bot) HotLogin(storage HotReloadStorage, opts ...HotLoginOptionFunc) error { +func (b *Bot) HotLogin(storage HotReloadStorage, opts ...BotLoginOption) error { hotLogin := &HotLogin{storage: storage} // 进行相关设置。 // 如果相对默认的行为进行修改,在opts里面进行追加即可。 - opts = append(defaultHotLoginOpts[:], opts...) - for _, opt := range opts { - opt(&hotLogin.opt) - } - return b.login(hotLogin) + opts = append(hotLoginDefaultOptions[:], opts...) + return b.login(hotLogin, opts...) } // PushLogin 免扫码登录 // 免扫码登录需要先扫码登录一次才可以进行扫码登录 // 扫码登录成功后需要利用微信号发送一条消息,然后在手机上进行主动退出。 // 这时候在进行一次 PushLogin 即可。 -func (b *Bot) PushLogin(storage HotReloadStorage, opts ...PushLoginOptionFunc) error { +func (b *Bot) PushLogin(storage HotReloadStorage, opts ...BotLoginOption) error { pushLogin := &PushLogin{storage: storage} // 进行相关设置。 // 如果相对默认的行为进行修改,在opts里面进行追加即可。 - opts = append(defaultPushLoginOpts[:], opts...) - for _, opt := range opts { - opt(&pushLogin.opt) - } - return b.login(pushLogin) + opts = append(pushLoginDefaultOptions[:], opts...) + return b.login(pushLogin, opts...) } // Logout 用户退出 @@ -337,7 +344,7 @@ func NewBot(c context.Context) *Bot { // mode不传入默认为 openwechat.Normal,详情见mode // // bot := openwechat.DefaultBot(openwechat.Desktop) -func DefaultBot(opts ...BotOptionFunc) *Bot { +func DefaultBot(prepares ...BotPreparer) *Bot { bot := NewBot(context.Background()) // 获取二维码回调 bot.UUIDCallback = PrintlnQrcodeUrl @@ -354,8 +361,8 @@ func DefaultBot(opts ...BotOptionFunc) *Bot { bot.SyncCheckCallback = func(resp SyncCheckResponse) { log.Printf("RetCode:%s Selector:%s", resp.RetCode, resp.Selector) } - for _, opt := range opts { - opt(bot) + for _, prepare := range prepares { + prepare.Prepare(bot) } return bot } diff --git a/bot_login.go b/bot_login.go index 32a9431..344c8e5 100644 --- a/bot_login.go +++ b/bot_login.go @@ -4,6 +4,160 @@ import ( "time" ) +type BotPreparer interface { + Prepare(*Bot) +} + +type BotLoginOption interface { + BotPreparer + OnError(*Bot, error) error + OnSuccess(*Bot) error +} + +// BotOptionGroup 是一个 BotLoginOption 的集合 +// 用于将多个 BotLoginOption 组合成一个 BotLoginOption +type BotOptionGroup []BotLoginOption + +// Prepare 实现了 BotLoginOption 接口 +func (g BotOptionGroup) Prepare(bot *Bot) { + for _, option := range g { + option.Prepare(bot) + } +} + +// OnError 实现了 BotLoginOption 接口 +func (g BotOptionGroup) OnError(b *Bot, err error) error { + // 当有一个 BotLoginOption 的 OnError 返回的 error 等于 nil 时,就会停止执行后续的 BotLoginOption + for _, option := range g { + currentErr := option.OnError(b, err) + if currentErr == nil { + return nil + } + if currentErr != err { + return currentErr + } + } + return err +} + +// OnSuccess 实现了 BotLoginOption 接口 +func (g BotOptionGroup) OnSuccess(b *Bot) error { + for _, option := range g { + if err := option.OnSuccess(b); err != nil { + return err + } + } + return nil +} + +type BaseBotLoginOption struct{} + +func (BaseBotLoginOption) Prepare(_ *Bot) {} + +func (BaseBotLoginOption) OnError(_ *Bot, err error) error { return err } + +func (BaseBotLoginOption) OnSuccess(_ *Bot) error { return nil } + +// DoNothingBotLoginOption 是一个空的 BotLoginOption,表示不做任何操作 +var DoNothingBotLoginOption = &BaseBotLoginOption{} + +// RetryLoginOption 在登录失败后进行扫码登录 +type RetryLoginOption struct { + BaseBotLoginOption + SacnLogin +} + +// Prepare 实现了 BotLoginOption 接口 +func (r *RetryLoginOption) Prepare(bot *Bot) { + r.UUIDCallback = bot.UUIDCallback + r.LoginCallBack = bot.LoginCallBack + r.ScanCallBack = bot.ScanCallBack +} + +// OnError 实现了 BotLoginOption 接口 +// 当登录失败后,会调用此方法进行扫码登录 +func (r *RetryLoginOption) OnError(bot *Bot, _ error) error { + return r.Login(bot) +} + +func NewRetryLoginOption() BotLoginOption { + return &RetryLoginOption{} +} + +// SyncReloadDataLoginOption 在登录成功后进行数据定时同步到指定的storage中 +type SyncReloadDataLoginOption struct { + BaseBotLoginOption + SyncLoopDuration time.Duration +} + +// OnSuccess 实现了 BotLoginOption 接口 +// 当登录成功后,会调用此方法进行数据定时同步到指定的storage中 +func (s SyncReloadDataLoginOption) OnSuccess(bot *Bot) error { + if s.SyncLoopDuration <= 0 { + return nil + } + syncer := NewHotReloadStorageSyncer(bot, s.SyncLoopDuration) + go func() { _ = syncer.Sync() }() + return nil +} + +func NewSyncReloadDataLoginOption(duration time.Duration) BotLoginOption { + return &SyncReloadDataLoginOption{SyncLoopDuration: duration} +} + +// WithoutLoginCallbackOption 不使用登录回调 +type WithoutLoginCallbackOption struct{ BaseBotLoginOption } + +// Prepare 实现了 BotLoginOption 接口 +// 将设置的 LoginCallback 置为 nil +func (w WithoutLoginCallbackOption) Prepare(b *Bot) { b.LoginCallBack = nil } + +func NewWithoutLoginCallbackOption() BotLoginOption { + return &WithoutLoginCallbackOption{} +} + +// WithoutScanCallbackOption 不使用扫码回调 +type WithoutScanCallbackOption struct{ BaseBotLoginOption } + +// Prepare 实现了 BotLoginOption 接口 +func (w WithoutScanCallbackOption) Prepare(b *Bot) { b.ScanCallBack = nil } + +func NewWithoutScanCallbackOption() BotLoginOption { + return &WithoutScanCallbackOption{} +} + +// WithoutUUIDCallbackOption 不使用UUID回调 +type WithoutUUIDCallbackOption struct{ BaseBotLoginOption } + +// Prepare 实现了 BotLoginOption 接口 +// 将设置的 UUIDCallback 置为 nil +func (w WithoutUUIDCallbackOption) Prepare(bot *Bot) { bot.UUIDCallback = nil } + +func NewWithoutUUIDCallbackOption() BotLoginOption { + return &WithoutUUIDCallbackOption{} +} + +// WithModeOption 指定使用哪种客户端模式 +type WithModeOption struct { + mode Mode +} + +// Prepare 实现了 BotLoginOption 接口 +func (w WithModeOption) Prepare(b *Bot) { b.Caller.Client.SetMode(w.mode) } + +func withMode(mode Mode) BotPreparer { + return WithModeOption{mode: mode} +} + +// btw, 这两个变量已经变了4回了, 但是为了兼容以前的代码, 还是得想着法儿让用户无感知的更新 +var ( + // Normal 网页版微信模式 + Normal = withMode(normal) + + // Desktop 桌面微信模式 + Desktop = withMode(desktop) +) + const ( defaultHotStorageSyncDuration = time.Minute * 5 ) @@ -14,7 +168,11 @@ type BotLogin interface { } // SacnLogin 扫码登录 -type SacnLogin struct{} +type SacnLogin struct { + UUIDCallback func(uuid string) + LoginCallBack func(body []byte) + ScanCallBack func(body []byte) +} // Login 实现了 BotLogin 接口 func (s *SacnLogin) Login(bot *Bot) error { @@ -29,63 +187,32 @@ func (s *SacnLogin) Login(bot *Bot) error { func (s *SacnLogin) checkLogin(bot *Bot, uuid string) error { bot.uuid = uuid loginChecker := &LoginChecker{ - Bot: bot, - Tip: "0", + Bot: bot, + Tip: "0", + UUIDCallback: s.UUIDCallback, + LoginCallBack: s.LoginCallBack, + ScanCallBack: s.ScanCallBack, } return loginChecker.CheckLogin() } -type hotLoginOption struct { - withRetry bool - syncDuration time.Duration - _ struct{} -} - -type HotLoginOptionFunc func(o *hotLoginOption) - -func HotLoginWithRetry(flag bool) HotLoginOptionFunc { - return func(o *hotLoginOption) { - o.withRetry = flag +var ( + hotLoginDefaultOptions = [...]BotLoginOption{ + NewSyncReloadDataLoginOption(defaultHotStorageSyncDuration), } -} - -// HotLoginWithSyncReloadData 定时同步 HotLogin 的数据 -func HotLoginWithSyncReloadData(duration time.Duration) HotLoginOptionFunc { - return func(o *hotLoginOption) { - o.syncDuration = duration + pushLoginDefaultOptions = [...]BotLoginOption{ + NewSyncReloadDataLoginOption(defaultHotStorageSyncDuration), + NewWithoutScanCallbackOption(), } -} - -var defaultHotLoginOpts = [...]HotLoginOptionFunc{ - HotLoginWithSyncReloadData(defaultHotStorageSyncDuration), -} +) // HotLogin 热登录模式 type HotLogin struct { storage HotReloadStorage - opt hotLoginOption } // Login 实现了 BotLogin 接口 func (h *HotLogin) Login(bot *Bot) error { - if err := h.loginWrapper(bot); err != nil { - return err - } - syncer := NewHotReloadStorageSyncer(bot, h.opt.syncDuration) - go func() { _ = syncer.Sync() }() - return nil -} - -func (h *HotLogin) loginWrapper(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 } @@ -97,84 +224,13 @@ func (h *HotLogin) hotLoginInit(bot *Bot) error { return bot.reload() } -type pushLoginOption struct { - withoutUUIDCallback bool - withoutScanCallback bool - withoutLoginCallback bool - withRetry bool - syncDuration time.Duration -} - -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 - } -} - -// PushLoginWithSyncReloadData 定时同步 PushLogin 的数据 -func PushLoginWithSyncReloadData(duration time.Duration) PushLoginOptionFunc { - return func(o *pushLoginOption) { - o.syncDuration = duration - } -} - -// defaultPushLoginOpts 默认的 PushLogin -var defaultPushLoginOpts = [...]PushLoginOptionFunc{ - PushLoginWithoutUUIDCallback(true), - PushLoginWithoutScanCallback(true), - PushLoginWithSyncReloadData(defaultHotStorageSyncDuration), -} - // PushLogin 免扫码登录模式 type PushLogin struct { storage HotReloadStorage - opt pushLoginOption } // Login 实现了 BotLogin 接口 func (p *PushLogin) Login(bot *Bot) error { - if err := p.loginWrapper(bot); err != nil { - return err - } - syncer := NewHotReloadStorageSyncer(bot, p.opt.syncDuration) - go func() { _ = syncer.Sync() }() - return nil -} - -func (p *PushLogin) loginWrapper(bot *Bot) error { - err := p.login(bot) - if err != nil && p.opt.withRetry { - scanLogin := SacnLogin{} - return scanLogin.Login(bot) - } - return err -} - -func (p *PushLogin) login(bot *Bot) error { if err := p.pushLoginInit(bot); err != nil { return err } @@ -197,28 +253,28 @@ func (p *PushLogin) pushLoginInit(bot *Bot) error { func (p *PushLogin) checkLogin(bot *Bot, uuid string) error { bot.uuid = uuid loginChecker := &LoginChecker{ - Bot: bot, - Tip: "1", - WithLoginCallback: p.opt.withoutLoginCallback, - WithoutUUIDCallback: p.opt.withoutUUIDCallback, - WithScanCallback: p.opt.withoutScanCallback, + Bot: bot, + Tip: "1", + UUIDCallback: bot.UUIDCallback, + LoginCallBack: bot.LoginCallBack, + ScanCallBack: bot.ScanCallBack, } return loginChecker.CheckLogin() } type LoginChecker struct { - Bot *Bot - Tip string - WithoutUUIDCallback bool - WithLoginCallback bool - WithScanCallback bool + Bot *Bot + Tip string + UUIDCallback func(uuid string) + LoginCallBack func(body []byte) + ScanCallBack func(body []byte) } func (l *LoginChecker) CheckLogin() error { uuid := l.Bot.UUID() // 二维码获取回调 - if l.Bot.UUIDCallback != nil && !l.WithoutUUIDCallback { - l.Bot.UUIDCallback(uuid) + if cb := l.UUIDCallback; cb != nil { + cb(uuid) } var tip = l.Tip for { @@ -236,14 +292,14 @@ func (l *LoginChecker) CheckLogin() error { if err = l.Bot.HandleLogin(resp.Raw); err != nil { return err } - if l.Bot.LoginCallBack != nil && !l.WithLoginCallback { - l.Bot.LoginCallBack(resp.Raw) + if cb := l.LoginCallBack; cb != nil { + cb(resp.Raw) } return nil case StatusScanned: // 执行扫码回调 - if l.Bot.ScanCallBack != nil && !l.WithScanCallback { - l.Bot.ScanCallBack(resp.Raw) + if cb := l.ScanCallBack; cb != nil { + cb(resp.Raw) } case StatusTimeout: return ErrLoginTimeout @@ -252,3 +308,65 @@ func (l *LoginChecker) CheckLogin() error { } } } + +// # 下面都是即将废弃的函数。 +// # 为了兼容老版本暂时留了下来, 但是它的函数签名已经发生了改变。 +// # 如果你是使用的是openwechat提供的api来调用这些函数,那么你是感知不到变动的。 +// # openwechat内部对这些函数的调用做了兼容处理, 如果你的代码中调用了这些函数, 请尽快修改。 + +// Deprecated: 请使用 NewRetryLoginOption 代替 +// HotLoginWithRetry 热登录模式,如果登录失败会重试 +func HotLoginWithRetry(flag bool) BotLoginOption { + if flag { + return NewRetryLoginOption() + } + return DoNothingBotLoginOption +} + +// Deprecated: 请使用 NewRetryLoginOption 代替 +// HotLoginWithSyncReloadData 定时同步热登录数据 +func HotLoginWithSyncReloadData(duration time.Duration) BotLoginOption { + return NewSyncReloadDataLoginOption(duration) +} + +// Deprecated: 请使用 NewWithoutLoginCallbackOption 代替 +// PushLoginWithoutUUIDCallback 免扫码登录模式,不执行UUID回调 +func PushLoginWithoutUUIDCallback(flag bool) BotLoginOption { + if !flag { + return DoNothingBotLoginOption + } + return NewWithoutLoginCallbackOption() +} + +// Deprecated: 请使用 NewWithoutScanCallbackOption 代替 +// PushLoginWithoutScanCallback 免扫码登录模式,不执行扫码回调 +func PushLoginWithoutScanCallback(flag bool) BotLoginOption { + if !flag { + return DoNothingBotLoginOption + } + return NewWithoutScanCallbackOption() +} + +// Deprecated: 请使用 NewWithoutLoginCallbackOption 代替 +// PushLoginWithoutLoginCallback 免扫码登录模式,不执行登录回调 +func PushLoginWithoutLoginCallback(flag bool) BotLoginOption { + if !flag { + return DoNothingBotLoginOption + } + return NewWithoutLoginCallbackOption() +} + +// Deprecated: 请使用 NewRetryLoginOption 代替 +// PushLoginWithRetry 免扫码登录模式,如果登录失败会重试 +func PushLoginWithRetry(flag bool) BotLoginOption { + if !flag { + return DoNothingBotLoginOption + } + return NewRetryLoginOption() +} + +// Deprecated: 请使用 NewSyncReloadDataLoginOption 代替 +// PushLoginWithSyncReloadData 定时同步热登录数据 +func PushLoginWithSyncReloadData(duration time.Duration) BotLoginOption { + return NewSyncReloadDataLoginOption(duration) +} diff --git a/bot_option.go b/bot_option.go deleted file mode 100644 index e5e659e..0000000 --- a/bot_option.go +++ /dev/null @@ -1,30 +0,0 @@ -package openwechat - -import ( - "context" -) - -// BotOptionFunc 用于设置Bot的选项 -type BotOptionFunc func(*Bot) - -// Normal 网页版微信 -func Normal(b *Bot) { - b.Caller.Client.SetMode(normal) -} - -// Desktop 模式 -func Desktop(b *Bot) { - b.Caller.Client.SetMode(desktop) -} - -func WithContext(ctx context.Context) BotOptionFunc { - return func(b *Bot) { - b.context = ctx - } -} - -func WithDeviceID(deviceID string) BotOptionFunc { - return func(b *Bot) { - b.SetDeviceId(deviceID) - } -}