From e751e13afde16351b1e8c1d96a3b00b42d5e928e Mon Sep 17 00:00:00 2001
From: eatMoreApple <15055461510@163.com>
Date: Fri, 30 Apr 2021 00:04:45 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6=E5=8F=91?=
=?UTF-8?q?=E9=80=81=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
bot_test.go | 26 +++
caller.go | 67 ++++--
client.go | 168 +++++---------
global.go | 147 ++++++------
items.go | 247 ++++++++++----------
message.go | 629 ++++++++++++++++++++++++++------------------------
parser.go | 90 ++++----
url.go | 3 +
user.go | 654 ++++++++++++++++++++++++++--------------------------
9 files changed, 1046 insertions(+), 985 deletions(-)
diff --git a/bot_test.go b/bot_test.go
index fc302b2..623f0df 100644
--- a/bot_test.go
+++ b/bot_test.go
@@ -291,3 +291,29 @@ func TestRevokeMessage(t *testing.T) {
t.Error(err)
}
}
+
+func TestSendFile(t *testing.T) {
+ bot := defaultBot(Desktop)
+ if err := bot.HotLogin(NewJsonFileHotReloadStorage("2.json"), true); err != nil {
+ t.Error(err)
+ return
+ }
+ self, err := bot.GetCurrentUser()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ fh, err := self.FileHelper()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ f, _ := os.Open("Taylor+Swift+-+Red")
+ defer f.Close()
+ msg, err := self.SendFileToFriend(fh, f)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ t.Log(msg.MsgId)
+}
diff --git a/caller.go b/caller.go
index d7f6a0f..0a08f07 100644
--- a/caller.go
+++ b/caller.go
@@ -200,47 +200,64 @@ func (c *Caller) WebWxOplog(request *BaseRequest, remarkName, toUserName string)
return parseBaseResponseError(resp)
}
-// 发送图片消息接口
-func (c *Caller) WebWxSendImageMsg(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*SentMessage, error) {
+func (c *Caller) UploadMedia(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName, mediaType string) (*UploadResponse, error) {
// 首先尝试上传图片
- sate, err := file.Stat()
- if err != nil {
- return nil, err
- }
-
- var resp *ReturnResponse
- if sate.Size() <= chunkSize {
- resp = NewReturnResponse(c.Client.WebWxUploadMedia(file, request, info, fromUserName, toUserName, "pic"))
- } else {
- resp = NewReturnResponse(c.Client.WebWxUploadMediaByChunk(file, request, info, fromUserName, toUserName, "pic"))
- }
+ resp := NewReturnResponse(c.Client.WebWxUploadMediaByChunk(file, request, info, fromUserName, toUserName, mediaType))
// 无错误上传成功之后获取请求结果,判断结果是否正常
if resp.Err() != nil {
return nil, resp.Err()
}
defer resp.Body.Close()
- var item struct {
- BaseResponse BaseResponse
- MediaId string
- }
+ var item UploadResponse
- if err = resp.ScanJSON(&item); err != nil {
- return nil, err
+ if err := resp.ScanJSON(&item); err != nil {
+ return &item, err
}
if !item.BaseResponse.Ok() {
- return nil, item.BaseResponse
+ return &item, item.BaseResponse
}
if len(item.MediaId) == 0 {
- return nil, errors.New("upload failed")
+ 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, "pic")
+ if err != nil {
+ return nil, err
}
// 构造新的图片类型的信息
- msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, item.MediaId)
+ msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, resp.MediaId)
// 发送图片信息
- resp = NewReturnResponse(c.Client.WebWxSendMsgImg(msg, request, info))
+ return c.WebWxSendAppMsg(msg, request, info)
+}
+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, "doc")
+ if err != nil {
+ return nil, err
+ }
+ // 构造新的图片类型的信息
+ msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, resp.MediaId)
+ stat, _ := file.Stat()
+ appMsg := NewFileAppMessage(stat, resp.MediaId)
+ content, err := appMsg.XmlByte()
+ if err != nil {
+ return nil, err
+ }
+ msg.Content = string(content)
+ return c.WebWxSendAppMsg(msg, req, info)
+}
+
+// 发送媒体消息
+func (c *Caller) WebWxSendAppMsg(msg *SendMessage, req *BaseRequest, info *LoginInfo) (*SentMessage, error) {
+ resp := NewReturnResponse(c.Client.WebWxSendMsgImg(msg, req, info))
sendSuccessMsg := &SentMessage{SendMessage: msg}
- err = parseMessageResponseError(resp, sendSuccessMsg)
+ err := parseMessageResponseError(resp, sendSuccessMsg)
return sendSuccessMsg, err
}
@@ -312,7 +329,7 @@ func parseMessageResponseError(resp *ReturnResponse, msg *SentMessage) error {
if !messageResp.BaseResponse.Ok() {
return messageResp.BaseResponse
}
- //// 发送成功之后将msgId赋值给SendMessage
+ // 发送成功之后将msgId赋值给SendMessage
msg.MsgId = messageResp.MsgID
return nil
}
diff --git a/client.go b/client.go
index d2e41ee..2eef103 100644
--- a/client.go
+++ b/client.go
@@ -251,104 +251,12 @@ func (c *Client) WebWxGetHeadImg(headImageUrl string) (*http.Response, error) {
return c.Do(req)
}
-// 上传文件
-func (c *Client) WebWxUploadMedia(file *os.File, request *BaseRequest, info *LoginInfo, forUserName, toUserName, mediaType string) (*http.Response, error) {
- // 获取文件上传的类型
- contentType, err := GetFileContentType(file)
- if err != nil {
- return nil, err
- }
- path, _ := url.Parse(c.webWxUpLoadMediaUrl)
- params := url.Values{}
- params.Add("f", "json")
- path.RawQuery = params.Encode()
- sate, err := file.Stat()
- if err != nil {
- return nil, err
- }
- cookies := c.Jar.Cookies(path)
- webWxDataTicket := getWebWxDataTicket(cookies)
-
- // 文件复位
- if _, err = file.Seek(0, 0); err != nil {
- return nil, err
- }
-
- buffer := bytes.Buffer{}
- if _, err := buffer.ReadFrom(file); err != nil {
- return nil, err
- }
- data := buffer.Bytes()
- fileMd5 := fmt.Sprintf("%x", md5.Sum(data))
-
- uploadMediaRequest := map[string]interface{}{
- "UploadType": 2,
- "BaseRequest": request,
- "ClientMediaId": time.Now().Unix() * 1e4,
- "TotalLen": sate.Size(),
- "StartPos": 0,
- "DataLen": sate.Size(),
- "MediaType": 4,
- "FromUserName": forUserName,
- "ToUserName": toUserName,
- "FileMd5": fileMd5,
- }
- uploadMediaRequestByte, err := json.Marshal(uploadMediaRequest)
- if err != nil {
- return nil, err
- }
-
- content := map[string]interface{}{
- "id": "WU_FILE_0",
- "name": file.Name(),
- "type": contentType,
- "lastModifiedDate": sate.ModTime().Format(TimeFormat),
- "size": sate.Size(),
- "mediatype": mediaType,
- "webwx_data_ticket": webWxDataTicket,
- "pass_ticket": info.PassTicket,
- }
- body, err := ToBuffer(content)
- if err != nil {
- return nil, err
- }
- writer := multipart.NewWriter(body)
- if err = writer.WriteField("uploadmediarequest", string(uploadMediaRequestByte)); err != nil {
- return nil, err
- }
- if w, err := writer.CreateFormFile("filename", file.Name()); err != nil {
- return nil, err
- } else {
- if _, err = w.Write(data); err != nil {
- return nil, err
- }
- }
- ct := writer.FormDataContentType()
- if err = writer.Close(); err != nil {
- return nil, err
- }
- req, _ := http.NewRequest(http.MethodPost, path.String(), body)
-
- req.Header.Set("Content-Type", ct)
- return c.Do(req)
-}
-
func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, info *LoginInfo, forUserName, toUserName, mediaType string) (*http.Response, error) {
// 获取文件上传的类型
contentType, err := GetFileContentType(file)
if err != nil {
return nil, err
}
- path, _ := url.Parse(c.webWxUpLoadMediaUrl)
- params := url.Values{}
- params.Add("f", "json")
- path.RawQuery = params.Encode()
- sate, err := file.Stat()
- if err != nil {
- return nil, err
- }
- cookies := c.Jar.Cookies(path)
- webWxDataTicket := getWebWxDataTicket(cookies)
// 将文件的游标复原到原点
// 上面获取文件的类型的时候已经读取了512个字节
@@ -363,6 +271,20 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
data := buffer.Bytes()
fileMd5 := fmt.Sprintf("%x", md5.Sum(data))
+ sate, err := file.Stat()
+ if err != nil {
+ return nil, err
+ }
+
+ path, _ := url.Parse(c.webWxUpLoadMediaUrl)
+ params := url.Values{}
+ params.Add("f", "json")
+
+ path.RawQuery = params.Encode()
+
+ cookies := c.Jar.Cookies(path)
+ webWxDataTicket := getWebWxDataTicket(cookies)
+
uploadMediaRequest := map[string]interface{}{
"UploadType": 2,
"BaseRequest": request,
@@ -375,18 +297,40 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
"ToUserName": toUserName,
"FileMd5": fileMd5,
}
+
uploadMediaRequestByte, err := json.Marshal(uploadMediaRequest)
if err != nil {
return nil, err
}
- chunks := sate.Size() / chunkSize
- if chunks*chunkSize < sate.Size() {
- chunks++
+ var chunks int64
+
+ if sate.Size() > chunkSize {
+ chunks = sate.Size() / chunkSize
+ if chunks*chunkSize < sate.Size() {
+ chunks++
+ }
+ } else {
+ chunks = 1
}
var resp *http.Response
+ content := map[string]string{
+ "id": "WU_FILE_0",
+ "name": file.Name(),
+ "type": contentType,
+ "lastModifiedDate": sate.ModTime().Format(TimeFormat),
+ "size": strconv.FormatInt(sate.Size(), 10),
+ "mediatype": mediaType,
+ "webwx_data_ticket": webWxDataTicket,
+ "pass_ticket": info.PassTicket,
+ }
+
+ if chunks > 1 {
+ content["chunks"] = strconv.FormatInt(chunks, 10)
+ }
+ // 分块上传
for chunk := 0; int64(chunk) < chunks; chunk++ {
var isLastTime bool
@@ -394,18 +338,10 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
isLastTime = true
}
- content := map[string]string{
- "id": "WU_FILE_0",
- "name": file.Name(),
- "type": contentType,
- "lastModifiedDate": sate.ModTime().Format(TimeFormat),
- "size": strconv.FormatInt(sate.Size(), 10),
- "mediatype": mediaType,
- "webwx_data_ticket": webWxDataTicket,
- "pass_ticket": info.PassTicket,
- "chunks": strconv.FormatInt(chunks, 10),
- "chunk": strconv.Itoa(chunk),
+ if chunks > 1 {
+ content["chunk"] = strconv.Itoa(chunk)
}
+
var formBuffer bytes.Buffer
writer := multipart.NewWriter(&formBuffer)
@@ -450,7 +386,7 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
}
}
}
-
+ // 将最后一次携带文件信息的response返回
return resp, err
}
@@ -645,3 +581,21 @@ func (c *Client) WebWxRevokeMsg(msg *SentMessage, request *BaseRequest) (*http.R
req.Header.Set("Content-Type", jsonContentType)
return c.Do(req)
}
+
+// 校验上传文件
+func (c *Client) webWxCheckUpload(stat os.FileInfo, request *BaseRequest, fileMd5, fromUserName, toUserName string) (*http.Response, error) {
+ path, _ := url.Parse(c.webWxCheckUploadUrl)
+ content := map[string]interface{}{
+ "BaseRequest": request,
+ "FileMd5": fileMd5,
+ "FileName": stat.Name(),
+ "FileSize": stat.Size(),
+ "FileType": 7,
+ "FromUserName": fromUserName,
+ "ToUserName": toUserName,
+ }
+ body, _ := ToBuffer(content)
+ req, _ := http.NewRequest(http.MethodPost, path.String(), body)
+ req.Header.Add("Content-Type", jsonContentType)
+ return c.Do(req)
+}
diff --git a/global.go b/global.go
index 765d136..1fc90bd 100644
--- a/global.go
+++ b/global.go
@@ -1,94 +1,96 @@
package openwechat
import (
- "errors"
- "regexp"
+ "errors"
+ "regexp"
)
var (
- uuidRegexp = regexp.MustCompile(`uuid = "(.*?)";`)
- statusCodeRegexp = regexp.MustCompile(`window.code=(\d+);`)
- syncCheckRegexp = regexp.MustCompile(`window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}`)
- redirectUriRegexp = regexp.MustCompile(`window.redirect_uri="(.*?)"`)
+ uuidRegexp = regexp.MustCompile(`uuid = "(.*?)";`)
+ statusCodeRegexp = regexp.MustCompile(`window.code=(\d+);`)
+ syncCheckRegexp = regexp.MustCompile(`window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}`)
+ redirectUriRegexp = regexp.MustCompile(`window.redirect_uri="(.*?)"`)
)
const (
- appId = "wx782c26e4c19acffb"
- jsLoginUrl = "https://login.wx.qq.com/jslogin"
- qrcodeUrl = "https://login.weixin.qq.com/qrcode/"
- loginUrl = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login"
+ appId = "wx782c26e4c19acffb"
+ jsLoginUrl = "https://login.wx.qq.com/jslogin"
+ qrcodeUrl = "https://login.weixin.qq.com/qrcode/"
+ loginUrl = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login"
- // Normal urls
+ // Normal urls
- baseNormalUrl = "https://wx2.qq.com"
- webWxNewLoginPageNormalUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage"
- webWxInitNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit"
- webWxStatusNotifyNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify"
- webWxSyncNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync"
- webWxSendMsgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg"
- webWxGetContactNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact"
- webWxSendMsgImgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsgimg"
- webWxSendAppMsgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendappmsg"
- webWxBatchGetContactNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact"
- webWxOplogNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxoplog"
- webWxVerifyUserNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxverifyuser"
- syncCheckNormalUrl = "https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck"
- webWxUpLoadMediaNormalUrl = "https://file.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia"
- webWxGetMsgImgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetmsgimg"
- webWxGetVoiceNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetvoice"
- webWxGetVideoNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetvideo"
- webWxLogoutNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxlogout"
- webWxGetMediaNormalUrl = "https://file.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetmedia"
- webWxUpdateChatRoomNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxupdatechatroom"
- webWxRevokeMsgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxrevokemsg"
+ baseNormalUrl = "https://wx2.qq.com"
+ webWxNewLoginPageNormalUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage"
+ webWxInitNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit"
+ webWxStatusNotifyNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify"
+ webWxSyncNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync"
+ webWxSendMsgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg"
+ webWxGetContactNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact"
+ webWxSendMsgImgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsgimg"
+ webWxSendAppMsgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendappmsg"
+ webWxBatchGetContactNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact"
+ webWxOplogNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxoplog"
+ webWxVerifyUserNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxverifyuser"
+ syncCheckNormalUrl = "https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck"
+ webWxUpLoadMediaNormalUrl = "https://file.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia"
+ webWxGetMsgImgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetmsgimg"
+ webWxGetVoiceNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetvoice"
+ webWxGetVideoNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetvideo"
+ webWxLogoutNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxlogout"
+ webWxGetMediaNormalUrl = "https://file.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetmedia"
+ webWxUpdateChatRoomNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxupdatechatroom"
+ webWxRevokeMsgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxrevokemsg"
+ webWxCheckUploadNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxcheckupload"
- // Desktop urls
+ // Desktop urls
- baseDesktopUrl = "https://wx.qq.com"
- webWxNewLoginPageDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?mod=desktop"
- webWxInitDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit"
- webWxStatusNotifyDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify"
- webWxSyncDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync"
- webWxSendMsgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg"
- webWxGetContactDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact"
- webWxSendMsgImgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsgimg"
- webWxSendAppMsgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendappmsg"
- webWxBatchGetContactDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact"
- webWxOplogDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxoplog"
- webWxVerifyUserDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxverifyuser"
- syncCheckDesktopUrl = "https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck"
- webWxUpLoadMediaDesktopUrl = "https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia"
- webWxGetMsgImgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmsgimg"
- webWxGetVoiceDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetvoice"
- webWxGetVideoDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetvideo"
- webWxLogoutDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxlogout"
- webWxGetMediaDesktopUrl = "https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmedia"
- webWxUpdateChatRoomDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxupdatechatroom"
- webWxRevokeMsgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxrevokemsg"
+ baseDesktopUrl = "https://wx.qq.com"
+ webWxNewLoginPageDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?mod=desktop"
+ webWxInitDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit"
+ webWxStatusNotifyDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify"
+ webWxSyncDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync"
+ webWxSendMsgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg"
+ webWxGetContactDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact"
+ webWxSendMsgImgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsgimg"
+ webWxSendAppMsgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendappmsg"
+ webWxBatchGetContactDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact"
+ webWxOplogDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxoplog"
+ webWxVerifyUserDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxverifyuser"
+ syncCheckDesktopUrl = "https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck"
+ webWxUpLoadMediaDesktopUrl = "https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia"
+ webWxGetMsgImgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmsgimg"
+ webWxGetVoiceDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetvoice"
+ webWxGetVideoDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetvideo"
+ webWxLogoutDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxlogout"
+ webWxGetMediaDesktopUrl = "https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmedia"
+ webWxUpdateChatRoomDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxupdatechatroom"
+ webWxRevokeMsgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxrevokemsg"
+ webWxCheckUploadDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxcheckupload"
- jsonContentType = "application/json; charset=utf-8"
- uosPatchClientVersion = "2.0.0"
- uosPatchExtspam = "Gp8ICJkIEpkICggwMDAwMDAwMRAGGoAI1GiJSIpeO1RZTq9QBKsRbPJdi84ropi16EYI10WB6g74sGmRwSNXjPQnYUKYotKkvLGpshucCaeWZMOylnc6o2AgDX9grhQQx7fm2DJRTyuNhUlwmEoWhjoG3F0ySAWUsEbH3bJMsEBwoB//0qmFJob74ffdaslqL+IrSy7LJ76/G5TkvNC+J0VQkpH1u3iJJs0uUYyLDzdBIQ6Ogd8LDQ3VKnJLm4g/uDLe+G7zzzkOPzCjXL+70naaQ9medzqmh+/SmaQ6uFWLDQLcRln++wBwoEibNpG4uOJvqXy+ql50DjlNchSuqLmeadFoo9/mDT0q3G7o/80P15ostktjb7h9bfNc+nZVSnUEJXbCjTeqS5UYuxn+HTS5nZsPVxJA2O5GdKCYK4x8lTTKShRstqPfbQpplfllx2fwXcSljuYi3YipPyS3GCAqf5A7aYYwJ7AvGqUiR2SsVQ9Nbp8MGHET1GxhifC692APj6SJxZD3i1drSYZPMMsS9rKAJTGz2FEupohtpf2tgXm6c16nDk/cw+C7K7me5j5PLHv55DFCS84b06AytZPdkFZLj7FHOkcFGJXitHkX5cgww7vuf6F3p0yM/W73SoXTx6GX4G6Hg2rYx3O/9VU2Uq8lvURB4qIbD9XQpzmyiFMaytMnqxcZJcoXCtfkTJ6pI7a92JpRUvdSitg967VUDUAQnCXCM/m0snRkR9LtoXAO1FUGpwlp1EfIdCZFPKNnXMeqev0j9W9ZrkEs9ZWcUEexSj5z+dKYQBhIICviYUQHVqBTZSNy22PlUIeDeIs11j7q4t8rD8LPvzAKWVqXE+5lS1JPZkjg4y5hfX1Dod3t96clFfwsvDP6xBSe1NBcoKbkyGxYK0UvPGtKQEE0Se2zAymYDv41klYE9s+rxp8e94/H8XhrL9oGm8KWb2RmYnAE7ry9gd6e8ZuBRIsISlJAE/e8y8xFmP031S6Lnaet6YXPsFpuFsdQs535IjcFd75hh6DNMBYhSfjv456cvhsb99+fRw/KVZLC3yzNSCbLSyo9d9BI45Plma6V8akURQA/qsaAzU0VyTIqZJkPDTzhuCl92vD2AD/QOhx6iwRSVPAxcRFZcWjgc2wCKh+uCYkTVbNQpB9B90YlNmI3fWTuUOUjwOzQRxJZj11NsimjOJ50qQwTTFj6qQvQ1a/I+MkTx5UO+yNHl718JWcR3AXGmv/aa9rD1eNP8ioTGlOZwPgmr2sor2iBpKTOrB83QgZXP+xRYkb4zVC+LoAXEoIa1+zArywlgREer7DLePukkU6wHTkuSaF+ge5Of1bXuU4i938WJHj0t3D8uQxkJvoFi/EYN/7u2P1zGRLV4dHVUsZMGCCtnO6BBigFMAA="
+ jsonContentType = "application/json; charset=utf-8"
+ uosPatchClientVersion = "2.0.0"
+ uosPatchExtspam = "Gp8ICJkIEpkICggwMDAwMDAwMRAGGoAI1GiJSIpeO1RZTq9QBKsRbPJdi84ropi16EYI10WB6g74sGmRwSNXjPQnYUKYotKkvLGpshucCaeWZMOylnc6o2AgDX9grhQQx7fm2DJRTyuNhUlwmEoWhjoG3F0ySAWUsEbH3bJMsEBwoB//0qmFJob74ffdaslqL+IrSy7LJ76/G5TkvNC+J0VQkpH1u3iJJs0uUYyLDzdBIQ6Ogd8LDQ3VKnJLm4g/uDLe+G7zzzkOPzCjXL+70naaQ9medzqmh+/SmaQ6uFWLDQLcRln++wBwoEibNpG4uOJvqXy+ql50DjlNchSuqLmeadFoo9/mDT0q3G7o/80P15ostktjb7h9bfNc+nZVSnUEJXbCjTeqS5UYuxn+HTS5nZsPVxJA2O5GdKCYK4x8lTTKShRstqPfbQpplfllx2fwXcSljuYi3YipPyS3GCAqf5A7aYYwJ7AvGqUiR2SsVQ9Nbp8MGHET1GxhifC692APj6SJxZD3i1drSYZPMMsS9rKAJTGz2FEupohtpf2tgXm6c16nDk/cw+C7K7me5j5PLHv55DFCS84b06AytZPdkFZLj7FHOkcFGJXitHkX5cgww7vuf6F3p0yM/W73SoXTx6GX4G6Hg2rYx3O/9VU2Uq8lvURB4qIbD9XQpzmyiFMaytMnqxcZJcoXCtfkTJ6pI7a92JpRUvdSitg967VUDUAQnCXCM/m0snRkR9LtoXAO1FUGpwlp1EfIdCZFPKNnXMeqev0j9W9ZrkEs9ZWcUEexSj5z+dKYQBhIICviYUQHVqBTZSNy22PlUIeDeIs11j7q4t8rD8LPvzAKWVqXE+5lS1JPZkjg4y5hfX1Dod3t96clFfwsvDP6xBSe1NBcoKbkyGxYK0UvPGtKQEE0Se2zAymYDv41klYE9s+rxp8e94/H8XhrL9oGm8KWb2RmYnAE7ry9gd6e8ZuBRIsISlJAE/e8y8xFmP031S6Lnaet6YXPsFpuFsdQs535IjcFd75hh6DNMBYhSfjv456cvhsb99+fRw/KVZLC3yzNSCbLSyo9d9BI45Plma6V8akURQA/qsaAzU0VyTIqZJkPDTzhuCl92vD2AD/QOhx6iwRSVPAxcRFZcWjgc2wCKh+uCYkTVbNQpB9B90YlNmI3fWTuUOUjwOzQRxJZj11NsimjOJ50qQwTTFj6qQvQ1a/I+MkTx5UO+yNHl718JWcR3AXGmv/aa9rD1eNP8ioTGlOZwPgmr2sor2iBpKTOrB83QgZXP+xRYkb4zVC+LoAXEoIa1+zArywlgREer7DLePukkU6wHTkuSaF+ge5Of1bXuU4i938WJHj0t3D8uQxkJvoFi/EYN/7u2P1zGRLV4dHVUsZMGCCtnO6BBigFMAA="
)
// 消息类型
const (
- TextMessage = 1
- ImageMessage = 3
- AppMessage = 6
+ TextMessage = 1
+ ImageMessage = 3
+ AppMessage = 6
)
// 登录状态
const (
- statusSuccess = "200"
- statusScanned = "201"
- statusTimeout = "400"
- statusWait = "408"
+ statusSuccess = "200"
+ statusScanned = "201"
+ statusTimeout = "400"
+ statusWait = "408"
)
// errors
var (
- noSuchUserFoundError = errors.New("no such user found")
+ noSuchUserFoundError = errors.New("no such user found")
)
// ALL跟search函数搭配
@@ -97,10 +99,19 @@ const ALL = 0
// 性别
const (
- MALE = 1
- FEMALE = 2
+ MALE = 1
+ FEMALE = 2
)
-const chunkSize int64 = 512 * 1024
+const (
+ // 分块上传时每次上传的文件的大小
+ chunkSize int64 = (1 << 20) / 2 // 0.5m
+ // 需要检测的文件大小
+ needCheckSize int64 = 25 << 20 // 20m
+ // 最大文件上传大小
+ maxFileUploadSize int64 = 50 << 20 // 50m
+ // 最大图片上传大小
+ maxImageUploadSize int64 = 20 << 20 // 20m
+)
const TimeFormat = "Mon Jan 02 2006 15:04:05 GMT+0800 (中国标准时间)"
diff --git a/items.go b/items.go
index d5f8c07..a1f4d41 100644
--- a/items.go
+++ b/items.go
@@ -1,8 +1,8 @@
package openwechat
import (
- "errors"
- "fmt"
+ "errors"
+ "fmt"
)
/*
@@ -11,194 +11,199 @@ import (
// 登录信息
type LoginInfo struct {
- Ret int `xml:"ret"`
- WxUin int `xml:"wxuin"`
- IsGrayScale int `xml:"isgrayscale"`
- Message string `xml:"message"`
- SKey string `xml:"skey"`
- WxSid string `xml:"wxsid"`
- PassTicket string `xml:"pass_ticket"`
+ Ret int `xml:"ret"`
+ WxUin int `xml:"wxuin"`
+ IsGrayScale int `xml:"isgrayscale"`
+ Message string `xml:"message"`
+ SKey string `xml:"skey"`
+ WxSid string `xml:"wxsid"`
+ PassTicket string `xml:"pass_ticket"`
}
func (l LoginInfo) Ok() bool {
- return l.Ret == 0
+ return l.Ret == 0
}
func (l LoginInfo) Error() string {
- return l.Message
+ return l.Message
}
// 初始的请求信息
// 几乎所有的请求都要携带该参数
type BaseRequest struct {
- Uin int
- Sid, Skey, DeviceID string
+ Uin int
+ Sid, Skey, DeviceID string
}
// 大部分返回对象都携带该信息
type BaseResponse struct {
- Ret int
- ErrMsg string
+ Ret int
+ ErrMsg string
}
func (b BaseResponse) Ok() bool {
- return b.Ret == 0
+ return b.Ret == 0
}
func (b BaseResponse) Error() string {
- if err := getResponseErrorWithRetCode(b.Ret); err != nil {
- return err.Error()
- }
- return ""
+ if err := getResponseErrorWithRetCode(b.Ret); err != nil {
+ return err.Error()
+ }
+ return ""
}
func getResponseErrorWithRetCode(code int) error {
- switch code {
- case 0:
- return nil
- case 1:
- return errors.New("param error")
- case -14:
- return errors.New("ticket error")
- case 1100:
- return errors.New("not login warn")
- case 1101:
- return errors.New("not login check")
- case 1102:
- return errors.New("cookie invalid error")
- case 1203:
- return errors.New("login env error")
- case 1205:
- return errors.New("opt too often")
- default:
- return fmt.Errorf("base response ret code %d", code)
- }
+ switch code {
+ case 0:
+ return nil
+ case 1:
+ return errors.New("param error")
+ case -14:
+ return errors.New("ticket error")
+ case 1100:
+ return errors.New("not login warn")
+ case 1101:
+ return errors.New("not login check")
+ case 1102:
+ return errors.New("cookie invalid error")
+ case 1203:
+ return errors.New("login env error")
+ case 1205:
+ return errors.New("opt too often")
+ default:
+ return fmt.Errorf("base response ret code %d", code)
+ }
}
type SyncKey struct {
- Count int
- List []struct{ Key, Val int64 }
+ Count int
+ List []struct{ Key, Val int64 }
}
// 初始化的相应信息
type WebInitResponse struct {
- Count int
- ClientVersion int
- GrayScale int
- InviteStartCount int
- MPSubscribeMsgCount int
- ClickReportInterval int
- SystemTime int64
- ChatSet string
- SKey string
- BaseResponse BaseResponse
- SyncKey SyncKey
- User User
- MPSubscribeMsgList []MPSubscribeMsg
- ContactList []User
+ Count int
+ ClientVersion int
+ GrayScale int
+ InviteStartCount int
+ MPSubscribeMsgCount int
+ ClickReportInterval int
+ SystemTime int64
+ ChatSet string
+ SKey string
+ BaseResponse BaseResponse
+ SyncKey SyncKey
+ User User
+ MPSubscribeMsgList []MPSubscribeMsg
+ ContactList []User
}
// 公众号的订阅信息
type MPSubscribeMsg struct {
- MPArticleCount int
- Time int64
- UserName string
- NickName string
- MPArticleList []struct {
- Title string
- Cover string
- Digest string
- Url string
- }
+ MPArticleCount int
+ Time int64
+ UserName string
+ NickName string
+ MPArticleList []struct {
+ Title string
+ Cover string
+ Digest string
+ Url string
+ }
}
type UserDetailItem struct {
- UserName string
- EncryChatRoomId string
+ UserName string
+ EncryChatRoomId string
}
type UserDetailItemList []UserDetailItem
func NewUserDetailItemList(members Members) UserDetailItemList {
- var list UserDetailItemList
- for _, member := range members {
- item := UserDetailItem{UserName: member.UserName, EncryChatRoomId: member.EncryChatRoomId}
- list = append(list, item)
- }
- return list
+ var list UserDetailItemList
+ for _, member := range members {
+ item := UserDetailItem{UserName: member.UserName, EncryChatRoomId: member.EncryChatRoomId}
+ list = append(list, item)
+ }
+ return list
}
type SyncCheckResponse struct {
- RetCode string
- Selector string
+ RetCode string
+ Selector string
}
func (s *SyncCheckResponse) Success() bool {
- return s.RetCode == "0"
+ return s.RetCode == "0"
}
func (s *SyncCheckResponse) NorMal() bool {
- return s.Success() && s.Selector == "0"
+ return s.Success() && s.Selector == "0"
}
// 实现error接口
func (s *SyncCheckResponse) Error() string {
- switch s.RetCode {
- case "0":
- return ""
- case "1":
- return "param error"
- case "-14":
- return "ticker error"
- case "1100":
- return "not login warn"
- case "1101":
- return "not login check"
- case "1102":
- return "cookie invalid error"
- case "1203":
- return "login env error"
- case "1205":
- return "opt too often"
- default:
- return fmt.Sprintf("sync check response error code %s", s.RetCode)
- }
+ switch s.RetCode {
+ case "0":
+ return ""
+ case "1":
+ return "param error"
+ case "-14":
+ return "ticker error"
+ case "1100":
+ return "not login warn"
+ case "1101":
+ return "not login check"
+ case "1102":
+ return "cookie invalid error"
+ case "1203":
+ return "login env error"
+ case "1205":
+ return "opt too often"
+ default:
+ return fmt.Sprintf("sync check response error code %s", s.RetCode)
+ }
}
type WebWxSyncResponse struct {
- AddMsgCount int
- ContinueFlag int
- DelContactCount int
- ModChatRoomMemberCount int
- ModContactCount int
- Skey string
- SyncCheckKey SyncKey
- SyncKey SyncKey
- BaseResponse BaseResponse
- ModChatRoomMemberList Members
- AddMsgList []*Message
+ AddMsgCount int
+ ContinueFlag int
+ DelContactCount int
+ ModChatRoomMemberCount int
+ ModContactCount int
+ Skey string
+ SyncCheckKey SyncKey
+ SyncKey SyncKey
+ BaseResponse BaseResponse
+ ModChatRoomMemberList Members
+ AddMsgList []*Message
}
type WebWxContactResponse struct {
- MemberCount int
- Seq int
- BaseResponse BaseResponse
- MemberList []*User
+ MemberCount int
+ Seq int
+ BaseResponse BaseResponse
+ MemberList []*User
}
type WebWxBatchContactResponse struct {
- Count int
- BaseResponse BaseResponse
- ContactList []*User
+ Count int
+ BaseResponse BaseResponse
+ ContactList []*User
}
type CheckLoginResponse struct {
- Code string
- Raw []byte
+ Code string
+ Raw []byte
}
type MessageResponse struct {
- BaseResponse BaseResponse
- LocalID string
- MsgID string
+ BaseResponse BaseResponse
+ LocalID string
+ MsgID string
+}
+
+type UploadResponse struct {
+ BaseResponse BaseResponse
+ MediaId string
}
diff --git a/message.go b/message.go
index 91b98cd..105ddf8 100644
--- a/message.go
+++ b/message.go
@@ -1,473 +1,504 @@
package openwechat
import (
- "context"
- "encoding/xml"
- "errors"
- "fmt"
- "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) (*SentMessage, 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) (*SentMessage, error) {
- return m.Reply(TextMessage, content, "")
+ return m.Reply(TextMessage, content, "")
}
// 回复图片消息
func (m *Message) ReplyImage(file *os.File) (*SentMessage, 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 && m.FromUserName == "fmessage"
+ 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) IsTransferAccounts() bool {
- return m.IsMedia() && m.FileName == "微信转账"
+ return m.IsMedia() && m.FileName == "微信转账"
}
// 判断当前是否发出红包
func (m *Message) IsSendRedPacket() bool {
- return m.IsSystem() && m.Content == "发出红包,请在手机上查看"
+ return m.IsSystem() && m.Content == "发出红包,请在手机上查看"
}
// 判断当前是否收到红包
func (m *Message) IsReceiveRedPacket() bool {
- return m.IsSystem() && m.Content == "收到红包,请在手机上查看"
+ return m.IsSystem() && m.Content == "收到红包,请在手机上查看"
}
func (m *Message) IsSysNotice() bool {
- return m.MsgType == 9999
+ return m.MsgType == 9999
}
// 判断是否为操作通知消息
func (m *Message) StatusNotify() bool {
- return m.MsgType == 51
+ return m.MsgType == 51
}
// 判断消息是否为文件类型的消息
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() (*FriendAddMessage, error) {
- if !m.IsFriendAdd() {
- return nil, errors.New("friend add message required")
- }
- var f FriendAddMessage
- content := XmlFormString(m.Content)
- err := xml.Unmarshal([]byte(content), &f)
- return &f, err
+ if !m.IsFriendAdd() {
+ return nil, errors.New("friend add message required")
+ }
+ var f FriendAddMessage
+ content := XmlFormString(m.Content)
+ err := xml.Unmarshal([]byte(content), &f)
+ return &f, err
}
// 获取撤回消息的内容
func (m *Message) RevokeMsg() (*RevokeMsg, error) {
- if !m.IsRecalled() {
- return nil, errors.New("recalled message required")
- }
- var r RevokeMsg
- content := XmlFormString(m.Content)
- err := xml.Unmarshal([]byte(content), &r)
- return &r, err
+ if !m.IsRecalled() {
+ return nil, errors.New("recalled message required")
+ }
+ var r RevokeMsg
+ content := XmlFormString(m.Content)
+ err := xml.Unmarshal([]byte(content), &r)
+ return &r, 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())
+ 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:]
+ }
+ }
+ }
}
// 发送消息的结构体
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().UnixNano()/1e2, 10)
- return &SendMessage{
- Type: msgType,
- Content: content,
- FromUserName: fromUserName,
- ToUserName: toUserName,
- LocalID: id,
- ClientMsgId: id,
- MediaId: mediaId,
- }
+ id := strconv.FormatInt(time.Now().UnixNano()/1e2, 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 FriendAddMessage 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"`
+ 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"`
}
// 撤回消息Content
type RevokeMsg struct {
- SysMsg xml.Name `xml:"sysmsg"`
- Type string `xml:"type,attr"`
- RevokeMsg struct {
- OldMsgId int64 `xml:"oldmsgid"`
- MsgId int64 `xml:"msgid"`
- Session string `xml:"session"`
- ReplaceMsg string `xml:"replacemsg"`
- } `xml:"revokemsg"`
+ SysMsg xml.Name `xml:"sysmsg"`
+ Type string `xml:"type,attr"`
+ RevokeMsg struct {
+ OldMsgId int64 `xml:"oldmsgid"`
+ MsgId int64 `xml:"msgid"`
+ Session string `xml:"session"`
+ ReplaceMsg string `xml:"replacemsg"`
+ } `xml:"revokemsg"`
}
// 已发送的信息
type SentMessage struct {
- *SendMessage
- Self *Self
- MsgId string
+ *SendMessage
+ Self *Self
+ MsgId string
}
// 撤回该消息
func (s *SentMessage) Revoke() error {
- return s.Self.RevokeMessage(s)
+ return s.Self.RevokeMessage(s)
+}
+
+type FileAppMessage struct {
+ AppMsg xml.Name `xml:"appmsg"`
+ Type int `xml:"type"`
+ AppId string `xml:"appid,attr"` // wxeb7ec651dd0aefa9
+ SdkVer string `xml:"sdkver,attr"`
+ Title string `xml:"title"`
+ Des string `xml:"des"`
+ Action string `xml:"action"`
+ Content string `xml:"content"`
+ Url string `xml:"url"`
+ LowUrl string `xml:"lowurl"`
+ ExtInfo string `xml:"extinfo"`
+ AppAttach struct {
+ TotalLen int64 `xml:"totallen"`
+ AttachId string `xml:"attachid"`
+ FileExt string `xml:"fileext"`
+ } `xml:"appattach"`
+}
+
+func (f FileAppMessage) XmlByte() ([]byte, error) {
+ return xml.Marshal(f)
+}
+
+func NewFileAppMessage(stat os.FileInfo, attachId string) *FileAppMessage {
+ m := &FileAppMessage{AppId: "wxeb7ec651dd0aefa9", Title: stat.Name()}
+ m.AppAttach.AttachId = attachId
+ m.AppAttach.TotalLen = stat.Size()
+ m.AppAttach.FileExt = getFileExt(stat.Name())
+ return m
}
diff --git a/parser.go b/parser.go
index a2c3e3d..1fb625c 100644
--- a/parser.go
+++ b/parser.go
@@ -1,66 +1,74 @@
package openwechat
import (
- "bytes"
- "encoding/json"
- "math/rand"
- "mime/multipart"
- "net/http"
- "strconv"
- "strings"
- "time"
+ "bytes"
+ "encoding/json"
+ "math/rand"
+ "mime/multipart"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
)
func ToBuffer(v interface{}) (*bytes.Buffer, error) {
- buf, err := json.Marshal(v)
- if err != nil {
- return nil, err
- }
- return bytes.NewBuffer(buf), nil
+ buf, err := json.Marshal(v)
+ if err != nil {
+ return nil, err
+ }
+ return bytes.NewBuffer(buf), nil
}
// 获取随机设备id
func GetRandomDeviceId() string {
- rand.Seed(time.Now().Unix())
- var builder strings.Builder
- builder.WriteString("e")
- for i := 0; i < 15; i++ {
- r := rand.Intn(9)
- builder.WriteString(strconv.Itoa(r))
- }
- return builder.String()
+ rand.Seed(time.Now().Unix())
+ var builder strings.Builder
+ builder.WriteString("e")
+ for i := 0; i < 15; i++ {
+ r := rand.Intn(9)
+ builder.WriteString(strconv.Itoa(r))
+ }
+ return builder.String()
}
func getWebWxDataTicket(cookies []*http.Cookie) string {
- for _, cookie := range cookies {
- if cookie.Name == "webwx_data_ticket" {
- return cookie.Value
- }
- }
- return ""
+ for _, cookie := range cookies {
+ if cookie.Name == "webwx_data_ticket" {
+ return cookie.Value
+ }
+ }
+ return ""
}
// Form Xml 格式化
func XmlFormString(text string) string {
- lt := strings.ReplaceAll(text, "<", "<")
- gt := strings.ReplaceAll(lt, ">", ">")
- br := strings.ReplaceAll(gt, "
", "\n")
- return strings.ReplaceAll(br, "&", "&")
+ lt := strings.ReplaceAll(text, "<", "<")
+ gt := strings.ReplaceAll(lt, ">", ">")
+ br := strings.ReplaceAll(gt, "
", "\n")
+ return strings.ReplaceAll(br, "&", "&")
}
func getTotalDuration(delay ...time.Duration) time.Duration {
- var total time.Duration
- for _, d := range delay {
- total += d
- }
- return total
+ var total time.Duration
+ for _, d := range delay {
+ total += d
+ }
+ return total
}
// 获取文件上传的类型
func GetFileContentType(file multipart.File) (string, error) {
- data := make([]byte, 512)
- if _, err := file.Read(data); err != nil {
- return "", err
- }
- return http.DetectContentType(data), nil
+ data := make([]byte, 512)
+ if _, err := file.Read(data); err != nil {
+ return "", err
+ }
+ return http.DetectContentType(data), nil
+}
+
+func getFileExt(name string) string {
+ results := strings.Split(name, ".")
+ if len(results) == 0 {
+ return "undefined"
+ }
+ return results[len(results)-1]
}
diff --git a/url.go b/url.go
index 8f11727..42a539e 100644
--- a/url.go
+++ b/url.go
@@ -23,6 +23,7 @@ type UrlManager struct {
webWxGetMediaUrl string
webWxUpdateChatRoomUrl string
webWxRevokeMsg string
+ webWxCheckUploadUrl string
}
var (
@@ -49,6 +50,7 @@ var (
webWxGetMediaUrl: webWxGetMediaDesktopUrl,
webWxUpdateChatRoomUrl: webWxUpdateChatRoomDesktopUrl,
webWxRevokeMsg: webWxRevokeMsgDesktopUrl,
+ webWxCheckUploadUrl: webWxCheckUploadDesktopUrl,
}
// 网页版
@@ -74,6 +76,7 @@ var (
webWxGetMediaUrl: webWxGetMediaNormalUrl,
webWxUpdateChatRoomUrl: webWxUpdateChatRoomNormalUrl,
webWxRevokeMsg: webWxRevokeMsgNormalUrl,
+ webWxCheckUploadUrl: webWxCheckUploadNormalUrl,
}
)
diff --git a/user.go b/user.go
index 5e60b9d..a181f96 100644
--- a/user.go
+++ b/user.go
@@ -1,336 +1,342 @@
package openwechat
import (
- "bytes"
- "errors"
- "fmt"
- "net/http"
- "os"
- "strings"
+ "bytes"
+ "errors"
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
)
// 抽象的用户结构: 好友 群组 公众号
type User struct {
- Uin int
- HideInputBarFlag int
- StarFriend int
- Sex int
- AppAccountFlag int
- VerifyFlag int
- ContactFlag int
- WebWxPluginSwitch int
- HeadImgFlag int
- SnsFlag int
- IsOwner int
- MemberCount int
- ChatRoomId int
- UniFriend int
- OwnerUin int
- Statues int
- AttrStatus int
- Province string
- City string
- Alias string
- DisplayName string
- KeyWord string
- EncryChatRoomId string
- UserName string
- NickName string
- HeadImgUrl string
- RemarkName string
- PYInitial string
- PYQuanPin string
- RemarkPYInitial string
- RemarkPYQuanPin string
- Signature string
+ Uin int
+ HideInputBarFlag int
+ StarFriend int
+ Sex int
+ AppAccountFlag int
+ VerifyFlag int
+ ContactFlag int
+ WebWxPluginSwitch int
+ HeadImgFlag int
+ SnsFlag int
+ IsOwner int
+ MemberCount int
+ ChatRoomId int
+ UniFriend int
+ OwnerUin int
+ Statues int
+ AttrStatus int
+ Province string
+ City string
+ Alias string
+ DisplayName string
+ KeyWord string
+ EncryChatRoomId string
+ UserName string
+ NickName string
+ HeadImgUrl string
+ RemarkName string
+ PYInitial string
+ PYQuanPin string
+ RemarkPYInitial string
+ RemarkPYQuanPin string
+ Signature string
- MemberList Members
+ MemberList Members
- Self *Self
+ Self *Self
}
// implement fmt.Stringer
func (u *User) String() string {
- return fmt.Sprintf("", u.NickName)
+ return fmt.Sprintf("", u.NickName)
}
// 获取用户头像
func (u *User) GetAvatarResponse() (*http.Response, error) {
- return u.Self.Bot.Caller.Client.WebWxGetHeadImg(u.HeadImgUrl)
+ return u.Self.Bot.Caller.Client.WebWxGetHeadImg(u.HeadImgUrl)
}
// 下载用户头像
func (u *User) SaveAvatar(filename string) error {
- resp, err := u.GetAvatarResponse()
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- buffer := bytes.Buffer{}
- if _, err := buffer.ReadFrom(resp.Body); err != nil {
- return err
- }
- file, err := os.Create(filename)
- if err != nil {
- return err
- }
- defer file.Close()
- _, err = file.Write(buffer.Bytes())
- return err
+ resp, err := u.GetAvatarResponse()
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ buffer := bytes.Buffer{}
+ if _, err := buffer.ReadFrom(resp.Body); err != nil {
+ return err
+ }
+ file, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ _, err = file.Write(buffer.Bytes())
+ return err
}
// 获取用户的详情
func (u *User) Detail() (*User, error) {
- members := Members{u}
- request := u.Self.Bot.storage.Request
- newMembers, err := u.Self.Bot.Caller.WebWxBatchGetContact(members, request)
- if err != nil {
- return nil, err
- }
- user := newMembers.First()
- user.Self = u.Self
- return user, nil
+ members := Members{u}
+ request := u.Self.Bot.storage.Request
+ newMembers, err := u.Self.Bot.Caller.WebWxBatchGetContact(members, request)
+ if err != nil {
+ return nil, err
+ }
+ user := newMembers.First()
+ user.Self = u.Self
+ return user, nil
}
// 判断是否为好友
func (u *User) IsFriend() bool {
- return !u.IsGroup() && strings.HasPrefix(u.UserName, "@") && u.VerifyFlag == 0
+ return !u.IsGroup() && strings.HasPrefix(u.UserName, "@") && u.VerifyFlag == 0
}
// 判断是否为群组
func (u *User) IsGroup() bool {
- return strings.HasPrefix(u.UserName, "@@") && u.VerifyFlag == 0
+ return strings.HasPrefix(u.UserName, "@@") && u.VerifyFlag == 0
}
// 判断是否为公众号
func (u *User) IsMP() bool {
- return u.VerifyFlag == 8 || u.VerifyFlag == 24 || u.VerifyFlag == 136
+ return u.VerifyFlag == 8 || u.VerifyFlag == 24 || u.VerifyFlag == 136
}
// 自己,当前登录用户对象
type Self struct {
- *User
- Bot *Bot
- fileHelper *Friend
- members Members
- friends Friends
- groups Groups
- mps Mps
+ *User
+ Bot *Bot
+ fileHelper *Friend
+ members Members
+ friends Friends
+ groups Groups
+ mps Mps
}
// 获取所有的好友、群组、公众号信息
func (s *Self) Members(update ...bool) (Members, error) {
- // 首先判断缓存里有没有,如果没有则去更新缓存
- if s.members == nil {
- if err := s.updateMembers(); err != nil {
- return nil, err
- }
- return s.members, nil
- }
- // 判断是否需要更新,如果传入的参数不为nil,则取最后一个
- if len(update) > 0 && update[0] {
- if err := s.updateMembers(); err != nil {
- return nil, err
- }
- }
- return s.members, nil
+ // 首先判断缓存里有没有,如果没有则去更新缓存
+ if s.members == nil {
+ if err := s.updateMembers(); err != nil {
+ return nil, err
+ }
+ return s.members, nil
+ }
+ // 判断是否需要更新,如果传入的参数不为nil,则取最后一个
+ if len(update) > 0 && update[0] {
+ if err := s.updateMembers(); err != nil {
+ return nil, err
+ }
+ }
+ return s.members, nil
}
// 更新联系人处理
func (s *Self) updateMembers() error {
- info := s.Bot.storage.LoginInfo
- members, err := s.Bot.Caller.WebWxGetContact(info)
- if err != nil {
- return err
- }
- members.SetOwner(s)
- s.members = members
- return nil
+ info := s.Bot.storage.LoginInfo
+ members, err := s.Bot.Caller.WebWxGetContact(info)
+ if err != nil {
+ return err
+ }
+ members.SetOwner(s)
+ s.members = members
+ return nil
}
// 获取文件传输助手对象,封装成Friend返回
// fh, err := self.FileHelper() // or fh := openwechat.NewFriendHelper(self)
func (s *Self) FileHelper() (*Friend, error) {
- // 如果缓存里有,直接返回,否则去联系人里面找
- if s.fileHelper != nil {
- return s.fileHelper, nil
- }
- members, err := s.Members()
- if err != nil {
- return nil, err
- }
- users := members.SearchByUserName(1, "filehelper")
- if users == nil {
- return NewFriendHelper(s), nil
- }
- s.fileHelper = &Friend{users.First()}
- return s.fileHelper, nil
+ // 如果缓存里有,直接返回,否则去联系人里面找
+ if s.fileHelper != nil {
+ return s.fileHelper, nil
+ }
+ members, err := s.Members()
+ if err != nil {
+ return nil, err
+ }
+ users := members.SearchByUserName(1, "filehelper")
+ if users == nil {
+ return NewFriendHelper(s), nil
+ }
+ s.fileHelper = &Friend{users.First()}
+ return s.fileHelper, nil
}
// 获取所有的好友
func (s *Self) Friends(update ...bool) (Friends, error) {
- if s.friends == nil || (len(update) > 0 && update[0]) {
- if _, err := s.Members(true); err != nil {
- return nil, err
- }
- s.friends = s.members.Friends()
- }
- return s.friends, nil
+ if s.friends == nil || (len(update) > 0 && update[0]) {
+ if _, err := s.Members(true); err != nil {
+ return nil, err
+ }
+ s.friends = s.members.Friends()
+ }
+ return s.friends, nil
}
// 获取所有的群组
func (s *Self) Groups(update ...bool) (Groups, error) {
- if s.groups == nil || (len(update) > 0 && update[0]) {
- if _, err := s.Members(true); err != nil {
- return nil, err
- }
- s.groups = s.members.Groups()
- }
- return s.groups, nil
+ if s.groups == nil || (len(update) > 0 && update[0]) {
+ if _, err := s.Members(true); err != nil {
+ return nil, err
+ }
+ s.groups = s.members.Groups()
+ }
+ return s.groups, nil
}
// 获取所有的公众号
func (s *Self) Mps(update ...bool) (Mps, error) {
- if s.mps == nil || (len(update) > 0 && update[0]) {
- if _, err := s.Members(true); err != nil {
- return nil, err
- }
- s.mps = s.members.MPs()
- }
- return s.mps, nil
+ if s.mps == nil || (len(update) > 0 && update[0]) {
+ if _, err := s.Members(true); err != nil {
+ return nil, err
+ }
+ s.mps = s.members.MPs()
+ }
+ return s.mps, nil
}
// 更新所有的联系人信息
func (s *Self) UpdateMembersDetail() error {
- // 先获取所有的联系人
- members, err := s.Members()
- if err != nil {
- return err
- }
- return members.detail(s)
+ // 先获取所有的联系人
+ members, err := s.Members()
+ if err != nil {
+ return err
+ }
+ return members.detail(s)
}
// 抽象发送消息接口
func (s *Self) sendMessageToUser(user *User, msg *SendMessage) (*SentMessage, error) {
- msg.FromUserName = s.UserName
- msg.ToUserName = user.UserName
- info := s.Bot.storage.LoginInfo
- request := s.Bot.storage.Request
- successSendMessage, err := s.Bot.Caller.WebWxSendMsg(msg, info, request)
- if err != nil {
- return nil, err
- }
- successSendMessage.Self = s
- return successSendMessage, nil
+ msg.FromUserName = s.UserName
+ msg.ToUserName = user.UserName
+ info := s.Bot.storage.LoginInfo
+ request := s.Bot.storage.Request
+ successSendMessage, err := s.Bot.Caller.WebWxSendMsg(msg, info, request)
+ if err != nil {
+ return nil, err
+ }
+ successSendMessage.Self = s
+ return successSendMessage, nil
}
// 发送消息给好友
func (s *Self) SendMessageToFriend(friend *Friend, msg *SendMessage) (*SentMessage, error) {
- return s.sendMessageToUser(friend.User, msg)
+ return s.sendMessageToUser(friend.User, msg)
}
// 发送文本消息给好友
func (s *Self) SendTextToFriend(friend *Friend, text string) (*SentMessage, error) {
- msg := NewTextSendMessage(text, s.UserName, friend.UserName)
- return s.SendMessageToFriend(friend, msg)
+ msg := NewTextSendMessage(text, s.UserName, friend.UserName)
+ return s.SendMessageToFriend(friend, msg)
}
// 发送图片消息给好友
func (s *Self) SendImageToFriend(friend *Friend, file *os.File) (*SentMessage, error) {
- req := s.Bot.storage.Request
- info := s.Bot.storage.LoginInfo
- return s.Bot.Caller.WebWxSendImageMsg(file, req, info, s.UserName, friend.UserName)
+ req := s.Bot.storage.Request
+ info := s.Bot.storage.LoginInfo
+ return s.Bot.Caller.WebWxSendImageMsg(file, req, info, s.UserName, friend.UserName)
+}
+
+func (s *Self) SendFileToFriend(friend *Friend, file *os.File) (*SentMessage, error) {
+ req := s.Bot.storage.Request
+ info := s.Bot.storage.LoginInfo
+ return s.Bot.Caller.WebWxSendFile(file, req, info, s.UserName, friend.UserName)
}
// 设置好友备注
// 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)
+ req := s.Bot.storage.Request
+ return s.Bot.Caller.WebWxOplog(req, remarkName, friend.UserName)
}
// 拉多名好友进群
// 最好自己是群主,成功率高一点,因为有的群允许非群组拉人,而有的群不允许
func (s *Self) AddFriendsIntoGroup(group *Group, friends ...*Friend) error {
- if len(friends) == 0 {
- return nil
- }
- // 获取群的所有的群员
- groupMembers, err := group.Members()
- if err != nil {
- return err
- }
- // 判断当前的成员在不在群里面
- for _, friend := range friends {
- for _, member := range groupMembers {
- if member.UserName == friend.UserName {
- return fmt.Errorf("user %s has alreay in this group", friend.String())
- }
- }
- }
- req := s.Bot.storage.Request
- info := s.Bot.storage.LoginInfo
- return s.Bot.Caller.AddFriendIntoChatRoom(req, info, group, friends...)
+ if len(friends) == 0 {
+ return nil
+ }
+ // 获取群的所有的群员
+ groupMembers, err := group.Members()
+ if err != nil {
+ return err
+ }
+ // 判断当前的成员在不在群里面
+ for _, friend := range friends {
+ for _, member := range groupMembers {
+ if member.UserName == friend.UserName {
+ return fmt.Errorf("user %s has alreay in this group", friend.String())
+ }
+ }
+ }
+ req := s.Bot.storage.Request
+ info := s.Bot.storage.LoginInfo
+ return s.Bot.Caller.AddFriendIntoChatRoom(req, info, group, friends...)
}
// 从群聊中移除用户
// Deprecated
// 无论是网页版,还是程序上都不起作用
func (s *Self) RemoveMemberFromGroup(group *Group, members Members) error {
- if len(members) == 0 {
- return nil
- }
- if group.IsOwner == 0 {
- return errors.New("group owner required")
- }
- groupMembers, err := group.Members()
- if err != nil {
- return err
- }
- // 判断用户是否在群聊中
- var count int
- for _, member := range members {
- for _, gm := range groupMembers {
- if gm.UserName == member.UserName {
- count++
- }
- }
- }
- if count != len(members) {
- return errors.New("invalid members")
- }
- req := s.Bot.storage.Request
- info := s.Bot.storage.LoginInfo
- return s.Bot.Caller.RemoveFriendFromChatRoom(req, info, group, members...)
+ if len(members) == 0 {
+ return nil
+ }
+ if group.IsOwner == 0 {
+ return errors.New("group owner required")
+ }
+ groupMembers, err := group.Members()
+ if err != nil {
+ return err
+ }
+ // 判断用户是否在群聊中
+ var count int
+ for _, member := range members {
+ for _, gm := range groupMembers {
+ if gm.UserName == member.UserName {
+ count++
+ }
+ }
+ }
+ if count != len(members) {
+ return errors.New("invalid members")
+ }
+ req := s.Bot.storage.Request
+ info := s.Bot.storage.LoginInfo
+ return s.Bot.Caller.RemoveFriendFromChatRoom(req, info, group, members...)
}
// 拉好友进多个群聊
// AddFriendIntoGroups, 名字和上面的有点像
func (s *Self) AddFriendIntoManyGroups(friend *Friend, groups ...*Group) error {
- for _, group := range groups {
- if err := s.AddFriendsIntoGroup(group, friend); err != nil {
- return err
- }
- }
- return nil
+ for _, group := range groups {
+ if err := s.AddFriendsIntoGroup(group, friend); err != nil {
+ return err
+ }
+ }
+ return nil
}
// 发送消息给群组
func (s *Self) SendMessageToGroup(group *Group, msg *SendMessage) (*SentMessage, error) {
- return s.sendMessageToUser(group.User, msg)
+ return s.sendMessageToUser(group.User, msg)
}
// 发送文本消息给群组
func (s *Self) SendTextToGroup(group *Group, text string) (*SentMessage, error) {
- msg := NewTextSendMessage(text, s.UserName, group.UserName)
- return s.SendMessageToGroup(group, msg)
+ msg := NewTextSendMessage(text, s.UserName, group.UserName)
+ return s.SendMessageToGroup(group, msg)
}
// 发送图片消息给群组
func (s *Self) SendImageToGroup(group *Group, file *os.File) (*SentMessage, error) {
- req := s.Bot.storage.Request
- info := s.Bot.storage.LoginInfo
- return s.Bot.Caller.WebWxSendImageMsg(file, req, info, s.UserName, group.UserName)
+ req := s.Bot.storage.Request
+ info := s.Bot.storage.LoginInfo
+ return s.Bot.Caller.WebWxSendImageMsg(file, req, info, s.UserName, group.UserName)
}
// 撤回消息
@@ -339,7 +345,7 @@ func (s *Self) SendImageToGroup(group *Group, file *os.File) (*SentMessage, erro
// self.RevokeMessage(sentMessage) // or sentMessage.Revoke()
// }
func (s *Self) RevokeMessage(msg *SentMessage) error {
- return s.Bot.Caller.WebWxRevokeMsg(msg, s.Bot.storage.Request)
+ return s.Bot.Caller.WebWxRevokeMsg(msg, s.Bot.storage.Request)
}
// 抽象的用户组
@@ -347,158 +353,158 @@ type Members []*User
// 统计数量
func (m Members) Count() int {
- return len(m)
+ return len(m)
}
// 获取第一个
func (m Members) First() *User {
- if m.Count() > 0 {
- u := m[0]
- return u
- }
- return nil
+ if m.Count() > 0 {
+ u := m[0]
+ return u
+ }
+ return nil
}
// 获取最后一个
func (m Members) Last() *User {
- if m.Count() > 0 {
- u := m[m.Count()-1]
- return u
- }
- return nil
+ if m.Count() > 0 {
+ u := m[m.Count()-1]
+ return u
+ }
+ return nil
}
// 设置owner
// 请不要随意设置
func (m Members) SetOwner(s *Self) {
- for _, member := range m {
- member.Self = s
- }
+ for _, member := range m {
+ member.Self = s
+ }
}
// 根据用户名查找
func (m Members) SearchByUserName(limit int, username string) (results Members) {
- return m.Search(limit, func(user *User) bool { return user.UserName == username })
+ return m.Search(limit, func(user *User) bool { return user.UserName == username })
}
// 根据昵称查找
func (m Members) SearchByNickName(limit int, nickName string) (results Members) {
- return m.Search(limit, func(user *User) bool { return user.NickName == nickName })
+ return m.Search(limit, func(user *User) bool { return user.NickName == nickName })
}
// 根据备注查找
func (m Members) SearchByRemarkName(limit int, remarkName string) (results Members) {
- return m.Search(limit, func(user *User) bool { return user.RemarkName == remarkName })
+ return m.Search(limit, func(user *User) bool { return user.RemarkName == remarkName })
}
// 根据自定义条件查找
func (m Members) Search(limit int, condFuncList ...func(user *User) bool) (results Members) {
- if condFuncList == nil {
- return m
- }
- if limit <= 0 {
- limit = m.Count()
- }
- for _, member := range m {
- if count := len(results); count == limit {
- break
- }
- var passCount int
- for _, condFunc := range condFuncList {
- if condFunc(member) {
- passCount++
- } else {
- break
- }
- }
- if passCount == len(condFuncList) {
- results = append(results, member)
- }
- }
- return
+ if condFuncList == nil {
+ return m
+ }
+ if limit <= 0 {
+ limit = m.Count()
+ }
+ for _, member := range m {
+ if count := len(results); count == limit {
+ break
+ }
+ var passCount int
+ for _, condFunc := range condFuncList {
+ if condFunc(member) {
+ passCount++
+ } else {
+ break
+ }
+ }
+ if passCount == len(condFuncList) {
+ results = append(results, member)
+ }
+ }
+ return
}
func (m Members) Friends() Friends {
- friends := make(Friends, 0)
- for _, mb := range m {
- if mb.IsFriend() {
- friend := &Friend{mb}
- friends = append(friends, friend)
- }
- }
- return friends
+ friends := make(Friends, 0)
+ for _, mb := range m {
+ if mb.IsFriend() {
+ friend := &Friend{mb}
+ friends = append(friends, friend)
+ }
+ }
+ return friends
}
func (m Members) Groups() Groups {
- groups := make(Groups, 0)
- for _, mb := range m {
- if mb.IsGroup() {
- group := &Group{mb}
- groups = append(groups, group)
- }
- }
- return groups
+ groups := make(Groups, 0)
+ for _, mb := range m {
+ if mb.IsGroup() {
+ group := &Group{mb}
+ groups = append(groups, group)
+ }
+ }
+ return groups
}
func (m Members) MPs() Mps {
- mps := make(Mps, 0)
- for _, mb := range m {
- if mb.IsMP() {
- mp := &Mp{mb}
- mps = append(mps, mp)
- }
- }
- return mps
+ mps := make(Mps, 0)
+ for _, mb := range m {
+ if mb.IsMP() {
+ mp := &Mp{mb}
+ mps = append(mps, mp)
+ }
+ }
+ return mps
}
// 获取当前Members的详情
func (m Members) detail(self *Self) error {
- // 获取他们的数量
- members := m
+ // 获取他们的数量
+ members := m
- count := members.Count()
- // 一次更新50个,分情况讨论
+ count := members.Count()
+ // 一次更新50个,分情况讨论
- // 获取总的需要更新的次数
- var times int
- if count < 50 {
- times = 1
- } else {
- times = count / 50
- }
- var newMembers Members
- request := self.Bot.storage.Request
- var pMembers Members
- // 分情况依次更新
- for i := 1; i <= times; i++ {
- if times == 1 {
- pMembers = members
- } else {
- pMembers = members[(i-1)*50 : i*50]
- }
- nMembers, err := self.Bot.Caller.WebWxBatchGetContact(pMembers, request)
- if err != nil {
- return err
- }
- newMembers = append(newMembers, nMembers...)
- }
- // 最后判断是否全部更新完毕
- total := times * 50
- if total < count {
- // 将全部剩余的更新完毕
- left := count - total
- pMembers = members[total : total+left]
- nMembers, err := self.Bot.Caller.WebWxBatchGetContact(pMembers, request)
- if err != nil {
- return err
- }
- newMembers = append(newMembers, nMembers...)
- }
- if len(newMembers) > 0 {
- newMembers.SetOwner(self)
- self.members = newMembers
- }
- return nil
+ // 获取总的需要更新的次数
+ var times int
+ if count < 50 {
+ times = 1
+ } else {
+ times = count / 50
+ }
+ var newMembers Members
+ request := self.Bot.storage.Request
+ var pMembers Members
+ // 分情况依次更新
+ for i := 1; i <= times; i++ {
+ if times == 1 {
+ pMembers = members
+ } else {
+ pMembers = members[(i-1)*50 : i*50]
+ }
+ nMembers, err := self.Bot.Caller.WebWxBatchGetContact(pMembers, request)
+ if err != nil {
+ return err
+ }
+ newMembers = append(newMembers, nMembers...)
+ }
+ // 最后判断是否全部更新完毕
+ total := times * 50
+ if total < count {
+ // 将全部剩余的更新完毕
+ left := count - total
+ pMembers = members[total : total+left]
+ nMembers, err := self.Bot.Caller.WebWxBatchGetContact(pMembers, request)
+ if err != nil {
+ return err
+ }
+ newMembers = append(newMembers, nMembers...)
+ }
+ if len(newMembers) > 0 {
+ newMembers.SetOwner(self)
+ self.members = newMembers
+ }
+ return nil
}
// 这里为了兼容Desktop版本找不到文件传输助手的问题
@@ -506,5 +512,5 @@ func (m Members) detail(self *Self) error {
// 这种形式的对象可能缺少一些其他属性
// 但是不影响发送信息的功能
func NewFriendHelper(self *Self) *Friend {
- return &Friend{&User{UserName: "filehelper", Self: self}}
+ return &Friend{&User{UserName: "filehelper", Self: self}}
}