From 3c7fec07f360cb1b1cabff3bf59900c4bcc8a169 Mon Sep 17 00:00:00 2001 From: eatMoreApple <15055461510@163.com> Date: Thu, 29 Apr 2021 21:13:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E5=A4=A7=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- bot_test.go | 490 +++++++++++---------- caller.go | 438 ++++++++++--------- client.go | 991 ++++++++++++++++++++++--------------------- doc.md => doc/doc.md | 0 stroage.go | 106 ++--- 6 files changed, 1006 insertions(+), 1021 deletions(-) rename doc.md => doc/doc.md (100%) diff --git a/README.md b/README.md index c379a62..8d70626 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ -[文档](doc.md) +[文档](doc/doc.md) diff --git a/bot_test.go b/bot_test.go index fec65e1..fc302b2 100644 --- a/bot_test.go +++ b/bot_test.go @@ -1,295 +1,293 @@ package openwechat import ( - "os" - "testing" - "time" + "os" + "testing" + "time" ) func defaultBot(modes ...mode) *Bot { - bot := DefaultBot(modes...) - bot.UUIDCallback = PrintlnQrcodeUrl - return bot + bot := DefaultBot(modes...) + bot.UUIDCallback = PrintlnQrcodeUrl + return bot } func getSelf(modes ...mode) (*Self, error) { - bot := defaultBot(modes...) - if err := bot.Login(); err != nil { - return nil, err - } - return bot.GetCurrentUser() + bot := defaultBot(modes...) + if err := bot.Login(); err != nil { + return nil, err + } + return bot.GetCurrentUser() } func TestBotLogin(t *testing.T) { - bot := defaultBot() - if err := bot.Login(); err != nil { - t.Error(err) - return - } - self, err := bot.GetCurrentUser() - if err != nil { - t.Error(err) - return - } - t.Log(self.NickName) + bot := defaultBot() + if err := bot.Login(); err != nil { + t.Error(err) + return + } + self, err := bot.GetCurrentUser() + if err != nil { + t.Error(err) + return + } + t.Log(self.NickName) } func TestMessage(t *testing.T) { - bot := defaultBot() - bot.MessageHandler = func(msg *Message) { - t.Log(msg.MsgType) - t.Log(msg.Content) - if msg.IsMedia() { - t.Log(msg.Content) - t.Log(msg.FileName) - } - if msg.IsCard() { - c, _ := msg.Card() - t.Log(c.Alias) - } - if msg.IsSystem() { - t.Log(msg.Content) - } - if msg.IsRecalled() { - t.Log(msg.Content) - } - } - if err := bot.Login(); err != nil { - t.Error(err) - return - } - bot.Block() + bot := defaultBot() + bot.MessageHandler = func(msg *Message) { + t.Log(msg.MsgType) + t.Log(msg.Content) + if msg.IsMedia() { + t.Log(msg.Content) + t.Log(msg.FileName) + } + if msg.IsCard() { + c, _ := msg.Card() + t.Log(c.Alias) + } + if msg.IsSystem() { + t.Log(msg.Content) + } + if msg.IsRecalled() { + t.Log(msg.Content) + } + } + if err := bot.Login(); err != nil { + t.Error(err) + return + } + bot.Block() } func TestFriend(t *testing.T) { - self, err := getSelf() - if err != nil { - t.Error(err) - return - } - friends, err := self.Friends() - if err != nil { - t.Error(err) - return - } - t.Log(friends) + self, err := getSelf() + if err != nil { + t.Error(err) + return + } + friends, err := self.Friends() + if err != nil { + t.Error(err) + return + } + t.Log(friends) } func TestGroup(t *testing.T) { - self, err := getSelf() - if err != nil { - t.Error(err) - return - } - group, err := self.Groups() - if err != nil { - t.Error(err) - return - } - t.Log(group) - g := group.SearchByNickName(1, "杭州Gopher群组") - if g.First() != nil { - members, err := g.First().Members() - if err != nil { - t.Error(err) - return - } - t.Log(members.Count()) - } + self, err := getSelf() + if err != nil { + t.Error(err) + return + } + group, err := self.Groups() + if err != nil { + t.Error(err) + return + } + t.Log(group) + g := group.SearchByNickName(1, "杭州Gopher群组") + if g.First() != nil { + members, err := g.First().Members() + if err != nil { + t.Error(err) + return + } + t.Log(members.Count()) + } } func TestMps(t *testing.T) { - self, err := getSelf() - if err != nil { - t.Error(err) - return - } - mps, err := self.Mps() - if err != nil { - t.Error(err) - return - } - t.Log(mps) + self, err := getSelf() + if err != nil { + t.Error(err) + return + } + mps, err := self.Mps() + if err != nil { + t.Error(err) + return + } + t.Log(mps) } func TestAddFriendIntoChatRoom(t *testing.T) { - self, err := getSelf(Desktop) - if err != nil { - t.Error(err) - return - } - groups, err := self.Groups() - if err != nil { - t.Error(err) - return - } - friends, err := self.Friends() - if err != nil { - t.Error(err) - return - } - searchGroups := groups.SearchByNickName(1, "厉害了") - if g := searchGroups.First(); g != nil { - addFriends := friends.SearchByRemarkName(1, "1") - if err := g.AddFriendsIn(addFriends...); err != nil { - t.Error(err) - } - } + self, err := getSelf(Desktop) + if err != nil { + t.Error(err) + return + } + groups, err := self.Groups() + if err != nil { + t.Error(err) + return + } + friends, err := self.Friends() + if err != nil { + t.Error(err) + return + } + searchGroups := groups.SearchByNickName(1, "厉害了") + if g := searchGroups.First(); g != nil { + addFriends := friends.SearchByRemarkName(1, "1") + if err := g.AddFriendsIn(addFriends...); err != nil { + t.Error(err) + } + } } func TestRemoveFriendIntoChatRoom(t *testing.T) { - self, err := getSelf() - if err != nil { - t.Error(err) - return - } - groups, err := self.Groups() - if err != nil { - t.Error(err) - return - } - friends, err := self.Friends() - if err != nil { - t.Error(err) - return - } - searchGroups := groups.SearchByNickName(1, "厉害了") - if g := searchGroups.First(); g != nil { - addFriends := friends.SearchByRemarkName(1, "大爷") - if f := addFriends.First(); f != nil { - if err := g.RemoveMembers(Members{f.User}); err != nil { - t.Error(err) - } - } - } + self, err := getSelf() + if err != nil { + t.Error(err) + return + } + groups, err := self.Groups() + if err != nil { + t.Error(err) + return + } + friends, err := self.Friends() + if err != nil { + t.Error(err) + return + } + searchGroups := groups.SearchByNickName(1, "厉害了") + if g := searchGroups.First(); g != nil { + addFriends := friends.SearchByRemarkName(1, "大爷") + if f := addFriends.First(); f != nil { + if err := g.RemoveMembers(Members{f.User}); err != nil { + t.Error(err) + } + } + } } func TestLogout(t *testing.T) { - bot := defaultBot() - bot.MessageHandler = func(msg *Message) { - if msg.Content == "logout" { - msg.Bot.Logout() - } - } - if err := bot.Login(); err != nil { - t.Error(err) - return - } - bot.Block() + bot := defaultBot() + bot.MessageHandler = func(msg *Message) { + if msg.Content == "logout" { + msg.Bot.Logout() + } + } + if err := bot.Login(); err != nil { + t.Error(err) + return + } + bot.Block() } func TestSendMessage(t *testing.T) { - bot := defaultBot() - if err := bot.Login(); err != nil { - t.Error(err) - return - } - self, err := bot.GetCurrentUser() - if err != nil { - t.Error(err) - return - } - helper, err := self.FileHelper() - if err != nil { - t.Error(err) - return - } - if _, err = helper.SendText("test message! received ?"); err != nil { - t.Error(err) - return - } - time.Sleep(time.Second) - if _, err = self.SendTextToFriend(helper, "send test message twice ! received?"); err != nil { - t.Error(err) - return - } + bot := defaultBot() + if err := bot.Login(); err != nil { + t.Error(err) + return + } + self, err := bot.GetCurrentUser() + if err != nil { + t.Error(err) + return + } + helper, err := self.FileHelper() + if err != nil { + t.Error(err) + return + } + if _, err = helper.SendText("test message! received ?"); err != nil { + t.Error(err) + return + } + time.Sleep(time.Second) + if _, err = self.SendTextToFriend(helper, "send test message twice ! received?"); err != nil { + t.Error(err) + return + } } func TestAgreeFriendsAdd(t *testing.T) { - bot := defaultBot() - bot.MessageHandler = func(msg *Message) { - if msg.IsFriendAdd() { - if err := msg.Agree(); err != nil { - t.Error(err) - } - bot.Logout() - } - } - if err := bot.Login(); err != nil { - t.Error(err) - return - } - bot.Block() + bot := defaultBot() + bot.MessageHandler = func(msg *Message) { + if msg.IsFriendAdd() { + if err := msg.Agree(); err != nil { + t.Error(err) + } + bot.Logout() + } + } + if err := bot.Login(); err != nil { + t.Error(err) + return + } + bot.Block() } func TestHotLogin(t *testing.T) { - filename := "test.json" - bot := defaultBot() - s := NewJsonFileHotReloadStorage(filename) - 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) + filename := "test.json" + bot := defaultBot() + s := NewJsonFileHotReloadStorage(filename) + 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) } func TestFriendHelper(t *testing.T) { - bot := defaultBot() - if err := bot.HotLogin(NewJsonFileHotReloadStorage("2.json"), true); err != nil { - t.Error(err) - return - } - self, err := bot.GetCurrentUser() - if err != nil { - t.Error(err) - return - } - fh, err := self.FileHelper() - if err != nil { - t.Error(err) - return - } - f, _ := os.Open("webwxgetmsgimg.jpeg") - //f, _ := os.Open("2.jpeg") - defer f.Close() - msg, err := fh.SendImage(f) - //msg, err := fh.SendText("hh") - if err != nil { - t.Error(err) - return - } - t.Log(msg.MsgId) + bot := defaultBot(Desktop) + if err := bot.HotLogin(NewJsonFileHotReloadStorage("2.json"), true); err != nil { + t.Error(err) + return + } + self, err := bot.GetCurrentUser() + if err != nil { + t.Error(err) + return + } + fh, err := self.FileHelper() + if err != nil { + t.Error(err) + return + } + f, _ := os.Open("webwxgetmsgimg.jpg") + defer f.Close() + msg, err := fh.SendImage(f) + if err != nil { + t.Error(err) + return + } + t.Log(msg.MsgId) } func TestRevokeMessage(t *testing.T) { - bot := defaultBot(Desktop) - if err := bot.HotLogin(NewJsonFileHotReloadStorage("2.json")); err != nil { - t.Error(err) - return - } - self, err := bot.GetCurrentUser() - if err != nil { - t.Error(err) - return - } - friends, err := self.Friends() - if err != nil { - t.Error(err) - return - } - fs := friends.SearchByRemarkName(1, "1") - ms, err := fs.First().SendText("test") - if err != nil { - t.Error(err) - return - } - time.Sleep(time.Second) - if err := ms.Revoke(); err != nil { - t.Error(err) - } + bot := defaultBot(Desktop) + if err := bot.HotLogin(NewJsonFileHotReloadStorage("2.json")); err != nil { + t.Error(err) + return + } + self, err := bot.GetCurrentUser() + if err != nil { + t.Error(err) + return + } + friends, err := self.Friends() + if err != nil { + t.Error(err) + return + } + fs := friends.SearchByRemarkName(1, "1") + ms, err := fs.First().SendText("test") + if err != nil { + t.Error(err) + return + } + time.Sleep(time.Second) + if err := ms.Revoke(); err != nil { + t.Error(err) + } } diff --git a/caller.go b/caller.go index b96d55b..d7f6a0f 100644 --- a/caller.go +++ b/caller.go @@ -1,320 +1,318 @@ package openwechat import ( - "errors" - "fmt" - "os" + "errors" + "os" ) // 调用请求和解析请求 // 上层模块可以直接获取封装后的请求结果 type Caller struct { - Client *Client + Client *Client } // Constructor for Caller func NewCaller(client *Client) *Caller { - return &Caller{Client: client} + return &Caller{Client: client} } // Default Constructor for Caller func DefaultCaller(urlManager UrlManager) *Caller { - return NewCaller(DefaultClient(urlManager)) + return NewCaller(DefaultClient(urlManager)) } // 获取登录的uuid 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 - } - // 正则匹配uuid字符串 - results := uuidRegexp.FindSubmatch(data) - if len(results) != 2 { - // 如果没有匹配到,可能微信的接口做了修改,或者当前机器的ip被加入了黑名单 - return "", errors.New("uuid does not match") - } - return string(results[1]), nil + 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 + } + // 正则匹配uuid字符串 + results := uuidRegexp.FindSubmatch(data) + if len(results) != 2 { + // 如果没有匹配到,可能微信的接口做了修改,或者当前机器的ip被加入了黑名单 + 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 - } - // 正则匹配检测的code - // 具体code参考global.go - results := statusCodeRegexp.FindSubmatch(data) - if len(results) != 2 { - return nil, errors.New("error status code match") - } - code := string(results[1]) - return &CheckLoginResponse{Code: code, Raw: data}, nil + 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 + } + // 正则匹配检测的code + // 具体code参考global.go + results := statusCodeRegexp.FindSubmatch(data) + if len(results) != 2 { + return nil, errors.New("error status code match") + } + code := string(results[1]) + return &CheckLoginResponse{Code: code, Raw: data}, nil } // 获取登录信息 func (c *Caller) GetLoginInfo(body []byte) (*LoginInfo, error) { - // 从响应体里面获取需要跳转的url - 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 - // xml结构体序列化储存 - if err := resp.ScanXML(&loginInfo); err != nil { - return nil, err - } - if !loginInfo.Ok() { - return nil, loginInfo - } - return &loginInfo, nil + // 从响应体里面获取需要跳转的url + 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 + // xml结构体序列化储存 + if err := resp.ScanXML(&loginInfo); err != nil { + return nil, err + } + if !loginInfo.Ok() { + return nil, loginInfo + } + 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 + 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 + 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() - 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 + resp := NewReturnResponse(c.Client.SyncCheck(info, response)) + if resp.Err() != nil { + return nil, resp.Err() + } + defer resp.Body.Close() + data, err := resp.ReadAll() + 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 + 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 } // 获取联系人的详情 // 注: Members参数的长度不要大于50 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 + 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 + 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) (*SentMessage, error) { - resp := NewReturnResponse(c.Client.WebWxSendMsg(msg, info, request)) - sendSuccessMsg := &SentMessage{SendMessage: msg} - err := parseMessageResponseError(resp, sendSuccessMsg) - return sendSuccessMsg, err + resp := NewReturnResponse(c.Client.WebWxSendMsg(msg, info, request)) + sendSuccessMsg := &SentMessage{SendMessage: msg} + err := parseMessageResponseError(resp, sendSuccessMsg) + return sendSuccessMsg, err } // 修改用户备注接口 func (c *Caller) WebWxOplog(request *BaseRequest, remarkName, toUserName string) error { - resp := NewReturnResponse(c.Client.WebWxOplog(request, remarkName, toUserName)) - return parseBaseResponseError(resp) + 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) (*SentMessage, error) { - // 首先尝试上传图片 - sate, err := file.Stat() - if err != nil { - return nil, err - } + // 首先尝试上传图片 + sate, err := file.Stat() + if err != nil { + return nil, err + } - var resp *ReturnResponse - if sate.Size() <= chunkSize { - resp = NewReturnResponse(c.Client.WebWxUploadMedia(file, request, info, fromUserName, toUserName, "pic")) - } else { - resp = NewReturnResponse(c.Client.WebWxUploadMediaByChunk(file, request, info, fromUserName, toUserName, "pic")) - } - // 无错误上传成功之后获取请求结果,判断结果是否正常 - if resp.Err() != nil { - return nil, resp.Err() - } - defer resp.Body.Close() - data, _ := resp.ReadAll() - fmt.Println(string(data)) - var item struct { - BaseResponse BaseResponse - MediaId string - } + var resp *ReturnResponse + if sate.Size() <= chunkSize { + resp = NewReturnResponse(c.Client.WebWxUploadMedia(file, request, info, fromUserName, toUserName, "pic")) + } else { + resp = NewReturnResponse(c.Client.WebWxUploadMediaByChunk(file, request, info, fromUserName, toUserName, "pic")) + } + // 无错误上传成功之后获取请求结果,判断结果是否正常 + if resp.Err() != nil { + return nil, resp.Err() + } + defer resp.Body.Close() - if err = resp.ScanJSON(&item); err != nil { - return nil, err - } - if !item.BaseResponse.Ok() { - return nil, item.BaseResponse - } - if len(item.MediaId) == 0 { - return nil, errors.New("upload failed") - } - // 构造新的图片类型的信息 - msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, item.MediaId) - // 发送图片信息 - resp = NewReturnResponse(c.Client.WebWxSendMsgImg(msg, request, info)) + var item struct { + BaseResponse BaseResponse + MediaId string + } - sendSuccessMsg := &SentMessage{SendMessage: msg} - err = parseMessageResponseError(resp, sendSuccessMsg) - return sendSuccessMsg, err + if err = resp.ScanJSON(&item); err != nil { + return nil, err + } + if !item.BaseResponse.Ok() { + return nil, item.BaseResponse + } + if len(item.MediaId) == 0 { + return nil, errors.New("upload failed") + } + // 构造新的图片类型的信息 + msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, item.MediaId) + // 发送图片信息 + resp = NewReturnResponse(c.Client.WebWxSendMsgImg(msg, request, info)) + + sendSuccessMsg := &SentMessage{SendMessage: msg} + err = parseMessageResponseError(resp, sendSuccessMsg) + return sendSuccessMsg, err } // 用户退出 func (c *Caller) Logout(info *LoginInfo) error { - resp := NewReturnResponse(c.Client.Logout(info)) - return parseBaseResponseError(resp) + resp := NewReturnResponse(c.Client.Logout(info)) + return parseBaseResponseError(resp) } // 拉好友入群 func (c *Caller) AddFriendIntoChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*Friend) error { - if len(friends) == 0 { - return errors.New("no friends found") - } - resp := NewReturnResponse(c.Client.AddMemberIntoChatRoom(req, info, group, friends...)) - return parseBaseResponseError(resp) + if len(friends) == 0 { + return errors.New("no friends found") + } + resp := NewReturnResponse(c.Client.AddMemberIntoChatRoom(req, info, group, friends...)) + return parseBaseResponseError(resp) } // 从群聊中移除用户 func (c *Caller) RemoveFriendFromChatRoom(req *BaseRequest, info *LoginInfo, group *Group, users ...*User) error { - if len(users) == 0 { - return errors.New("no users found") - } - resp := NewReturnResponse(c.Client.RemoveMemberFromChatRoom(req, info, group, users...)) - return parseBaseResponseError(resp) + if len(users) == 0 { + return errors.New("no users found") + } + resp := NewReturnResponse(c.Client.RemoveMemberFromChatRoom(req, info, group, users...)) + return parseBaseResponseError(resp) } // 同意加好友请求 func (c *Caller) WebWxVerifyUser(storage *Storage, info RecommendInfo, verifyContent string) error { - resp := NewReturnResponse(c.Client.WebWxVerifyUser(storage, info, verifyContent)) - return parseBaseResponseError(resp) + resp := NewReturnResponse(c.Client.WebWxVerifyUser(storage, info, verifyContent)) + return parseBaseResponseError(resp) } // 撤回消息操作 func (c *Caller) WebWxRevokeMsg(msg *SentMessage, request *BaseRequest) error { - resp := NewReturnResponse(c.Client.WebWxRevokeMsg(msg, request)) - return parseBaseResponseError(resp) + resp := NewReturnResponse(c.Client.WebWxRevokeMsg(msg, request)) + return parseBaseResponseError(resp) } // 处理响应返回的结果是否正常 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 + 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 } func parseMessageResponseError(resp *ReturnResponse, msg *SentMessage) error { - if resp.Err() != nil { - return resp.Err() - } + if resp.Err() != nil { + return resp.Err() + } - defer resp.Body.Close() + defer resp.Body.Close() - var messageResp MessageResponse + var messageResp MessageResponse - if err := resp.ScanJSON(&messageResp); err != nil { - return err - } + if err := resp.ScanJSON(&messageResp); err != nil { + return err + } - if !messageResp.BaseResponse.Ok() { - return messageResp.BaseResponse - } - //// 发送成功之后将msgId赋值给SendMessage - msg.MsgId = messageResp.MsgID - return nil + if !messageResp.BaseResponse.Ok() { + return messageResp.BaseResponse + } + //// 发送成功之后将msgId赋值给SendMessage + msg.MsgId = messageResp.MsgID + return nil } diff --git a/client.go b/client.go index 3c7e9ca..d2e41ee 100644 --- a/client.go +++ b/client.go @@ -1,646 +1,647 @@ package openwechat import ( - "bytes" - "crypto/md5" - "encoding/json" - "fmt" - "mime/multipart" - "net/http" - "net/http/cookiejar" - "net/url" - "os" - "strconv" - "strings" - "sync" - "time" + "bytes" + "crypto/md5" + "encoding/json" + "fmt" + "mime/multipart" + "net/http" + "net/http/cookiejar" + "net/url" + "os" + "strconv" + "strings" + "sync" + "time" ) // http请求客户端 // 客户端需要维持Session会话 // 并且客户端不允许跳转 type Client struct { - *http.Client - UrlManager - mu sync.Mutex - cookies map[string][]*http.Cookie + *http.Client + UrlManager + mu sync.Mutex + cookies map[string][]*http.Cookie } func NewClient(client *http.Client, urlManager UrlManager) *Client { - return &Client{Client: client, UrlManager: urlManager} + return &Client{Client: client, UrlManager: urlManager} } // 自动存储cookie // 设置客户端不自动跳转 func DefaultClient(urlManager UrlManager) *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, urlManager) + jar, _ := cookiejar.New(nil) + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + Jar: jar, + } + return NewClient(client, urlManager) } // 抽象Do方法,将所有的有效的cookie存入Client.cookies // 方便热登陆时获取 func (c *Client) Do(req *http.Request) (*http.Response, error) { - req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36") - resp, err := c.Client.Do(req) - if err != nil { - return resp, err - } - c.mu.Lock() - defer c.mu.Unlock() - cookies := resp.Cookies() - if c.cookies == nil { - c.cookies = make(map[string][]*http.Cookie) - } - c.cookies[resp.Request.URL.String()] = cookies - return resp, err + req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36") + resp, err := c.Client.Do(req) + if err != nil { + return resp, err + } + c.mu.Lock() + defer c.mu.Unlock() + cookies := resp.Cookies() + if c.cookies == nil { + c.cookies = make(map[string][]*http.Cookie) + } + c.cookies[resp.Request.URL.String()] = cookies + return resp, err } // 获取当前client的所有的有效的client func (c *Client) GetCookieMap() map[string][]*http.Cookie { - return c.cookies + return c.cookies } // 获取登录的uuid func (c *Client) GetLoginUUID() (*http.Response, error) { - path, _ := url.Parse(jsLoginUrl) - params := url.Values{} - params.Add("appid", appId) - params.Add("redirect_uri", c.webWxNewLoginPageUrl) - params.Add("fun", "new") - params.Add("lang", "zh_CN") - params.Add("_", strconv.FormatInt(time.Now().Unix(), 10)) - path.RawQuery = params.Encode() - req, _ := http.NewRequest(http.MethodGet, path.String(), nil) - return c.Do(req) + path, _ := url.Parse(jsLoginUrl) + params := url.Values{} + params.Add("appid", appId) + params.Add("redirect_uri", c.webWxNewLoginPageUrl) + params.Add("fun", "new") + params.Add("lang", "zh_CN") + params.Add("_", strconv.FormatInt(time.Now().Unix(), 10)) + path.RawQuery = params.Encode() + req, _ := http.NewRequest(http.MethodGet, path.String(), nil) + return c.Do(req) } // 获取登录的二维吗 func (c *Client) GetLoginQrcode(uuid string) (*http.Response, error) { - path := qrcodeUrl + uuid - return c.Get(path) + 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() - req, _ := http.NewRequest(http.MethodGet, path.String(), nil) - return c.Do(req) + 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() + req, _ := http.NewRequest(http.MethodGet, path.String(), nil) + return c.Do(req) } // GetLoginInfo 请求获取LoginInfo func (c *Client) GetLoginInfo(path string) (*http.Response, error) { - req, _ := http.NewRequest(http.MethodGet, path, nil) - req.Header.Add("client-version", uosPatchClientVersion) - req.Header.Add("extspam", uosPatchExtspam) - return c.Do(req) + req, _ := http.NewRequest(http.MethodGet, path, nil) + req.Header.Add("client-version", uosPatchClientVersion) + req.Header.Add("extspam", uosPatchExtspam) + return c.Do(req) } // 请求获取初始化信息 func (c *Client) WebInit(request *BaseRequest) (*http.Response, error) { - path, _ := url.Parse(c.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 - } - req, _ := http.NewRequest(http.MethodPost, path.String(), body) - req.Header.Add("Content-Type", jsonContentType) - return c.Do(req) + path, _ := url.Parse(c.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 + } + req, _ := http.NewRequest(http.MethodPost, path.String(), body) + req.Header.Add("Content-Type", jsonContentType) + return c.Do(req) } // 通知手机已登录 func (c *Client) WebWxStatusNotify(request *BaseRequest, response *WebInitResponse, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.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) + path, _ := url.Parse(c.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(c.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)) - var syncKeyStringSlice []string - // 将SyncKey里面的元素按照特定的格式拼接起来 - 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) - return c.Do(req) + path, _ := url.Parse(c.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)) + var syncKeyStringSlice []string + // 将SyncKey里面的元素按照特定的格式拼接起来 + 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) + return c.Do(req) } // 获取联系人信息 func (c *Client) WebWxGetContact(info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.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() - req, _ := http.NewRequest(http.MethodGet, path.String(), nil) - return c.Do(req) + path, _ := url.Parse(c.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() + req, _ := http.NewRequest(http.MethodGet, path.String(), nil) + return c.Do(req) } // 获取联系人详情 func (c *Client) WebWxBatchGetContact(members Members, request *BaseRequest) (*http.Response, error) { - path, _ := url.Parse(c.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) + path, _ := url.Parse(c.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(c.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) + path, _ := url.Parse(c.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) + 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(c.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) + msg.Type = TextMessage + path, _ := url.Parse(c.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 := c.baseUrl + headImageUrl - req, _ := http.NewRequest(http.MethodGet, path, nil) - return c.Do(req) + path := c.baseUrl + headImageUrl + req, _ := http.NewRequest(http.MethodGet, path, nil) + return c.Do(req) } // 上传文件 func (c *Client) WebWxUploadMedia(file *os.File, request *BaseRequest, info *LoginInfo, forUserName, toUserName, mediaType string) (*http.Response, error) { - // 获取文件上传的类型 - contentType, err := GetFileContentType(file) - if err != nil { - return nil, err - } - path, _ := url.Parse(c.webWxUpLoadMediaUrl) - params := url.Values{} - params.Add("f", "json") - path.RawQuery = params.Encode() - sate, err := file.Stat() - if err != nil { - return nil, err - } - cookies := c.Jar.Cookies(path) - webWxDataTicket := getWebWxDataTicket(cookies) + // 获取文件上传的类型 + contentType, err := GetFileContentType(file) + if err != nil { + return nil, err + } + path, _ := url.Parse(c.webWxUpLoadMediaUrl) + params := url.Values{} + params.Add("f", "json") + path.RawQuery = params.Encode() + sate, err := file.Stat() + if err != nil { + return nil, err + } + cookies := c.Jar.Cookies(path) + webWxDataTicket := getWebWxDataTicket(cookies) - // 文件复位 - if _, err = file.Seek(0, 0); err != nil { - return nil, err - } + // 文件复位 + if _, err = file.Seek(0, 0); err != nil { + return nil, err + } - buffer := bytes.Buffer{} - if _, err := buffer.ReadFrom(file); err != nil { - return nil, err - } - data := buffer.Bytes() - fileMd5 := fmt.Sprintf("%x", md5.Sum(data)) + buffer := bytes.Buffer{} + if _, err := buffer.ReadFrom(file); err != nil { + return nil, err + } + data := buffer.Bytes() + fileMd5 := fmt.Sprintf("%x", md5.Sum(data)) - 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 - } + 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": sate.ModTime().Format(TimeFormat), - "size": sate.Size(), - "mediatype": mediaType, - "webwx_data_ticket": webWxDataTicket, - "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 - } - req, _ := http.NewRequest(http.MethodPost, path.String(), body) + content := map[string]interface{}{ + "id": "WU_FILE_0", + "name": file.Name(), + "type": contentType, + "lastModifiedDate": sate.ModTime().Format(TimeFormat), + "size": sate.Size(), + "mediatype": mediaType, + "webwx_data_ticket": webWxDataTicket, + "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 + } + req, _ := http.NewRequest(http.MethodPost, path.String(), body) - req.Header.Set("Content-Type", ct) - return c.Do(req) + req.Header.Set("Content-Type", ct) + return c.Do(req) } func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, info *LoginInfo, forUserName, toUserName, mediaType string) (*http.Response, error) { - // 获取文件上传的类型 - contentType, err := GetFileContentType(file) - if err != nil { - return nil, err - } - path, _ := url.Parse(c.webWxUpLoadMediaUrl) - params := url.Values{} - params.Add("f", "json") - path.RawQuery = params.Encode() - sate, err := file.Stat() - if err != nil { - return nil, err - } - cookies := c.Jar.Cookies(path) - webWxDataTicket := getWebWxDataTicket(cookies) + // 获取文件上传的类型 + contentType, err := GetFileContentType(file) + if err != nil { + return nil, err + } + path, _ := url.Parse(c.webWxUpLoadMediaUrl) + params := url.Values{} + params.Add("f", "json") + path.RawQuery = params.Encode() + sate, err := file.Stat() + if err != nil { + return nil, err + } + cookies := c.Jar.Cookies(path) + webWxDataTicket := getWebWxDataTicket(cookies) - // 将文件的游标复原到原点 - // 上面获取文件的类型的时候已经读取了512个字节 - if _, err = file.Seek(0, 0); err != nil { - return nil, err - } + // 将文件的游标复原到原点 + // 上面获取文件的类型的时候已经读取了512个字节 + if _, err = file.Seek(0, 0); err != nil { + return nil, err + } - buffer := bytes.Buffer{} - if _, err := buffer.ReadFrom(file); err != nil { - return nil, err - } - data := buffer.Bytes() - fileMd5 := fmt.Sprintf("%x", md5.Sum(data)) + buffer := bytes.Buffer{} + if _, err := buffer.ReadFrom(file); err != nil { + return nil, err + } + data := buffer.Bytes() + fileMd5 := fmt.Sprintf("%x", md5.Sum(data)) - 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 - } + 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 + } - chunks := sate.Size() / chunkSize - if chunks*chunkSize < sate.Size() { - chunks++ - } + chunks := sate.Size() / chunkSize + if chunks*chunkSize < sate.Size() { + chunks++ + } - var resp *http.Response + var resp *http.Response - for chunk := 0; int64(chunk) < chunks; chunk++ { - content := map[string]interface{}{ - "id": "WU_FILE_0", - "name": file.Name(), - "type": contentType, - "lastModifiedDate": sate.ModTime().Format(TimeFormat), - "size": sate.Size(), - "mediatype": mediaType, - "webwx_data_ticket": webWxDataTicket, - "pass_ticket": info.PassTicket, - "chunks": chunks, - "chunk": chunk, - } - 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 - } + for chunk := 0; int64(chunk) < chunks; chunk++ { - var isLastTime bool - if int64(chunk)+1 == chunks { - isLastTime = true - } + var isLastTime bool + if int64(chunk)+1 == chunks { + isLastTime = true + } - if w, err := writer.CreateFormFile("filename", file.Name()); err != nil { - return nil, err - } else { - var chunkData []byte - // 判断是不是最后一次 - if !isLastTime { - chunkData = data[int64(chunk)*chunkSize : (int64(chunk)+1)*chunkSize] - } else { - chunkData = data[int64(chunk)*chunkSize:] - } - if _, err = w.Write(chunkData); err != nil { - return nil, err - } - } - ct := writer.FormDataContentType() - if err = writer.Close(); err != nil { - return nil, err - } - req, _ := http.NewRequest(http.MethodPost, path.String(), body) - req.Header.Set("Content-Type", ct) - //req.Header.Add("Referer", c.baseUrl) - //req.Header.Add("Origin", c.baseUrl) - //// Host: file.wx2.qq.com - //req.Header.Add("Host", "file.wx2.qq.com") - // 发送数据 - resp, err = c.Do(req) + content := map[string]string{ + "id": "WU_FILE_0", + "name": file.Name(), + "type": contentType, + "lastModifiedDate": sate.ModTime().Format(TimeFormat), + "size": strconv.FormatInt(sate.Size(), 10), + "mediatype": mediaType, + "webwx_data_ticket": webWxDataTicket, + "pass_ticket": info.PassTicket, + "chunks": strconv.FormatInt(chunks, 10), + "chunk": strconv.Itoa(chunk), + } + var formBuffer bytes.Buffer - // 如果不是最后一次, 解析有没有错误 - if !isLastTime { - returnResp := NewReturnResponse(resp, err) - if err := parseBaseResponseError(returnResp); err != nil { - return nil, err - } - } - } + writer := multipart.NewWriter(&formBuffer) + if err = writer.WriteField("uploadmediarequest", string(uploadMediaRequestByte)); err != nil { + return nil, err + } - return resp, err + for k, v := range content { + if err := writer.WriteField(k, v); err != nil { + return nil, err + } + } + + if w, err := writer.CreateFormFile("filename", file.Name()); err != nil { + return nil, err + } else { + var chunkData []byte + // 判断是不是最后一次 + if !isLastTime { + chunkData = data[int64(chunk)*chunkSize : (int64(chunk)+1)*chunkSize] + } else { + chunkData = data[int64(chunk)*chunkSize:] + } + if _, err = w.Write(chunkData); err != nil { + return nil, err + } + } + ct := writer.FormDataContentType() + if err = writer.Close(); err != nil { + return nil, err + } + req, _ := http.NewRequest(http.MethodPost, path.String(), &formBuffer) + req.Header.Set("Content-Type", ct) + // 发送数据 + resp, err = c.Do(req) + + // 如果不是最后一次, 解析有没有错误 + if !isLastTime { + returnResp := NewReturnResponse(resp, err) + if err := parseBaseResponseError(returnResp); err != nil { + return nil, err + } + } + } + + return resp, err } // 发送图片 // 这个接口依赖上传文件的接口 // 发送的图片必须是已经成功上传的图片 func (c *Client) WebWxSendMsgImg(msg *SendMessage, request *BaseRequest, info *LoginInfo) (*http.Response, error) { - msg.Type = ImageMessage - path, _ := url.Parse(c.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) + msg.Type = ImageMessage + path, _ := url.Parse(c.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(c.webWxSendAppMsgUrl) - params := url.Values{} - params.Add("fun", "async") - params.Add("f", "json") - path.RawQuery = params.Encode() - return c.sendMessage(request, path.String(), msg) + msg.Type = AppMessage + path, _ := url.Parse(c.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(c.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) + path, _ := url.Parse(c.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 *Storage, info RecommendInfo, verifyContent string) (*http.Response, error) { - loginInfo := storage.LoginInfo - path, _ := url.Parse(c.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.Request, - "Opcode": 3, - "SceneList": [1]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) + loginInfo := storage.LoginInfo + path, _ := url.Parse(c.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.Request, + "Opcode": 3, + "SceneList": [1]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) } // 获取图片消息的图片响应 func (c *Client) WebWxGetMsgImg(msg *Message, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.webWxGetMsgImgUrl) - params := url.Values{} - params.Add("MsgID", msg.MsgId) - params.Add("skey", info.SKey) - params.Add("type", "slave") - path.RawQuery = params.Encode() - req, _ := http.NewRequest(http.MethodGet, path.String(), nil) - return c.Do(req) + path, _ := url.Parse(c.webWxGetMsgImgUrl) + params := url.Values{} + params.Add("MsgID", msg.MsgId) + params.Add("skey", info.SKey) + params.Add("type", "slave") + path.RawQuery = params.Encode() + req, _ := http.NewRequest(http.MethodGet, path.String(), nil) + return c.Do(req) } // 获取语音消息的语音响应 func (c *Client) WebWxGetVoice(msg *Message, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.webWxGetVoiceUrl) - params := url.Values{} - params.Add("msgid", msg.MsgId) - params.Add("skey", info.SKey) - path.RawQuery = params.Encode() - req, _ := http.NewRequest(http.MethodGet, path.String(), nil) - return c.Do(req) + path, _ := url.Parse(c.webWxGetVoiceUrl) + params := url.Values{} + params.Add("msgid", msg.MsgId) + params.Add("skey", info.SKey) + path.RawQuery = params.Encode() + req, _ := http.NewRequest(http.MethodGet, path.String(), nil) + return c.Do(req) } // 获取视频消息的视频响应 func (c *Client) WebWxGetVideo(msg *Message, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.webWxGetVideoUrl) - params := url.Values{} - params.Add("msgid", msg.MsgId) - params.Add("skey", info.SKey) - path.RawQuery = params.Encode() - req, _ := http.NewRequest(http.MethodGet, path.String(), nil) - return c.Do(req) + path, _ := url.Parse(c.webWxGetVideoUrl) + params := url.Values{} + params.Add("msgid", msg.MsgId) + params.Add("skey", info.SKey) + path.RawQuery = params.Encode() + req, _ := http.NewRequest(http.MethodGet, path.String(), nil) + return c.Do(req) } // 获取文件消息的文件响应 func (c *Client) WebWxGetMedia(msg *Message, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.webWxGetMediaUrl) - params := url.Values{} - params.Add("sender", msg.FromUserName) - params.Add("mediaid", msg.MediaId) - params.Add("encryfilename", msg.EncryFileName) - params.Add("fromuser", fmt.Sprintf("%d", info.WxUin)) - params.Add("pass_ticket", info.PassTicket) - params.Add("webwx_data_ticket", getWebWxDataTicket(c.Jar.Cookies(path))) - path.RawQuery = params.Encode() - req, _ := http.NewRequest(http.MethodGet, path.String(), nil) - return c.Do(req) + path, _ := url.Parse(c.webWxGetMediaUrl) + params := url.Values{} + params.Add("sender", msg.FromUserName) + params.Add("mediaid", msg.MediaId) + params.Add("encryfilename", msg.EncryFileName) + params.Add("fromuser", fmt.Sprintf("%d", info.WxUin)) + params.Add("pass_ticket", info.PassTicket) + params.Add("webwx_data_ticket", getWebWxDataTicket(c.Jar.Cookies(path))) + path.RawQuery = params.Encode() + req, _ := http.NewRequest(http.MethodGet, path.String(), nil) + return c.Do(req) } // 用户退出 func (c *Client) Logout(info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.webWxLogoutUrl) - params := url.Values{} - params.Add("redirect", "1") - params.Add("type", "1") - params.Add("skey", info.SKey) - path.RawQuery = params.Encode() - req, _ := http.NewRequest(http.MethodGet, path.String(), nil) - return c.Do(req) + path, _ := url.Parse(c.webWxLogoutUrl) + params := url.Values{} + params.Add("redirect", "1") + params.Add("type", "1") + params.Add("skey", info.SKey) + path.RawQuery = params.Encode() + req, _ := http.NewRequest(http.MethodGet, path.String(), nil) + return c.Do(req) } // 添加用户进群聊 func (c *Client) AddMemberIntoChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*Friend) (*http.Response, error) { - path, _ := url.Parse(c.webWxUpdateChatRoomUrl) - params := url.Values{} - params.Add("fun", "addmember") - params.Add("pass_ticket", info.PassTicket) - params.Add("lang", "zh_CN") - path.RawQuery = params.Encode() - addMemberList := make([]string, 0) - for _, friend := range friends { - addMemberList = append(addMemberList, friend.UserName) - } - content := map[string]interface{}{ - "ChatRoomName": group.UserName, - "BaseRequest": req, - "AddMemberList": strings.Join(addMemberList, ","), - } - buffer, _ := ToBuffer(content) - requ, _ := http.NewRequest(http.MethodPost, path.String(), buffer) - requ.Header.Set("Content-Type", jsonContentType) - return c.Do(requ) + path, _ := url.Parse(c.webWxUpdateChatRoomUrl) + params := url.Values{} + params.Add("fun", "addmember") + params.Add("pass_ticket", info.PassTicket) + params.Add("lang", "zh_CN") + path.RawQuery = params.Encode() + addMemberList := make([]string, 0) + for _, friend := range friends { + addMemberList = append(addMemberList, friend.UserName) + } + content := map[string]interface{}{ + "ChatRoomName": group.UserName, + "BaseRequest": req, + "AddMemberList": strings.Join(addMemberList, ","), + } + buffer, _ := ToBuffer(content) + requ, _ := http.NewRequest(http.MethodPost, path.String(), buffer) + requ.Header.Set("Content-Type", jsonContentType) + return c.Do(requ) } // 从群聊中移除用户 func (c *Client) RemoveMemberFromChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*User) (*http.Response, error) { - path, _ := url.Parse(c.webWxUpdateChatRoomUrl) - params := url.Values{} - params.Add("fun", "delmember") - params.Add("lang", "zh_CN") - params.Add("pass_ticket", info.PassTicket) - delMemberList := make([]string, 0) - for _, friend := range friends { - delMemberList = append(delMemberList, friend.UserName) - } - content := map[string]interface{}{ - "ChatRoomName": group.UserName, - "BaseRequest": req, - "DelMemberList": strings.Join(delMemberList, ","), - } - buffer, _ := ToBuffer(content) - requ, _ := http.NewRequest(http.MethodPost, path.String(), buffer) - requ.Header.Set("Content-Type", jsonContentType) - return c.Do(requ) + path, _ := url.Parse(c.webWxUpdateChatRoomUrl) + params := url.Values{} + params.Add("fun", "delmember") + params.Add("lang", "zh_CN") + params.Add("pass_ticket", info.PassTicket) + delMemberList := make([]string, 0) + for _, friend := range friends { + delMemberList = append(delMemberList, friend.UserName) + } + content := map[string]interface{}{ + "ChatRoomName": group.UserName, + "BaseRequest": req, + "DelMemberList": strings.Join(delMemberList, ","), + } + buffer, _ := ToBuffer(content) + requ, _ := http.NewRequest(http.MethodPost, path.String(), buffer) + requ.Header.Set("Content-Type", jsonContentType) + return c.Do(requ) } // 撤回消息 func (c *Client) WebWxRevokeMsg(msg *SentMessage, request *BaseRequest) (*http.Response, error) { - content := map[string]interface{}{ - "BaseRequest": request, - "ClientMsgId": msg.ClientMsgId, - "SvrMsgId": msg.MsgId, - "ToUserName": msg.ToUserName, - } - buffer, _ := ToBuffer(content) - req, _ := http.NewRequest(http.MethodPost, c.webWxRevokeMsg, buffer) - req.Header.Set("Content-Type", jsonContentType) - return c.Do(req) + content := map[string]interface{}{ + "BaseRequest": request, + "ClientMsgId": msg.ClientMsgId, + "SvrMsgId": msg.MsgId, + "ToUserName": msg.ToUserName, + } + buffer, _ := ToBuffer(content) + req, _ := http.NewRequest(http.MethodPost, c.webWxRevokeMsg, buffer) + req.Header.Set("Content-Type", jsonContentType) + return c.Do(req) } diff --git a/doc.md b/doc/doc.md similarity index 100% rename from doc.md rename to doc/doc.md diff --git a/stroage.go b/stroage.go index dd34b4e..497ffb8 100644 --- a/stroage.go +++ b/stroage.go @@ -1,100 +1,88 @@ package openwechat import ( - "bytes" - "encoding/json" - "net/http" - "os" + "bytes" + "encoding/json" + "net/http" + "os" ) // 身份信息, 维持整个登陆的Session会话 type Storage struct { - LoginInfo *LoginInfo - Request *BaseRequest - Response *WebInitResponse + LoginInfo *LoginInfo + Request *BaseRequest + Response *WebInitResponse } // 热登陆存储接口 type HotReloadStorage interface { - GetCookie() map[string][]*http.Cookie // 获取client.cookie - GetBaseRequest() *BaseRequest // 获取BaseRequest - GetLoginInfo() *LoginInfo // 获取LoginInfo - Dump(cookies map[string][]*http.Cookie, req *BaseRequest, info *LoginInfo) error // 实现该方法, 将必要信息进行序列化 - Load() error // 实现该方法, 将存储媒介的内容反序列化 + GetCookie() map[string][]*http.Cookie // 获取client.cookie + GetBaseRequest() *BaseRequest // 获取BaseRequest + GetLoginInfo() *LoginInfo // 获取LoginInfo + Dump(cookies map[string][]*http.Cookie, req *BaseRequest, info *LoginInfo) error // 实现该方法, 将必要信息进行序列化 + Load() error // 实现该方法, 将存储媒介的内容反序列化 } // 实现HotReloadStorage接口 // 默认以json文件的形式存储 type JsonFileHotReloadStorage struct { - Cookie map[string][]*http.Cookie - Req *BaseRequest - Info *LoginInfo - filename string + Cookie map[string][]*http.Cookie + Req *BaseRequest + Info *LoginInfo + filename string } // 将信息写入json文件 func (f *JsonFileHotReloadStorage) Dump(cookies map[string][]*http.Cookie, req *BaseRequest, info *LoginInfo) error { - 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) - } + file, err := os.OpenFile(f.filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm) - if err != nil { - return err - } - defer file.Close() + if err != nil { + return err + } - f.Cookie = cookies - f.Req = req - f.Info = info + defer file.Close() - data, err := json.Marshal(f) - if err != nil { - return err - } - _, err = file.Write(data) - return err + f.Cookie = cookies + f.Req = req + f.Info = info + + data, err := json.Marshal(f) + if err != nil { + return err + } + _, err = file.Write(data) + return err } // 从文件中读取信息 func (f *JsonFileHotReloadStorage) 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) + 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 + } + err = json.Unmarshal(buffer.Bytes(), f) + return err } func (f *JsonFileHotReloadStorage) GetCookie() map[string][]*http.Cookie { - return f.Cookie + return f.Cookie } func (f *JsonFileHotReloadStorage) GetBaseRequest() *BaseRequest { - return f.Req + return f.Req } func (f *JsonFileHotReloadStorage) GetLoginInfo() *LoginInfo { - return f.Info + return f.Info } func NewJsonFileHotReloadStorage(filename string) *JsonFileHotReloadStorage { - return &JsonFileHotReloadStorage{filename: filename} + return &JsonFileHotReloadStorage{filename: filename} }