[style]: 更新 BotLoginOption (#196)

This commit is contained in:
多吃点苹果 2023-01-12 12:20:34 +08:00 committed by GitHub
parent 71fe8b87ec
commit cc4d650796
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 274 additions and 179 deletions

43
bot.go
View File

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

View File

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

View File

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