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