first commit

This commit is contained in:
Ivy1996-encode 2021-02-17 13:33:20 +08:00
parent 0877f225bd
commit 0f1d42f6f1
15 changed files with 1952 additions and 0 deletions

177
bot.go Normal file
View File

@ -0,0 +1,177 @@
package openwechat
import (
"errors"
"fmt"
)
type Bot struct {
Caller *Caller
self *Self
storage WechatStorage
ScanCallBack func(body []byte)
LoginCallBack func(body []byte)
UUIDCallback func(uuid string)
messageHandlerGroups *MessageHandlerGroup
err error
exit chan bool
}
func (b *Bot) Alive() bool {
if b.self == nil {
return false
}
select {
case <-b.exit:
return false
default:
return true
}
}
func (b *Bot) GetCurrentUser() (*Self, error) {
if b.self == nil {
return nil, errors.New("user not login")
}
return b.self, nil
}
func (b *Bot) Login() error {
b.prepare()
uuid, err := b.Caller.GetLoginUUID()
if err != nil {
return err
}
if b.UUIDCallback != nil {
b.UUIDCallback(uuid)
}
for {
resp, err := b.Caller.CheckLogin(uuid)
if err != nil {
return err
}
switch resp.Code {
case statusSuccess:
return b.login(resp.Raw)
case statusScanned:
if b.ScanCallBack != nil {
b.ScanCallBack(resp.Raw)
}
case statusTimeout:
return errors.New("login time out")
case statusWait:
continue
}
}
}
func (b *Bot) login(data []byte) error {
if b.LoginCallBack != nil {
b.LoginCallBack(data)
}
info, err := b.Caller.GetLoginInfo(data)
if err != nil {
return err
}
b.storage.SetLoginInfo(*info)
request := BaseRequest{
Uin: info.WxUin,
Sid: info.WxSid,
Skey: info.SKey,
DeviceID: GetRandomDeviceId(),
}
b.storage.SetBaseRequest(request)
resp, err := b.Caller.WebInit(request)
if err != nil {
return err
}
b.self = &Self{Bot: b, User: &resp.User}
b.storage.SetWebInitResponse(*resp)
if err = b.Caller.WebWxStatusNotify(request, *resp, *info); err != nil {
return err
}
go func() {
b.stopAsyncCALL(b.asyncCall())
}()
return nil
}
func (b *Bot) asyncCall() error {
var (
err error
resp *SyncCheckResponse
)
for b.Alive() {
info := b.storage.GetLoginInfo()
response := b.storage.GetWebInitResponse()
resp, err = b.Caller.SyncCheck(info, response)
if err != nil {
return err
}
if !resp.Success() {
return fmt.Errorf("unknow code got %s", resp.RetCode)
}
if !resp.NorMal() {
if err = b.getMessage(); err != nil {
return err
}
}
}
return err
}
func (b *Bot) stopAsyncCALL(err error) {
if err != nil {
b.exit <- true
b.err = err
}
}
func (b *Bot) getMessage() error {
info := b.storage.GetLoginInfo()
response := b.storage.GetWebInitResponse()
request := b.storage.GetBaseRequest()
resp, err := b.Caller.WebWxSync(request, response, info)
if err != nil {
return err
}
response.SyncKey = resp.SyncKey
b.storage.SetWebInitResponse(response)
for _, message := range resp.AddMsgList {
processMessage(message, b)
b.messageHandlerGroups.ProcessMessage(message)
}
return nil
}
func (b *Bot) prepare() {
if b.storage == nil {
panic("WechatStorage can not be nil")
}
if b.messageHandlerGroups == nil {
panic("message can not be nil")
}
}
func (b *Bot) RegisterMessageHandler(handler MessageHandler) {
b.messageHandlerGroups.RegisterHandler(handler)
}
func (b *Bot) Block() {
<-b.exit
}
func NewBot(caller *Caller, storage WechatStorage) *Bot {
return &Bot{Caller: caller, storage: storage}
}
func DefaultBot() *Bot {
return NewBot(DefaultCaller(), NewSimpleWechatStorage())
}
func PrintlnQrcodeUrl(uuid string) {
println(qrcodeUrl + uuid)
}

26
bot_test.go Normal file
View File

@ -0,0 +1,26 @@
package openwechat
import (
"fmt"
"testing"
)
func TestDefaultBot(t *testing.T) {
messageHandler := func(message Message) {
fmt.Println(message)
}
bot := DefaultBot(messageHandler)
bot.UUIDCallback = PrintlnQrcodeUrl
if err := bot.Login(); err != nil {
fmt.Println(err)
return
}
//for bot.Alive() {
// message := messageHandler.GetMessage()
// if message.Content == "6666" {
// err := message.ReplyText("nihao")
// fmt.Println(err)
// }
// fmt.Println(message)
//}
}

216
caller.go Normal file
View File

