From 324d693290d925648f16cf0084a8f13ff25b0f3c Mon Sep 17 00:00:00 2001 From: eatmoreapple <15055461510@163.com> Date: Mon, 16 Aug 2021 22:27:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8F=91=E9=80=81=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- caller.go | 557 +++++++++++++------------- client.go | 1060 +++++++++++++++++++++++++------------------------- relations.go | 12 +- url.go | 1 + user.go | 14 + 5 files changed, 846 insertions(+), 798 deletions(-) diff --git a/caller.go b/caller.go index b899128..6c808cc 100644 --- a/caller.go +++ b/caller.go @@ -1,400 +1,411 @@ package openwechat import ( - "bytes" - "encoding/json" - "errors" - "net/http" - "net/url" - "os" + "bytes" + "encoding/json" + "errors" + "net/http" + "net/url" + "os" ) // Caller 调用请求和解析请求 // 上层模块可以直接获取封装后的请求结果 type Caller struct { - Client *Client - path *url.URL + Client *Client + path *url.URL } // NewCaller Constructor for Caller func NewCaller(client *Client) *Caller { - return &Caller{Client: client} + return &Caller{Client: client} } // DefaultCaller Default Constructor for Caller func DefaultCaller() *Caller { - return NewCaller(DefaultClient()) + return NewCaller(DefaultClient()) } // GetLoginUUID 获取登录的uuid func (c *Caller) GetLoginUUID() (string, error) { - resp, err := c.Client.GetLoginUUID() - if err != nil { - return "", err - } + resp, err := c.Client.GetLoginUUID() + if err != nil { + return "", err + } - defer resp.Body.Close() + defer resp.Body.Close() - var buffer bytes.Buffer - if _, err := buffer.ReadFrom(resp.Body); err != nil { - return "", err - } - // 正则匹配uuid字符串 - results := uuidRegexp.FindSubmatch(buffer.Bytes()) - if len(results) != 2 { - // 如果没有匹配到,可能微信的接口做了修改,或者当前机器的ip被加入了黑名单 - return "", errors.New("uuid does not match") - } - return string(results[1]), nil + var buffer bytes.Buffer + if _, err := buffer.ReadFrom(resp.Body); err != nil { + return "", err + } + // 正则匹配uuid字符串 + results := uuidRegexp.FindSubmatch(buffer.Bytes()) + if len(results) != 2 { + // 如果没有匹配到,可能微信的接口做了修改,或者当前机器的ip被加入了黑名单 + return "", errors.New("uuid does not match") + } + return string(results[1]), nil } // CheckLogin 检查是否登录成功 func (c *Caller) CheckLogin(uuid string) (*CheckLoginResponse, error) { - resp, err := c.Client.CheckLogin(uuid) - if err != nil { - return nil, err - } - defer resp.Body.Close() + resp, err := c.Client.CheckLogin(uuid) + if err != nil { + return nil, err + } + defer resp.Body.Close() - var buffer bytes.Buffer - if _, err := buffer.ReadFrom(resp.Body); err != nil { - return nil, err - } - // 正则匹配检测的code - // 具体code参考global.go - results := statusCodeRegexp.FindSubmatch(buffer.Bytes()) - if len(results) != 2 { - return nil, errors.New("error status code match") - } - code := string(results[1]) - return &CheckLoginResponse{Code: code, Raw: buffer.Bytes()}, nil + var buffer bytes.Buffer + if _, err := buffer.ReadFrom(resp.Body); err != nil { + return nil, err + } + // 正则匹配检测的code + // 具体code参考global.go + results := statusCodeRegexp.FindSubmatch(buffer.Bytes()) + if len(results) != 2 { + return nil, errors.New("error status code match") + } + code := string(results[1]) + return &CheckLoginResponse{Code: code, Raw: buffer.Bytes()}, nil } // GetLoginInfo 获取登录信息 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, err := url.Parse(string(results[1])) - if err != nil { - return nil, err - } - c.Client.Domain = WechatDomain(path.Host) - resp, err := c.Client.GetLoginInfo(path.String()) - if err != nil { - uErr, ok := err.(*url.Error) - if ok && (uErr.Err.Error() == ErrMissLocationHeader.Error()) { - return nil, ErrLoginForbiddenError - } - return nil, err - } - defer resp.Body.Close() + // 从响应体里面获取需要跳转的url + results := redirectUriRegexp.FindSubmatch(body) + if len(results) != 2 { + return nil, errors.New("redirect url does not match") + } + path, err := url.Parse(string(results[1])) + if err != nil { + return nil, err + } + c.Client.Domain = WechatDomain(path.Host) + resp, err := c.Client.GetLoginInfo(path.String()) + if err != nil { + uErr, ok := err.(*url.Error) + if ok && (uErr.Err.Error() == ErrMissLocationHeader.Error()) { + return nil, ErrLoginForbiddenError + } + return nil, err + } + defer resp.Body.Close() - var loginInfo LoginInfo - // xml结构体序列化储存 - if err := scanXml(resp, &loginInfo); err != nil { - return nil, err - } - if !loginInfo.Ok() { - return nil, loginInfo - } - return &loginInfo, nil + var loginInfo LoginInfo + // xml结构体序列化储存 + if err := scanXml(resp, &loginInfo); err != nil { + return nil, err + } + if !loginInfo.Ok() { + return nil, loginInfo + } + return &loginInfo, nil } // WebInit 获取初始化信息 func (c *Caller) WebInit(request *BaseRequest) (*WebInitResponse, error) { - resp, err := c.Client.WebInit(request) - if err != nil { - return nil, err - } - var webInitResponse WebInitResponse - defer resp.Body.Close() - if err := scanJson(resp, &webInitResponse); err != nil { - return nil, err - } - return &webInitResponse, nil + resp, err := c.Client.WebInit(request) + if err != nil { + return nil, err + } + var webInitResponse WebInitResponse + defer resp.Body.Close() + if err := scanJson(resp, &webInitResponse); err != nil { + return nil, err + } + return &webInitResponse, nil } // WebWxStatusNotify 通知手机已登录 func (c *Caller) WebWxStatusNotify(request *BaseRequest, response *WebInitResponse, info *LoginInfo) error { - resp, err := c.Client.WebWxStatusNotify(request, response, info) - if err != nil { - return err - } - var item struct{ BaseResponse BaseResponse } - defer resp.Body.Close() - if err := scanJson(resp, &item); err != nil { - return err - } - if !item.BaseResponse.Ok() { - return item.BaseResponse - } - return nil + resp, err := c.Client.WebWxStatusNotify(request, response, info) + if err != nil { + return err + } + var item struct{ BaseResponse BaseResponse } + defer resp.Body.Close() + if err := scanJson(resp, &item); err != nil { + return err + } + if !item.BaseResponse.Ok() { + return item.BaseResponse + } + return nil } // SyncCheck 异步获取是否有新的消息 func (c *Caller) SyncCheck(info *LoginInfo, response *WebInitResponse) (*SyncCheckResponse, error) { - resp, err := c.Client.SyncCheck(info, response) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var buffer bytes.Buffer - if _, err := buffer.ReadFrom(resp.Body); err != nil { - return nil, err - } - results := syncCheckRegexp.FindSubmatch(buffer.Bytes()) - 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, err := c.Client.SyncCheck(info, response) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var buffer bytes.Buffer + if _, err := buffer.ReadFrom(resp.Body); err != nil { + return nil, err + } + results := syncCheckRegexp.FindSubmatch(buffer.Bytes()) + 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 } // WebWxGetContact 获取所有的联系人 func (c *Caller) WebWxGetContact(info *LoginInfo) (Members, error) { - resp, err := c.Client.WebWxGetContact(info) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var item WebWxContactResponse - if err := scanJson(resp, &item); err != nil { - return nil, err - } - if !item.BaseResponse.Ok() { - return nil, item.BaseResponse - } - return item.MemberList, nil + resp, err := c.Client.WebWxGetContact(info) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var item WebWxContactResponse + if err := scanJson(resp, &item); err != nil { + return nil, err + } + if !item.BaseResponse.Ok() { + return nil, item.BaseResponse + } + return item.MemberList, nil } // WebWxBatchGetContact 获取联系人的详情 // 注: Members参数的长度不要大于50 func (c *Caller) WebWxBatchGetContact(members Members, request *BaseRequest) (Members, error) { - resp, err := c.Client.WebWxBatchGetContact(members, request) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var item WebWxBatchContactResponse - if err := scanJson(resp, &item); err != nil { - return nil, err - } - if !item.BaseResponse.Ok() { - return nil, item.BaseResponse - } - return item.ContactList, nil + resp, err := c.Client.WebWxBatchGetContact(members, request) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var item WebWxBatchContactResponse + if err := scanJson(resp, &item); err != nil { + return nil, err + } + if !item.BaseResponse.Ok() { + return nil, item.BaseResponse + } + return item.ContactList, nil } // WebWxSync 获取新的消息接口 func (c *Caller) WebWxSync(request *BaseRequest, response *WebInitResponse, info *LoginInfo) (*WebWxSyncResponse, error) { - resp, err := c.Client.WebWxSync(request, response, info) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var webWxSyncResponse WebWxSyncResponse - if err := scanJson(resp, &webWxSyncResponse); err != nil { - return nil, err - } - return &webWxSyncResponse, nil + resp, err := c.Client.WebWxSync(request, response, info) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var webWxSyncResponse WebWxSyncResponse + if err := scanJson(resp, &webWxSyncResponse); err != nil { + return nil, err + } + return &webWxSyncResponse, nil } // WebWxSendMsg 发送消息接口 func (c *Caller) WebWxSendMsg(msg *SendMessage, info *LoginInfo, request *BaseRequest) (*SentMessage, error) { - resp, err := c.Client.WebWxSendMsg(msg, info, request) - return getSuccessSentMessage(msg, resp, err) + resp, err := c.Client.WebWxSendMsg(msg, info, request) + return getSuccessSentMessage(msg, resp, err) } // WebWxOplog 修改用户备注接口 func (c *Caller) WebWxOplog(request *BaseRequest, remarkName, toUserName string) error { - resp, err := c.Client.WebWxOplog(request, remarkName, toUserName) - if err != nil { - return err - } - return parseBaseResponseError(resp) + resp, err := c.Client.WebWxOplog(request, remarkName, toUserName) + if err != nil { + return err + } + return parseBaseResponseError(resp) } func (c *Caller) UploadMedia(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*UploadResponse, error) { - // 首先尝试上传图片 - resp, err := c.Client.WebWxUploadMediaByChunk(file, request, info, fromUserName, toUserName) - // 无错误上传成功之后获取请求结果,判断结果是否正常 - if err != nil { - return nil, err - } - defer resp.Body.Close() + // 首先尝试上传图片 + resp, err := c.Client.WebWxUploadMediaByChunk(file, request, info, fromUserName, toUserName) + // 无错误上传成功之后获取请求结果,判断结果是否正常 + if err != nil { + return nil, err + } + defer resp.Body.Close() - var item UploadResponse + var item UploadResponse - if err := scanJson(resp, &item); err != nil { - return &item, err - } - if !item.BaseResponse.Ok() { - return &item, item.BaseResponse - } - if len(item.MediaId) == 0 { - return &item, errors.New("upload failed") - } - return &item, nil + if err := scanJson(resp, &item); err != nil { + return &item, err + } + if !item.BaseResponse.Ok() { + return &item, item.BaseResponse + } + if len(item.MediaId) == 0 { + return &item, errors.New("upload failed") + } + return &item, nil } // WebWxSendImageMsg 发送图片消息接口 func (c *Caller) WebWxSendImageMsg(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*SentMessage, error) { - // 首先尝试上传图片 - resp, err := c.UploadMedia(file, request, info, fromUserName, toUserName) - if err != nil { - return nil, err - } - // 构造新的图片类型的信息 - msg := NewMediaSendMessage(MsgTypeImage, fromUserName, toUserName, resp.MediaId) - // 发送图片信息 - resp1, err := c.Client.WebWxSendMsgImg(msg, request, info) - return getSuccessSentMessage(msg, resp1, err) + // 首先尝试上传图片 + resp, err := c.UploadMedia(file, request, info, fromUserName, toUserName) + if err != nil { + return nil, err + } + // 构造新的图片类型的信息 + msg := NewMediaSendMessage(MsgTypeImage, fromUserName, toUserName, resp.MediaId) + // 发送图片信息 + resp1, err := c.Client.WebWxSendMsgImg(msg, request, info) + return getSuccessSentMessage(msg, resp1, err) } func (c *Caller) WebWxSendFile(file *os.File, req *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*SentMessage, error) { - resp, err := c.UploadMedia(file, req, info, fromUserName, toUserName) - if err != nil { - return nil, err - } - // 构造新的文件类型的信息 - stat, _ := file.Stat() - appMsg := NewFileAppMessage(stat, resp.MediaId) - content, err := appMsg.XmlByte() - if err != nil { - return nil, err - } - msg := NewSendMessage(AppMessage, string(content), fromUserName, toUserName, "") - return c.WebWxSendAppMsg(msg, req) + resp, err := c.UploadMedia(file, req, info, fromUserName, toUserName) + if err != nil { + return nil, err + } + // 构造新的文件类型的信息 + stat, _ := file.Stat() + appMsg := NewFileAppMessage(stat, resp.MediaId) + content, err := appMsg.XmlByte() + if err != nil { + return nil, err + } + msg := NewSendMessage(AppMessage, string(content), fromUserName, toUserName, "") + return c.WebWxSendAppMsg(msg, req) +} + +func (c *Caller) WebWxSendVideoMsg(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*SentMessage, error) { + resp, err := c.UploadMedia(file, request, info, fromUserName, toUserName) + if err != nil { + return nil, err + } + // 构造新的图片类型的信息 + msg := NewMediaSendMessage(MsgTypeVideo, fromUserName, toUserName, resp.MediaId) + resp2, err := c.Client.WebWxSendVideoMsg(request, msg) + return getSuccessSentMessage(msg, resp2, err) } // WebWxSendAppMsg 发送媒体消息 func (c *Caller) WebWxSendAppMsg(msg *SendMessage, req *BaseRequest) (*SentMessage, error) { - resp, err := c.Client.WebWxSendAppMsg(msg, req) - return getSuccessSentMessage(msg, resp, err) + resp, err := c.Client.WebWxSendAppMsg(msg, req) + return getSuccessSentMessage(msg, resp, err) } // Logout 用户退出 func (c *Caller) Logout(info *LoginInfo) error { - resp, err := c.Client.Logout(info) - if err != nil { - return err - } - return parseBaseResponseError(resp) + resp, err := c.Client.Logout(info) + if err != nil { + return err + } + return parseBaseResponseError(resp) } // AddFriendIntoChatRoom 拉好友入群 func (c *Caller) AddFriendIntoChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*Friend) error { - if len(friends) == 0 { - return errors.New("no friends found") - } - resp, err := c.Client.AddMemberIntoChatRoom(req, info, group, friends...) - if err != nil { - return err - } - return parseBaseResponseError(resp) + if len(friends) == 0 { + return errors.New("no friends found") + } + resp, err := c.Client.AddMemberIntoChatRoom(req, info, group, friends...) + if err != nil { + return err + } + return parseBaseResponseError(resp) } // RemoveFriendFromChatRoom 从群聊中移除用户 func (c *Caller) RemoveFriendFromChatRoom(req *BaseRequest, info *LoginInfo, group *Group, users ...*User) error { - if len(users) == 0 { - return errors.New("no users found") - } - resp, err := c.Client.RemoveMemberFromChatRoom(req, info, group, users...) - if err != nil { - return err - } - return parseBaseResponseError(resp) + if len(users) == 0 { + return errors.New("no users found") + } + resp, err := c.Client.RemoveMemberFromChatRoom(req, info, group, users...) + if err != nil { + return err + } + return parseBaseResponseError(resp) } // WebWxVerifyUser 同意加好友请求 func (c *Caller) WebWxVerifyUser(storage *Storage, info RecommendInfo, verifyContent string) error { - resp, err := c.Client.WebWxVerifyUser(storage, info, verifyContent) - if err != nil { - return err - } - return parseBaseResponseError(resp) + resp, err := c.Client.WebWxVerifyUser(storage, info, verifyContent) + if err != nil { + return err + } + return parseBaseResponseError(resp) } // WebWxRevokeMsg 撤回消息操作 func (c *Caller) WebWxRevokeMsg(msg *SentMessage, request *BaseRequest) error { - resp, err := c.Client.WebWxRevokeMsg(msg, request) - if err != nil { - return err - } - return parseBaseResponseError(resp) + resp, err := c.Client.WebWxRevokeMsg(msg, request) + if err != nil { + return err + } + return parseBaseResponseError(resp) } // WebWxStatusAsRead 将消息设置为已读 func (c *Caller) WebWxStatusAsRead(request *BaseRequest, info *LoginInfo, msg *Message) error { - resp, err := c.Client.WebWxStatusAsRead(request, info, msg) - if err != nil { - return err - } - return parseBaseResponseError(resp) + resp, err := c.Client.WebWxStatusAsRead(request, info, msg) + if err != nil { + return err + } + return parseBaseResponseError(resp) } // WebWxRelationPin 将联系人是否置顶 func (c *Caller) WebWxRelationPin(request *BaseRequest, user *User, op uint8) error { - resp, err := c.Client.WebWxRelationPin(request, op, user) - if err != nil { - return err - } - return parseBaseResponseError(resp) + resp, err := c.Client.WebWxRelationPin(request, op, user) + if err != nil { + return err + } + return parseBaseResponseError(resp) } // WebWxPushLogin 免扫码登陆接口 func (c *Caller) WebWxPushLogin(uin int) (*PushLoginResponse, error) { - resp, err := c.Client.WebWxPushLogin(uin) - if err != nil { - return nil, err - } - defer resp.Body.Close() - var item PushLoginResponse - if err := json.NewDecoder(resp.Body).Decode(&item); err != nil { - return nil, err - } - return &item, nil + resp, err := c.Client.WebWxPushLogin(uin) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var item PushLoginResponse + if err := json.NewDecoder(resp.Body).Decode(&item); err != nil { + return nil, err + } + return &item, nil } // 处理响应返回的结果是否正常 func parseBaseResponseError(resp *http.Response) error { - defer resp.Body.Close() - var item struct{ BaseResponse BaseResponse } - if err := scanJson(resp, &item); err != nil { - return err - } - if !item.BaseResponse.Ok() { - return item.BaseResponse - } - return nil + defer resp.Body.Close() + var item struct{ BaseResponse BaseResponse } + if err := scanJson(resp, &item); err != nil { + return err + } + if !item.BaseResponse.Ok() { + return item.BaseResponse + } + return nil } func parseMessageResponseError(resp *http.Response, msg *SentMessage) error { - defer resp.Body.Close() + defer resp.Body.Close() - var messageResp MessageResponse + var messageResp MessageResponse - if err := scanJson(resp, &messageResp); err != nil { - return err - } + if err := scanJson(resp, &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 } func getSuccessSentMessage(msg *SendMessage, resp *http.Response, err error) (*SentMessage, error) { - if err != nil { - return nil, err - } - sendSuccessMsg := &SentMessage{SendMessage: msg} - err = parseMessageResponseError(resp, sendSuccessMsg) - return sendSuccessMsg, err + if err != nil { + return nil, err + } + sendSuccessMsg := &SentMessage{SendMessage: msg} + err = parseMessageResponseError(resp, sendSuccessMsg) + return sendSuccessMsg, err } diff --git a/client.go b/client.go index b49db35..cb4bbd3 100644 --- a/client.go +++ b/client.go @@ -1,27 +1,27 @@ package openwechat import ( - "bufio" - "bytes" - "crypto/md5" - "encoding/json" - "fmt" - "io" - "mime/multipart" - "net/http" - "net/http/cookiejar" - "net/url" - "os" - "strconv" - "strings" - "sync" - "time" + "bufio" + "bytes" + "crypto/md5" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/http/cookiejar" + "net/url" + "os" + "strconv" + "strings" + "sync" + "time" ) // HttpHook 请求上下文钩子 type HttpHook interface { - BeforeRequest(req *http.Request) - AfterRequest(response *http.Response, err error) + BeforeRequest(req *http.Request) + AfterRequest(response *http.Response, err error) } type HttpHooks []HttpHook @@ -29,7 +29,7 @@ type HttpHooks []HttpHook type UserAgentHook struct{} func (u UserAgentHook) BeforeRequest(req *http.Request) { - 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") + 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") } func (u UserAgentHook) AfterRequest(response *http.Response, err error) {} @@ -38,681 +38,693 @@ func (u UserAgentHook) AfterRequest(response *http.Response, err error) {} // 客户端需要维持Session会话 // 并且客户端不允许跳转 type Client struct { - HttpHooks HttpHooks - *http.Client - Domain WechatDomain - mode mode - mu sync.Mutex - cookies map[string][]*http.Cookie + HttpHooks HttpHooks + *http.Client + Domain WechatDomain + mode mode + mu sync.Mutex + cookies map[string][]*http.Cookie } func NewClient(client *http.Client) *Client { - return &Client{Client: client} + return &Client{Client: client} } // DefaultClient 自动存储cookie // 设置客户端不自动跳转 func DefaultClient() *Client { - jar, _ := cookiejar.New(nil) - client := &http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - Jar: jar, - } - c := NewClient(client) - c.AddHttpHook(UserAgentHook{}) - return c + jar, _ := cookiejar.New(nil) + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + Jar: jar, + } + c := NewClient(client) + c.AddHttpHook(UserAgentHook{}) + return c } func (c *Client) AddHttpHook(hooks ...HttpHook) { - c.HttpHooks = append(c.HttpHooks, hooks...) + c.HttpHooks = append(c.HttpHooks, hooks...) } func (c *Client) do(req *http.Request) (*http.Response, error) { - for _, hook := range c.HttpHooks { - hook.BeforeRequest(req) - } - resp, err := c.Client.Do(req) - for _, hook := range c.HttpHooks { - hook.AfterRequest(resp, err) - } - return resp, err + for _, hook := range c.HttpHooks { + hook.BeforeRequest(req) + } + resp, err := c.Client.Do(req) + for _, hook := range c.HttpHooks { + hook.AfterRequest(resp, err) + } + return resp, err } func (c *Client) setCookie(resp *http.Response) { - c.mu.Lock() - defer c.mu.Unlock() - cookies := resp.Cookies() - if c.cookies == nil { - c.cookies = make(map[string][]*http.Cookie) - } - path := fmt.Sprintf("%s://%s%s", resp.Request.URL.Scheme, resp.Request.URL.Host, resp.Request.URL.Path) - c.cookies[path] = cookies + c.mu.Lock() + defer c.mu.Unlock() + cookies := resp.Cookies() + if c.cookies == nil { + c.cookies = make(map[string][]*http.Cookie) + } + path := fmt.Sprintf("%s://%s%s", resp.Request.URL.Scheme, resp.Request.URL.Host, resp.Request.URL.Path) + c.cookies[path] = cookies } // Do 抽象Do方法,将所有的有效的cookie存入Client.cookies // 方便热登陆时获取 func (c *Client) Do(req *http.Request) (*http.Response, error) { - resp, err := c.do(req) - if err == nil { - c.setCookie(resp) - } - return resp, err + resp, err := c.do(req) + if err == nil { + c.setCookie(resp) + } + return resp, err } // GetCookieMap 获取当前client的所有的有效的client func (c *Client) GetCookieMap() map[string][]*http.Cookie { - return c.cookies + return c.cookies } // GetLoginUUID 获取登录的uuid func (c *Client) GetLoginUUID() (*http.Response, error) { - path, _ := url.Parse(jslogin) - params := url.Values{} - redirectUrl, _ := url.Parse(webwxnewloginpage) - if c.mode == Desktop { - p := url.Values{"mod": {"desktop"}} - redirectUrl.RawQuery = p.Encode() - } - params.Add("redirect_uri", redirectUrl.String()) - params.Add("appid", appId) - params.Add("fun", "new") - params.Add("lang", "zh_CN") - params.Add("_", strconv.FormatInt(time.Now().Unix(), 10)) + path, _ := url.Parse(jslogin) + params := url.Values{} + redirectUrl, _ := url.Parse(webwxnewloginpage) + if c.mode == Desktop { + p := url.Values{"mod": {"desktop"}} + redirectUrl.RawQuery = p.Encode() + } + params.Add("redirect_uri", redirectUrl.String()) + params.Add("appid", appId) + 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.RawQuery = params.Encode() + req, _ := http.NewRequest(http.MethodGet, path.String(), nil) + return c.Do(req) } // GetLoginQrcode 获取登录的二维吗 func (c *Client) GetLoginQrcode(uuid string) (*http.Response, error) { - path := qrcode + uuid - return c.Get(path) + path := qrcode + uuid + return c.Get(path) } // CheckLogin 检查是否登录 func (c *Client) CheckLogin(uuid string) (*http.Response, error) { - path, _ := url.Parse(login) - 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(login) + 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) - if c.mode == Desktop { - req.Header.Add("client-version", uosPatchClientVersion) - req.Header.Add("extspam", uosPatchExtspam) - } - return c.Do(req) + req, _ := http.NewRequest(http.MethodGet, path, nil) + if c.mode == Desktop { + req.Header.Add("client-version", uosPatchClientVersion) + req.Header.Add("extspam", uosPatchExtspam) + } + return c.Do(req) } // WebInit 请求获取初始化信息 func (c *Client) WebInit(request *BaseRequest) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxinit) - 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.Domain.BaseHost() + webwxinit) + 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) } // WebWxStatusNotify 通知手机已登录 func (c *Client) WebWxStatusNotify(request *BaseRequest, response *WebInitResponse, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxstatusnotify) - 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.Domain.BaseHost() + webwxstatusnotify) + 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) } // SyncCheck 异步检查是否有新的消息返回 func (c *Client) SyncCheck(info *LoginInfo, response *WebInitResponse) (*http.Response, error) { - path, _ := url.Parse(c.Domain.SyncHost() + synccheck) - 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 = make([]string, response.SyncKey.Count) - // 将SyncKey里面的元素按照特定的格式拼接起来 - for index, item := range response.SyncKey.List { - i := fmt.Sprintf("%d_%d", item.Key, item.Val) - syncKeyStringSlice[index] = 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.Domain.SyncHost() + synccheck) + 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 = make([]string, response.SyncKey.Count) + // 将SyncKey里面的元素按照特定的格式拼接起来 + for index, item := range response.SyncKey.List { + i := fmt.Sprintf("%d_%d", item.Key, item.Val) + syncKeyStringSlice[index] = 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) } // WebWxGetContact 获取联系人信息 func (c *Client) WebWxGetContact(info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxgetcontact) - 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.Domain.BaseHost() + webwxgetcontact) + 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) } // WebWxBatchGetContact 获取联系人详情 func (c *Client) WebWxBatchGetContact(members Members, request *BaseRequest) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxbatchgetcontact) - 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.Domain.BaseHost() + webwxbatchgetcontact) + 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) } // WebWxSync 获取消息接口 func (c *Client) WebWxSync(request *BaseRequest, response *WebInitResponse, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxsync) - 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.Domain.BaseHost() + webwxsync) + 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) } // WebWxSendMsg 发送文本消息 func (c *Client) WebWxSendMsg(msg *SendMessage, info *LoginInfo, request *BaseRequest) (*http.Response, error) { - msg.Type = MsgTypeText - path, _ := url.Parse(c.Domain.BaseHost() + webwxsendmsg) - 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 = MsgTypeText + path, _ := url.Parse(c.Domain.BaseHost() + webwxsendmsg) + 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) } // WebWxGetHeadImg 获取用户的头像 func (c *Client) WebWxGetHeadImg(user *User) (*http.Response, error) { - var path string - if user.HeadImgUrl != "" { - path = c.Domain.BaseHost() + user.HeadImgUrl - } else { - params := url.Values{} - params.Add("username", user.UserName) - params.Add("skey", user.Self.Bot.Storage.Request.Skey) - params.Add("type", "big") - URL, _ := url.Parse(c.Domain.BaseHost() + webwxgeticon) - URL.RawQuery = params.Encode() - path = URL.String() - } - req, _ := http.NewRequest(http.MethodGet, path, nil) - return c.Do(req) + var path string + if user.HeadImgUrl != "" { + path = c.Domain.BaseHost() + user.HeadImgUrl + } else { + params := url.Values{} + params.Add("username", user.UserName) + params.Add("skey", user.Self.Bot.Storage.Request.Skey) + params.Add("type", "big") + URL, _ := url.Parse(c.Domain.BaseHost() + webwxgeticon) + URL.RawQuery = params.Encode() + path = URL.String() + } + req, _ := http.NewRequest(http.MethodGet, path, nil) + return c.Do(req) } func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, info *LoginInfo, forUserName, toUserName string) (*http.Response, error) { - // 获取文件上传的类型 - contentType, err := GetFileContentType(file) - if err != nil { - return nil, err - } + // 获取文件上传的类型 + contentType, err := GetFileContentType(file) + if err != nil { + return nil, err + } - // 将文件的游标复原到原点 - // 上面获取文件的类型的时候已经读取了512个字节 - if _, err = file.Seek(0, 0); err != nil { - return nil, err - } + // 将文件的游标复原到原点 + // 上面获取文件的类型的时候已经读取了512个字节 + if _, err = file.Seek(0, 0); err != nil { + return nil, err + } - reader := bufio.NewReader(file) + reader := bufio.NewReader(file) - h := md5.New() - if _, err = io.Copy(h, reader); err != nil { - return nil, err - } + h := md5.New() + if _, err = io.Copy(h, reader); err != nil { + return nil, err + } - fileMd5 := fmt.Sprintf("%x", h.Sum(nil)) + fileMd5 := fmt.Sprintf("%x", h.Sum(nil)) - sate, err := file.Stat() - if err != nil { - return nil, err - } + sate, err := file.Stat() + if err != nil { + return nil, err + } - // 获取文件的类型 - mediaType := getMessageType(sate.Name()) + // 获取文件的类型 + mediaType := getMessageType(sate.Name()) - path, _ := url.Parse(c.Domain.FileHost() + webwxuploadmedia) - params := url.Values{} - params.Add("f", "json") + path, _ := url.Parse(c.Domain.FileHost() + webwxuploadmedia) + params := url.Values{} + params.Add("f", "json") - path.RawQuery = params.Encode() + path.RawQuery = params.Encode() - cookies := c.Jar.Cookies(path) - webWxDataTicket := getWebWxDataTicket(cookies) + cookies := c.Jar.Cookies(path) + webWxDataTicket := getWebWxDataTicket(cookies) - 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, - } + 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 - } + uploadMediaRequestByte, err := json.Marshal(uploadMediaRequest) + if err != nil { + return nil, err + } - var chunks int64 + var chunks int64 - if sate.Size() > chunkSize { - chunks = sate.Size() / chunkSize - if chunks*chunkSize < sate.Size() { - chunks++ - } - } else { - chunks = 1 - } + if sate.Size() > chunkSize { + chunks = sate.Size() / chunkSize + if chunks*chunkSize < sate.Size() { + chunks++ + } + } else { + chunks = 1 + } - var resp *http.Response + var resp *http.Response - 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, - } + 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, + } - if chunks > 1 { - content["chunks"] = strconv.FormatInt(chunks, 10) - } + if chunks > 1 { + content["chunks"] = strconv.FormatInt(chunks, 10) + } - if _, err = file.Seek(0, 0); err != nil { - return nil, err - } + if _, err = file.Seek(0, 0); err != nil { + return nil, err + } - // 分块上传 - for chunk := 0; int64(chunk) < chunks; chunk++ { + // 分块上传 + 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 chunks > 1 { - content["chunk"] = strconv.Itoa(chunk) - } + if chunks > 1 { + content["chunk"] = strconv.Itoa(chunk) + } - var formBuffer bytes.Buffer + var formBuffer bytes.Buffer - writer := multipart.NewWriter(&formBuffer) - if err = writer.WriteField("uploadmediarequest", string(uploadMediaRequestByte)); err != nil { - return nil, err - } + writer := multipart.NewWriter(&formBuffer) + if err = writer.WriteField("uploadmediarequest", string(uploadMediaRequestByte)); err != nil { + return nil, err + } - for k, v := range content { - if err := writer.WriteField(k, v); err != nil { - return nil, 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 { - chunkData := make([]byte, chunkSize) - if _, err := file.Read(chunkData); err != nil && err != io.EOF { - return nil, err - } - 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 err != nil { - return nil, err - } - // 如果不是最后一次, 解析有没有错误 - if !isLastTime { - if err := parseBaseResponseError(resp); err != nil { - return nil, err - } - } - } - // 将最后一次携带文件信息的response返回 - return resp, err + if w, err := writer.CreateFormFile("filename", file.Name()); err != nil { + return nil, err + } else { + chunkData := make([]byte, chunkSize) + if _, err := file.Read(chunkData); err != nil && err != io.EOF { + return nil, err + } + 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 err != nil { + return nil, err + } + // 如果不是最后一次, 解析有没有错误 + if !isLastTime { + if err := parseBaseResponseError(resp); err != nil { + return nil, err + } + } + } + // 将最后一次携带文件信息的response返回 + return resp, err } // WebWxSendMsgImg 发送图片 // 这个接口依赖上传文件的接口 // 发送的图片必须是已经成功上传的图片 func (c *Client) WebWxSendMsgImg(msg *SendMessage, request *BaseRequest, info *LoginInfo) (*http.Response, error) { - msg.Type = MsgTypeImage - path, _ := url.Parse(c.Domain.BaseHost() + webwxsendmsgimg) - 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 = MsgTypeImage + path, _ := url.Parse(c.Domain.BaseHost() + webwxsendmsgimg) + 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) } // WebWxSendAppMsg 发送文件信息 func (c *Client) WebWxSendAppMsg(msg *SendMessage, request *BaseRequest) (*http.Response, error) { - msg.Type = AppMessage - path, _ := url.Parse(c.Domain.BaseHost() + webwxsendappmsg) - 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.Domain.BaseHost() + webwxsendappmsg) + params := url.Values{} + params.Add("fun", "async") + params.Add("f", "json") + path.RawQuery = params.Encode() + return c.sendMessage(request, path.String(), msg) } // WebWxOplog 用户重命名接口 func (c *Client) WebWxOplog(request *BaseRequest, remarkName, userName string) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxoplog) - 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.Domain.BaseHost() + webwxoplog) + 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) } // WebWxVerifyUser 添加用户为好友接口 func (c *Client) WebWxVerifyUser(storage *Storage, info RecommendInfo, verifyContent string) (*http.Response, error) { - loginInfo := storage.LoginInfo - path, _ := url.Parse(c.Domain.BaseHost() + webwxverifyuser) - 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.Domain.BaseHost() + webwxverifyuser) + 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) } // WebWxGetMsgImg 获取图片消息的图片响应 func (c *Client) WebWxGetMsgImg(msg *Message, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxgetmsgimg) - 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.Domain.BaseHost() + webwxgetmsgimg) + 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) } // WebWxGetVoice 获取语音消息的语音响应 func (c *Client) WebWxGetVoice(msg *Message, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxgetvoice) - 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) - req.Header.Add("Referer", path.String()) - req.Header.Add("Range", "bytes=0-") - return c.Do(req) + path, _ := url.Parse(c.Domain.BaseHost() + webwxgetvoice) + 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) + req.Header.Add("Referer", path.String()) + req.Header.Add("Range", "bytes=0-") + return c.Do(req) } // WebWxGetVideo 获取视频消息的视频响应 func (c *Client) WebWxGetVideo(msg *Message, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxgetvideo) - 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) - req.Header.Add("Referer", path.String()) - req.Header.Add("Range", "bytes=0-") - return c.Do(req) + path, _ := url.Parse(c.Domain.BaseHost() + webwxgetvideo) + 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) + req.Header.Add("Referer", path.String()) + req.Header.Add("Range", "bytes=0-") + return c.Do(req) } // WebWxGetMedia 获取文件消息的文件响应 func (c *Client) WebWxGetMedia(msg *Message, info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.Domain.FileHost() + webwxgetmedia) - 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) - req.Header.Add("Referer", path.String()) - req.Header.Add("Range", "bytes=0-") - return c.Do(req) + path, _ := url.Parse(c.Domain.FileHost() + webwxgetmedia) + 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) + req.Header.Add("Referer", path.String()) + req.Header.Add("Range", "bytes=0-") + return c.Do(req) } // Logout 用户退出 func (c *Client) Logout(info *LoginInfo) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxlogout) - 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.Domain.BaseHost() + webwxlogout) + 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) } // AddMemberIntoChatRoom 添加用户进群聊 func (c *Client) AddMemberIntoChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*Friend) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxupdatechatroom) - 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, len(friends)) - for index, friend := range friends { - addMemberList[index] = 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.Domain.BaseHost() + webwxupdatechatroom) + 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, len(friends)) + for index, friend := range friends { + addMemberList[index] = 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) } // RemoveMemberFromChatRoom 从群聊中移除用户 func (c *Client) RemoveMemberFromChatRoom(req *BaseRequest, info *LoginInfo, group *Group, friends ...*User) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxupdatechatroom) - params := url.Values{} - params.Add("fun", "delmember") - params.Add("lang", "zh_CN") - params.Add("pass_ticket", info.PassTicket) - delMemberList := make([]string, len(friends)) - for index, friend := range friends { - delMemberList[index] = 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.Domain.BaseHost() + webwxupdatechatroom) + params := url.Values{} + params.Add("fun", "delmember") + params.Add("lang", "zh_CN") + params.Add("pass_ticket", info.PassTicket) + delMemberList := make([]string, len(friends)) + for index, friend := range friends { + delMemberList[index] = 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) } // WebWxRevokeMsg 撤回消息 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.Domain.BaseHost()+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.Domain.BaseHost()+webwxrevokemsg, buffer) + req.Header.Set("Content-Type", jsonContentType) + return c.Do(req) } // 校验上传文件 func (c *Client) webWxCheckUpload(stat os.FileInfo, request *BaseRequest, fileMd5, fromUserName, toUserName string) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxcheckupload) - content := map[string]interface{}{ - "BaseRequest": request, - "FileMd5": fileMd5, - "FileName": stat.Name(), - "FileSize": stat.Size(), - "FileType": 7, - "FromUserName": fromUserName, - "ToUserName": toUserName, - } - 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.Domain.BaseHost() + webwxcheckupload) + content := map[string]interface{}{ + "BaseRequest": request, + "FileMd5": fileMd5, + "FileName": stat.Name(), + "FileSize": stat.Size(), + "FileType": 7, + "FromUserName": fromUserName, + "ToUserName": toUserName, + } + body, _ := ToBuffer(content) + req, _ := http.NewRequest(http.MethodPost, path.String(), body) + req.Header.Add("Content-Type", jsonContentType) + return c.Do(req) } func (c *Client) WebWxStatusAsRead(request *BaseRequest, info *LoginInfo, msg *Message) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxstatusnotify) - content := map[string]interface{}{ - "BaseRequest": request, - "DeviceID": request.DeviceID, - "Sid": request.Sid, - "Skey": request.Skey, - "Uin": info.WxUin, - "ClientMsgId": time.Now().Unix(), - "Code": 1, - "FromUserName": msg.ToUserName, - "ToUserName": msg.FromUserName, - } - 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.Domain.BaseHost() + webwxstatusnotify) + content := map[string]interface{}{ + "BaseRequest": request, + "DeviceID": request.DeviceID, + "Sid": request.Sid, + "Skey": request.Skey, + "Uin": info.WxUin, + "ClientMsgId": time.Now().Unix(), + "Code": 1, + "FromUserName": msg.ToUserName, + "ToUserName": msg.FromUserName, + } + body, _ := ToBuffer(content) + req, _ := http.NewRequest(http.MethodPost, path.String(), body) + req.Header.Add("Content-Type", jsonContentType) + return c.Do(req) } // WebWxRelationPin 联系人置顶接口 func (c *Client) WebWxRelationPin(request *BaseRequest, op uint8, user *User) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxoplog) - content := map[string]interface{}{ - "BaseRequest": request, - "CmdId": 3, - "OP": op, - "RemarkName": user.RemarkName, - "UserName": user.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.Domain.BaseHost() + webwxoplog) + content := map[string]interface{}{ + "BaseRequest": request, + "CmdId": 3, + "OP": op, + "RemarkName": user.RemarkName, + "UserName": user.UserName, + } + body, _ := ToBuffer(content) + req, _ := http.NewRequest(http.MethodPost, path.String(), body) + req.Header.Add("Content-Type", jsonContentType) + return c.Do(req) } // WebWxPushLogin 免扫码登陆接口 func (c *Client) WebWxPushLogin(uin int) (*http.Response, error) { - path, _ := url.Parse(c.Domain.BaseHost() + webwxpushloginurl) - params := url.Values{"uin": {strconv.Itoa(uin)}} - path.RawQuery = params.Encode() - req, _ := http.NewRequest(http.MethodGet, path.String(), nil) - return c.Do(req) + path, _ := url.Parse(c.Domain.BaseHost() + webwxpushloginurl) + params := url.Values{"uin": {strconv.Itoa(uin)}} + path.RawQuery = params.Encode() + req, _ := http.NewRequest(http.MethodGet, path.String(), nil) + return c.Do(req) +} + +// WebWxSendVideoMsg 发送视频消息接口 +func (c *Client) WebWxSendVideoMsg(request *BaseRequest, msg *SendMessage) (*http.Response, error) { + path, _ := url.Parse(c.Domain.BaseHost() + webwxsendvideomsg) + params := url.Values{} + params.Add("fun", "async") + params.Add("f", "json") + params.Add("lang", "zh_CN") + params.Add("pass_ticket", "pass_ticket") + path.RawQuery = params.Encode() + return c.sendMessage(request, path.String(), msg) } diff --git a/relations.go b/relations.go index e8c1ec5..7d1bd64 100644 --- a/relations.go +++ b/relations.go @@ -33,6 +33,11 @@ func (f *Friend) SendImage(file *os.File) (*SentMessage, error) { return f.Self.SendImageToFriend(f, file) } +// SendVideo 发送图片消息 +func (f *Friend) SendVideo(file *os.File) (*SentMessage, error) { + return f.Self.SendVideoToFriend(f, file) +} + // SendFile 发送文件消息 func (f *Friend) SendFile(file *os.File) (*SentMessage, error) { return f.Self.SendFileToFriend(f, file) @@ -173,7 +178,7 @@ func (f Friends) SendImage(file *os.File, delay ...time.Duration) error { } // SendFile 群发文件 -func (f Friends)SendFile(file *os.File, delay ...time.Duration) error { +func (f Friends) SendFile(file *os.File, delay ...time.Duration) error { total := getTotalDuration(delay...) var ( sentMessage *SentMessage @@ -216,6 +221,11 @@ func (g *Group) SendImage(file *os.File) (*SentMessage, error) { return g.Self.SendImageToGroup(g, file) } +// SendVideo 发行视频消息给当前的群组 +func (g *Group) SendVideo(file *os.File) (*SentMessage, error) { + return g.Self.SendVideoToGroup(g, file) +} + // SendFile 发送文件给当前的群组 func (g *Group) SendFile(file *os.File) (*SentMessage, error) { return g.Self.SendFileToGroup(g, file) diff --git a/url.go b/url.go index af6da1d..00c01ba 100644 --- a/url.go +++ b/url.go @@ -17,6 +17,7 @@ const ( webwxgetcontact = "/cgi-bin/mmwebwx-bin/webwxgetcontact" webwxsendmsgimg = "/cgi-bin/mmwebwx-bin/webwxsendmsgimg" webwxsendappmsg = "/cgi-bin/mmwebwx-bin/webwxsendappmsg" + webwxsendvideomsg = "/cgi-bin/mmwebwx-bin/webwxsendvideomsg" webwxbatchgetcontact = "/cgi-bin/mmwebwx-bin/webwxbatchgetcontact" webwxoplog = "/cgi-bin/mmwebwx-bin/webwxoplog" webwxverifyuser = "/cgi-bin/mmwebwx-bin/webwxverifyuser" diff --git a/user.go b/user.go index 28d3f94..61fa6a4 100644 --- a/user.go +++ b/user.go @@ -253,6 +253,13 @@ func (s *Self) SendImageToFriend(friend *Friend, file *os.File) (*SentMessage, e return s.Bot.Caller.WebWxSendImageMsg(file, req, info, s.UserName, friend.UserName) } +// SendVideoToFriend 发送视频给好友 +func (s *Self) SendVideoToFriend(friend *Friend, file *os.File) (*SentMessage, error) { + req := s.Bot.Storage.Request + info := s.Bot.Storage.LoginInfo + return s.Bot.Caller.WebWxSendVideoMsg(file, req, info, s.UserName, friend.UserName) +} + // SendFileToFriend 发送文件给好友 func (s *Self) SendFileToFriend(friend *Friend, file *os.File) (*SentMessage, error) { req := s.Bot.Storage.Request @@ -351,6 +358,13 @@ func (s *Self) SendImageToGroup(group *Group, file *os.File) (*SentMessage, erro return s.Bot.Caller.WebWxSendImageMsg(file, req, info, s.UserName, group.UserName) } +// SendVideoToGroup 发送视频给群组 +func (s *Self) SendVideoToGroup(group *Group, file *os.File) (*SentMessage, error) { + req := s.Bot.Storage.Request + info := s.Bot.Storage.LoginInfo + return s.Bot.Caller.WebWxSendVideoMsg(file, req, info, s.UserName, group.UserName) +} + // SendFileToGroup 发送文件给群组 func (s *Self) SendFileToGroup(group *Group, file *os.File) (*SentMessage, error) { req := s.Bot.Storage.Request