支持热登陆

This commit is contained in:
eatMoreApple 2021-04-27 18:36:50 +08:00
parent 2761e43c49
commit a8c646b33d
4 changed files with 491 additions and 351 deletions

59
bot.go
View File

@ -7,15 +7,18 @@ import (
) )
type Bot struct { type Bot struct {
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 isHot bool
exit chan bool err error
Caller *Caller exit chan bool
self *Self Caller *Caller
storage *Storage self *Self
storage *Storage
hotReloadStorage HotReloadStorage
mode mode
} }
// 判断当前用户是否正常在线 // 判断当前用户是否正常在线
@ -39,6 +42,20 @@ func (b *Bot) GetCurrentUser() (*Self, error) {
return b.self, nil return b.self, nil
} }
func (b *Bot) HotLogin(storage HotReloadStorage) error {
b.isHot = true
b.hotReloadStorage = storage
if err := storage.Load(); err != nil {
return b.Login()
}
cookies := storage.GetCookie()
path := b.Caller.Client.getBaseUrl()
b.Caller.Client.Jar.SetCookies(path, cookies)
b.storage.LoginInfo = storage.GetLoginInfo()
b.storage.Request = storage.GetBaseRequest()
return b.webInit()
}
// 用户登录 // 用户登录
// 该方法会一直阻塞,直到用户扫码登录,或者二维码过期 // 该方法会一直阻塞,直到用户扫码登录,或者二维码过期
func (b *Bot) Login() error { func (b *Bot) Login() error {
@ -105,8 +122,22 @@ func (b *Bot) login(data []byte) error {
// 将BaseRequest存到storage里面方便后续调用 // 将BaseRequest存到storage里面方便后续调用
b.storage.Request = request b.storage.Request = request
if b.isHot {
cookies := b.Caller.Client.getCookies()
if err := b.hotReloadStorage.Dump(cookies, request, info); err != nil {
return err
}
}
return b.webInit()
}
func (b *Bot) webInit() error {
req := b.storage.Request
info := b.storage.LoginInfo
// 获取初始化的用户信息和一些必要的参数 // 获取初始化的用户信息和一些必要的参数
resp, err := b.Caller.WebInit(request) resp, err := b.Caller.WebInit(req)
if err != nil { if err != nil {
return err return err
} }
@ -116,7 +147,7 @@ func (b *Bot) login(data []byte) error {
b.storage.Response = resp b.storage.Response = resp
// 通知手机客户端已经登录 // 通知手机客户端已经登录
if err = b.Caller.WebWxStatusNotify(request, resp, info); err != nil { if err = b.Caller.WebWxStatusNotify(req, resp, info); err != nil {
return err return err
} }
// 开启协程,轮训获取是否有新的消息返回 // 开启协程,轮训获取是否有新的消息返回
@ -208,12 +239,6 @@ func DefaultBot(modes ...mode) *Bot {
return NewBot(DefaultCaller(urlManager)) return NewBot(DefaultCaller(urlManager))
} }
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
} }

View File

@ -220,3 +220,18 @@ func TestAgreeFriendsAdd(t *testing.T) {
} }
bot.Block() bot.Block()
} }
func TestHotLogin(t *testing.T) {
bot := defaultBot()
s := NewFileHotReloadStorage("2.json")
if err := bot.HotLogin(s); err != nil {
t.Error(err)
return
}
self, err := bot.GetCurrentUser()
if err != nil {
t.Error(err)
return
}
t.Log(self.NickName)
}

678
client.go
View File