@ -0,0 +1,216 @@
package openwechat
import (
"errors"
"fmt"
"os"
)
type Caller struct {
Client *Client
}
func NewCaller(client *Client) *Caller {
return &Caller{Client: client}
}
func DefaultCaller() *Caller {
return NewCaller(DefaultClient())
}
func (c *Caller) GetLoginUUID() (string, error) {
resp := NewReturnResponse(c.Client.GetLoginUUID())
if resp.Err() != nil {
return "", resp.Err()
}
defer resp.Body.Close()
data, err := resp.ReadAll()
if err != nil {
return "", err
}
results := uuidRegexp.FindSubmatch(data)
if len(results) != 2 {
return "", errors.New("uuid does not match")
}
return string(results[1]), nil
}
func (c *Caller) CheckLogin(uuid string) (*CheckLoginResponse, error) {
resp := NewReturnResponse(c.Client.CheckLogin(uuid))
if resp.Err() != nil {
return nil, resp.Err()
}
defer resp.Body.Close()
data, err := resp.ReadAll()
if err != nil {
return nil, err
}
results := statusCodeRegexp.FindSubmatch(data)
if len(results) != 2 {
return nil, nil
}
code := string(results[1])
return &CheckLoginResponse{Code: code, Raw: data}, nil
}
func (c *Caller) GetLoginInfo(body []byte) (*LoginInfo, error) {
results := redirectUriRegexp.FindSubmatch(body)
if len(results) != 2 {
return nil, errors.New("redirect url does not match")
}
path := string(results[1])
resp := NewReturnResponse(c.Client.GetLoginInfo(path))
if resp.Err() != nil {
return nil, resp.Err()
}
defer resp.Body.Close()
var loginInfo LoginInfo
if err := resp.ScanXML(&loginInfo); err != nil {
return nil, err
}
return &loginInfo, nil
}
func (c *Caller) WebInit(request BaseRequest) (*WebInitResponse, error) {
resp := NewReturnResponse(c.Client.WebInit(request))
if resp.Err() != nil {
return nil, resp.Err()
}
var webInitResponse WebInitResponse
defer resp.Body.Close()
if err := resp.ScanJSON(&webInitResponse); err != nil {
return nil, err
}
return &webInitResponse, nil
}
func (c *Caller) WebWxStatusNotify(request BaseRequest, response WebInitResponse, info LoginInfo) error {
resp := NewReturnResponse(c.Client.WebWxStatusNotify(request, response, info))
if resp.Err() != nil {
return resp.Err()
}
var item struct{ BaseResponse BaseResponse }
defer resp.Body.Close()
if err := resp.ScanJSON(&item); err != nil {
return err
}
if !item.BaseResponse.Ok() {
return item.BaseResponse
}
return nil
}
func (c *Caller) SyncCheck(info LoginInfo, response WebInitResponse) (*SyncCheckResponse, error) {
resp := NewReturnResponse(c.Client.SyncCheck(info, response))
if resp.Err() != nil {
return nil, resp.Err()
}
defer resp.Body.Close()
data, err := resp.ReadAll()
fmt.Println(string(data))
if err != nil {
return nil, err
}
results := syncCheckRegexp.FindSubmatch(data)
if len(results) != 3 {
return nil, errors.New("parse sync key failed")
}
retCode, selector := string(results[1]), string(results[2])
syncCheckResponse := &SyncCheckResponse{RetCode: retCode, Selector: selector}
return syncCheckResponse, nil
}
func (c *Caller) WebWxGetContact(info LoginInfo) (Members, error) {
resp := NewReturnResponse(c.Client.WebWxGetContact(info))
if resp.Err() != nil {
return nil, resp.Err()
}
defer resp.Body.Close()
var item WebWxContactResponse
if err := resp.ScanJSON(&item); err != nil {
return nil, err
}
if !item.BaseResponse.Ok() {
return nil, item.BaseResponse
}
return item.MemberList, nil
}
func (c *Caller) WebWxBatchGetContact(members Members, request BaseRequest) (Members, error) {
resp := NewReturnResponse(c.Client.WebWxBatchGetContact(members, request))
if resp.Err() != nil {
return nil, resp.Err()
}
defer resp.Body.Close()
var item WebWxBatchContactResponse
if err := resp.ScanJSON(&item); err != nil {
return nil, err
}
if !item.BaseResponse.Ok() {
return nil, item.BaseResponse
}
return item.ContactList, nil
}
func (c *Caller) WebWxSync(request BaseRequest, response WebInitResponse, info LoginInfo) (*WebWxSyncResponse, error) {
resp := NewReturnResponse(c.Client.WebWxSync(request, response, info))
if resp.Err() != nil {
return nil, resp.Err()
}
defer resp.Body.Close()
var webWxSyncResponse WebWxSyncResponse
if err := resp.ScanJSON(&webWxSyncResponse); err != nil {
return nil, err
}
return &webWxSyncResponse, nil
}
func (c *Caller) WebWxSendMsg(msg *SendMessage, info LoginInfo, request BaseRequest) error {
resp := NewReturnResponse(c.Client.WebWxSendMsg(msg, info, request))
return parseBaseResponseError(resp)
}
func (c *Caller) WebWxOplog(request BaseRequest, remarkName, toUserName string) error {
resp := NewReturnResponse(c.Client.WebWxOplog(request, remarkName, toUserName))
return parseBaseResponseError(resp)
}
func (c *Caller) WebWxSendImageMsg(file *os.File, request BaseRequest, info LoginInfo, fromUserName, toUserName string) error {
resp := NewReturnResponse(c.Client.WebWxUploadMedia(file, request, info, fromUserName, toUserName, "image/jpeg", "pic"))
if resp.Err() != nil {
return resp.Err()
}
defer resp.Body.Close()
var item struct {
BaseResponse BaseResponse
MediaId string
}
if err := resp.ScanJSON(&item); err != nil {
return err
}
if !item.BaseResponse.Ok() {
return item.BaseResponse
}
msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, item.MediaId)
resp = NewReturnResponse(c.Client.WebWxSendMsgImg(msg, request, info))
return parseBaseResponseError(resp)
}
//func (c *Caller) WebWxBatchGetContact() {
//
//}
func parseBaseResponseError(resp *ReturnResponse) error {
if resp.Err() != nil {
return resp.Err()
}
defer resp.Body.Close()
var item struct{ BaseResponse BaseResponse }
if err := resp.ScanJSON(&item); err != nil {
return err
}
if !item.BaseResponse.Ok() {
return item.BaseResponse
}
return nil
}

328
client.go Normal file
View File

