删除storage接口的依赖

This commit is contained in:
eatMoreApple 2021-04-12 11:59:52 +08:00
parent 1c885a9182
commit f80cb2ebd2
7 changed files with 748 additions and 792 deletions

321
bot.go
View File

@ -1,221 +1,216 @@
package openwechat package openwechat
import ( import (
"errors" "errors"
"fmt" "fmt"
) )
type Bot struct { type Bot struct {
Caller *Caller Caller *Caller
self *Self self *Self
storage WechatStorage storage *Storage
ScanCallBack func(body []byte) ScanCallBack func(body []byte)
LoginCallBack func(body []byte) LoginCallBack func(body []byte)
UUIDCallback func(uuid string) UUIDCallback func(uuid string)
MessageHandler func(msg *Message) MessageHandler func(msg *Message)
err error err error
exit chan bool exit chan bool
} }
// 判断当前用户是否正常在线 // 判断当前用户是否正常在线
func (b *Bot) Alive() bool { func (b *Bot) Alive() bool {
if b.self == nil { if b.self == nil {
return false return false
} }
select { select {
case <-b.exit: case <-b.exit:
return false return false
default: default:
return true return true
} }
} }
// 获取当前的用户 // 获取当前的用户
func (b *Bot) GetCurrentUser() (*Self, error) { func (b *Bot) GetCurrentUser() (*Self, error) {
if b.self == nil { if b.self == nil {
return nil, errors.New("user not login") return nil, errors.New("user not login")
} }
return b.self, nil return b.self, nil
} }
// 用户登录 // 用户登录
// 该方法会一直阻塞,直到用户扫码登录,或者二维码过期 // 该方法会一直阻塞,直到用户扫码登录,或者二维码过期
func (b *Bot) Login() error { func (b *Bot) Login() error {
b.prepare() uuid, err := b.Caller.GetLoginUUID()
uuid, err := b.Caller.GetLoginUUID() if err != nil {
if err != nil { return err
return err }
} if b.UUIDCallback != nil {
if b.UUIDCallback != nil { b.UUIDCallback(uuid)
b.UUIDCallback(uuid) }
} for {
for { resp, err := b.Caller.CheckLogin(uuid)
resp, err := b.Caller.CheckLogin(uuid) if err != nil {
if err != nil { return err
return err }
} switch resp.Code {
switch resp.Code { case statusSuccess:
case statusSuccess: return b.login(resp.Raw)
return b.login(resp.Raw) case statusScanned:
case statusScanned: if b.ScanCallBack != nil {
if b.ScanCallBack != nil { b.ScanCallBack(resp.Raw)
b.ScanCallBack(resp.Raw) }
} case statusTimeout:
case statusTimeout: return errors.New("login time out")
return errors.New("login time out") case statusWait:
case statusWait: continue
continue }
} }
}
} }
func (b *Bot) Logout() error { func (b *Bot) Logout() error {
if b.Alive() { if b.Alive() {
info := b.storage.GetLoginInfo() info := b.storage.LoginInfo
if err := b.Caller.Logout(info); err != nil { if err := b.Caller.Logout(info); err != nil {
return err return err
} }
b.stopAsyncCALL(errors.New("logout")) b.stopAsyncCALL(errors.New("logout"))
return nil return nil
} }
return errors.New("user not login") return errors.New("user not login")
} }
// 登录逻辑 // 登录逻辑
func (b *Bot) login(data []byte) error { func (b *Bot) login(data []byte) error {
// 判断是否有登录回调,如果有执行它 // 判断是否有登录回调,如果有执行它
if b.LoginCallBack != nil { if b.LoginCallBack != nil {
b.LoginCallBack(data) b.LoginCallBack(data)
} }
// 获取登录的一些基本的信息 // 获取登录的一些基本的信息
info, err := b.Caller.GetLoginInfo(data) info, err := b.Caller.GetLoginInfo(data)
if err != nil { if err != nil {
return err return err
} }
// 将LoginInfo存到storage里面 // 将LoginInfo存到storage里面
b.storage.SetLoginInfo(*info) b.storage.LoginInfo = info
// 构建BaseRequest // 构建BaseRequest
request := BaseRequest{ request := &BaseRequest{
Uin: info.WxUin, Uin: info.WxUin,
Sid: info.WxSid, Sid: info.WxSid,
Skey: info.SKey, Skey: info.SKey,
DeviceID: GetRandomDeviceId(), DeviceID: GetRandomDeviceId(),
} }
// 将BaseRequest存到storage里面方便后续调用 // 将BaseRequest存到storage里面方便后续调用
b.storage.SetBaseRequest(request) b.storage.Request = request
// 获取初始化的用户信息和一些必要的参数 // 获取初始化的用户信息和一些必要的参数
resp, err := b.Caller.WebInit(request) resp, err := b.Caller.WebInit(request)
if err != nil { if err != nil {
return err return err
} }
// 设置当前的用户 // 设置当前的用户
b.self = &Self{Bot: b, User: &resp.User} b.self = &Self{Bot: b, User: &resp.User}
b.self.Self = b.self b.self.Self = b.self
b.storage.SetWebInitResponse(*resp) b.storage.Response = resp
// 通知手机客户端已经登录 // 通知手机客户端已经登录
if err = b.Caller.WebWxStatusNotify(request, *resp, *info); err != nil { if err = b.Caller.WebWxStatusNotify(request, resp, info); err != nil {
return err return err
} }
// 开启协程,轮训获取是否有新的消息返回 // 开启协程,轮训获取是否有新的消息返回
go func() { go func() {
b.stopAsyncCALL(b.asyncCall()) b.stopAsyncCALL(b.asyncCall())
}() }()
return nil return nil
} }
// 轮训请求 // 轮训请求
// 根据状态码判断是否有新的请求 // 根据状态码判断是否有新的请求
func (b *Bot) asyncCall() error { func (b *Bot) asyncCall() error {
var ( var (
err error err error
resp *SyncCheckResponse resp *SyncCheckResponse
) )
for b.Alive() { for b.Alive() {
info := b.storage.GetLoginInfo() info := b.storage.LoginInfo
response := b.storage.GetWebInitResponse() response := b.storage.Response
resp, err = b.Caller.SyncCheck(info, response) resp, err = b.Caller.SyncCheck(info, response)
if err != nil { if err != nil {
return err return err
} }
// 如果不是正常的状态码返回,发生了错误,直接退出 // 如果不是正常的状态码返回,发生了错误,直接退出
if !resp.Success() { if !resp.Success() {
return fmt.Errorf("unknow code got %s", resp.RetCode) return fmt.Errorf("unknow code got %s", resp.RetCode)
} }
// 如果Selector不为0则获取消息 // 如果Selector不为0则获取消息
if !resp.NorMal() { if !resp.NorMal() {
if err = b.getMessage(); err != nil { if err = b.getMessage(); err != nil {
return err return err
} }
} }
} }
return err return err
} }
func (b *Bot) stopAsyncCALL(err error) { func (b *Bot) stopAsyncCALL(err error) {
b.exit <- true b.exit <- true
b.err = err b.err = err
b.self = nil b.self = nil
} }
// 获取新的消息 // 获取新的消息
func (b *Bot) getMessage() error { func (b *Bot) getMessage() error {
info := b.storage.GetLoginInfo() resp, err := b.Caller.WebWxSync(b.storage.Request, b.storage.Response, b.storage.LoginInfo)
response := b.storage.GetWebInitResponse() if err != nil {
request := b.storage.GetBaseRequest() return err
resp, err := b.Caller.WebWxSync(request, response, info) }
if err != nil { // 更新SyncKey并且重新存入storage
return err b.storage.Response.SyncKey = resp.SyncKey
} // 遍历所有的新的消息,依次处理
// 更新SyncKey并且重新存入storage for _, message := range resp.AddMsgList {
response.SyncKey = resp.SyncKey // 根据不同的消息类型来进行处理,方便后续统一调用
b.storage.SetWebInitResponse(response) message.init(b)
// 遍历所有的新的消息,依次处理 // 调用自定义的处理方法
for _, message := range resp.AddMsgList { if handler := b.MessageHandler; handler != nil {
// 根据不同的消息类型来进行处理,方便后续统一调用 handler(message)
message.init(b) }
// 调用自定义的处理方法 }
if handler := b.MessageHandler; handler != nil { return nil
handler(message)
}
}
return nil
}
func (b *Bot) prepare() {
if b.storage == nil {
b.storage = NewSimpleWechatStorage()
}
} }
// 当消息同步发生了错误或者用户主动在手机上退出,该方法会立即返回,否则会一直阻塞 // 当消息同步发生了错误或者用户主动在手机上退出,该方法会立即返回,否则会一直阻塞
func (b *Bot) Block() error { func (b *Bot) Block() error {
if b.self == nil { if b.self == nil {
return errors.New("`Block` must be called after user login") return errors.New("`Block` must be called after user login")
} }
if _, closed := <-b.exit; !closed { if _, closed := <-b.exit; !closed {
return errors.New("can not call `Block` after user logout") return errors.New("can not call `Block` after user logout")
} }
close(b.exit) close(b.exit)
return nil return nil
} }
func NewBot(caller *Caller, storage WechatStorage) *Bot { func NewBot(caller *Caller) *Bot {
return &Bot{Caller: caller, storage: storage, exit: make(chan bool)} return &Bot{Caller: caller, storage: &Storage{}, exit: make(chan bool)}
} }
func DefaultBot() *Bot { func DefaultBot() *Bot {
return NewBot(DefaultCaller(), NewSimpleWechatStorage()) return NewBot(DefaultCaller())
}
type Storage struct {
LoginInfo *LoginInfo
Request *BaseRequest
Response *WebInitResponse
} }
func GetQrcodeUrl(uuid string) string { func GetQrcodeUrl(uuid string) string {
return qrcodeUrl + uuid return qrcodeUrl + uuid
} }
func PrintlnQrcodeUrl(uuid string) { func PrintlnQrcodeUrl(uuid string) {
println("访问下面网址扫描二维码登录") println("访问下面网址扫描二维码登录")
println(GetQrcodeUrl(uuid)) println(GetQrcodeUrl(uuid))
} }

