first commit
This commit is contained in:
parent
0877f225bd
commit
0f1d42f6f1
177
bot.go
Normal file
177
bot.go
Normal 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
26
bot_test.go
Normal 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
216
caller.go
Normal 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
328
client.go
Normal 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
47
global.go
Normal 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"
|
||||
)
|
44
http.go
Normal file
44
http.go
Normal 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
149
items.go
Normal 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
249
manager.go
Normal 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
213
message.go
Normal 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
17
message_handler.go
Normal 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
83
parser.go
Normal 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
77
relations.go
Normal 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
45
stroage.go
Normal 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
278
user.go
Normal 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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user