@ -0,0 +1,328 @@
package openwechat
import (
"bytes"
"crypto/md5"
"encoding/json"
"fmt"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"strconv"
"strings"
"time"
)
type Client struct {
*http.Client
}
func NewClient(client *http.Client) *Client {
return &Client{Client: client}
}
func DefaultClient() *Client {
jar, _ := cookiejar.New(nil)
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Jar: jar,
}
return NewClient(client)
}
func (c *Client) GetLoginUUID() (*http.Response, error) {
path, _ := url.Parse(jsLoginUrl)
params := url.Values{}
params.Add("appid", appId)
params.Add("redirect_uri", webWxNewLoginPage)
params.Add("fun", "new")
params.Add("lang", "zh_CN")
params.Add("_", strconv.FormatInt(time.Now().Unix(), 10))
path.RawQuery = params.Encode()
return c.Get(path.String())
}
func (c *Client) GetLoginQrcode(uuid string) (*http.Response, error) {
path := qrcodeUrl + uuid
return c.Get(path)
}
func (c *Client) CheckLogin(uuid string) (*http.Response, error) {
path, _ := url.Parse(loginUrl)
now := time.Now().Unix()
params := url.Values{}
params.Add("r", strconv.FormatInt(now/1579, 10))
params.Add("_", strconv.FormatInt(now, 10))
params.Add("loginicon", "true")
params.Add("uuid", uuid)
params.Add("tip", "0")
path.RawQuery = params.Encode()
return c.Get(path.String())
}
func (c *Client) GetLoginInfo(path string) (*http.Response, error) {
return c.Get(path)
}
func (c *Client) WebInit(request BaseRequest) (*http.Response, error) {
path, _ := url.Parse(webWxInitUrl)
params := url.Values{}
params.Add("_", fmt.Sprintf("%d", time.Now().Unix()))
path.RawQuery = params.Encode()
content := struct{ BaseRequest BaseRequest }{BaseRequest: request}
body, err := ToBuffer(content)
if err != nil {
return nil, err
}
return c.Post(path.String(), jsonContentType, body)
}
func (c *Client) WebWxStatusNotify(request BaseRequest, response WebInitResponse, info LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxStatusNotifyUrl)
params := url.Values{}
params.Add("lang", "zh_CN")
params.Add("pass_ticket", info.PassTicket)
username := response.User.UserName
content := map[string]interface{}{
"BaseRequest": request,
"ClientMsgId": time.Now().Unix(),
"Code": 3,
"FromUserName": username,
"ToUserName": username,
}
path.RawQuery = params.Encode()
buffer, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, path.String(), buffer)
req.Header.Add("Content-Type", jsonContentType)
return c.Do(req)
}
func (c *Client) SyncCheck(info LoginInfo, response WebInitResponse) (*http.Response, error) {
path, _ := url.Parse(syncCheckUrl)
params := url.Values{}
params.Add("r", strconv.FormatInt(time.Now().Unix(), 10))
params.Add("skey", info.SKey)
params.Add("sid", info.WxSid)
params.Add("uin", strconv.Itoa(info.WxUin))
params.Add("deviceid", GetRandomDeviceId())
params.Add("_", strconv.FormatInt(time.Now().Unix(), 10))
syncKeyStringSlice := make([]string, 0)
for _, item := range response.SyncKey.List {
i := fmt.Sprintf("%d_%d", item.Key, item.Val)
syncKeyStringSlice = append(syncKeyStringSlice, i)
}
syncKey := strings.Join(syncKeyStringSlice, "|")
params.Add("synckey", syncKey)
path.RawQuery = params.Encode()
req, _ := http.NewRequest(http.MethodGet, path.String(), nil)
req.Header.Add("User-Agent", "Mozilla/5.0")
return c.Do(req)
}
func (c *Client) WebWxGetContact(info LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxGetContactUrl)
params := url.Values{}
params.Add("r", strconv.FormatInt(time.Now().Unix(), 10))
params.Add("skey", info.SKey)
params.Add("req", "0")
path.RawQuery = params.Encode()
return c.Get(path.String())
}
func (c *Client) WebWxBatchGetContact(members Members, request BaseRequest) (*http.Response, error) {
path, _ := url.Parse(webWxBatchGetContactUrl)
params := url.Values{}
params.Add("type", "ex")
params.Add("r", strconv.FormatInt(time.Now().Unix(), 10))
path.RawQuery = params.Encode()
list := NewUserDetailItemList(members)
content := map[string]interface{}{
"BaseRequest": request,
"Count": members.Count(),
"List": list,
}
body, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, path.String(), body)
req.Header.Add("Content-Type", jsonContentType)
return c.Do(req)
}
func (c *Client) WebWxSync(request BaseRequest, response WebInitResponse, info LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxSyncUrl)
params := url.Values{}
params.Add("sid", info.WxSid)
params.Add("skey", info.SKey)
params.Add("pass_ticket", info.PassTicket)
path.RawQuery = params.Encode()
content := map[string]interface{}{
"BaseRequest": request,
"SyncKey": response.SyncKey,
"rr": strconv.FormatInt(time.Now().Unix(), 10),
}
data, _ := json.Marshal(content)
body := bytes.NewBuffer(data)
req, _ := http.NewRequest(http.MethodPost, path.String(), body)
req.Header.Add("Content-Type", jsonContentType)
return c.Do(req)
}
func (c *Client) sendMessage(request BaseRequest, url string, msg *SendMessage) (*http.Response, error) {
content := map[string]interface{}{
"BaseRequest": request,
"Msg": msg,
"Scene": 0,
}
body, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, url, body)
req.Header.Add("Content-Type", jsonContentType)
return c.Do(req)
}
func (c *Client) WebWxSendMsg(msg *SendMessage, info LoginInfo, request BaseRequest) (*http.Response, error) {
msg.Type = TextMessage
path, _ := url.Parse(webWxSendMsgUrl)
params := url.Values{}
params.Add("lang", "zh_CN")
params.Add("pass_ticket", info.PassTicket)
path.RawQuery = params.Encode()
return c.sendMessage(request, path.String(), msg)
}
func (c *Client) WebWxGetHeadImg(headImageUrl string) (*http.Response, error) {
path := baseUrl + headImageUrl
return c.Get(path)
}
func (c *Client) WebWxUploadMedia(file *os.File, request BaseRequest, info LoginInfo, forUserName, toUserName, contentType, mediaType string) (*http.Response, error) {
path, _ := url.Parse(webWxUpLoadMediaUrl)
params := url.Values{}
params.Add("f", "json")
path.RawQuery = params.Encode()
sate, err := file.Stat()
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
fileMd5 := fmt.Sprintf("%x", md5.Sum(data))
cookies := c.Jar.Cookies(path)
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": time.Now().Format(http.TimeFormat),
"size": sate.Size(),
"mediatype": mediaType,
"webwx_data_ticket": getWebWxDataTicket(cookies),
"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
}
return c.Post(path.String(), ct, body)
}
func (c *Client) WebWxSendMsgImg(msg *SendMessage, request BaseRequest, info LoginInfo) (*http.Response, error) {
msg.Type = ImageMessage
path, _ := url.Parse(webWxSendMsgImgUrl)
params := url.Values{}
params.Add("fun", "async")
params.Add("f", "json")
params.Add("lang", "zh_CN")
params.Add("pass_ticket", info.PassTicket)
path.RawQuery = params.Encode()
return c.sendMessage(request, path.String(), msg)
}
func (c *Client) WebWxSendAppMsg(msg *SendMessage, request BaseRequest) (*http.Response, error) {
msg.Type = AppMessage
path, _ := url.Parse(webWxSendAppMsgUrl)
params := url.Values{}
params.Add("fun", "async")
params.Add("f", "json")
path.RawQuery = params.Encode()
return c.sendMessage(request, path.String(), msg)
}
func (c *Client) WebWxOplog(request BaseRequest, remarkName, userName string, ) (*http.Response, error) {
path, _ := url.Parse(webWxOplogUrl)
params := url.Values{}
params.Add("lang", "zh_CN")
path.RawQuery = params.Encode()
content := map[string]interface{}{
"BaseRequest": request,
"CmdId": 2,
"RemarkName": remarkName,
"UserName": userName,
}
body, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, path.String(), body)
req.Header.Add("Content-Type", jsonContentType)
return c.Do(req)
}
func (c *Client) WebWxVerifyUser(storage WechatStorage, info RecommendInfo, verifyContent string) (*http.Response, error) {
loginInfo := storage.GetLoginInfo()
path, _ := url.Parse(webWxVerifyUserUrl)
params := url.Values{}
params.Add("r", strconv.FormatInt(time.Now().Unix(), 10))
params.Add("lang", "zh_CN")
params.Add("pass_ticket", loginInfo.PassTicket)
path.RawQuery = params.Encode()
content := map[string]interface{}{
"BaseRequest": storage.GetBaseRequest(),
"Opcode": 3,
"SceneList": []int{33},
"SceneListCount": 1,
"VerifyContent": verifyContent,
"VerifyUserList": []interface{}{map[string]string{
"Value": info.UserName,
"VerifyUserTicket": info.Ticket,
}},
"VerifyUserListSize": 1,
"skey": loginInfo.SKey,
}
body, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, path.String(), body)
req.Header.Add("Content-Type", jsonContentType)
return c.Do(req)
}