View File

@ -19,6 +19,7 @@ func TestDefaultBot(t *testing.T) {
return return
} }
self, _ := bot.GetCurrentUser() self, _ := bot.GetCurrentUser()
self.SaveAvatar("2.png")
fileHelper, _ := self.FileHelper() fileHelper, _ := self.FileHelper()
fileHelper.SendText("6666") fileHelper.SendText("6666")
group, _ := self.Groups() group, _ := self.Groups()

View File

@ -84,7 +84,7 @@ func (c *Caller) GetLoginInfo(body []byte) (*LoginInfo, error) {
} }
// 获取初始化信息 // 获取初始化信息
func (c *Caller) WebInit(request BaseRequest) (*WebInitResponse, error) { func (c *Caller) WebInit(request *BaseRequest) (*WebInitResponse, error) {
resp := NewReturnResponse(c.Client.WebInit(request)) resp := NewReturnResponse(c.Client.WebInit(request))
if resp.Err() != nil { if resp.Err() != nil {
return nil, resp.Err() return nil, resp.Err()
@ -98,7 +98,7 @@ func (c *Caller) WebInit(request BaseRequest) (*WebInitResponse, error) {
} }
// 通知手机已登录 // 通知手机已登录
func (c *Caller) WebWxStatusNotify(request BaseRequest, response WebInitResponse, info LoginInfo) error { func (c *Caller) WebWxStatusNotify(request *BaseRequest, response *WebInitResponse, info *LoginInfo) error {
resp := NewReturnResponse(c.Client.WebWxStatusNotify(request, response, info)) resp := NewReturnResponse(c.Client.WebWxStatusNotify(request, response, info))
if resp.Err() != nil { if resp.Err() != nil {
return resp.Err() return resp.Err()
@ -115,7 +115,7 @@ func (c *Caller) WebWxStatusNotify(request BaseRequest, response WebInitResponse
} }
// 异步获取是否有新的消息 // 异步获取是否有新的消息
func (c *Caller) SyncCheck(info LoginInfo, response WebInitResponse) (*SyncCheckResponse, error) { func (c *Caller) SyncCheck(info *LoginInfo, response *WebInitResponse) (*SyncCheckResponse, error) {
resp := NewReturnResponse(c.Client.SyncCheck(info, response)) resp := NewReturnResponse(c.Client.SyncCheck(info, response))
if resp.Err() != nil { if resp.Err() != nil {
return nil, resp.Err() return nil, resp.Err()
@ -135,7 +135,7 @@ func (c *Caller) SyncCheck(info LoginInfo, response WebInitResponse) (*SyncCheck
} }
// 获取所有的联系人 // 获取所有的联系人
func (c *Caller) WebWxGetContact(info LoginInfo) (Members, error) { func (c *Caller) WebWxGetContact(info *LoginInfo) (Members, error) {
resp := NewReturnResponse(c.Client.WebWxGetContact(info)) resp := NewReturnResponse(c.Client.WebWxGetContact(info))
if resp.Err() != nil { if resp.Err() != nil {
return nil, resp.Err() return nil, resp.Err()
@ -153,7 +153,7 @@ func (c *Caller) WebWxGetContact(info LoginInfo) (Members, error) {
// 获取联系人的详情 // 获取联系人的详情
// 注: Members参数的长度不要大于50 // 注: Members参数的长度不要大于50
func (c *Caller) WebWxBatchGetContact(members Members, request BaseRequest) (Members, error) { func (c *Caller) WebWxBatchGetContact(members Members, request *BaseRequest) (Members, error) {
resp := NewReturnResponse(c.Client.WebWxBatchGetContact(members, request)) resp := NewReturnResponse(c.Client.WebWxBatchGetContact(members, request))
if resp.Err() != nil { if resp.Err() != nil {
return nil, resp.Err() return nil, resp.Err()
@ -170,7 +170,7 @@ func (c *Caller) WebWxBatchGetContact(members Members, request BaseRequest) (Mem
} }
// 获取新的消息接口 // 获取新的消息接口
func (c *Caller) WebWxSync(request BaseRequest, response WebInitResponse, info LoginInfo) (*WebWxSyncResponse, error) { func (c *Caller) WebWxSync(request *BaseRequest, response *WebInitResponse, info *LoginInfo) (*WebWxSyncResponse, error) {
resp := NewReturnResponse(c.Client.WebWxSync(request, response, info)) resp := NewReturnResponse(c.Client.WebWxSync(request, response, info))
if resp.Err() != nil { if resp.Err() != nil {
return nil, resp.Err() return nil, resp.Err()
@ -184,19 +184,19 @@ func (c *Caller) WebWxSync(request BaseRequest, response WebInitResponse, info L
} }
// 发送消息接口 // 发送消息接口
func (c *Caller) WebWxSendMsg(msg *SendMessage, info LoginInfo, request BaseRequest) error { func (c *Caller) WebWxSendMsg(msg *SendMessage, info *LoginInfo, request *BaseRequest) error {
resp := NewReturnResponse(c.Client.WebWxSendMsg(msg, info, request)) resp := NewReturnResponse(c.Client.WebWxSendMsg(msg, info, request))
return parseBaseResponseError(resp) return parseBaseResponseError(resp)
} }
// 修改用户备注接口 // 修改用户备注接口
func (c *Caller) WebWxOplog(request BaseRequest, remarkName, toUserName string) error { func (c *Caller) WebWxOplog(request *BaseRequest, remarkName, toUserName string) error {
resp := NewReturnResponse(c.Client.WebWxOplog(request, remarkName, toUserName)) resp := NewReturnResponse(c.Client.WebWxOplog(request, remarkName, toUserName))
return parseBaseResponseError(resp) return parseBaseResponseError(resp)
} }
// 发送图片消息接口 // 发送图片消息接口
func (c *Caller) WebWxSendImageMsg(file *os.File, request BaseRequest, info LoginInfo, fromUserName, toUserName string) error { 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")) resp := NewReturnResponse(c.Client.WebWxUploadMedia(file, request, info, fromUserName, toUserName, "image/jpeg", "pic"))
// 无错误上传成功之后获取请求结果,判断结果是否正常 // 无错误上传成功之后获取请求结果,判断结果是否正常
@ -222,7 +222,7 @@ func (c *Caller) WebWxSendImageMsg(file *os.File, request BaseRequest, info Logi
} }
// 用户退出 // 用户退出
func (c *Caller) Logout(info LoginInfo) error { func (c *Caller) Logout(info *LoginInfo) error {
resp := NewReturnResponse(c.Client.Logout(info)) resp := NewReturnResponse(c.Client.Logout(info))
return parseBaseResponseError(resp) return parseBaseResponseError(resp)
} }

628
client.go
View File

@ -1,18 +1,18 @@
package openwechat package openwechat
import ( import (
"bytes" "bytes"
"crypto/md5" "crypto/md5"
"encoding/json" "encoding/json"
"fmt" "fmt"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"net/url" "net/url"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time" "time"
) )
// http请求客户端 // http请求客户端
@ -21,387 +21,387 @@ import (
type Client struct{ *http.Client } type Client struct{ *http.Client }
func NewClient(client *http.Client) *Client { func NewClient(client *http.Client) *Client {
return &Client{Client: client} return &Client{Client: client}
} }
// 自动存储cookie // 自动存储cookie
// 设置客户端不自动跳转 // 设置客户端不自动跳转
func DefaultClient() *Client { func DefaultClient() *Client {
jar, _ := cookiejar.New(nil) jar, _ := cookiejar.New(nil)
client := &http.Client{ client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error { CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse return http.ErrUseLastResponse
}, },
Jar: jar, Jar: jar,
} }
return NewClient(client) return NewClient(client)
} }
// 获取登录的uuid // 获取登录的uuid
func (c *Client) GetLoginUUID() (*http.Response, error) { func (c *Client) GetLoginUUID() (*http.Response, error) {
path, _ := url.Parse(jsLoginUrl) path, _ := url.Parse(jsLoginUrl)
params := url.Values{} params := url.Values{}
params.Add("appid", appId) params.Add("appid", appId)
params.Add("redirect_uri", webWxNewLoginPage) params.Add("redirect_uri", webWxNewLoginPage)
params.Add("fun", "new") params.Add("fun", "new")
params.Add("lang", "zh_CN") params.Add("lang", "zh_CN")
params.Add("_", strconv.FormatInt(time.Now().Unix(), 10)) params.Add("_", strconv.FormatInt(time.Now().Unix(), 10))
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.Get(path.String()) return c.Get(path.String())
} }
// 获取登录的二维吗 // 获取登录的二维吗
func (c *Client) GetLoginQrcode(uuid string) (*http.Response, error) { func (c *Client) GetLoginQrcode(uuid string) (*http.Response, error) {
path := qrcodeUrl + uuid path := qrcodeUrl + uuid
return c.Get(path) return c.Get(path)
} }
// 检查是否登录 // 检查是否登录
func (c *Client) CheckLogin(uuid string) (*http.Response, error) { func (c *Client) CheckLogin(uuid string) (*http.Response, error) {
path, _ := url.Parse(loginUrl) path, _ := url.Parse(loginUrl)
now := time.Now().Unix() now := time.Now().Unix()
params := url.Values{} params := url.Values{}
params.Add("r", strconv.FormatInt(now/1579, 10)) params.Add("r", strconv.FormatInt(now/1579, 10))
params.Add("_", strconv.FormatInt(now, 10)) params.Add("_", strconv.FormatInt(now, 10))
params.Add("loginicon", "true") params.Add("loginicon", "true")
params.Add("uuid", uuid) params.Add("uuid", uuid)
params.Add("tip", "0") params.Add("tip", "0")
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.Get(path.String()) return c.Get(path.String())
} }
// 请求获取LoginInfo // 请求获取LoginInfo
func (c *Client) GetLoginInfo(path string) (*http.Response, error) { func (c *Client) GetLoginInfo(path string) (*http.Response, error) {
return c.Get(path) return c.Get(path)
} }
// 请求获取初始化信息 // 请求获取初始化信息
func (c *Client) WebInit(request BaseRequest) (*http.Response, error) { func (c *Client) WebInit(request *BaseRequest) (*http.Response, error) {
path, _ := url.Parse(webWxInitUrl) path, _ := url.Parse(webWxInitUrl)
params := url.Values{} params := url.Values{}
params.Add("_", fmt.Sprintf("%d", time.Now().Unix())) params.Add("_", fmt.Sprintf("%d", time.Now().Unix()))
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
content := struct{ BaseRequest BaseRequest }{BaseRequest: request} content := struct{ BaseRequest *BaseRequest }{BaseRequest: request}
body, err := ToBuffer(content) body, err := ToBuffer(content)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return c.Post(path.String(), jsonContentType, body) return c.Post(path.String(), jsonContentType, body)
} }
// 通知手机已登录 // 通知手机已登录
func (c *Client) WebWxStatusNotify(request BaseRequest, response WebInitResponse, info LoginInfo) (*http.Response, error) { func (c *Client) WebWxStatusNotify(request *BaseRequest, response *WebInitResponse, info *LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxStatusNotifyUrl) path, _ := url.Parse(webWxStatusNotifyUrl)
params := url.Values{} params := url.Values{}
params.Add("lang", "zh_CN") params.Add("lang", "zh_CN")
params.Add("pass_ticket", info.PassTicket) params.Add("pass_ticket", info.PassTicket)
username := response.User.UserName username := response.User.UserName
content := map[string]interface{}{ content := map[string]interface{}{
"BaseRequest": request, "BaseRequest": request,
"ClientMsgId": time.Now().Unix(), "ClientMsgId": time.Now().Unix(),
"Code": 3, "Code": 3,
"FromUserName": username, "FromUserName": username,
"ToUserName": username, "ToUserName": username,
} }
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
buffer, _ := ToBuffer(content) buffer, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, path.String(), buffer) req, _ := http.NewRequest(http.MethodPost, path.String(), buffer)
req.Header.Add("Content-Type", jsonContentType) req.Header.Add("Content-Type", jsonContentType)
return c.Do(req) return c.Do(req)
} }
// 异步检查是否有新的消息返回 // 异步检查是否有新的消息返回
func (c *Client) SyncCheck(info LoginInfo, response WebInitResponse) (*http.Response, error) { func (c *Client) SyncCheck(info *LoginInfo, response *WebInitResponse) (*http.Response, error) {
path, _ := url.Parse(syncCheckUrl) path, _ := url.Parse(syncCheckUrl)
params := url.Values{} params := url.Values{}
params.Add("r", strconv.FormatInt(time.Now().Unix(), 10)) params.Add("r", strconv.FormatInt(time.Now().Unix(), 10))
params.Add("skey", info.SKey) params.Add("skey", info.SKey)
params.Add("sid", info.WxSid) params.Add("sid", info.WxSid)
params.Add("uin", strconv.Itoa(info.WxUin)) params.Add("uin", strconv.Itoa(info.WxUin))
params.Add("deviceid", GetRandomDeviceId()) params.Add("deviceid", GetRandomDeviceId())
params.Add("_", strconv.FormatInt(time.Now().Unix(), 10)) params.Add("_", strconv.FormatInt(time.Now().Unix(), 10))
var syncKeyStringSlice []string var syncKeyStringSlice []string
// 将SyncKey里面的元素按照特定的格式拼接起来 // 将SyncKey里面的元素按照特定的格式拼接起来
for _, item := range response.SyncKey.List { for _, item := range response.SyncKey.List {
i := fmt.Sprintf("%d_%d", item.Key, item.Val) i := fmt.Sprintf("%d_%d", item.Key, item.Val)
syncKeyStringSlice = append(syncKeyStringSlice, i) syncKeyStringSlice = append(syncKeyStringSlice, i)
} }
syncKey := strings.Join(syncKeyStringSlice, "|") syncKey := strings.Join(syncKeyStringSlice, "|")
params.Add("synckey", syncKey) params.Add("synckey", syncKey)
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
req, _ := http.NewRequest(http.MethodGet, path.String(), nil) req, _ := http.NewRequest(http.MethodGet, path.String(), nil)
return c.Do(req) return c.Do(req)
} }
// 获取联系人信息 // 获取联系人信息
func (c *Client) WebWxGetContact(info LoginInfo) (*http.Response, error) { func (c *Client) WebWxGetContact(info *LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxGetContactUrl) path, _ := url.Parse(webWxGetContactUrl)
params := url.Values{} params := url.Values{}
params.Add("r", strconv.FormatInt(time.Now().Unix(), 10)) params.Add("r", strconv.FormatInt(time.Now().Unix(), 10))
params.Add("skey", info.SKey) params.Add("skey", info.SKey)
params.Add("req", "0") params.Add("req", "0")
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.Get(path.String()) return c.Get(path.String())
} }
// 获取联系人详情 // 获取联系人详情
func (c *Client) WebWxBatchGetContact(members Members, request BaseRequest) (*http.Response, error) { func (c *Client) WebWxBatchGetContact(members Members, request *BaseRequest) (*http.Response, error) {
path, _ := url.Parse(webWxBatchGetContactUrl) path, _ := url.Parse(webWxBatchGetContactUrl)
params := url.Values{} params := url.Values{}
params.Add("type", "ex") params.Add("type", "ex")
params.Add("r", strconv.FormatInt(time.Now().Unix(), 10)) params.Add("r", strconv.FormatInt(time.Now().Unix(), 10))
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
list := NewUserDetailItemList(members) list := NewUserDetailItemList(members)
content := map[string]interface{}{ content := map[string]interface{}{
"BaseRequest": request, "BaseRequest": request,
"Count": members.Count(), "Count": members.Count(),
"List": list, "List": list,
} }
body, _ := ToBuffer(content) body, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, path.String(), body) req, _ := http.NewRequest(http.MethodPost, path.String(), body)
req.Header.Add("Content-Type", jsonContentType) req.Header.Add("Content-Type", jsonContentType)
return c.Do(req) return c.Do(req)
} }
// 获取消息接口 // 获取消息接口
func (c *Client) WebWxSync(request BaseRequest, response WebInitResponse, info LoginInfo) (*http.Response, error) { func (c *Client) WebWxSync(request *BaseRequest, response *WebInitResponse, info *LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxSyncUrl) path, _ := url.Parse(webWxSyncUrl)
params := url.Values{} params := url.Values{}
params.Add("sid", info.WxSid) params.Add("sid", info.WxSid)
params.Add("skey", info.SKey) params.Add("skey", info.SKey)
params.Add("pass_ticket", info.PassTicket) params.Add("pass_ticket", info.PassTicket)
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
content := map[string]interface{}{ content := map[string]interface{}{
"BaseRequest": request, "BaseRequest": request,
"SyncKey": response.SyncKey, "SyncKey": response.SyncKey,
"rr": strconv.FormatInt(time.Now().Unix(), 10), "rr": strconv.FormatInt(time.Now().Unix(), 10),
} }
data, _ := json.Marshal(content) data, _ := json.Marshal(content)
body := bytes.NewBuffer(data) body := bytes.NewBuffer(data)
req, _ := http.NewRequest(http.MethodPost, path.String(), body) req, _ := http.NewRequest(http.MethodPost, path.String(), body)
req.Header.Add("Content-Type", jsonContentType) req.Header.Add("Content-Type", jsonContentType)
return c.Do(req) return c.Do(req)
} }
// 发送消息 // 发送消息
func (c *Client) sendMessage(request BaseRequest, url string, msg *SendMessage) (*http.Response, error) { func (c *Client) sendMessage(request *BaseRequest, url string, msg *SendMessage) (*http.Response, error) {
content := map[string]interface{}{ content := map[string]interface{}{
"BaseRequest": request, "BaseRequest": request,
"Msg": msg, "Msg": msg,
"Scene": 0, "Scene": 0,
} }
body, _ := ToBuffer(content) body, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, url, body) req, _ := http.NewRequest(http.MethodPost, url, body)
req.Header.Add("Content-Type", jsonContentType) req.Header.Add("Content-Type", jsonContentType)
return c.Do(req) return c.Do(req)
} }
// 发送文本消息 // 发送文本消息
func (c *Client) WebWxSendMsg(msg *SendMessage, info LoginInfo, request BaseRequest) (*http.Response, error) { func (c *Client) WebWxSendMsg(msg *SendMessage, info *LoginInfo, request *BaseRequest) (*http.Response, error) {
msg.Type = TextMessage msg.Type = TextMessage
path, _ := url.Parse(webWxSendMsgUrl) path, _ := url.Parse(webWxSendMsgUrl)
params := url.Values{} params := url.Values{}
params.Add("lang", "zh_CN") params.Add("lang", "zh_CN")
params.Add("pass_ticket", info.PassTicket) params.Add("pass_ticket", info.PassTicket)
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.sendMessage(request, path.String(), msg) return c.sendMessage(request, path.String(), msg)
} }
// 获取用户的头像 // 获取用户的头像
func (c *Client) WebWxGetHeadImg(headImageUrl string) (*http.Response, error) { func (c *Client) WebWxGetHeadImg(headImageUrl string) (*http.Response, error) {
path := baseUrl + headImageUrl path := baseUrl + headImageUrl
return c.Get(path) return c.Get(path)
} }
// 上传文件 // 上传文件
func (c *Client) WebWxUploadMedia(file *os.File, request BaseRequest, info LoginInfo, forUserName, toUserName, contentType, mediaType string) (*http.Response, error) { func (c *Client) WebWxUploadMedia(file *os.File, request *BaseRequest, info *LoginInfo, forUserName, toUserName, contentType, mediaType string) (*http.Response, error) {
path, _ := url.Parse(webWxUpLoadMediaUrl) path, _ := url.Parse(webWxUpLoadMediaUrl)
params := url.Values{} params := url.Values{}
params.Add("f", "json") params.Add("f", "json")
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
sate, err := file.Stat() sate, err := file.Stat()
if err != nil { if err != nil {
return nil, err return nil, err
} }
buffer := bytes.Buffer{} buffer := bytes.Buffer{}
if _, err := buffer.ReadFrom(file); err != nil { if _, err := buffer.ReadFrom(file); err != nil {
return nil, err return nil, err
} }
data := buffer.Bytes() data := buffer.Bytes()
fileMd5 := fmt.Sprintf("%x", md5.Sum(data)) fileMd5 := fmt.Sprintf("%x", md5.Sum(data))
cookies := c.Jar.Cookies(path) cookies := c.Jar.Cookies(path)
uploadMediaRequest := map[string]interface{}{ uploadMediaRequest := map[string]interface{}{
"UploadType": 2, "UploadType": 2,
"BaseRequest": request, "BaseRequest": request,
"ClientMediaId": time.Now().Unix() * 1e4, "ClientMediaId": time.Now().Unix() * 1e4,
"TotalLen": sate.Size(), "TotalLen": sate.Size(),
"StartPos": 0, "StartPos": 0,
"DataLen": sate.Size(), "DataLen": sate.Size(),
"MediaType": 4, "MediaType": 4,
"FromUserName": forUserName, "FromUserName": forUserName,
"ToUserName": toUserName, "ToUserName": toUserName,
"FileMd5": fileMd5, "FileMd5": fileMd5,
} }
uploadMediaRequestByte, err := json.Marshal(uploadMediaRequest) uploadMediaRequestByte, err := json.Marshal(uploadMediaRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
content := map[string]interface{}{ content := map[string]interface{}{
"id": "WU_FILE_0", "id": "WU_FILE_0",
"name": file.Name(), "name": file.Name(),
"type": contentType, "type": contentType,
"lastModifiedDate": time.Now().Format(http.TimeFormat), "lastModifiedDate": time.Now().Format(http.TimeFormat),
"size": sate.Size(), "size": sate.Size(),
"mediatype": mediaType, "mediatype": mediaType,
"webwx_data_ticket": getWebWxDataTicket(cookies), "webwx_data_ticket": getWebWxDataTicket(cookies),
"pass_ticket": info.PassTicket, "pass_ticket": info.PassTicket,
} }
body, err := ToBuffer(content) body, err := ToBuffer(content)
if err != nil { if err != nil {
return nil, err return nil, err
} }
writer := multipart.NewWriter(body) writer := multipart.NewWriter(body)
if err = writer.WriteField("uploadmediarequest", string(uploadMediaRequestByte)); err != nil { if err = writer.WriteField("uploadmediarequest", string(uploadMediaRequestByte)); err != nil {
return nil, err return nil, err
} }
if w, err := writer.CreateFormFile("filename", file.Name()); err != nil { if w, err := writer.CreateFormFile("filename", file.Name()); err != nil {
return nil, err return nil, err
} else { } else {
if _, err = w.Write(data); err != nil { if _, err = w.Write(data); err != nil {
return nil, err return nil, err
} }
} }
ct := writer.FormDataContentType() ct := writer.FormDataContentType()
if err = writer.Close(); err != nil { if err = writer.Close(); err != nil {
return nil, err return nil, err
} }
return c.Post(path.String(), ct, body) return c.Post(path.String(), ct, body)
} }
// 发送图片 // 发送图片
// 这个接口依赖上传文件的接口 // 这个接口依赖上传文件的接口
// 发送的图片必须是已经成功上传的图片 // 发送的图片必须是已经成功上传的图片
func (c *Client) WebWxSendMsgImg(msg *SendMessage, request BaseRequest, info LoginInfo) (*http.Response, error) { func (c *Client) WebWxSendMsgImg(msg *SendMessage, request *BaseRequest, info *LoginInfo) (*http.Response, error) {
msg.Type = ImageMessage msg.Type = ImageMessage
path, _ := url.Parse(webWxSendMsgImgUrl) path, _ := url.Parse(webWxSendMsgImgUrl)
params := url.Values{} params := url.Values{}
params.Add("fun", "async") params.Add("fun", "async")
params.Add("f", "json") params.Add("f", "json")
params.Add("lang", "zh_CN") params.Add("lang", "zh_CN")
params.Add("pass_ticket", info.PassTicket) params.Add("pass_ticket", info.PassTicket)
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.sendMessage(request, path.String(), msg) return c.sendMessage(request, path.String(), msg)
} }
// 发送文件信息 // 发送文件信息
func (c *Client) WebWxSendAppMsg(msg *SendMessage, request BaseRequest) (*http.Response, error) { func (c *Client) WebWxSendAppMsg(msg *SendMessage, request *BaseRequest) (*http.Response, error) {
msg.Type = AppMessage msg.Type = AppMessage
path, _ := url.Parse(webWxSendAppMsgUrl) path, _ := url.Parse(webWxSendAppMsgUrl)
params := url.Values{} params := url.Values{}
params.Add("fun", "async") params.Add("fun", "async")
params.Add("f", "json") params.Add("f", "json")
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.sendMessage(request, path.String(), msg) return c.sendMessage(request, path.String(), msg)
} }
// 用户重命名接口 // 用户重命名接口
func (c *Client) WebWxOplog(request BaseRequest, remarkName, userName string, ) (*http.Response, error) { func (c *Client) WebWxOplog(request *BaseRequest, remarkName, userName string, ) (*http.Response, error) {
path, _ := url.Parse(webWxOplogUrl) path, _ := url.Parse(webWxOplogUrl)
params := url.Values{} params := url.Values{}
params.Add("lang", "zh_CN") params.Add("lang", "zh_CN")
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
content := map[string]interface{}{ content := map[string]interface{}{
"BaseRequest": request, "BaseRequest": request,
"CmdId": 2, "CmdId": 2,
"RemarkName": remarkName, "RemarkName": remarkName,
"UserName": userName, "UserName": userName,
} }
body, _ := ToBuffer(content) body, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, path.String(), body) req, _ := http.NewRequest(http.MethodPost, path.String(), body)
req.Header.Add("Content-Type", jsonContentType) req.Header.Add("Content-Type", jsonContentType)
return c.Do(req) return c.Do(req)
} }
// 添加用户为好友接口 // 添加用户为好友接口
func (c *Client) WebWxVerifyUser(storage WechatStorage, info RecommendInfo, verifyContent string) (*http.Response, error) { func (c *Client) WebWxVerifyUser(storage *Storage, info RecommendInfo, verifyContent string) (*http.Response, error) {
loginInfo := storage.GetLoginInfo() loginInfo := storage.LoginInfo
path, _ := url.Parse(webWxVerifyUserUrl) path, _ := url.Parse(webWxVerifyUserUrl)
params := url.Values{} params := url.Values{}
params.Add("r", strconv.FormatInt(time.Now().Unix(), 10)) params.Add("r", strconv.FormatInt(time.Now().Unix(), 10))
params.Add("lang", "zh_CN") params.Add("lang", "zh_CN")
params.Add("pass_ticket", loginInfo.PassTicket) params.Add("pass_ticket", loginInfo.PassTicket)
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
content := map[string]interface{}{ content := map[string]interface{}{
"BaseRequest": storage.GetBaseRequest(), "BaseRequest": storage.Request,
"Opcode": 3, "Opcode": 3,
"SceneList": []int{33}, "SceneList": []int{33},
"SceneListCount": 1, "SceneListCount": 1,
"VerifyContent": verifyContent, "VerifyContent": verifyContent,
"VerifyUserList": []interface{}{map[string]string{ "VerifyUserList": []interface{}{map[string]string{
"Value": info.UserName, "Value": info.UserName,
"VerifyUserTicket": info.Ticket, "VerifyUserTicket": info.Ticket,
}}, }},
"VerifyUserListSize": 1, "VerifyUserListSize": 1,
"skey": loginInfo.SKey, "skey": loginInfo.SKey,
} }
body, _ := ToBuffer(content) body, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, path.String(), body) req, _ := http.NewRequest(http.MethodPost, path.String(), body)
req.Header.Add("Content-Type", jsonContentType) req.Header.Add("Content-Type", jsonContentType)
return c.Do(req) return c.Do(req)
} }
// 获取图片消息的图片响应 // 获取图片消息的图片响应
func (c *Client) WebWxGetMsgImg(msg *Message, info LoginInfo) (*http.Response, error) { func (c *Client) WebWxGetMsgImg(msg *Message, info *LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxGetMsgImgUrl) path, _ := url.Parse(webWxGetMsgImgUrl)
params := url.Values{} params := url.Values{}
params.Add("MsgID", msg.MsgId) params.Add("MsgID", msg.MsgId)
params.Add("skey", info.SKey) params.Add("skey", info.SKey)
params.Add("type", "slave") params.Add("type", "slave")
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.Get(path.String()) return c.Get(path.String())
} }
// 获取语音消息的语音响应 // 获取语音消息的语音响应
func (c *Client) WebWxGetVoice(msg *Message, info LoginInfo) (*http.Response, error) { func (c *Client) WebWxGetVoice(msg *Message, info *LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxGetVoiceUrl) path, _ := url.Parse(webWxGetVoiceUrl)
params := url.Values{} params := url.Values{}
params.Add("msgid", msg.MsgId) params.Add("msgid", msg.MsgId)
params.Add("skey", info.SKey) params.Add("skey", info.SKey)
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.Get(path.String()) return c.Get(path.String())
} }
// 获取视频消息的视频响应 // 获取视频消息的视频响应
func (c *Client) WebWxGetVideo(msg *Message, info LoginInfo) (*http.Response, error) { func (c *Client) WebWxGetVideo(msg *Message, info *LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxGetVideoUrl) path, _ := url.Parse(webWxGetVideoUrl)
params := url.Values{} params := url.Values{}
params.Add("msgid", msg.MsgId) params.Add("msgid", msg.MsgId)
params.Add("skey", info.SKey) params.Add("skey", info.SKey)
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.Get(path.String()) return c.Get(path.String())
} }
// 获取文件消息的文件响应 // 获取文件消息的文件响应
func (c *Client) WebWxGetMedia(msg *Message, info LoginInfo) (*http.Response, error) { func (c *Client) WebWxGetMedia(msg *Message, info *LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxGetMediaUrl) path, _ := url.Parse(webWxGetMediaUrl)
params := url.Values{} params := url.Values{}
params.Add("sender", msg.FromUserName) params.Add("sender", msg.FromUserName)
params.Add("mediaid", msg.MediaId) params.Add("mediaid", msg.MediaId)
params.Add("encryfilename", msg.EncryFileName) params.Add("encryfilename", msg.EncryFileName)
params.Add("fromuser", fmt.Sprintf("%d", info.WxUin)) params.Add("fromuser", fmt.Sprintf("%d", info.WxUin))
params.Add("pass_ticket", info.PassTicket) params.Add("pass_ticket", info.PassTicket)
params.Add("webwx_data_ticket", getWebWxDataTicket(c.Jar.Cookies(path))) params.Add("webwx_data_ticket", getWebWxDataTicket(c.Jar.Cookies(path)))
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.Get(path.String()) return c.Get(path.String())
} }
// 用户退出 // 用户退出
func (c *Client) Logout(info LoginInfo) (*http.Response, error) { func (c *Client) Logout(info *LoginInfo) (*http.Response, error) {
path, _ := url.Parse(webWxLogoutUrl) path, _ := url.Parse(webWxLogoutUrl)
params := url.Values{} params := url.Values{}
params.Add("redirect", "1") params.Add("redirect", "1")
params.Add("type", "1") params.Add("type", "1")
params.Add("skey", info.SKey) params.Add("skey", info.SKey)
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
return c.Get(path.String()) return c.Get(path.String())
} }

View File

@ -127,8 +127,8 @@ func (m *Message) IsSendByGroup() bool {
// 回复消息 // 回复消息
func (m *Message) Reply(msgType int, content, mediaId string) error { func (m *Message) Reply(msgType int, content, mediaId string) error {
msg := NewSendMessage(msgType, content, m.Bot.self.User.UserName, m.FromUserName, mediaId) msg := NewSendMessage(msgType, content, m.Bot.self.User.UserName, m.FromUserName, mediaId)
info := m.Bot.storage.GetLoginInfo() info := m.Bot.storage.LoginInfo
request := m.Bot.storage.GetBaseRequest() request := m.Bot.storage.Request
return m.Bot.Caller.WebWxSendMsg(msg, info, request) return m.Bot.Caller.WebWxSendMsg(msg, info, request)
} }
@ -139,8 +139,8 @@ func (m *Message) ReplyText(content string) error {
// 回复图片消息 // 回复图片消息
func (m *Message) ReplyImage(file *os.File) error { func (m *Message) ReplyImage(file *os.File) error {
info := m.Bot.storage.GetLoginInfo() info := m.Bot.storage.LoginInfo
request := m.Bot.storage.GetBaseRequest() request := m.Bot.storage.Request
return m.Bot.Caller.WebWxSendImageMsg(file, request, info, m.Bot.self.UserName, m.FromUserName) return m.Bot.Caller.WebWxSendImageMsg(file, request, info, m.Bot.self.UserName, m.FromUserName)
} }
@ -199,16 +199,16 @@ func (m *Message) GetFile() (*http.Response, error) {
return nil, errors.New("invalid message type") return nil, errors.New("invalid message type")
} }
if m.IsPicture() { if m.IsPicture() {
return m.Bot.Caller.Client.WebWxGetMsgImg(m, m.Bot.storage.GetLoginInfo()) return m.Bot.Caller.Client.WebWxGetMsgImg(m, m.Bot.storage.LoginInfo)
} }
if m.IsVoice() { if m.IsVoice() {
return m.Bot.Caller.Client.WebWxGetVoice(m, m.Bot.storage.GetLoginInfo()) return m.Bot.Caller.Client.WebWxGetVoice(m, m.Bot.storage.LoginInfo)
} }
if m.IsVideo() { if m.IsVideo() {
return m.Bot.Caller.Client.WebWxGetVideo(m, m.Bot.storage.GetLoginInfo()) return m.Bot.Caller.Client.WebWxGetVideo(m, m.Bot.storage.LoginInfo)
} }
if m.IsMedia() { if m.IsMedia() {
return m.Bot.Caller.Client.WebWxGetMedia(m, m.Bot.storage.GetLoginInfo()) return m.Bot.Caller.Client.WebWxGetMedia(m, m.Bot.storage.LoginInfo)
} }
return nil, errors.New("unsupported type") return nil, errors.New("unsupported type")
} }

View File

@ -1,49 +1,3 @@
package openwechat package openwechat
// WechatStorage
// 可以根据自己的情况来实现该接口
type WechatStorage interface {
SetLoginInfo(loginInfo LoginInfo)
SetBaseRequest(baseRequest BaseRequest)
SetWebInitResponse(webInitResponse WebInitResponse)
GetLoginInfo() LoginInfo
GetBaseRequest() BaseRequest
GetWebInitResponse() WebInitResponse
}
// implement WechatStorage
// 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
}

508
user.go
View File

@ -1,349 +1,355 @@
package openwechat package openwechat
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
) )
type User struct { type User struct {
Uin int Uin int
HideInputBarFlag int HideInputBarFlag int
StarFriend int StarFriend int
Sex int Sex int
AppAccountFlag int AppAccountFlag int
VerifyFlag int VerifyFlag int
ContactFlag int ContactFlag int
WebWxPluginSwitch int WebWxPluginSwitch int
HeadImgFlag int HeadImgFlag int
SnsFlag int SnsFlag int
UserName string UserName string
NickName string NickName string
HeadImgUrl string HeadImgUrl string
RemarkName string RemarkName string
PYInitial string PYInitial string
PYQuanPin string PYQuanPin string
RemarkPYInitial string RemarkPYInitial string
RemarkPYQuanPin string RemarkPYQuanPin string
Signature string Signature string
MemberCount int MemberCount int
MemberList Members MemberList Members
OwnerUin int OwnerUin int
Statues int Statues int
AttrStatus int AttrStatus int
Province string Province string
City string City string
Alias string Alias string
UniFriend int UniFriend int
DisplayName string DisplayName string
ChatRoomId int ChatRoomId int
KeyWord string KeyWord string
EncryChatRoomId string EncryChatRoomId string
IsOwner int IsOwner int
Self *Self Self *Self
} }
// implement fmt.Stringer // implement fmt.Stringer
func (u *User) String() string { func (u *User) String() string {
return fmt.Sprintf("<User:%s>", u.NickName) return fmt.Sprintf("<User:%s>", u.NickName)
} }
// 获取用户头像 // 获取用户头像
func (u *User) GetAvatarResponse() (*http.Response, error) { func (u *User) GetAvatarResponse() (*http.Response, error) {
return u.Self.Bot.Caller.Client.WebWxGetHeadImg(u.HeadImgUrl) return u.Self.Bot.Caller.Client.WebWxGetHeadImg(u.HeadImgUrl)
} }
// 下载用户头像 // 下载用户头像
func (u *User) SaveAvatar(filename string) error { func (u *User) SaveAvatar(filename string) error {
resp, err := u.GetAvatarResponse() resp, err := u.GetAvatarResponse()
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
buffer := bytes.Buffer{} buffer := bytes.Buffer{}
if _, err := buffer.ReadFrom(resp.Body); err != nil { if _, err := buffer.ReadFrom(resp.Body); err != nil {
return err return err
} }
return os.WriteFile(filename, buffer.Bytes(), os.ModePerm) file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(buffer.Bytes())
return err
} }
func (u *User) sendMsg(msg *SendMessage) error { func (u *User) sendMsg(msg *SendMessage) error {
msg.FromUserName = u.Self.UserName msg.FromUserName = u.Self.UserName
msg.ToUserName = u.UserName msg.ToUserName = u.UserName
info := u.Self.Bot.storage.GetLoginInfo() info := u.Self.Bot.storage.LoginInfo
request := u.Self.Bot.storage.GetBaseRequest() request := u.Self.Bot.storage.Request
return u.Self.Bot.Caller.WebWxSendMsg(msg, info, request) return u.Self.Bot.Caller.WebWxSendMsg(msg, info, request)
} }
func (u *User) sendText(content string) error { func (u *User) sendText(content string) error {
msg := NewTextSendMessage(content, u.Self.UserName, u.UserName) msg := NewTextSendMessage(content, u.Self.UserName, u.UserName)
return u.sendMsg(msg) return u.sendMsg(msg)
} }
func (u *User) sendImage(file *os.File) error { func (u *User) sendImage(file *os.File) error {
request := u.Self.Bot.storage.GetBaseRequest() request := u.Self.Bot.storage.Request
info := u.Self.Bot.storage.GetLoginInfo() info := u.Self.Bot.storage.LoginInfo
return u.Self.Bot.Caller.WebWxSendImageMsg(file, request, info, u.Self.UserName, u.UserName) return u.Self.Bot.Caller.WebWxSendImageMsg(file, request, info, u.Self.UserName, u.UserName)
} }
func (u *User) setRemarkName(remarkName string) error { func (u *User) setRemarkName(remarkName string) error {
request := u.Self.Bot.storage.GetBaseRequest() request := u.Self.Bot.storage.Request
return u.Self.Bot.Caller.WebWxOplog(request, remarkName, u.UserName) return u.Self.Bot.Caller.WebWxOplog(request, remarkName, u.UserName)
} }
// 获取用户的详情 // 获取用户的详情
func (u *User) Detail() (*User, error) { func (u *User) Detail() (*User, error) {
members := Members{u} members := Members{u}
request := u.Self.Bot.storage.GetBaseRequest() request := u.Self.Bot.storage.Request
newMembers, err := u.Self.Bot.Caller.WebWxBatchGetContact(members, request) newMembers, err := u.Self.Bot.Caller.WebWxBatchGetContact(members, request)
if err != nil { if err != nil {
return nil, err return nil, err
} }
user := newMembers.First() user := newMembers.First()
user.Self = u.Self user.Self = u.Self
return user, nil return user, nil
} }
type Self struct { type Self struct {
*User *User
Bot *Bot Bot *Bot
fileHelper *Friend fileHelper *Friend
members Members members Members
friends Friends friends Friends
groups Groups groups Groups
} }
// 获取所有的好友、群组、公众号信息 // 获取所有的好友、群组、公众号信息
func (s *Self) Members(update ...bool) (Members, error) { func (s *Self) Members(update ...bool) (Members, error) {
// 首先判断缓存里有没有,如果没有则去更新缓存 // 首先判断缓存里有没有,如果没有则去更新缓存
if s.members == nil { if s.members == nil {
if err := s.updateMembers(); err != nil { if err := s.updateMembers(); err != nil {
return nil, err return nil, err
} }
return s.members, nil return s.members, nil
} }
// 判断是否需要更新,如果传入的参数不为nil,则取最后一个 // 判断是否需要更新,如果传入的参数不为nil,则取最后一个
var isUpdate bool var isUpdate bool
if len(update) > 0 { if len(update) > 0 {
isUpdate = update[len(update)-1] isUpdate = update[len(update)-1]
} }
// 如果需要更新,则直接更新缓存 // 如果需要更新,则直接更新缓存
if isUpdate { if isUpdate {
if err := s.updateMembers(); err != nil { if err := s.updateMembers(); err != nil {
return nil, err return nil, err
} }
} }
return s.members, nil return s.members, nil
} }
// 更新联系人处理 // 更新联系人处理
func (s *Self) updateMembers() error { func (s *Self) updateMembers() error {
info := s.Bot.storage.GetLoginInfo() info := s.Bot.storage.LoginInfo
members, err := s.Bot.Caller.WebWxGetContact(info) members, err := s.Bot.Caller.WebWxGetContact(info)
if err != nil { if err != nil {
return err return err
} }
members.SetOwner(s) members.SetOwner(s)
s.members = members s.members = members
return nil return nil
} }
// 获取文件传输助手对象封装成Friend返回 // 获取文件传输助手对象封装成Friend返回
func (s *Self) FileHelper() (*Friend, error) { func (s *Self) FileHelper() (*Friend, error) {
// 如果缓存里有,直接返回,否则去联系人里面找 // 如果缓存里有,直接返回,否则去联系人里面找
if s.fileHelper != nil { if s.fileHelper != nil {
return s.fileHelper, nil return s.fileHelper, nil
} }
members, err := s.Members() members, err := s.Members()
if err != nil { if err != nil {
return nil, err return nil, err
} }
users := members.SearchByUserName(1, "filehelper") users := members.SearchByUserName(1, "filehelper")
if users == nil { if users == nil {
return nil, noSuchUserFoundError return nil, noSuchUserFoundError
} }
s.fileHelper = &Friend{users.First()} s.fileHelper = &Friend{users.First()}
return s.fileHelper, nil return s.fileHelper, nil
} }
// 获取所有的好友 // 获取所有的好友
func (s *Self) Friends(update ...bool) (Friends, error) { func (s *Self) Friends(update ...bool) (Friends, error) {
if s.friends == nil { if s.friends == nil {
if err := s.updateFriends(true); err != nil { if err := s.updateFriends(true); err != nil {
return nil, err return nil, err
} }
} }
return s.friends, nil return s.friends, nil
} }
// 获取所有的群组 // 获取所有的群组
func (s *Self) Groups(update ...bool) (Groups, error) { func (s *Self) Groups(update ...bool) (Groups, error) {
if s.groups == nil { if s.groups == nil {
if err := s.updateGroups(update...); err != nil { if err := s.updateGroups(update...); err != nil {
return nil, err return nil, err
} }
} }
return s.groups, nil return s.groups, nil
} }
// 更新好友处理 // 更新好友处理
func (s *Self) updateFriends(update ...bool) error { func (s *Self) updateFriends(update ...bool) error {
var isUpdate bool var isUpdate bool
if len(update) > 0 { if len(update) > 0 {
isUpdate = update[len(update)-1] isUpdate = update[len(update)-1]
} }
if isUpdate || s.members == nil { if isUpdate || s.members == nil {
if err := s.updateMembers(); err != nil { if err := s.updateMembers(); err != nil {
return err return err
} }
} }
var friends Friends var friends Friends
for _, member := range s.members { for _, member := range s.members {
if isFriend(*member) { if isFriend(*member) {
friend := &Friend{member} friend := &Friend{member}
friend.Self = s friend.Self = s
friends = append(friends, friend) friends = append(friends, friend)
} }
} }
s.friends = friends s.friends = friends
return nil return nil
} }
// 更新群组处理 // 更新群组处理
func (s *Self) updateGroups(update ...bool) error { func (s *Self) updateGroups(update ...bool) error {
var isUpdate bool var isUpdate bool
if len(update) > 0 { if len(update) > 0 {
isUpdate = update[len(update)-1] isUpdate = update[len(update)-1]
} }
if isUpdate || s.members == nil { if isUpdate || s.members == nil {
if err := s.updateMembers(); err != nil { if err := s.updateMembers(); err != nil {
return err return err
} }
} }
var groups Groups var groups Groups
for _, member := range s.members { for _, member := range s.members {
if isGroup(*member) { if isGroup(*member) {
group := &Group{member} group := &Group{member}
groups = append(groups, group) groups = append(groups, group)
} }
} }
s.groups = groups s.groups = groups
return nil return nil
} }
// 更新所有的联系人信息 // 更新所有的联系人信息
func (s *Self) UpdateMembersDetail() error { func (s *Self) UpdateMembersDetail() error {
// 先获取所有的联系人 // 先获取所有的联系人
members, err := s.Members() members, err := s.Members()
if err != nil { if err != nil {
return err return err
} }
// 获取他们的数量 // 获取他们的数量
count := members.Count() count := members.Count()
// 一次更新50个,分情况讨论 // 一次更新50个,分情况讨论
// 获取总的需要更新的次数 // 获取总的需要更新的次数
var times int var times int
if count < 50 { if count < 50 {
times = 1 times = 1
} else { } else {
times = count / 50 times = count / 50
} }
var newMembers Members var newMembers Members
request := s.Bot.storage.GetBaseRequest() request := s.Bot.storage.Request
var pMembers Members var pMembers Members
// 分情况依次更新 // 分情况依次更新
for i := 1; i <= times; i++ { for i := 1; i <= times; i++ {
if times == 1 { if times == 1 {
pMembers = members pMembers = members
} else { } else {
pMembers = members[(i-1)*50 : i*50] pMembers = members[(i-1)*50 : i*50]
} }
nMembers, err := s.Bot.Caller.WebWxBatchGetContact(pMembers, request) nMembers, err := s.Bot.Caller.WebWxBatchGetContact(pMembers, request)
if err != nil { if err != nil {
return err return err
} }
newMembers = append(newMembers, nMembers...) newMembers = append(newMembers, nMembers...)
} }
// 最后判断是否全部更新完毕 // 最后判断是否全部更新完毕
total := times * 50 total := times * 50
if total < count { if total < count {
// 将全部剩余的更新完毕 // 将全部剩余的更新完毕
left := count - total left := count - total
pMembers = members[total : total+left] pMembers = members[total : total+left]
nMembers, err := s.Bot.Caller.WebWxBatchGetContact(pMembers, request) nMembers, err := s.Bot.Caller.WebWxBatchGetContact(pMembers, request)
if err != nil { if err != nil {
return err return err
} }
newMembers = append(newMembers, nMembers...) newMembers = append(newMembers, nMembers...)
} }
newMembers.SetOwner(s) newMembers.SetOwner(s)
s.members = newMembers s.members = newMembers
return nil return nil
} }
type Members []*User type Members []*User
func (m Members) Count() int { func (m Members) Count() int {
return len(m) return len(m)
} }
func (m Members) First() *User { func (m Members) First() *User {
if m.Count() > 0 { if m.Count() > 0 {
return m[0] return m[0]
} }
return nil return nil
} }
func (m Members) Last() *User { func (m Members) Last() *User {
if m.Count() > 0 { if m.Count() > 0 {
return m[m.Count()-1] return m[m.Count()-1]
} }
return nil return nil
} }
func (m Members) SetOwner(s *Self) { func (m Members) SetOwner(s *Self) {
for _, member := range m { for _, member := range m {
member.Self = s member.Self = s
} }
} }
func (m Members) SearchByUserName(limit int, username string) (results Members) { func (m Members) SearchByUserName(limit int, username string) (results Members) {
return m.Search(limit, func(user *User) bool { return user.UserName == username }) return m.Search(limit, func(user *User) bool { return user.UserName == username })
} }
func (m Members) SearchByNickName(limit int, nickName string) (results Members) { func (m Members) SearchByNickName(limit int, nickName string) (results Members) {
return m.Search(limit, func(user *User) bool { return user.NickName == nickName }) return m.Search(limit, func(user *User) bool { return user.NickName == nickName })
} }
func (m Members) SearchByRemarkName(limit int, remarkName string) (results Members) { func (m Members) SearchByRemarkName(limit int, remarkName string) (results Members) {
return m.Search(limit, func(user *User) bool { return user.RemarkName == remarkName }) return m.Search(limit, func(user *User) bool { return user.RemarkName == remarkName })
} }
func (m Members) Search(limit int, condFuncList ...func(user *User) bool) (results Members) { func (m Members) Search(limit int, condFuncList ...func(user *User) bool) (results Members) {
if condFuncList == nil { if condFuncList == nil {
return m return m
} }
if limit <= 0 { if limit <= 0 {
limit = m.Count() limit = m.Count()
} }
for _, member := range m { for _, member := range m {
if results.Count() == limit { if results.Count() == limit {
break break
} }
var passCount int var passCount int
for _, condFunc := range condFuncList { for _, condFunc := range condFuncList {
if condFunc(member) { if condFunc(member) {
passCount++ passCount++
} else { } else {
break break
} }
} }
if passCount == len(condFuncList) { if passCount == len(condFuncList) {
results = append(results, member) results = append(results, member)
} }
} }
return return
} }