diff --git a/bot_test.go b/bot_test.go
index 66d1eaf..40d87f0 100644
--- a/bot_test.go
+++ b/bot_test.go
@@ -3,6 +3,7 @@ package openwechat
import (
"fmt"
"testing"
+ "time"
)
func defaultBot(modes ...mode) *Bot {
@@ -189,6 +190,7 @@ func TestSendMessage(t *testing.T) {
t.Error(err)
return
}
+ time.Sleep(time.Second)
if err = self.SendTextToFriend(helper, "send test message twice ! received?"); err != nil {
t.Error(err)
return
diff --git a/message.go b/message.go
index 84e6d7c..6c61711 100644
--- a/message.go
+++ b/message.go
@@ -1,270 +1,271 @@
package openwechat
import (
- "context"
- "encoding/xml"
- "errors"
- "net/http"
- "os"
- "strings"
- "sync"
- "time"
- "unicode"
+ "context"
+ "encoding/xml"
+ "errors"
+ "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
}
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
}
// 往消息上下文中设置值
// 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 {
@@ -276,76 +277,77 @@ func (m *Message) init(bot *Bot) {
// 发送消息的结构体
type SendMessage struct {
- Type int
- Content string
- FromUserName string
- ToUserName string
- LocalID int64
- ClientMsgId int64
- 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 {
- return &SendMessage{
- Type: msgType,
- Content: content,
- FromUserName: fromUserName,
- ToUserName: toUserName,
- LocalID: time.Now().Unix() * 1e4,
- ClientMsgId: time.Now().Unix() * 1e4,
- 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 {
- Alias string
- AttrStatus int64
- City string
- Content string
- NickName string
- OpCode int
- Province string
- QQNum int64
- Scene int
- Sex int
- Signature string
- Ticket string
- UserName string
- VerifyFlag int
+ 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"`
- 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: 这个是名片用户的微信号
- ImageStatus int `xml:"imagestatus,attr"`
- Scene int `xml:"scene,attr"`
- Province string `xml:"province,attr"`
- City string `xml:"city,attr"`
- Sign string `xml:"sign,attr"`
- Sex int `xml:"sex,attr"`
- Certflag int `xml:"certflag,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"`
}