47
global.go Normal file
View File

@ -0,0 +1,47 @@
package openwechat
import "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="(.*?)"`)
)
const (
appId = "wx782c26e4c19acffb"
baseUrl = "https://wx2.qq.com"
jsLoginUrl = "https://login.wx.qq.com/jslogin"
webWxNewLoginPage = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage"
qrcodeUrl = "https://login.weixin.qq.com/qrcode/"
loginUrl = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login"
webWxInitUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit"
webWxStatusNotifyUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify"
webWxSyncUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync"
webWxSendMsgUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg"
webWxGetContactUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact"
webWxSendMsgImgUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsgimg"
webWxSendAppMsgUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendappmsg"
webWxBatchGetContactUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact"
webWxOplogUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxoplog"
webWxVerifyUserUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxverifyuser"
syncCheckUrl = "https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck"
webWxUpLoadMediaUrl = "https://file.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia"
jsonContentType = "application/json; charset=utf-8"
)
const (
TextMessage = 1
ImageMessage = 3
AppMessage = 6
)
const (
statusSuccess = "200"
statusScanned = "201"
statusTimeout = "400"
statusWait = "408"
)

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module openwechat
go 1.15

44
http.go Normal file
View File

@ -0,0 +1,44 @@
package openwechat
import (
"encoding/json"
"encoding/xml"
"io/ioutil"
"net/http"
)
type ReturnResponse struct {
*http.Response
err error
}
func NewReturnResponse(response *http.Response, err error) *ReturnResponse {
return &ReturnResponse{Response: response, err: err}
}
func (r *ReturnResponse) Err() error {
return r.err
}
func (r *ReturnResponse) ScanJSON(v interface{}) error {
if data, err := r.ReadAll(); err != nil {
return err
} else {
return json.Unmarshal(data, v)
}
}
func (r *ReturnResponse) ScanXML(v interface{}) error {
if data, err := r.ReadAll(); err != nil {
return err
} else {
return xml.Unmarshal(data, v)
}
}
func (r *ReturnResponse) ReadAll() ([]byte, error) {
if r.Err() != nil {
return nil, r.Err()
}
return ioutil.ReadAll(r.Body)
}

149
items.go Normal file
View File

