添加文件发送接口

This commit is contained in:
eatMoreApple 2021-04-30 00:04:45 +08:00
parent 3c7fec07f3
commit e751e13afd
9 changed files with 1046 additions and 985 deletions

View File

@ -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)
}

View File

@ -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
View File

@ -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
View File

@ -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
View File

@ -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
}

View File

@ -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
}

View File

@ -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, "&lt;", "<")
gt := strings.ReplaceAll(lt, "&gt;", ">")
br := strings.ReplaceAll(gt, "<br/>", "\n")
return strings.ReplaceAll(br, "&amp;amp;", "&")
lt := strings.ReplaceAll(text, "&lt;", "<")
gt := strings.ReplaceAll(lt, "&gt;", ">")
br := strings.ReplaceAll(gt, "<br/>", "\n")
return strings.ReplaceAll(br, "&amp;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
View File

@ -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
View File

@ -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}}
}