From 2d4d7e7d781aed5a01213bed8a5bc00dabcf6932 Mon Sep 17 00:00:00 2001 From: eatMoreApple <15055461510@163.com> Date: Tue, 27 Apr 2021 15:17:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=90=8C=E6=84=8F=E5=A5=BD?= =?UTF-8?q?=E5=8F=8B=E8=AF=B7=E6=B1=82=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot_test.go | 17 ++ caller.go | 366 +++++++++++++++++++------------------ client.go | 2 +- message.go | 514 +++++++++++++++++++++++++++++----------------------- 4 files changed, 491 insertions(+), 408 deletions(-) diff --git a/bot_test.go b/bot_test.go index 40d87f0..e26e1e6 100644 --- a/bot_test.go +++ b/bot_test.go @@ -196,3 +196,20 @@ func TestSendMessage(t *testing.T) { return } } + +func TestAgreeFriendsAdd(t *testing.T) { + bot := defaultBot() + bot.MessageHandler = func(msg *Message) { + if msg.IsFriendAdd() { + if err := msg.Agree(); err != nil { + t.Error(err) + } + bot.Logout() + } + } + if err := bot.Login(); err != nil { + t.Error(err) + return + } + bot.Block() +} diff --git a/caller.go b/caller.go index ae2d6a1..fd1c183 100644 --- a/caller.go +++ b/caller.go @@ -1,262 +1,268 @@ package openwechat import ( - "errors" - "os" + "errors" + "os" ) // 调用请求和解析请求 // 上层模块可以直接获取封装后的请求结果 type Caller struct { - Client *Client + Client *Client } // Constructor for Caller func NewCaller(client *Client) *Caller { - return &Caller{Client: client} + return &Caller{Client: client} } // Default Constructor for Caller func DefaultCaller(urlManager UrlManager) *Caller { - return NewCaller(DefaultClient(urlManager)) + return NewCaller(DefaultClient(urlManager)) } // 获取登录的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 := string(results[1]) - resp := NewReturnResponse(c.Client.GetLoginInfo(path)) - 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 - } - return &loginInfo, nil + // 从响应体里面获取需要跳转的url + results := redirectUriRegexp.FindSubmatch(body) + if len(results) != 2 { + return nil, errors.New("redirect url does not match") + } + path := string(results[1]) + resp := NewReturnResponse(c.Client.GetLoginInfo(path)) + 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 + } + 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) error { - resp := NewReturnResponse(c.Client.WebWxSendMsg(msg, info, request)) - return parseBaseResponseError(resp) + resp := NewReturnResponse(c.Client.WebWxSendMsg(msg, info, request)) + return parseBaseResponseError(resp) } // 修改用户备注接口 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) WebWxSendImageMsg(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName string) error { - // 首先尝试上传图片 - resp := NewReturnResponse(c.Client.WebWxUploadMedia(file, request, info, fromUserName, toUserName, "image/jpeg", "pic")) - // 无错误上传成功之后获取请求结果,判断结果是否正常 - if resp.Err() != nil { - return resp.Err() - } - defer resp.Body.Close() - var item struct { - BaseResponse BaseResponse - MediaId string - } - if err := resp.ScanJSON(&item); err != nil { - return err - } - if !item.BaseResponse.Ok() { - return item.BaseResponse - } - // 构造新的图片类型的信息 - msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, item.MediaId) - // 发送图片信息 - resp = NewReturnResponse(c.Client.WebWxSendMsgImg(msg, request, info)) - return parseBaseResponseError(resp) + // 首先尝试上传图片 + resp := NewReturnResponse(c.Client.WebWxUploadMedia(file, request, info, fromUserName, toUserName, "image/jpeg", "pic")) + // 无错误上传成功之后获取请求结果,判断结果是否正常 + if resp.Err() != nil { + return resp.Err() + } + defer resp.Body.Close() + var item struct { + BaseResponse BaseResponse + MediaId string + } + if err := resp.ScanJSON(&item); err != nil { + return err + } + if !item.BaseResponse.Ok() { + return item.BaseResponse + } + // 构造新的图片类型的信息 + msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, item.MediaId) + // 发送图片信息 + resp = NewReturnResponse(c.Client.WebWxSendMsgImg(msg, request, info)) + return parseBaseResponseError(resp) } // 用户退出 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) } // 处理响应返回的结果是否正常 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 } diff --git a/client.go b/client.go index 25a55cf..d3085fe 100644 --- a/client.go +++ b/client.go @@ -340,7 +340,7 @@ func (c *Client) WebWxVerifyUser(storage *Storage, info RecommendInfo, verifyCon content := map[string]interface{}{ "BaseRequest": storage.Request, "Opcode": 3, - "SceneList": []int{33}, + "SceneList": [1]int{33}, "SceneListCount": 1, "VerifyContent": verifyContent, "VerifyUserList": []interface{}{map[string]string{ diff --git a/message.go b/message.go index 6c61711..b55d5c2 100644 --- a/message.go +++ b/message.go @@ -1,353 +1,413 @@ package openwechat import ( - "context" - "encoding/xml" - "errors" - "net/http" - "os" - "strconv" - "strings" - "sync" - "time" - "unicode" + "context" + "encoding/xml" + "errors" + "fmt" + "net/http" + "os" + "strconv" + "strings" + "sync" + "time" + "unicode" ) type Message struct { - IsAt bool - AppInfo struct { - Type int - AppID string - } - AppMsgType int - HasProductId int - ImgHeight int - ImgStatus int - ImgWidth int - ForwardFlag int - MsgType int - Status int - StatusNotifyCode int - SubMsgType int - VoiceLength int - CreateTime int64 - NewMsgId int64 - PlayLength int64 - MediaId string - MsgId string - EncryFileName string - FileName string - FileSize string - Content string - FromUserName string - OriContent string - StatusNotifyUserName string - Ticket string - ToUserName string - Url string - senderInGroupUserName string - RecommendInfo RecommendInfo - Bot *Bot - mu sync.RWMutex - Context context.Context - item map[string]interface{} + IsAt bool + AppInfo struct { + Type int + AppID string + } + AppMsgType int + HasProductId int + ImgHeight int + ImgStatus int + ImgWidth int + ForwardFlag int + MsgType int + Status int + StatusNotifyCode int + SubMsgType int + VoiceLength int + CreateTime int64 + NewMsgId int64 + PlayLength int64 + MediaId string + MsgId string + EncryFileName string + FileName string + FileSize string + Content string + FromUserName string + OriContent string + StatusNotifyUserName string + Ticket string + ToUserName string + Url string + senderInGroupUserName string + RecommendInfo RecommendInfo + Bot *Bot + mu sync.RWMutex + Context context.Context + item map[string]interface{} } // 获取消息的发送者 func (m *Message) Sender() (*User, error) { - members, err := m.Bot.self.Members(true) - if err != nil { - return nil, err - } - if m.FromUserName == m.Bot.self.User.UserName { - return m.Bot.self.User, nil - } - user := members.SearchByUserName(1, m.FromUserName) - if user == nil { - return nil, noSuchUserFoundError - } - return user.First().Detail() + members, err := m.Bot.self.Members(true) + if err != nil { + return nil, err + } + if m.FromUserName == m.Bot.self.User.UserName { + return m.Bot.self.User, nil + } + user := members.SearchByUserName(1, m.FromUserName) + if user == nil { + return nil, noSuchUserFoundError + } + return user.First().Detail() } // 获取消息在群里面的发送者 func (m *Message) SenderInGroup() (*User, error) { - if !m.IsSendByGroup() { - return nil, errors.New("message is not from group") - } - group, err := m.Sender() - if err != nil { - return nil, err - } - group, err = group.Detail() - if err != nil { - return nil, err - } - users := group.MemberList.SearchByUserName(1, m.senderInGroupUserName) - if users == nil { - return nil, noSuchUserFoundError - } - return users.First(), nil + if !m.IsSendByGroup() { + return nil, errors.New("message is not from group") + } + group, err := m.Sender() + if err != nil { + return nil, err + } + group, err = group.Detail() + if err != nil { + return nil, err + } + users := group.MemberList.SearchByUserName(1, m.senderInGroupUserName) + if users == nil { + return nil, noSuchUserFoundError + } + return users.First(), nil } // 获取消息的接收者 func (m *Message) Receiver() (*User, error) { - if m.IsSendByGroup() { - if sender, err := m.Sender(); err != nil { - return nil, err - } else { - users := sender.MemberList.SearchByUserName(1, m.ToUserName) - if users == nil { - return nil, noSuchUserFoundError - } - return users.First(), nil - } - } else { - users := m.Bot.self.MemberList.SearchByUserName(1, m.ToUserName) - if users == nil { - return nil, noSuchUserFoundError - } - return users.First(), nil - } + if m.IsSendByGroup() { + if sender, err := m.Sender(); err != nil { + return nil, err + } else { + users := sender.MemberList.SearchByUserName(1, m.ToUserName) + if users == nil { + return nil, noSuchUserFoundError + } + return users.First(), nil + } + } else { + users := m.Bot.self.MemberList.SearchByUserName(1, m.ToUserName) + if users == nil { + return nil, noSuchUserFoundError + } + return users.First(), nil + } } // 判断消息是否由自己发送 func (m *Message) IsSendBySelf() bool { - return m.FromUserName == m.Bot.self.User.UserName + return m.FromUserName == m.Bot.self.User.UserName } // 判断消息是否由好友发送 func (m *Message) IsSendByFriend() bool { - return !m.IsSendByGroup() && strings.HasPrefix(m.FromUserName, "@") + return !m.IsSendByGroup() && strings.HasPrefix(m.FromUserName, "@") } // 判断消息是否由群组发送 func (m *Message) IsSendByGroup() bool { - return strings.HasPrefix(m.FromUserName, "@@") + return strings.HasPrefix(m.FromUserName, "@@") } // 回复消息 func (m *Message) Reply(msgType int, content, mediaId string) error { - msg := NewSendMessage(msgType, content, m.Bot.self.User.UserName, m.FromUserName, mediaId) - info := m.Bot.storage.LoginInfo - request := m.Bot.storage.Request - return m.Bot.Caller.WebWxSendMsg(msg, info, request) + msg := NewSendMessage(msgType, content, m.Bot.self.User.UserName, m.FromUserName, mediaId) + info := m.Bot.storage.LoginInfo + request := m.Bot.storage.Request + return m.Bot.Caller.WebWxSendMsg(msg, info, request) } // 回复文本消息 func (m *Message) ReplyText(content string) error { - return m.Reply(TextMessage, content, "") + return m.Reply(TextMessage, content, "") } // 回复图片消息 func (m *Message) ReplyImage(file *os.File) error { - info := m.Bot.storage.LoginInfo - request := m.Bot.storage.Request - return m.Bot.Caller.WebWxSendImageMsg(file, request, info, m.Bot.self.UserName, m.FromUserName) + info := m.Bot.storage.LoginInfo + request := m.Bot.storage.Request + return m.Bot.Caller.WebWxSendImageMsg(file, request, info, m.Bot.self.UserName, m.FromUserName) } func (m *Message) IsText() bool { - return m.MsgType == 1 && m.Url == "" + return m.MsgType == 1 && m.Url == "" } func (m *Message) IsMap() bool { - return m.MsgType == 1 && m.Url != "" + return m.MsgType == 1 && m.Url != "" } func (m *Message) IsPicture() bool { - return m.MsgType == 3 || m.MsgType == 47 + return m.MsgType == 3 || m.MsgType == 47 } func (m *Message) IsVoice() bool { - return m.MsgType == 34 + return m.MsgType == 34 } func (m *Message) IsFriendAdd() bool { - return m.MsgType == 37 + return m.MsgType == 37 && m.FromUserName == "fmessage" } func (m *Message) IsCard() bool { - return m.MsgType == 42 + return m.MsgType == 42 } func (m *Message) IsVideo() bool { - return m.MsgType == 43 || m.MsgType == 62 + return m.MsgType == 43 || m.MsgType == 62 } func (m *Message) IsMedia() bool { - return m.MsgType == 49 + return m.MsgType == 49 } func (m *Message) IsRecalled() bool { - return m.MsgType == 10002 + return m.MsgType == 10002 } func (m *Message) IsSystem() bool { - return m.MsgType == 10000 + return m.MsgType == 10000 } func (m *Message) IsNotify() bool { - return m.MsgType == 51 && m.StatusNotifyCode != 0 + return m.MsgType == 51 && m.StatusNotifyCode != 0 } // 判断消息是否为文件类型的消息 func (m *Message) HasFile() bool { - return m.IsPicture() || m.IsVoice() || m.IsVideo() || m.IsMedia() + return m.IsPicture() || m.IsVoice() || m.IsVideo() || m.IsMedia() } // 获取文件消息的文件 func (m *Message) GetFile() (*http.Response, error) { - if !m.HasFile() { - return nil, errors.New("invalid message type") - } - if m.IsPicture() { - return m.Bot.Caller.Client.WebWxGetMsgImg(m, m.Bot.storage.LoginInfo) - } - if m.IsVoice() { - return m.Bot.Caller.Client.WebWxGetVoice(m, m.Bot.storage.LoginInfo) - } - if m.IsVideo() { - return m.Bot.Caller.Client.WebWxGetVideo(m, m.Bot.storage.LoginInfo) - } - if m.IsMedia() { - return m.Bot.Caller.Client.WebWxGetMedia(m, m.Bot.storage.LoginInfo) - } - return nil, errors.New("unsupported type") + if !m.HasFile() { + return nil, errors.New("invalid message type") + } + if m.IsPicture() { + return m.Bot.Caller.Client.WebWxGetMsgImg(m, m.Bot.storage.LoginInfo) + } + if m.IsVoice() { + return m.Bot.Caller.Client.WebWxGetVoice(m, m.Bot.storage.LoginInfo) + } + if m.IsVideo() { + return m.Bot.Caller.Client.WebWxGetVideo(m, m.Bot.storage.LoginInfo) + } + if m.IsMedia() { + return m.Bot.Caller.Client.WebWxGetMedia(m, m.Bot.storage.LoginInfo) + } + return nil, errors.New("unsupported type") } // 获取card类型 func (m *Message) Card() (*Card, error) { - if !m.IsCard() { - return nil, errors.New("card message required") - } - var card Card - content := XmlFormString(m.Content) - err := xml.Unmarshal([]byte(content), &card) - return &card, err + if !m.IsCard() { + return nil, errors.New("card message required") + } + var card Card + content := XmlFormString(m.Content) + err := xml.Unmarshal([]byte(content), &card) + return &card, err +} + +// 获取FriendAddMessageContent内容 +func (m *Message) FriendAddMessageContent() (*FriendAddMessageContent, error) { + if !m.IsFriendAdd() { + return nil, errors.New("friend add message required") + } + var f FriendAddMessageContent + content := XmlFormString(m.Content) + err := xml.Unmarshal([]byte(content), &f) + return &f, err +} + +// 同意好友的请求 +func (m *Message) Agree(verifyContents ...string) error { + if !m.IsFriendAdd() { + return fmt.Errorf("friend add message required") + } + var builder strings.Builder + for _, v := range verifyContents { + builder.WriteString(v) + } + return m.Bot.Caller.WebWxVerifyUser(m.Bot.storage, m.RecommendInfo, builder.String()) } // 往消息上下文中设置值 // goroutine safe func (m *Message) Set(key string, value interface{}) { - m.mu.Lock() - defer m.mu.Unlock() - if m.item == nil { - m.item = make(map[string]interface{}) - } - m.item[key] = value + m.mu.Lock() + defer m.mu.Unlock() + if m.item == nil { + m.item = make(map[string]interface{}) + } + m.item[key] = value } // 从消息上下文中获取值 // goroutine safe func (m *Message) Get(key string) (value interface{}, exist bool) { - m.mu.RLock() - defer m.mu.RUnlock() - value, exist = m.item[key] - return + m.mu.RLock() + defer m.mu.RUnlock() + value, exist = m.item[key] + return } // 消息初始化,根据不同的消息作出不同的处理 func (m *Message) init(bot *Bot) { - m.Bot = bot - if m.IsSendByGroup() { - data := strings.Split(m.Content, ":
") - m.Content = strings.Join(data[1:], "") - m.senderInGroupUserName = data[0] - receiver, err := m.Receiver() - if err == nil { - displayName := receiver.DisplayName - if displayName == "" { - displayName = receiver.NickName - } - atFlag := "@" + displayName - index := len(atFlag) + 1 + 1 - if strings.HasPrefix(m.Content, atFlag) && unicode.IsSpace(rune(m.Content[index])) { - m.IsAt = true - m.Content = m.Content[index+1:] - } - } - } + m.Bot = bot + if m.IsSendByGroup() { + data := strings.Split(m.Content, ":
") + m.Content = strings.Join(data[1:], "") + m.senderInGroupUserName = data[0] + receiver, err := m.Receiver() + if err == nil { + displayName := receiver.DisplayName + if displayName == "" { + displayName = receiver.NickName + } + atFlag := "@" + displayName + index := len(atFlag) + 1 + 1 + if strings.HasPrefix(m.Content, atFlag) && unicode.IsSpace(rune(m.Content[index])) { + m.IsAt = true + m.Content = m.Content[index+1:] + } + } + } } -//func (m *Message) Agree() error { -// if !m.IsFriendAdd() { -// return fmt.Errorf("the excepted message type is 37, but got %d", m.MsgType) -// } -// return m.Bot.Caller.Client.WebWxVerifyUser(m.Bot.storage, m.RecommendInfo, "") -//} - // 发送消息的结构体 type SendMessage struct { - Type int - Content string - FromUserName string - ToUserName string - LocalID string - ClientMsgId string - MediaId string + Type int + Content string + FromUserName string + ToUserName string + LocalID string + ClientMsgId string + MediaId string } // SendMessage的构造方法 func NewSendMessage(msgType int, content, fromUserName, toUserName, mediaId string) *SendMessage { - id := strconv.FormatInt(time.Now().Unix()*1e4, 10) - return &SendMessage{ - Type: msgType, - Content: content, - FromUserName: fromUserName, - ToUserName: toUserName, - LocalID: id, - ClientMsgId: id, - MediaId: mediaId, - } + id := strconv.FormatInt(time.Now().Unix()*1e4, 10) + return &SendMessage{ + Type: msgType, + Content: content, + FromUserName: fromUserName, + ToUserName: toUserName, + LocalID: id, + ClientMsgId: id, + MediaId: mediaId, + } } // 文本消息的构造方法 func NewTextSendMessage(content, fromUserName, toUserName string) *SendMessage { - return NewSendMessage(TextMessage, content, fromUserName, toUserName, "") + return NewSendMessage(TextMessage, content, fromUserName, toUserName, "") } // 媒体消息的构造方法 func NewMediaSendMessage(msgType int, fromUserName, toUserName, mediaId string) *SendMessage { - return NewSendMessage(msgType, "", fromUserName, toUserName, mediaId) + return NewSendMessage(msgType, "", fromUserName, toUserName, mediaId) } // 一些特殊类型的消息会携带该结构体信息 type RecommendInfo struct { - OpCode int - Scene int - Sex int - VerifyFlag int - AttrStatus int64 - QQNum int64 - Alias string - City string - Content string - NickName string - Province string - Signature string - Ticket string - UserName string + OpCode int + Scene int + Sex int + VerifyFlag int + AttrStatus int64 + QQNum int64 + Alias string + City string + Content string + NickName string + Province string + Signature string + Ticket string + UserName string } // 名片消息内容 type Card struct { - XMLName xml.Name `xml:"msg"` - ImageStatus int `xml:"imagestatus,attr"` - Scene int `xml:"scene,attr"` - Sex int `xml:"sex,attr"` - Certflag int `xml:"certflag,attr"` - BigHeadImgUrl string `xml:"bigheadimgurl,attr"` - SmallHeadImgUrl string `xml:"smallheadimgurl,attr"` - UserName string `xml:"username,attr"` - NickName string `xml:"nickname,attr"` - ShortPy string `xml:"shortpy,attr"` - Alias string `xml:"alias,attr"` // Note: 这个是名片用户的微信号 - Province string `xml:"province,attr"` - City string `xml:"city,attr"` - Sign string `xml:"sign,attr"` - Certinfo string `xml:"certinfo,attr"` - BrandIconUrl string `xml:"brandIconUrl,attr"` - BrandHomeUr string `xml:"brandHomeUr,attr"` - BrandSubscriptConfigUrl string `xml:"brandSubscriptConfigUrl,attr"` - BrandFlags string `xml:"brandFlags,attr"` - RegionCode string `xml:"regionCode,attr"` + XMLName xml.Name `xml:"msg"` + ImageStatus int `xml:"imagestatus,attr"` + Scene int `xml:"scene,attr"` + Sex int `xml:"sex,attr"` + Certflag int `xml:"certflag,attr"` + BigHeadImgUrl string `xml:"bigheadimgurl,attr"` + SmallHeadImgUrl string `xml:"smallheadimgurl,attr"` + UserName string `xml:"username,attr"` + NickName string `xml:"nickname,attr"` + ShortPy string `xml:"shortpy,attr"` + Alias string `xml:"alias,attr"` // Note: 这个是名片用户的微信号 + Province string `xml:"province,attr"` + City string `xml:"city,attr"` + Sign string `xml:"sign,attr"` + Certinfo string `xml:"certinfo,attr"` + BrandIconUrl string `xml:"brandIconUrl,attr"` + BrandHomeUr string `xml:"brandHomeUr,attr"` + BrandSubscriptConfigUrl string `xml:"brandSubscriptConfigUrl,attr"` + BrandFlags string `xml:"brandFlags,attr"` + RegionCode string `xml:"regionCode,attr"` +} + +// 好友添加消息信息内容 +type FriendAddMessageContent struct { + XMLName xml.Name `xml:"msg"` + Shortpy int `xml:"shortpy,attr"` + ImageStatus int `xml:"imagestatus,attr"` + Scene int `xml:"scene,attr"` + PerCard int `xml:"percard,attr"` + Sex int `xml:"sex,attr"` + AlbumFlag int `xml:"albumflag,attr"` + AlbumStyle int `xml:"albumstyle,attr"` + SnsFlag int `xml:"snsflag,attr"` + Opcode int `xml:"opcode,attr"` + FromUserName string `xml:"fromusername,attr"` + EncryptUserName string `xml:"encryptusername,attr"` + FromNickName string `xml:"fromnickname,attr"` + Content string `xml:"content,attr"` + Country string `xml:"country,attr"` + Province string `xml:"province,attr"` + City string `xml:"city,attr"` + Sign string `xml:"sign,attr"` + Alias string `xml:"alias,attr"` + WeiBo string `xml:"weibo,attr"` + AlbumBgImgId string `xml:"albumbgimgid,attr"` + SnsBgImgId string `xml:"snsbgimgid,attr"` + SnsBgObjectId string `xml:"snsbgobjectid,attr"` + MHash string `xml:"mhash,attr"` + MFullHash string `xml:"mfullhash,attr"` + BigHeadImgUrl string `xml:"bigheadimgurl,attr"` + SmallHeadImgUrl string `xml:"smallheadimgurl,attr"` + Ticket string `xml:"ticket,attr"` + GoogleContact string `xml:"googlecontact,attr"` + QrTicket string `xml:"qrticket,attr"` + ChatRoomUserName string `xml:"chatroomusername,attr"` + SourceUserName string `xml:"sourceusername,attr"` + ShareCardUserName string `xml:"sharecardusername,attr"` + ShareCardNickName string `xml:"sharecardnickname,attr"` + CardVersion string `xml:"cardversion,attr"` + BrandList struct { + Count int `xml:"count,attr"` + Ver int64 `xml:"ver,attr"` + } `xml:"brandlist"` }