@ -0,0 +1,149 @@
package openwechat
import "fmt"
type LoginInfo struct {
Ret int `xml:"ret"`
Message string `xml:"message"`
SKey string `xml:"skey"`
WxSid string `xml:"wxsid"`
WxUin int `xml:"wxuin"`
PassTicket string `xml:"pass_ticket"`
IsGrayScale int `xml:"isgrayscale"`
}
type BaseRequest struct {
Uin int
Sid, Skey, DeviceID string
}
type BaseResponse struct {
ErrMsg string
Ret int
}
func (b BaseResponse) Ok() bool {
returnRet == 0
}
func (b BaseResponse) Error() string {
switch b.Ret {
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 "op too often"
default:
if b.ErrMsg != "" {
return b.ErrMsg
}
return fmt.Sprintf("base response error code %d", b.Ret)
}
}
type SyncKey struct {
Count int
List []struct{ Key, Val int64 }
}
type WebInitResponse struct {
BaseResponse BaseResponse
Count int
ChatSet string
SKey string
SyncKey SyncKey
User User
ClientVersion int
SystemTime int64
GrayScale int
InviteStartCount int
MPSubscribeMsgCount int
MPSubscribeMsgList []MPSubscribeMsg
ClickReportInterval int
ContactList []User
}
type MPSubscribeMsg struct {
UserName string
Time int64
NickName string
MPArticleCount int
MPArticleList []struct {
Title string
Cover string
Digest string
Url string
}
}
type UserDetailItem struct {
UserName string
EncryChatRoomId string
}
type UserDetailItemList []UserDetailItem
func NewUserDetailItemList(members Members) UserDetailItemList {
list := make(UserDetailItemList, members.Count()-1)
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
}
func (s *SyncCheckResponse) Success() bool {
return s.RetCode == "0"
}
func (s *SyncCheckResponse) NorMal() bool {
return s.Success() && s.Selector == "0"
}
type WebWxSyncResponse struct {
AddMsgCount int
AddMsgList []*Message
BaseResponse BaseResponse
ContinueFlag int
DelContactCount int
ModChatRoomMemberCount int
ModChatRoomMemberList Members
ModContactCount int
Skey string
SyncCheckKey SyncKey
SyncKey SyncKey
}
type WebWxContactResponse struct {
BaseResponse BaseResponse
MemberCount int
MemberList []*User
Seq int
}
type WebWxBatchContactResponse struct {
BaseResponse BaseResponse
ContactList []*User
Count int
}
type CheckLoginResponse struct {
Code string
Raw []byte
}

249
manager.go Normal file
View File

@ -0,0 +1,249 @@
package openwechat
//
//import (
// "encoding/json"
// "encoding/xml"
// "errors"
// "fmt"
// "io/ioutil"
// "log"
//)
//
//var DefaultMessageMaxLength uint64 = 200
//
//const (
// statusSuccess = "200"
// statusScanned = "201"
// statusTimeout = "400"
// statusWait = "408"
//)
//
//type Bot struct {
// Caller *Caller
// Self *Self
// ScanCallback func(body []byte)
// LoginCallback func(body []byte)
// storage WechatStorage
// messageHandler MessageHandler
// notAlive bool
// err error
//}
//
//func (m *Bot) GetLoginUUID() (uuid string, err error) {
// return m.Caller.GetLoginUUID()
//}
//
//func (m *Bot) checkLogin(uuid string) (body []byte, err error) {
// resp, err := m.Client.CheckLogin(uuid)
// if err != nil {
// return nil, err
// }
// defer resp.Body.Close()
// data, err := ioutil.ReadAll(resp.Body)
// if
//
// err != nil {
// return nil, err
// }
// return data, nil
//}
//
//func (m *Bot) CheckLogin(uuid string) error {
// var (
// body []byte
// err error
// )
// for {
// body, err = m.checkLogin(uuid)
// if err != nil {
// return err
// }
// results := statusCodeRegexp.FindSubmatch(body)
// if len(results) != 2 {
// return errors.New("login status code does not match")
// }
// code := string(results[1])
// switch code {
// case statusSuccess:
// return m.loginCallback(body)
// case statusScanned:
// if m.ScanCallback != nil {
// m.ScanCallback(body)
// }
// case statusWait:
// log.Println(string(body))
// case statusTimeout:
// return errors.New("login time out")
// default:
// return errors.New("unknow code found " + code)
// }
// }
//}
//
//func (m *Bot) getLoginInfo(body []byte) error {
// resp, err := m.Client.GetLoginInfo(body)
// if err != nil {
// return err
// }
// defer resp.Body.Close()
// data, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// return err
// }
// var loginInfo LoginInfo
// if err = xml.Unmarshal(data, &loginInfo); err != nil {
// return err
// }
// if loginInfo.Ret != 0 {
// return errors.New(loginInfo.Message)
// }
// m.storage.SetLoginInfo(loginInfo)
// return nil
//}
//
//func (m *Bot) webInit() error {
// loginInfo := m.storage.GetLoginInfo()
// baseRequest := BaseRequest{
// Uin: loginInfo.WxUin,
// Sid: loginInfo.WxSid,
// Skey: loginInfo.SKey,
// DeviceID: GetRandomDeviceId(),
// }
// m.storage.SetBaseRequest(baseRequest)
// resp, err := m.Client.WebInit(m.storage)
// if err != nil {
// return err
// }
// defer resp.Body.Close()
// data, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// return err
// }
// var webInitResponse WebInitResponse
// if err = json.Unmarshal(data, &webInitResponse); err != nil {
// return err
// }
// m.storage.SetWebInitResponse(webInitResponse)
// m.Self = &Self{User: &webInitResponse.User, Manager: m}
// return nil
//}
//
//func (m *Bot) WebWxStatusNotify() error {
// resp, err := m.Client.WebWxStatusNotify(m.storage)
// if err != nil {
// return err
// }
// defer resp.Body.Close()
// data, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// return err
// }
// var item map[string]interface{}
// err = json.Unmarshal(data, &item)
// if err != nil {
// return err
// }
// if request, ok := item["BaseResponse"].(map[string]interface{}); ok {
// if ret, exist := request["Ret"]; exist {
// if ret, ok := ret.(float64); ok {
// if ret == 0 {
// return nil
// }
// }
// }
// }
// return errors.New("web status notify failed")
//}
//
//func (m *Bot) SyncCheck() error {
// for m.Alive() {
// resp, err := m.Client.SyncCheck(m.storage)
// if err != nil {
// return err
// }
// data, err := ioutil.ReadAll(resp.Body)
// fmt.Println(string(data))
// resp.Body.Close()
// if err != nil {
// return err
// }
// results := syncCheckRegexp.FindSubmatch(data)
// if len(results) != 3 {
// return errors.New("parse sync key failed")
// }
// code, _ := results[1], results[2]
// switch string(code) {
// case "0":
// if err = m.getMessage(); err != nil {
// return err
// }
// case "1101":
// return errors.New("logout")
// }
// return fmt.Errorf("error ret code: %s", string(code))
// }
// return nil
//}
//
//func (m *Bot) getMessage() error {
// resp, err := m.Client.GetMessage(m.storage)
// if err != nil {
// return err
// }
// defer resp.Body.Close()
// data, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// return err
// }
// var syncKey struct{ SyncKey SyncKey }
// if err = json.Unmarshal(data, &syncKey); err != nil {
// return err
// }
// webInitResponse := m.storage.GetWebInitResponse()
// webInitResponse.SyncKey = syncKey.SyncKey
// m.storage.SetWebInitResponse(webInitResponse)
// var messageList MessageList
// if err = json.Unmarshal(data, &messageList); err != nil {
// return err
// }
// for _, message := range messageList.AddMsgList {
// message.ClientManager = m
// m.messageHandler.ReceiveMessage(message)
// }
// return nil
//}
//
//func (m *Bot) loginCallback(body []byte) error {
// var err error
// if m.LoginCallback != nil {
// m.LoginCallback(body)
// }
// if err = m.getLoginInfo(body); err != nil {
// return err
// }
// if err = m.webInit(); err != nil {
// return err
// }
// if err = m.WebWxStatusNotify(); err != nil {
// return err
// }
// go func() {
// if err := m.SyncCheck(); err != nil {
// m.exit(err)
// }
// }()
// return err
//}
//
//func (m *Bot) Alive() bool {
// return !m.notAlive
//}
//
//func (m *Bot) Err() error {
// return m.err
//}
//
//func (m *Bot) exit(err error) {
// m.notAlive = true
// m.err = err
//}

