添加文件发送接口
This commit is contained in:
parent
3c7fec07f3
commit
e751e13afd
26
bot_test.go
26
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)
|
||||
}
|
||||
|
67
caller.go
67
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
|
||||
}
|
||||
|
168
client.go
168
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)
|
||||
}
|
||||
|
147
global.go
147
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 (中国标准时间)"
|
||||
|
247
items.go
247
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
|
||||
}
|
||||
|
629
message.go
629
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, ":<br/>")
|
||||
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, ":<br/>")
|
||||
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
|
||||
}
|
||||
|
90
parser.go
90
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, "<br/>", "\n")
|
||||
return strings.ReplaceAll(br, "&amp;", "&")
|
||||
lt := strings.ReplaceAll(text, "<", "<")
|
||||
gt := strings.ReplaceAll(lt, ">", ">")
|
||||
br := strings.ReplaceAll(gt, "<br/>", "\n")
|
||||
return strings.ReplaceAll(br, "&amp;", "&")
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
3
url.go
3
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,
|
||||
}
|
||||
)
|
||||
|
||||
|
654
user.go
654
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("<User:%s>", u.NickName)
|
||||
return fmt.Sprintf("<User:%s>", 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}}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user