diff --git a/bot.go b/bot.go index 7da9626..e599dcd 100644 --- a/bot.go +++ b/bot.go @@ -1,40 +1,40 @@ package openwechat import ( - "context" - "errors" - "log" - "net/url" + "context" + "errors" + "log" + "net/url" ) type Bot struct { - ScanCallBack func(body []byte) // 扫码回调,可获取扫码用户的头像 - LoginCallBack func(body []byte) // 登陆回调 - LogoutCallBack func(bot *Bot) // 退出回调 - UUIDCallback func(uuid string) // 获取UUID的回调函数 - MessageHandler func(msg *Message) // 获取消息成功的handle - GetMessageErrorHandler func(err error) // 获取消息发生错误的handle - isHot bool - err error - context context.Context - cancel context.CancelFunc - Caller *Caller - self *Self - storage *Storage - hotReloadStorage HotReloadStorage + ScanCallBack func(body []byte) // 扫码回调,可获取扫码用户的头像 + LoginCallBack func(body []byte) // 登陆回调 + LogoutCallBack func(bot *Bot) // 退出回调 + UUIDCallback func(uuid string) // 获取UUID的回调函数 + MessageHandler func(msg *Message) // 获取消息成功的handle + GetMessageErrorHandler func(err error) // 获取消息发生错误的handle + isHot bool + err error + context context.Context + cancel context.CancelFunc + Caller *Caller + self *Self + storage *Storage + hotReloadStorage HotReloadStorage } // 判断当前用户是否正常在线 func (b *Bot) Alive() bool { - if b.self == nil { - return false - } - select { - case <-b.context.Done(): - return false - default: - return true - } + if b.self == nil { + return false + } + select { + case <-b.context.Done(): + return false + default: + return true + } } // 获取当前的用户 @@ -44,10 +44,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 } // 热登录,可实现重复登录, @@ -56,270 +56,278 @@ 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 + item := b.hotReloadStorage.GetHotReloadStorageItem() + cookies := item.Cookies + 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 = item.LoginInfo + b.storage.Request = item.BaseRequest + b.Caller.Client.domain = item.WechatDomain + 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: - // 判断是否有登录回调,如果有执行它 - 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 - } - } + 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() { - if b.LogoutCallBack != nil { - b.LogoutCallBack(b) - } - 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() { + if b.LogoutCallBack != nil { + b.LogoutCallBack(b) + } + 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 { - // 获取登录的一些基本的信息 - 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() + item := HotReloadStorageItem{ + BaseRequest: request, + Cookies: cookies, + LoginInfo: info, + WechatDomain: b.Caller.Client.domain, + } + if err := b.hotReloadStorage.Dump(item); 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.cancel() - b.err = err - b.self = nil - log.Printf("exit with : %s", err.Error()) + b.cancel() + 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") - } - <-b.context.Done() - return nil + if b.self == nil { + return errors.New("`Block` must be called after user login") + } + <-b.context.Done() + 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 { - ctx, cancel := context.WithCancel(context.Background()) - return &Bot{Caller: caller, storage: &Storage{}, context: ctx, cancel: cancel} + ctx, cancel := context.WithCancel(context.Background()) + return &Bot{Caller: caller, storage: &Storage{}, context: ctx, cancel: cancel} } // 默认的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] - } - caller := DefaultCaller() - caller.Client.mode = m - return NewBot(caller) + var m mode + if len(modes) == 0 { + m = Normal + } else { + m = modes[0] + } + caller := DefaultCaller() + caller.Client.mode = m + return NewBot(caller) } // 通过uuid获取登录二维码的url func GetQrcodeUrl(uuid string) string { - return qrcode + uuid + return qrcode + uuid } // 打印登录二维码 func PrintlnQrcodeUrl(uuid string) { - println("访问下面网址扫描二维码登录") - println(GetQrcodeUrl(uuid)) + println("访问下面网址扫描二维码登录") + println(GetQrcodeUrl(uuid)) } diff --git a/caller.go b/caller.go index d15fe11..b72965c 100644 --- a/caller.go +++ b/caller.go @@ -1,349 +1,351 @@ package openwechat import ( - "errors" - "net/http" - "net/url" - "os" + "errors" + "net/http" + "net/url" + "os" ) // 调用请求和解析请求 // 上层模块可以直接获取封装后的请求结果 type Caller struct { - Client *Client + Client *Client + path *url.URL } // Constructor for Caller func NewCaller(client *Client) *Caller { - return &Caller{Client: client} + return &Caller{Client: client} } // Default Constructor for Caller func DefaultCaller() *Caller { - return NewCaller(DefaultClient()) + return NewCaller(DefaultClient()) } // 获取登录的uuid func (c *Caller) GetLoginUUID() (string, error) { - resp := NewReturnResponse(c.Client.GetLoginUUID()) - if resp.Err() != nil { - return "", resp.Err() - } - defer resp.Body.Close() - data, err := resp.ReadAll() - if err != nil { - return "", err - } - // 正则匹配uuid字符串 - results := uuidRegexp.FindSubmatch(data) - if len(results) != 2 { - // 如果没有匹配到,可能微信的接口做了修改,或者当前机器的ip被加入了黑名单 - return "", errors.New("uuid does not match") - } - return string(results[1]), nil + resp := NewReturnResponse(c.Client.GetLoginUUID()) + if resp.Err() != nil { + return "", resp.Err() + } + defer resp.Body.Close() + data, err := resp.ReadAll() + if err != nil { + return "", err + } + // 正则匹配uuid字符串 + results := uuidRegexp.FindSubmatch(data) + if len(results) != 2 { + // 如果没有匹配到,可能微信的接口做了修改,或者当前机器的ip被加入了黑名单 + return "", errors.New("uuid does not match") + } + return string(results[1]), nil } // 检查是否登录成功 func (c *Caller) CheckLogin(uuid string) (*CheckLoginResponse, error) { - resp := NewReturnResponse(c.Client.CheckLogin(uuid)) - if resp.Err() != nil { - return nil, resp.Err() - } - defer resp.Body.Close() - data, err := resp.ReadAll() - if err != nil { - return nil, err - } - // 正则匹配检测的code - // 具体code参考global.go - results := statusCodeRegexp.FindSubmatch(data) - if len(results) != 2 { - return nil, errors.New("error status code match") - } - code := string(results[1]) - return &CheckLoginResponse{Code: code, Raw: data}, nil + resp := NewReturnResponse(c.Client.CheckLogin(uuid)) + if resp.Err() != nil { + return nil, resp.Err() + } + defer resp.Body.Close() + data, err := resp.ReadAll() + if err != nil { + return nil, err + } + // 正则匹配检测的code + // 具体code参考global.go + results := statusCodeRegexp.FindSubmatch(data) + if len(results) != 2 { + return nil, errors.New("error status code match") + } + code := string(results[1]) + return &CheckLoginResponse{Code: code, Raw: data}, nil } // 获取登录信息 func (c *Caller) GetLoginInfo(body []byte) (*LoginInfo, error) { - // 从响应体里面获取需要跳转的url - results := redirectUriRegexp.FindSubmatch(body) - if len(results) != 2 { - return nil, errors.New("redirect url does not match") - } - path, err := url.Parse(string(results[1])) - if err != nil { - return nil, err - } - host := path.Host - domain, err := getDomainByHost(host) - if err != nil { - return nil, err - } - c.Client.domain = domain - resp := NewReturnResponse(c.Client.GetLoginInfo(path.String())) - if resp.Err() != nil { - return nil, resp.Err() - } - defer resp.Body.Close() - var loginInfo LoginInfo - // xml结构体序列化储存 - if err := resp.ScanXML(&loginInfo); err != nil { - return nil, err - } - if !loginInfo.Ok() { - return nil, loginInfo - } - return &loginInfo, nil + // 从响应体里面获取需要跳转的url + results := redirectUriRegexp.FindSubmatch(body) + if len(results) != 2 { + return nil, errors.New("redirect url does not match") + } + path, err := url.Parse(string(results[1])) + if err != nil { + return nil, err + } + + domain, err := getDomainByHost(path.Host) + if err != nil { + return nil, err + } + c.Client.domain = domain + + resp := NewReturnResponse(c.Client.GetLoginInfo(path.String())) + if resp.Err() != nil { + return nil, resp.Err() + } + defer resp.Body.Close() + var loginInfo LoginInfo + // xml结构体序列化储存 + if err := resp.ScanXML(&loginInfo); err != nil { + return nil, err + } + if !loginInfo.Ok() { + return nil, loginInfo + } + return &loginInfo, nil } // 获取初始化信息 func (c *Caller) WebInit(request *BaseRequest) (*WebInitResponse, error) { - resp := NewReturnResponse(c.Client.WebInit(request)) - if resp.Err() != nil { - return nil, resp.Err() - } - var webInitResponse WebInitResponse - defer resp.Body.Close() - if err := resp.ScanJSON(&webInitResponse); err != nil { - return nil, err - } - return &webInitResponse, nil + resp := NewReturnResponse(c.Client.WebInit(request)) + if resp.Err() != nil { + return nil, resp.Err() + } + var webInitResponse WebInitResponse + defer resp.Body.Close() + if err := resp.ScanJSON(&webInitResponse); err != nil { + return nil, err + } + return &webInitResponse, nil } // 通知手机已登录 func (c *Caller) WebWxStatusNotify(request *BaseRequest, response *WebInitResponse, info *LoginInfo) error { - resp := NewReturnResponse(c.Client.WebWxStatusNotify(request, response, info)) - if resp.Err() != nil { - return resp.Err() - } - var item struct{ BaseResponse BaseResponse } - defer resp.Body.Close() - if err := resp.ScanJSON(&item); err != nil { - return err - } - if !item.BaseResponse.Ok() { - return item.BaseResponse - } - return nil + resp := NewReturnResponse(c.Client.WebWxStatusNotify(request, response, info)) + if resp.Err() != nil { + return resp.Err() + } + var item struct{ BaseResponse BaseResponse } + defer resp.Body.Close() + if err := resp.ScanJSON(&item); err != nil { + return err + } + if !item.BaseResponse.Ok() { + return item.BaseResponse + } + return nil } // 异步获取是否有新的消息 func (c *Caller) SyncCheck(info *LoginInfo, response *WebInitResponse) (*SyncCheckResponse, error) { - resp := NewReturnResponse(c.Client.SyncCheck(info, response)) - if resp.Err() != nil { - return nil, resp.Err() - } - defer resp.Body.Close() - data, err := resp.ReadAll() - if err != nil { - return nil, err - } - results := syncCheckRegexp.FindSubmatch(data) - if len(results) != 3 { - return nil, errors.New("parse sync key failed") - } - retCode, selector := string(results[1]), string(results[2]) - syncCheckResponse := &SyncCheckResponse{RetCode: retCode, Selector: selector} - return syncCheckResponse, nil + resp := NewReturnResponse(c.Client.SyncCheck(info, response)) + if resp.Err() != nil { + return nil, resp.Err() + } + defer resp.Body.Close() + data, err := resp.ReadAll() + if err != nil { + return nil, err + } + results := syncCheckRegexp.FindSubmatch(data) + if len(results) != 3 { + return nil, errors.New("parse sync key failed") + } + retCode, selector := string(results[1]), string(results[2]) + syncCheckResponse := &SyncCheckResponse{RetCode: retCode, Selector: selector} + return syncCheckResponse, nil } // 获取所有的联系人 func (c *Caller) WebWxGetContact(info *LoginInfo) (Members, error) { - resp := NewReturnResponse(c.Client.WebWxGetContact(info)) - if resp.Err() != nil { - return nil, resp.Err() - } - defer resp.Body.Close() - var item WebWxContactResponse - if err := resp.ScanJSON(&item); err != nil { - return nil, err - } - if !item.BaseResponse.Ok() { - return nil, item.BaseResponse - } - return item.MemberList, nil + resp := NewReturnResponse(c.Client.WebWxGetContact(info)) + if resp.Err() != nil { + return nil, resp.Err() + } + defer resp.Body.Close() + var item WebWxContactResponse + if err := resp.ScanJSON(&item); err != nil { + return nil, err + } + if !item.BaseResponse.Ok() { + return nil, item.BaseResponse + } + return item.MemberList, nil } // 获取联系人的详情 // 注: Members参数的长度不要大于50 func (c *Caller) WebWxBatchGetContact(members Members, request *BaseRequest) (Members, error) { - resp := NewReturnResponse(c.Client.WebWxBatchGetContact(members, request)) - if resp.Err() != nil { - return nil, resp.Err() - } - defer resp.Body.Close() - var item WebWxBatchContactResponse - if err := resp.ScanJSON(&item); err != nil { - return nil, err - } - if !item.BaseResponse.Ok() { - return nil, item.BaseResponse - } - return item.ContactList, nil + resp := NewReturnResponse(c.Client.WebWxBatchGetContact(members, request)) + if resp.Err() != nil { + return nil, resp.Err() + } + defer resp.Body.Close() + var item WebWxBatchContactResponse + if err := resp.ScanJSON(&item); err != nil { + return nil, err + } + if !item.BaseResponse.Ok() { + return nil, item.BaseResponse + } + return item.ContactList, nil } // 获取新的消息接口 func (c *Caller) WebWxSync(request *BaseRequest, response *WebInitResponse, info *LoginInfo) (*WebWxSyncResponse, error) { - resp := NewReturnResponse(c.Client.WebWxSync(request, response, info)) - if resp.Err() != nil { - return nil, resp.Err() - } - defer resp.Body.Close() - var webWxSyncResponse WebWxSyncResponse - if err := resp.ScanJSON(&webWxSyncResponse); err != nil { - return nil, err - } - return &webWxSyncResponse, nil + resp := NewReturnResponse(c.Client.WebWxSync(request, response, info)) + if resp.Err() != nil { + return nil, resp.Err() + } + defer resp.Body.Close() + var webWxSyncResponse WebWxSyncResponse + if err := resp.ScanJSON(&webWxSyncResponse); err != nil { + return nil, err + } + return &webWxSyncResponse, nil } // 发送消息接口 func (c *Caller) WebWxSendMsg(msg *SendMessage, info *LoginInfo, request *BaseRequest) (*SentMessage, error) { - resp, err := c.Client.WebWxSendMsg(msg, info, request) - return getSuccessSentMessage(msg, resp, err) + resp, err := c.Client.WebWxSendMsg(msg, info, request) + return getSuccessSentMessage(msg, resp, err) } // 修改用户备注接口 func (c *Caller) WebWxOplog(request *BaseRequest, remarkName, toUserName string) error { - resp := NewReturnResponse(c.Client.WebWxOplog(request, remarkName, toUserName)) - return parseBaseResponseError(resp) + resp := NewReturnResponse(c.Client.WebWxOplog(request, remarkName, toUserName)) + return parseBaseResponseError(resp) } func (c *Caller) UploadMedia(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*UploadResponse, error) { - // 首先尝试上传图片 - resp := NewReturnResponse(c.Client.WebWxUploadMediaByChunk(file, request, info, fromUserName, toUserName)) - // 无错误上传成功之后获取请求结果,判断结果是否正常 - if resp.Err() != nil { - return nil, resp.Err() - } - defer resp.Body.Close() + // 首先尝试上传图片 + resp := NewReturnResponse(c.Client.WebWxUploadMediaByChunk(file, request, info, fromUserName, toUserName)) + // 无错误上传成功之后获取请求结果,判断结果是否正常 + if resp.Err() != nil { + return nil, resp.Err() + } + defer resp.Body.Close() - var item UploadResponse + var item UploadResponse - if err := resp.ScanJSON(&item); err != nil { - return &item, err - } - if !item.BaseResponse.Ok() { - return &item, item.BaseResponse - } - if len(item.MediaId) == 0 { - return &item, errors.New("upload failed") - } - return &item, nil + if err := resp.ScanJSON(&item); err != nil { + return &item, err + } + if !item.BaseResponse.Ok() { + return &item, item.BaseResponse + } + if len(item.MediaId) == 0 { + return &item, errors.New("upload failed") + } + return &item, nil } // 发送图片消息接口 func (c *Caller) WebWxSendImageMsg(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*SentMessage, error) { - // 首先尝试上传图片 - resp, err := c.UploadMedia(file, request, info, fromUserName, toUserName) - if err != nil { - return nil, err - } - // 构造新的图片类型的信息 - msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, resp.MediaId) - // 发送图片信息 - resp1, err := c.Client.WebWxSendMsgImg(msg, request, info) - return getSuccessSentMessage(msg, resp1, err) + // 首先尝试上传图片 + resp, err := c.UploadMedia(file, request, info, fromUserName, toUserName) + if err != nil { + return nil, err + } + // 构造新的图片类型的信息 + msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, resp.MediaId) + // 发送图片信息 + resp1, err := c.Client.WebWxSendMsgImg(msg, request, info) + return getSuccessSentMessage(msg, resp1, err) } func (c *Caller) WebWxSendFile(file *os.File, req *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*SentMessage, error) { - resp, err := c.UploadMedia(file, req, info, fromUserName, toUserName) - if err != nil { - return nil, err - } - // 构造新的文件类型的信息 - stat, _ := file.Stat() - appMsg := NewFileAppMessage(stat, resp.MediaId) - content, err := appMsg.XmlByte() - if err != nil { - return nil, err - } - msg := NewSendMessage(AppMessage, string(content), fromUserName, toUserName, "") - return c.WebWxSendAppMsg(msg, req) + resp, err := c.UploadMedia(file, req, info, fromUserName, toUserName) + if err != nil { + return nil, err + } + // 构造新的文件类型的信息 + stat, _ := file.Stat() + appMsg := NewFileAppMessage(stat, resp.MediaId) + content, err := appMsg.XmlByte() + if err != nil { + return nil, err + } + msg := NewSendMessage(AppMessage, string(content), fromUserName, toUserName, "") + return c.WebWxSendAppMsg(msg, req) } // 发送媒体消息 func (c *Caller) WebWxSendAppMsg(msg *SendMessage, req *BaseRequest) (*SentMessage, error) { - resp, err := c.Client.WebWxSendAppMsg(msg, req) - return getSuccessSentMessage(msg, resp, err) + resp, err := c.Client.WebWxSendAppMsg(msg, req) + return getSuccessSentMessage(msg, resp, err) } // 用户退出 func (c *Caller) Logout(info *LoginInfo) error { - resp := NewReturnResponse(c.Client.Logout(info)) - return parseBaseResponseError(resp) + resp := NewReturnResponse(c.Client.Logout(info)) + return parseBaseResponseError(resp) } // 拉好友入群 func (c *Caller) AddFriendIntoChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*Friend) error { - if len(friends) == 0 { - return errors.New("no friends found") - } - resp := NewReturnResponse(c.Client.AddMemberIntoChatRoom(req, info, group, friends...)) - return parseBaseResponseError(resp) + if len(friends) == 0 { + return errors.New("no friends found") + } + resp := NewReturnResponse(c.Client.AddMemberIntoChatRoom(req, info, group, friends...)) + return parseBaseResponseError(resp) } // 从群聊中移除用户 func (c *Caller) RemoveFriendFromChatRoom(req *BaseRequest, info *LoginInfo, group *Group, users ...*User) error { - if len(users) == 0 { - return errors.New("no users found") - } - resp := NewReturnResponse(c.Client.RemoveMemberFromChatRoom(req, info, group, users...)) - return parseBaseResponseError(resp) + if len(users) == 0 { + return errors.New("no users found") + } + resp := NewReturnResponse(c.Client.RemoveMemberFromChatRoom(req, info, group, users...)) + return parseBaseResponseError(resp) } // 同意加好友请求 func (c *Caller) WebWxVerifyUser(storage *Storage, info RecommendInfo, verifyContent string) error { - resp := NewReturnResponse(c.Client.WebWxVerifyUser(storage, info, verifyContent)) - return parseBaseResponseError(resp) + resp := NewReturnResponse(c.Client.WebWxVerifyUser(storage, info, verifyContent)) + return parseBaseResponseError(resp) } // 撤回消息操作 func (c *Caller) WebWxRevokeMsg(msg *SentMessage, request *BaseRequest) error { - resp := NewReturnResponse(c.Client.WebWxRevokeMsg(msg, request)) - return parseBaseResponseError(resp) + resp := NewReturnResponse(c.Client.WebWxRevokeMsg(msg, request)) + return parseBaseResponseError(resp) } // 处理响应返回的结果是否正常 func parseBaseResponseError(resp *ReturnResponse) error { - if resp.Err() != nil { - return resp.Err() - } - defer resp.Body.Close() - var item struct{ BaseResponse BaseResponse } - if err := resp.ScanJSON(&item); err != nil { - return err - } - if !item.BaseResponse.Ok() { - return item.BaseResponse - } - return nil + if resp.Err() != nil { + return resp.Err() + } + defer resp.Body.Close() + var item struct{ BaseResponse BaseResponse } + if err := resp.ScanJSON(&item); err != nil { + return err + } + if !item.BaseResponse.Ok() { + return item.BaseResponse + } + return nil } func parseMessageResponseError(resp *ReturnResponse, msg *SentMessage) error { - if resp.Err() != nil { - return resp.Err() - } + if resp.Err() != nil { + return resp.Err() + } - defer resp.Body.Close() + defer resp.Body.Close() - var messageResp MessageResponse + var messageResp MessageResponse - if err := resp.ScanJSON(&messageResp); err != nil { - return err - } + if err := resp.ScanJSON(&messageResp); err != nil { + return err + } - if !messageResp.BaseResponse.Ok() { - return messageResp.BaseResponse - } - // 发送成功之后将msgId赋值给SendMessage - msg.MsgId = messageResp.MsgID - return nil + if !messageResp.BaseResponse.Ok() { + return messageResp.BaseResponse + } + // 发送成功之后将msgId赋值给SendMessage + msg.MsgId = messageResp.MsgID + return nil } func getSuccessSentMessage(msg *SendMessage, resp *http.Response, err error) (*SentMessage, error) { - returnResp := NewReturnResponse(resp, err) - sendSuccessMsg := &SentMessage{SendMessage: msg} - err = parseMessageResponseError(returnResp, sendSuccessMsg) - return sendSuccessMsg, err + returnResp := NewReturnResponse(resp, err) + sendSuccessMsg := &SentMessage{SendMessage: msg} + err = parseMessageResponseError(returnResp, sendSuccessMsg) + return sendSuccessMsg, err } diff --git a/client.go b/client.go index 1e54d32..65ea6fe 100644 --- a/client.go +++ b/client.go @@ -40,7 +40,7 @@ func (u UserAgentHook) AfterRequest(response *http.Response, err error) {} type Client struct { HttpHooks HttpHooks *http.Client - domain *domain + domain *WechatDomain mode mode mu sync.Mutex cookies map[string][]*http.Cookie diff --git a/stroage.go b/stroage.go index 497ffb8..d7abcfe 100644 --- a/stroage.go +++ b/stroage.go @@ -1,88 +1,81 @@ package openwechat import ( - "bytes" - "encoding/json" - "net/http" - "os" + "bytes" + "encoding/json" + "net/http" + "os" ) // 身份信息, 维持整个登陆的Session会话 type Storage struct { - LoginInfo *LoginInfo - Request *BaseRequest - Response *WebInitResponse + LoginInfo *LoginInfo + Request *BaseRequest + Response *WebInitResponse +} + +type HotReloadStorageItem struct { + Cookies map[string][]*http.Cookie + BaseRequest *BaseRequest + LoginInfo *LoginInfo + WechatDomain *WechatDomain } // 热登陆存储接口 type HotReloadStorage interface { - GetCookie() map[string][]*http.Cookie // 获取client.cookie - GetBaseRequest() *BaseRequest // 获取BaseRequest - GetLoginInfo() *LoginInfo // 获取LoginInfo - Dump(cookies map[string][]*http.Cookie, req *BaseRequest, info *LoginInfo) error // 实现该方法, 将必要信息进行序列化 - Load() error // 实现该方法, 将存储媒介的内容反序列化 + GetHotReloadStorageItem() HotReloadStorageItem // 获取HotReloadStorageItem + Dump(item HotReloadStorageItem) error // 实现该方法, 将必要信息进行序列化 + Load() error // 实现该方法, 将存储媒介的内容反序列化 } // 实现HotReloadStorage接口 // 默认以json文件的形式存储 type JsonFileHotReloadStorage struct { - Cookie map[string][]*http.Cookie - Req *BaseRequest - Info *LoginInfo - filename string + item HotReloadStorageItem + filename string } // 将信息写入json文件 -func (f *JsonFileHotReloadStorage) Dump(cookies map[string][]*http.Cookie, req *BaseRequest, info *LoginInfo) error { +func (f *JsonFileHotReloadStorage) Dump(item HotReloadStorageItem) error { - file, err := os.OpenFile(f.filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) + file, err := os.OpenFile(f.filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) - if err != nil { - return err - } + if err != nil { + return err + } - defer file.Close() + defer file.Close() - f.Cookie = cookies - f.Req = req - f.Info = info + f.item = item - data, err := json.Marshal(f) - if err != nil { - return err - } - _, err = file.Write(data) - return err + data, err := json.Marshal(f.item) + if err != nil { + return err + } + _, err = file.Write(data) + return err } // 从文件中读取信息 func (f *JsonFileHotReloadStorage) Load() error { - file, err := os.Open(f.filename) + file, err := os.Open(f.filename) - if err != nil { - return err - } - defer file.Close() - var buffer bytes.Buffer - if _, err := buffer.ReadFrom(file); err != nil { - return err - } - err = json.Unmarshal(buffer.Bytes(), f) - return err + if err != nil { + return err + } + defer file.Close() + var buffer bytes.Buffer + if _, err := buffer.ReadFrom(file); err != nil { + return err + } + err = json.Unmarshal(buffer.Bytes(), &f.item) + return err } -func (f *JsonFileHotReloadStorage) GetCookie() map[string][]*http.Cookie { - return f.Cookie -} - -func (f *JsonFileHotReloadStorage) GetBaseRequest() *BaseRequest { - return f.Req -} - -func (f *JsonFileHotReloadStorage) GetLoginInfo() *LoginInfo { - return f.Info +func (f *JsonFileHotReloadStorage) GetHotReloadStorageItem() HotReloadStorageItem { + return f.item } func NewJsonFileHotReloadStorage(filename string) *JsonFileHotReloadStorage { - return &JsonFileHotReloadStorage{filename: filename} + return &JsonFileHotReloadStorage{filename: filename} } diff --git a/url.go b/url.go index c12736b..3f2bb99 100644 --- a/url.go +++ b/url.go @@ -47,18 +47,18 @@ var domainMap = map[string][]string{ "wechat.com": {"https://wechat.com", "https://file.web.wechat.com", "https://webpush.web.wechat.com"}, } -func getDomainByHost(host string) (*domain, error) { +func getDomainByHost(host string) (*WechatDomain, error) { value, exist := domainMap[host] if !exist { return nil, errors.New("invalid host") } - return &domain{ + return &WechatDomain{ BaseHost: value[0], FileHost: value[1], SyncHost: value[2], }, nil } -type domain struct { +type WechatDomain struct { BaseHost, FileHost, SyncHost string }