213
message.go Normal file
View File

@ -0,0 +1,213 @@
package openwechat
import (
"errors"
"os"
"strings"
"time"
)
type Message struct {
AppInfo struct {
AppID string
Type int
}
AppMsgType int
Content string
CreateTime int64
EncryFileName string
FileName string
FileSize string
ForwardFlag int
FromUserName string
HasProductId int
ImgHeight int
ImgStatus int
ImgWidth int
MediaId string
MsgId string
MsgType int
NewMsgId int64
OriContent string
PlayLength int64
RecommendInfo RecommendInfo
Status int
StatusNotifyCode int
StatusNotifyUserName string
SubMsgType int
Ticket string
ToUserName string
Url string
VoiceLength int
Bot *Bot
senderInGroupUserName string
}
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
}
for _, member := range members {
if member.UserName == m.FromUserName {
return member.Detail()
}
}
return nil, errors.New("no such user found")
}
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
}
for _, member := range group.MemberList {
if m.senderInGroupUserName == member.UserName {
return member, nil
}
}
return nil, errors.New("no such user found")
}
//
func (m *Message) IsSendBySelf() bool {
return m.FromUserName == m.Bot.self.User.UserName
}
func (m *Message) IsSendByFriend() bool {
return !m.IsSendByGroup() && strings.HasPrefix(m.FromUserName, "@")
}
func (m *Message) IsSendByGroup() bool {
return strings.HasPrefix(m.FromUserName, "@@")
}
func (m *Message) Reply(msgType int, content, mediaId string) error {
msg := NewSendMessage(msgType, content, m.Bot.self.User.UserName, m.FromUserName, mediaId)
info := m.Bot.storage.GetLoginInfo()
request := m.Bot.storage.GetBaseRequest()
return m.Bot.Caller.WebWxSendMsg(msg, info, request)
}
func (m *Message) ReplyText(content string) error {
return m.Reply(TextMessage, content, "")
}
func (m *Message) ReplyImage(file *os.File) error {
info := m.Bot.storage.GetLoginInfo()
request := m.Bot.storage.GetBaseRequest()
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 == ""
}
func (m *Message) IsMap() bool {
return m.MsgType == 1 && m.Url != ""
}
func (m *Message) IsPicture() bool {
return m.MsgType == 3 || m.MsgType == 47
}
func (m *Message) IsVoice() bool {
return m.MsgType == 34
}
func (m *Message) IsFriendAdd() bool {
return m.MsgType == 37
}
func (m *Message) IsCard() bool {
return m.MsgType == 42
}
func (m *Message) IsVideo() bool {
return m.MsgType == 43 || m.MsgType == 62
}
func (m *Message) IsSharing() bool {
return m.MsgType == 49
}
func (m *Message) IsRecalled() bool {
return m.MsgType == 10002
}
func (m *Message) IsSystem() bool {
return m.MsgType == 10000
}
//func (m Message) Agree() error {
// if !m.IsFriendAdd() {
// return fmt.Errorf("the excepted message type is 37, but got %d", m.MsgType)
// }
// m.ClientManager.Client.WebWxVerifyUser(m.ClientManager.storage, m.RecommendInfo, "")
//}
type SendMessage struct {
Type int
Content string
FromUserName string
ToUserName string
LocalID int64
ClientMsgId int64
MediaId string
}
func NewSendMessage(msgType int, content, fromUserName, toUserName, mediaId string) *SendMessage {
return &SendMessage{
Type: msgType,
Content: content,
FromUserName: fromUserName,
ToUserName: toUserName,
LocalID: time.Now().Unix() * 1e4,
ClientMsgId: time.Now().Unix() * 1e4,
MediaId: mediaId,
}
}
func NewTextSendMessage(content, fromUserName, toUserName string) *SendMessage {
return NewSendMessage(TextMessage, content, fromUserName, toUserName, "")
}
func NewMediaSendMessage(msgType int, fromUserName, toUserName, mediaId string) *SendMessage {
return NewSendMessage(msgType, "", fromUserName, toUserName, mediaId)
}
type RecommendInfo struct {
Alias string
AttrStatus int64
City string
Content string
NickName string
OpCode int
Province string
QQNum int64
Scene int
Sex int
Signature string
Ticket string
UserName string
VerifyFlag int
}
func processMessage(message *Message, bot *Bot) {
message.Bot = bot
if message.IsSendByGroup() {
data := strings.Split(message.Content, ":<br/>")
message.Content = strings.Join(data[1:], "")
message.senderInGroupUserName = data[0]
}
}

