diff --git a/bot.go b/bot.go index 7698029..0f31c2f 100644 --- a/bot.go +++ b/bot.go @@ -1,37 +1,37 @@ package openwechat import ( - "errors" - "log" - "net/url" + "errors" + "log" + "net/url" ) type Bot struct { - ScanCallBack func(body []byte) // 扫码回调,可获取扫码用户的头像 - LoginCallBack func(body []byte) // 登陆回调 - UUIDCallback func(uuid string) // 获取UUID的回调函数 - MessageHandler func(msg *Message) // 获取消息成功的handle - GetMessageErrorHandler func(err error) // 获取消息发生错误的handle - isHot bool - err error - exit chan bool - Caller *Caller - self *Self - storage *Storage - hotReloadStorage HotReloadStorage + ScanCallBack func(body []byte) // 扫码回调,可获取扫码用户的头像 + LoginCallBack func(body []byte) // 登陆回调 + UUIDCallback func(uuid string) // 获取UUID的回调函数 + MessageHandler func(msg *Message) // 获取消息成功的handle + GetMessageErrorHandler func(err error) // 获取消息发生错误的handle + isHot bool + err error + exit chan bool + Caller *Caller + self *Self + storage *Storage + hotReloadStorage HotReloadStorage } // 判断当前用户是否正常在线 func (b *Bot) Alive() bool { - if b.self == nil { - return false - } - select { - case <-b.exit: - return false - default: - return true - } + if b.self == nil { + return false + } + select { + case <-b.exit: + return false + default: + return true + } } // 获取当前的用户 @@ -41,10 +41,10 @@ func (b *Bot) Alive() bool { // } // fmt.Println(self.NickName) func (b *Bot) GetCurrentUser() (*Self, error) { - if b.self == nil { - return nil, errors.New("user not login") - } - return b.self, nil + if b.self == nil { + return nil, errors.New("user not login") + } + return b.self, nil } // 热登录,可实现重复登录, @@ -53,264 +53,268 @@ func (b *Bot) GetCurrentUser() (*Self, error) { // err := bot.HotLogin(storage, true) // fmt.Println(err) func (b *Bot) HotLogin(storage HotReloadStorage, retry ...bool) error { - b.isHot = true - b.hotReloadStorage = storage + b.isHot = true + b.hotReloadStorage = storage - var err error + var err error - // 如果load出错了,就执行正常登陆逻辑 - // 第一次没有数据load都会出错的 - if err = storage.Load(); err != nil { - return b.Login() - } + // 如果load出错了,就执行正常登陆逻辑 + // 第一次没有数据load都会出错的 + if err = storage.Load(); err != nil { + return b.Login() + } - if err = b.hotLoginInit(); err != nil { - return err - } + if err = b.hotLoginInit(); err != nil { + return err + } - // 如果webInit出错,则说明可能身份信息已经失效 - // 如果retry为True的话,则进行正常登陆 - if err = b.webInit(); err != nil { - if len(retry) > 0 { - if retry[0] { - return b.Login() - } - } - } - return err + // 如果webInit出错,则说明可能身份信息已经失效 + // 如果retry为True的话,则进行正常登陆 + if err = b.webInit(); err != nil { + if len(retry) > 0 { + if retry[0] { + return b.Login() + } + } + } + return err } // 热登陆初始化 func (b *Bot) hotLoginInit() error { - cookies := b.hotReloadStorage.GetCookie() - for u, ck := range cookies { - path, err := url.Parse(u) - if err != nil { - return err - } - b.Caller.Client.Jar.SetCookies(path, ck) - } - b.storage.LoginInfo = b.hotReloadStorage.GetLoginInfo() - b.storage.Request = b.hotReloadStorage.GetBaseRequest() - return nil + cookies := b.hotReloadStorage.GetCookie() + for u, ck := range cookies { + path, err := url.Parse(u) + if err != nil { + return err + } + b.Caller.Client.Jar.SetCookies(path, ck) + } + b.storage.LoginInfo = b.hotReloadStorage.GetLoginInfo() + b.storage.Request = b.hotReloadStorage.GetBaseRequest() + return nil } // 用户登录 // 该方法会一直阻塞,直到用户扫码登录,或者二维码过期 func (b *Bot) Login() error { - uuid, err := b.Caller.GetLoginUUID() - if err != nil { - return err - } - if b.UUIDCallback != nil { - b.UUIDCallback(uuid) - } - for { - resp, err := b.Caller.CheckLogin(uuid) - if err != nil { - return err - } - switch resp.Code { - case statusSuccess: - return b.handleLogin(resp.Raw) - case statusScanned: - if b.ScanCallBack != nil { - b.ScanCallBack(resp.Raw) - } - case statusTimeout: - return errors.New("login time out") - case statusWait: - continue - } - } + uuid, err := b.Caller.GetLoginUUID() + if err != nil { + return err + } + // 二维码获取回调 + 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 b.LoginCallBack != nil { + b.LoginCallBack(resp.Raw) + } + return b.handleLogin(resp.Raw) + case statusScanned: + // 执行扫码回调 + if b.ScanCallBack != nil { + b.ScanCallBack(resp.Raw) + } + case statusTimeout: + return errors.New("login time out") + case statusWait: + continue + } + } } // 用户退出 func (b *Bot) Logout() error { - if b.Alive() { - info := b.storage.LoginInfo - if err := b.Caller.Logout(info); err != nil { - return err - } - b.stopAsyncCALL(errors.New("logout")) - return nil - } - return errors.New("user not login") + if b.Alive() { + info := b.storage.LoginInfo + if err := b.Caller.Logout(info); err != nil { + return err + } + b.stopAsyncCALL(errors.New("logout")) + return nil + } + return errors.New("user not login") } // 登录逻辑 func (b *Bot) handleLogin(data []byte) error { - // 判断是否有登录回调,如果有执行它 - if b.LoginCallBack != nil { - b.LoginCallBack(data) - } - // 获取登录的一些基本的信息 - info, err := b.Caller.GetLoginInfo(data) - if err != nil { - return err - } - // 将LoginInfo存到storage里面 - b.storage.LoginInfo = info + // 获取登录的一些基本的信息 + info, err := b.Caller.GetLoginInfo(data) + if err != nil { + return err + } + // 将LoginInfo存到storage里面 + b.storage.LoginInfo = info - // 构建BaseRequest - request := &BaseRequest{ - Uin: info.WxUin, - Sid: info.WxSid, - Skey: info.SKey, - DeviceID: GetRandomDeviceId(), - } + // 构建BaseRequest + request := &BaseRequest{ + Uin: info.WxUin, + Sid: info.WxSid, + Skey: info.SKey, + DeviceID: GetRandomDeviceId(), + } - // 将BaseRequest存到storage里面方便后续调用 - b.storage.Request = request + // 将BaseRequest存到storage里面方便后续调用 + b.storage.Request = request - // 如果是热登陆,则将当前的重要信息写入hotReloadStorage - if b.isHot { - cookies := b.Caller.Client.GetCookieMap() - if err := b.hotReloadStorage.Dump(cookies, request, info); err != nil { - return err - } - } + // 如果是热登陆,则将当前的重要信息写入hotReloadStorage + if b.isHot { + cookies := b.Caller.Client.GetCookieMap() + if err := b.hotReloadStorage.Dump(cookies, request, info); err != nil { + return err + } + } - return b.webInit() + return b.webInit() } +// 根据有效凭证获取和初始化用户信息 func (b *Bot) webInit() error { - req := b.storage.Request - info := b.storage.LoginInfo - // 获取初始化的用户信息和一些必要的参数 - resp, err := b.Caller.WebInit(req) - if err != nil { - return err - } - // 设置当前的用户 - b.self = &Self{Bot: b, User: &resp.User} - b.self.Self = b.self - b.storage.Response = resp + req := b.storage.Request + info := b.storage.LoginInfo + // 获取初始化的用户信息和一些必要的参数 + resp, err := b.Caller.WebInit(req) + if err != nil { + return err + } + // 设置当前的用户 + b.self = &Self{Bot: b, User: &resp.User} + b.self.Self = b.self + b.storage.Response = resp - // 通知手机客户端已经登录 - if err = b.Caller.WebWxStatusNotify(req, resp, info); err != nil { - return err - } - // 开启协程,轮训获取是否有新的消息返回 - go func() { - if b.GetMessageErrorHandler == nil { - b.GetMessageErrorHandler = b.stopAsyncCALL - } - if err := b.asyncCall(); err != nil { - b.GetMessageErrorHandler(err) - } - }() - return nil + // 通知手机客户端已经登录 + if err = b.Caller.WebWxStatusNotify(req, resp, info); err != nil { + return err + } + // 开启协程,轮训获取是否有新的消息返回 + go func() { + if b.GetMessageErrorHandler == nil { + b.GetMessageErrorHandler = b.stopAsyncCALL + } + if err := b.asyncCall(); err != nil { + b.GetMessageErrorHandler(err) + } + }() + return nil } // 轮训请求 // 根据状态码判断是否有新的请求 func (b *Bot) asyncCall() error { - var ( - err error - resp *SyncCheckResponse - ) - for b.Alive() { - // 长轮训检查是否有消息返回 - resp, err = b.Caller.SyncCheck(b.storage.LoginInfo, b.storage.Response) - if err != nil { - return err - } - // 如果不是正常的状态码返回,发生了错误,直接退出 - if !resp.Success() { - return resp - } - // 如果Selector不为0,则获取消息 - if !resp.NorMal() { - if err = b.getNewWechatMessage(); err != nil { - return err - } - } - } - return err + var ( + err error + resp *SyncCheckResponse + ) + for b.Alive() { + // 长轮训检查是否有消息返回 + resp, err = b.Caller.SyncCheck(b.storage.LoginInfo, b.storage.Response) + if err != nil { + return err + } + // 如果不是正常的状态码返回,发生了错误,直接退出 + if !resp.Success() { + return resp + } + // 如果Selector不为0,则获取消息 + if !resp.NorMal() { + if err = b.getNewWechatMessage(); err != nil { + return err + } + } + } + return err } // 当获取消息发生错误时, 默认的错误处理行为 func (b *Bot) stopAsyncCALL(err error) { - b.exit <- true - b.err = err - b.self = nil - log.Printf("exit with : %s", err.Error()) + b.exit <- true + b.err = err + b.self = nil + log.Printf("exit with : %s", err.Error()) } // 获取新的消息 func (b *Bot) getNewWechatMessage() error { - resp, err := b.Caller.WebWxSync(b.storage.Request, b.storage.Response, b.storage.LoginInfo) - if err != nil { - return err - } - // 更新SyncKey并且重新存入storage - b.storage.Response.SyncKey = resp.SyncKey - // 遍历所有的新的消息,依次处理 - for _, message := range resp.AddMsgList { - // 根据不同的消息类型来进行处理,方便后续统一调用 - message.init(b) - // 调用自定义的处理方法 - if handler := b.MessageHandler; handler != nil { - handler(message) - } - } - return nil + resp, err := b.Caller.WebWxSync(b.storage.Request, b.storage.Response, b.storage.LoginInfo) + if err != nil { + return err + } + // 更新SyncKey并且重新存入storage + b.storage.Response.SyncKey = resp.SyncKey + // 遍历所有的新的消息,依次处理 + for _, message := range resp.AddMsgList { + // 根据不同的消息类型来进行处理,方便后续统一调用 + message.init(b) + // 调用自定义的处理方法 + if handler := b.MessageHandler; handler != nil { + handler(message) + } + } + return nil } // 当消息同步发生了错误或者用户主动在手机上退出,该方法会立即返回,否则会一直阻塞 func (b *Bot) Block() error { - if b.self == nil { - return errors.New("`Block` must be called after user login") - } - if _, closed := <-b.exit; !closed { - return errors.New("can not call `Block` after user logout") - } - close(b.exit) - return nil + if b.self == nil { + return errors.New("`Block` must be called after user login") + } + if _, notClose := <-b.exit; !notClose { + return errors.New("can not call `Block` after user logout") + } + close(b.exit) + return nil } // 获取当前Bot崩溃的原因 func (b *Bot) CrashReason() error { - return b.err + return b.err } // setter for Bot.MessageHandler func (b *Bot) MessageOnSuccess(h func(msg *Message)) { - b.MessageHandler = h + b.MessageHandler = h } // setter for Bot.GetMessageErrorHandler func (b *Bot) MessageOnError(h func(err error)) { - b.GetMessageErrorHandler = h + b.GetMessageErrorHandler = h } // Bot的构造方法,需要自己传入Caller func NewBot(caller *Caller) *Bot { - return &Bot{Caller: caller, storage: &Storage{}, exit: make(chan bool)} + return &Bot{Caller: caller, storage: &Storage{}, exit: make(chan bool)} } // 默认的Bot的构造方法, // mode不传入默认为openwechat.Normal,详情见mode // bot := openwechat.DefaultBot(openwechat.Desktop) func DefaultBot(modes ...mode) *Bot { - var m mode - if len(modes) == 0 { - m = Normal - } else { - m = modes[0] - } - urlManager := GetUrlManagerByMode(m) - return NewBot(DefaultCaller(urlManager)) + var m mode + if len(modes) == 0 { + m = Normal + } else { + m = modes[0] + } + urlManager := GetUrlManagerByMode(m) + return NewBot(DefaultCaller(urlManager)) } // 通过uuid获取登录二维码的url func GetQrcodeUrl(uuid string) string { - return qrcodeUrl + uuid + return qrcodeUrl + uuid } // 打印登录二维码 func PrintlnQrcodeUrl(uuid string) { - println("访问下面网址扫描二维码登录") - println(GetQrcodeUrl(uuid)) + println("访问下面网址扫描二维码登录") + println(GetQrcodeUrl(uuid)) } diff --git a/go.mod b/go.mod index 085ad61..506d574 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module github.com/eatMoreApple/openwechat go 1.15 + +require ( + golang.org/x/mod v0.4.2 // indirect + golang.org/x/net v0.0.0-20210427231257-85d9c07bbe3a // indirect + golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 // indirect + golang.org/x/tools v0.1.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5bcc7a7 --- /dev/null +++ b/go.sum @@ -0,0 +1,29 @@ +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210427231257-85d9c07bbe3a/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/user.go b/user.go index 8099744..9e93dc6 100644 --- a/user.go +++ b/user.go @@ -9,6 +9,7 @@ import ( "strings" ) +// 抽象的用户结构,包含 好友 群组 公众号 type User struct { Uin int HideInputBarFlag int @@ -148,6 +149,7 @@ func (s *Self) updateMembers() error { } // 获取文件传输助手对象,封装成Friend返回 +// fh, err := self.FileHelper() // or fh := openwechat.NewFriendHelper(self) func (s *Self) FileHelper() (*Friend, error) { // 如果缓存里有,直接返回,否则去联系人里面找 if s.fileHelper != nil { @@ -241,6 +243,7 @@ func (s *Self) SendImageToFriend(friend *Friend, file *os.File) (*SentMessage, e } // 设置好友备注 +// self.SetRemarkNameToFriend(friend, "remark") // or friend.SetRemarkName("remark") func (s *Self) SetRemarkNameToFriend(friend *Friend, remarkName string) error { req := s.Bot.storage.Request return s.Bot.Caller.WebWxOplog(req, remarkName, friend.UserName) @@ -331,6 +334,10 @@ func (s *Self) SendImageToGroup(group *Group, file *os.File) (*SentMessage, erro } // 撤回消息 +// sentMessage, err := friend.SendText("message") +// if err == nil { +// self.RevokeMessage(sentMessage) // or sentMessage.Revoke() +// } func (s *Self) RevokeMessage(msg *SentMessage) error { return s.Bot.Caller.WebWxRevokeMsg(msg, s.Bot.storage.Request) }