@ -1,454 +1,464 @@
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请求客户端
// 客户端需要维持Session会话 // 客户端需要维持Session会话
// 并且客户端不允许跳转 // 并且客户端不允许跳转
type Client struct { type Client struct {
*http.Client *http.Client
UrlManager UrlManager
} }
func NewClient(client *http.Client, urlManager UrlManager) *Client { func NewClient(client *http.Client, urlManager UrlManager) *Client {
return &Client{Client: client, UrlManager: urlManager} return &Client{Client: client, UrlManager: urlManager}
} }
// 自动存储cookie // 自动存储cookie
// 设置客户端不自动跳转 // 设置客户端不自动跳转
func DefaultClient(urlManager UrlManager) *Client { func DefaultClient(urlManager UrlManager) *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, urlManager) return NewClient(client, urlManager)
}
func (c *Client) getBaseUrl() *url.URL {
path, _ := url.Parse(c.UrlManager.baseUrl)
return path
}
func (c *Client) getCookies() []*http.Cookie {
path := c.getBaseUrl()
return c.Jar.Cookies(path)
} }
// 获取登录的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", c.webWxNewLoginPageUrl) params.Add("redirect_uri", c.webWxNewLoginPageUrl)
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())
} }
// GetLoginInfo 请求获取LoginInfo // GetLoginInfo 请求获取LoginInfo
func (c *Client) GetLoginInfo(path string) (*http.Response, error) { func (c *Client) GetLoginInfo(path string) (*http.Response, error) {
req, _ := http.NewRequest(http.MethodGet, path, nil) req, _ := http.NewRequest(http.MethodGet, path, nil)
req.Header.Add("client-version", uosPatchClientVersion) req.Header.Add("client-version", uosPatchClientVersion)
req.Header.Add("extspam", uosPatchExtspam) req.Header.Add("extspam", uosPatchExtspam)
return c.Do(req) return c.Do(req)
} }
// 请求获取初始化信息 // 请求获取初始化信息
func (c *Client) WebInit(request *BaseRequest) (*http.Response, error) { func (c *Client) WebInit(request *BaseRequest) (*http.Response, error) {
path, _ := url.Parse(c.webWxInitUrl) path, _ := url.Parse(c.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(c.webWxStatusNotifyUrl) path, _ := url.Parse(c.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(c.syncCheckUrl) path, _ := url.Parse(c.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(c.webWxGetContactUrl) path, _ := url.Parse(c.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(c.webWxBatchGetContactUrl) path, _ := url.Parse(c.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(c.webWxSyncUrl) path, _ := url.Parse(c.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(c.webWxSendMsgUrl) path, _ := url.Parse(c.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 := c.baseUrl + headImageUrl path := c.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(c.webWxUpLoadMediaUrl) path, _ := url.Parse(c.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(c.webWxSendMsgImgUrl) path, _ := url.Parse(c.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(c.webWxSendAppMsgUrl) path, _ := url.Parse(c.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(c.webWxOplogUrl) path, _ := url.Parse(c.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 *Storage, info RecommendInfo, verifyContent string) (*http.Response, error) { func (c *Client) WebWxVerifyUser(storage *Storage, info RecommendInfo, verifyContent string) (*http.Response, error) {
loginInfo := storage.LoginInfo loginInfo := storage.LoginInfo
path, _ := url.Parse(c.webWxVerifyUserUrl) path, _ := url.Parse(c.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.Request, "BaseRequest": storage.Request,
"Opcode": 3, "Opcode": 3,
"SceneList": [1]int{33}, "SceneList": [1]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(c.webWxGetMsgImgUrl) path, _ := url.Parse(c.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(c.webWxGetVoiceUrl) path, _ := url.Parse(c.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(c.webWxGetVideoUrl) path, _ := url.Parse(c.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(c.webWxGetMediaUrl) path, _ := url.Parse(c.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(c.webWxLogoutUrl) path, _ := url.Parse(c.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())
} }
// 添加用户进群聊 // 添加用户进群聊
func (c *Client) AddMemberIntoChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*Friend) (*http.Response, error) { func (c *Client) AddMemberIntoChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*Friend) (*http.Response, error) {
path, _ := url.Parse(c.webWxUpdateChatRoomUrl) path, _ := url.Parse(c.webWxUpdateChatRoomUrl)
params := url.Values{} params := url.Values{}
params.Add("fun", "addmember") params.Add("fun", "addmember")
params.Add("pass_ticket", info.PassTicket) params.Add("pass_ticket", info.PassTicket)
params.Add("lang", "zh_CN") params.Add("lang", "zh_CN")
path.RawQuery = params.Encode() path.RawQuery = params.Encode()
addMemberList := make([]string, 0) addMemberList := make([]string, 0)
for _, friend := range friends { for _, friend := range friends {
addMemberList = append(addMemberList, friend.UserName) addMemberList = append(addMemberList, friend.UserName)
} }
content := map[string]interface{}{ content := map[string]interface{}{
"ChatRoomName": group.UserName, "ChatRoomName": group.UserName,
"BaseRequest": req, "BaseRequest": req,
"AddMemberList": strings.Join(addMemberList, ","), "AddMemberList": strings.Join(addMemberList, ","),
} }
buffer, _ := ToBuffer(content) buffer, _ := ToBuffer(content)
return c.Post(path.String(), jsonContentType, buffer) return c.Post(path.String(), jsonContentType, buffer)
} }
// 从群聊中移除用户 // 从群聊中移除用户
func (c *Client) RemoveMemberFromChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*User) (*http.Response, error) { func (c *Client) RemoveMemberFromChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*User) (*http.Response, error) {
path, _ := url.Parse(c.webWxUpdateChatRoomUrl) path, _ := url.Parse(c.webWxUpdateChatRoomUrl)
params := url.Values{} params := url.Values{}
params.Add("fun", "delmember") params.Add("fun", "delmember")
params.Add("lang", "zh_CN") params.Add("lang", "zh_CN")
params.Add("pass_ticket", info.PassTicket) params.Add("pass_ticket", info.PassTicket)
delMemberList := make([]string, 0) delMemberList := make([]string, 0)
for _, friend := range friends { for _, friend := range friends {
delMemberList = append(delMemberList, friend.UserName) delMemberList = append(delMemberList, friend.UserName)
} }
content := map[string]interface{}{ content := map[string]interface{}{
"ChatRoomName": group.UserName, "ChatRoomName": group.UserName,
"BaseRequest": req, "BaseRequest": req,
"DelMemberList": strings.Join(delMemberList, ","), "DelMemberList": strings.Join(delMemberList, ","),
} }
buffer, _ := ToBuffer(content) buffer, _ := ToBuffer(content)
return c.Post(path.String(), jsonContentType, buffer) return c.Post(path.String(), jsonContentType, buffer)
} }

View File

@ -1,3 +1,93 @@
package openwechat package openwechat
import (
"bytes"
"encoding/json"
"net/http"
"os"
)
type Storage struct {
LoginInfo *LoginInfo
Request *BaseRequest
Response *WebInitResponse
}
type HotReloadStorage interface {
GetCookie() []*http.Cookie
GetBaseRequest() *BaseRequest
GetLoginInfo() *LoginInfo
Dump(cookies []*http.Cookie, req *BaseRequest, info *LoginInfo) error
Load() error
}
type FileHotReloadStorage struct {
Cookie []*http.Cookie
Req *BaseRequest
Info *LoginInfo
filename string
}
func (f *FileHotReloadStorage) Dump(cookies []*http.Cookie, req *BaseRequest, info *LoginInfo) error {
f.Cookie = cookies
f.Req = req
f.Info = info
var (
file *os.File
err error
)
_, err = os.Stat(f.filename)
if err != nil {
if os.IsNotExist(err) {
file, err = os.Create(f.filename)
if err != nil {
return err
}
}
}
if file == nil {
file, err = os.Open(f.filename)
}
if err != nil {
return err
}
defer file.Close()
data, err := json.Marshal(f)
if err != nil {
return err
}
_, err = file.Write(data)
return err
}
func (f *FileHotReloadStorage) Load() error {
file, err := os.Open(f.filename)
if err != nil {
return err
}
defer file.Close()
var buffer bytes.Buffer
if _, err := buffer.ReadFrom(file); err != nil {
return err
}
return json.Unmarshal(buffer.Bytes(), f)
}
func (f FileHotReloadStorage) GetCookie() []*http.Cookie {
return f.Cookie
}
func (f FileHotReloadStorage) GetBaseRequest() *BaseRequest {
return f.Req
}
func (f FileHotReloadStorage) GetLoginInfo() *LoginInfo {
return f.Info
}
func NewFileHotReloadStorage(filename string) *FileHotReloadStorage {
return &FileHotReloadStorage{filename: filename}
}