17
message_handler.go Normal file
View File

@ -0,0 +1,17 @@
package openwechat
type MessageHandler func(message *Message)
type MessageHandlerGroup struct {
handlers []MessageHandler
}
func (m MessageHandlerGroup) ProcessMessage(message *Message) {
for _, handler := range m.handlers {
handler(message)
}
}
func (m *MessageHandlerGroup) RegisterHandler(handler MessageHandler) {
m.handlers = append(m.handlers, handler)
}

83
parser.go Normal file
View File

@ -0,0 +1,83 @@
package openwechat
import (
"bytes"
"encoding/json"
"io/ioutil"
"math/rand"
"net/http"
"strconv"
"time"
)
func ToBuffer(v interface{}) (*bytes.Buffer, error) {
buf, err := json.Marshal(v)
if err != nil {
return nil, err
}
return bytes.NewBuffer(buf), nil
}
func GetRandomDeviceId() string {
rand.Seed(time.Now().Unix())
str := ""
for i := 0; i < 15; i++ {
r := rand.Intn(9)
str += strconv.Itoa(r)
}
return "e" + str
}
//func getSendMessageError(body io.Reader) error {
// data, err := ioutil.ReadAll(body)
// if err != nil {
// return err
// }
// var item struct{ BaseResponse BaseResponse }
// if err = json.Unmarshal(data, &item); err != nil {
// return err
// }
// if !item.BaseResponse.Ok() {
// return errors.New(item.BaseResponse.ErrMsg)
// }
// return nil
//}
func getWebWxDataTicket(cookies []*http.Cookie) string {
for _, cookie := range cookies {
if cookie.Name == "webwx_data_ticket" {
return cookie.Value
}
}
return ""
}
func getUpdateMember(resp *http.Response, err error) (Members, error) {
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var item struct {
BaseResponse BaseResponse
ContactList Members
}
if err = json.Unmarshal(data, &item); err != nil {
return nil, err
}
if !item.BaseResponse.Ok() {
return nil, item.BaseResponse
}
return item.ContactList, nil
}
func getResponseBody(resp *http.Response) ([]byte, error) {
if data, err := ioutil.ReadAll(resp.Body); err != nil {
return nil, err
} else {
return data, nil
}
}

77
relations.go Normal file
View File

@ -0,0 +1,77 @@
package openwechat
import (
"fmt"
"os"
"strings"
)
type Friend struct{ *User }
// implement fmt.Stringer
func (f Friend) String() string {
return fmt.Sprintf("<Friend:%s>", f.NickName)
}
func (f *Friend) RemarkName(name string) error {
return f.remakeName(name)
}
func (f *Friend) SendMsg(msg *SendMessage) error {
return f.sendMsg(msg)
}
func (f *Friend) SendText(content string) error {
return f.sendText(content)
}
func (f *Friend) SendImage(file *os.File) error {
return f.sendImage(file)
}
type Friends []*Friend
func (f Friends) Count() int {
return len(f)
}
type Group struct{ *User }
// implement fmt.Stringer
func (g Group) String() string {
return fmt.Sprintf("<Group:%s>", g.NickName)
}
func (g *Group) SendMsg(msg *SendMessage) error {
return g.sendMsg(msg)
}
func (g *Group) SendText(content string) error {
return g.sendText(content)
}
func (g *Group) SendImage(file *os.File) error {
return g.sendImage(file)
}
func (g *Group) Members() (Members, error) {
group, err := g.Detail()
if err != nil {
return nil, err
}
return group.MemberList, nil
}
type Groups []*Group
func (g Groups) Count() int {
return len(g)
}
func isFriend(user User) bool {
return !isGroup(user) && strings.HasPrefix(user.UserName, "@") && user.VerifyFlag == 0
}
func isGroup(user User) bool {
return strings.HasPrefix(user.UserName, "@@") && user.VerifyFlag == 0
}

45
stroage.go Normal file
View File

@ -0,0 +1,45 @@
package openwechat
type WechatStorage interface {
SetLoginInfo(loginInfo LoginInfo)
SetBaseRequest(baseRequest BaseRequest)
SetWebInitResponse(webInitResponse WebInitResponse)
GetLoginInfo() LoginInfo
GetBaseRequest() BaseRequest
GetWebInitResponse() WebInitResponse
}
// implement WechatStorage
type SimpleWechatStorage struct {
loginInfo LoginInfo
baseRequest BaseRequest
webInitResponse WebInitResponse
}
func NewSimpleWechatStorage() *SimpleWechatStorage {
return &SimpleWechatStorage{}
}
func (s *SimpleWechatStorage) SetLoginInfo(loginInfo LoginInfo) {
s.loginInfo = loginInfo
}
func (s *SimpleWechatStorage) SetBaseRequest(baseRequest BaseRequest) {
s.baseRequest = baseRequest
}
func (s *SimpleWechatStorage) SetWebInitResponse(webInitResponse WebInitResponse) {
s.webInitResponse = webInitResponse
}
func (s *SimpleWechatStorage) GetLoginInfo() LoginInfo {
return s.loginInfo
}
func (s *SimpleWechatStorage) GetBaseRequest() BaseRequest {
return s.baseRequest
}
func (s *SimpleWechatStorage) GetWebInitResponse() WebInitResponse {
return s.webInitResponse
}

278
user.go Normal file
View File

@ -0,0 +1,278 @@
package openwechat
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
)
type User struct {
Uin int
HideInputBarFlag int
StarFriend int
Sex int
AppAccountFlag int
VerifyFlag int
ContactFlag int
WebWxPluginSwitch int
HeadImgFlag int
SnsFlag int
UserName string
NickName string
HeadImgUrl string
RemarkName string
PYInitial string
PYQuanPin string
RemarkPYInitial string
RemarkPYQuanPin string
Signature string
MemberCount int
MemberList []*User
OwnerUin int
Statues int
AttrStatus int
Province string
City string
Alias string
UniFriend int
DisplayName string
ChatRoomId int
KeyWord string
EncryChatRoomId string
IsOwner int
Self *Self
}
// implement fmt.Stringer
func (u *User) String() string {
return fmt.Sprintf("<User:%s>", u.NickName)
}
//
func (u *User) GetAvatarResponse() (*http.Response, error) {
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()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return ioutil.WriteFile(filename, data, os.ModePerm)
}
func (u *User) sendMsg(msg *SendMessage) error {
msg.FromUserName = u.Self.UserName
msg.ToUserName = u.UserName
info := u.Self.Bot.storage.GetLoginInfo()
request := u.Self.Bot.storage.GetBaseRequest()
return u.Self.Bot.Caller.WebWxSendMsg(msg, info, request)
}
func (u *User) sendText(content string) error {
msg := NewTextSendMessage(content, u.Self.UserName, u.UserName)
return u.sendMsg(msg)
}
func (u *User) sendImage(file *os.File) error {
request := u.Self.Bot.storage.GetBaseRequest()
info := u.Self.Bot.storage.GetLoginInfo()
return u.Self.Bot.Caller.WebWxSendImageMsg(file, request, info, u.Self.UserName, u.UserName)
}
func (u *User) remakeName(remarkName string) error {
request := u.Self.Bot.storage.GetBaseRequest()
return u.Self.Bot.Caller.WebWxOplog(request, remarkName, u.UserName)
}
func (u *User) Detail() (*User, error) {
members := Members{u}
request := u.Self.Bot.storage.GetBaseRequest()
newMembers, err := u.Self.Bot.Caller.WebWxBatchGetContact(members, request)
if err != nil {
return nil, err
}
user := newMembers[0]
user.Self = u.Self
return user, nil
}
type Self struct {
*User
Bot *Bot
fileHelper *Friend
members Members
friends Friends
groups Groups
}
func (s *Self) Members(update ...bool) (Members, error) {
if s.members == nil {
if err := s.updateMembers(); err != nil {
return nil, err
} else {
return s.members, nil
}
}
var isUpdate bool
if len(update) > 0 {
isUpdate = update[len(update)-1]
}
if isUpdate {
if err := s.updateMembers(); err != nil {
return nil, err
}
}
return s.members, nil
}
func (s *Self) updateMembers() error {
info := s.Bot.storage.GetLoginInfo()
members, err := s.Bot.Caller.WebWxGetContact(info)
if err != nil {
return err
}
members.SetOwner(s)
s.members = members
return nil
}
func (s *Self) FileHelper() (*Friend, error) {
if s.fileHelper != nil {
return s.fileHelper, nil
}
members, err := s.Members()
if err != nil {
return nil, err
}
for _, member := range members {
if member.UserName == "filehelper" {
fileHelper := &Friend{member}
s.fileHelper = fileHelper
return s.fileHelper, nil
}
}
return nil, errors.New("filehelper does not exist")
}
func (s *Self) Friends(update ...bool) (Friends, error) {
if s.friends == nil {
if err := s.updateFriends(update...); err != nil {
return nil, err
}
}
return s.friends, nil
}
func (s *Self) Groups(update ...bool) (Groups, error) {
if s.groups == nil {
if err := s.updateGroups(update...); err != nil {
return nil, err
}
}
return s.groups, nil
}
func (s *Self) updateFriends(update ...bool) error {
var isUpdate bool
if len(update) > 0 {
isUpdate = update[len(update)-1]
}
if isUpdate || s.members == nil {
if err := s.updateMembers(); err != nil {
return err
}
}
friends := make(Friends, 0)
for _, member := range s.members {
if isFriend(*member) {
friend := &Friend{member}
friends = append(friends, friend)
}
}
s.friends = friends
return nil
}
func (s *Self) updateGroups(update ...bool) error {
var isUpdate bool
if len(update) > 0 {
isUpdate = update[len(update)-1]
}
if isUpdate || s.members == nil {
if err := s.updateMembers(); err != nil {
return err
}
}
groups := make(Groups, 0)
for _, member := range s.members {
if isGroup(*member) {
group := &Group{member}
groups = append(groups, group)
}
}
s.groups = groups
return nil
}
func (s *Self) UpdateMembersDetail() error {
members, err := s.Members()
if err != nil {
return err
}
count := members.Count()
var times int
if count < 50 {
times = 1
} else {
times = count / 50
}
newMembers := make(Members, 0)
request := s.Self.Bot.storage.GetBaseRequest()
var pMembers Members
for i := 0; i < times; i++ {
if times == 1 {
pMembers = members
} else {
pMembers = members[i*50 : (i+1)*times]
}
nMembers, err := s.Self.Bot.Caller.WebWxBatchGetContact(pMembers, request)
if err != nil {
return err
}
newMembers = append(newMembers, nMembers...)
}
total := times * 50
if total < count {
left := total - count
pMembers = members[total : total+left]
nMembers, err := s.Self.Bot.Caller.WebWxBatchGetContact(pMembers, request)
if err != nil {
return err
}
newMembers = append(newMembers, nMembers...)
}
newMembers.SetOwner(s)
s.members = newMembers
return nil
}
type Members []*User
func (m Members) Count() int {
return len(m)
}
func (m Members) SetOwner(s *Self) {
for _, member := range m {
member.Self = s
}
}