diff --git a/caller.go b/caller.go index 4019dea..b899128 100644 --- a/caller.go +++ b/caller.go @@ -1,385 +1,400 @@ package openwechat import ( - "bytes" - "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) } // 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 } // 处理响应返回的结果是否正常 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 f6e9a3a..8599d66 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,655 +38,664 @@ 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(headImageUrl string) (*http.Response, error) { - path := c.Domain.BaseHost() + headImageUrl - req, _ := http.NewRequest(http.MethodGet, path, nil) - return c.Do(req) + path := c.Domain.BaseHost() + headImageUrl + 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) - 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) + 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) - 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) + 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) - 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) + 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) } diff --git a/items.go b/items.go index 6b9d9f0..7ff7de8 100644 --- a/items.go +++ b/items.go @@ -1,8 +1,8 @@ package openwechat import ( - "errors" - "fmt" + "errors" + "fmt" ) /* @@ -11,222 +11,232 @@ import ( // LoginInfo 登录信息 type LoginInfo struct { - Ret int `xml:"ret"` - WxUin int `xml:"wxuin"` - IsGrayScale int `xml:"isgrayscale"` - Message string `xml:"message"` - SKey string `xml:"skey"` - WxSid string `xml:"wxsid"` - PassTicket string `xml:"pass_ticket"` + Ret int `xml:"ret"` + WxUin int `xml:"wxuin"` + IsGrayScale int `xml:"isgrayscale"` + Message string `xml:"message"` + SKey string `xml:"skey"` + WxSid string `xml:"wxsid"` + PassTicket string `xml:"pass_ticket"` } // errors const ( - errParamError = "param error" - errTicketError = "ticket error" - errLoginEnvError = "login env error" - errLoginFailedWarn = "failed login warn" - errLoginFailedCheck = "failed login check" - errCookieInvalidError = "cookie invalid error" - errOptTooOften = "opt too often" + errParamError = "param error" + errTicketError = "ticket error" + errLoginEnvError = "login env error" + errLoginFailedWarn = "failed login warn" + errLoginFailedCheck = "failed login check" + errCookieInvalidError = "cookie invalid error" + errOptTooOften = "opt too often" ) var ( - ErrParamError = errors.New(errParamError) - ErrTicketError = errors.New(errTicketError) - ErrLoginEnvError = errors.New(errLoginEnvError) - ErrLoginFailedWarn = errors.New(errLoginFailedWarn) - ErrLoginFailedCheck = errors.New(errLoginFailedCheck) - ErrCookieInvalidError = errors.New(errCookieInvalidError) - ErrOptTooOften = errors.New(errOptTooOften) - ErrBaseResponseError error + ErrParamError = errors.New(errParamError) + ErrTicketError = errors.New(errTicketError) + ErrLoginEnvError = errors.New(errLoginEnvError) + ErrLoginFailedWarn = errors.New(errLoginFailedWarn) + ErrLoginFailedCheck = errors.New(errLoginFailedCheck) + ErrCookieInvalidError = errors.New(errCookieInvalidError) + ErrOptTooOften = errors.New(errOptTooOften) + ErrBaseResponseError error ) func (l LoginInfo) Ok() bool { - return l.Ret == 0 + return l.Ret == 0 } func (l LoginInfo) Error() string { - return l.Message + return l.Message } // BaseRequest 初始的请求信息 // 几乎所有的请求都要携带该参数 type BaseRequest struct { - Uin int - Sid, Skey, DeviceID string + Uin int + Sid, Skey, DeviceID string } // BaseResponse 大部分返回对象都携带该信息 type BaseResponse struct { - Ret int - ErrMsg string + Ret int + ErrMsg string } func (b BaseResponse) Ok() bool { - return b.Ret == 0 + return b.Ret == 0 } func (b BaseResponse) Error() string { - if err := getResponseErrorWithRetCode(b.Ret); err != nil { - return err.Error() - } - return "" + if err := getResponseErrorWithRetCode(b.Ret); err != nil { + return err.Error() + } + return "" } func getResponseErrorWithRetCode(code int) error { - switch code { - case 0: - return nil - case 1: - return ErrParamError - case -14: - return ErrTicketError - case 1100: - return ErrLoginFailedWarn - case 1101: - return ErrLoginFailedCheck - case 1102: - return ErrCookieInvalidError - case 1203: - return ErrLoginEnvError - case 1205: - return ErrOptTooOften - default: - ErrBaseResponseError = fmt.Errorf("base response ret code %d", code) - return ErrBaseResponseError - } + switch code { + case 0: + return nil + case 1: + return ErrParamError + case -14: + return ErrTicketError + case 1100: + return ErrLoginFailedWarn + case 1101: + return ErrLoginFailedCheck + case 1102: + return ErrCookieInvalidError + case 1203: + return ErrLoginEnvError + case 1205: + return ErrOptTooOften + default: + ErrBaseResponseError = fmt.Errorf("base response ret code %d", code) + return ErrBaseResponseError + } } type SyncKey struct { - Count int - List []struct{ Key, Val int64 } + Count int + List []struct{ Key, Val int64 } } // WebInitResponse 初始化的相应信息 type WebInitResponse struct { - Count int - ClientVersion int - GrayScale int - InviteStartCount int - MPSubscribeMsgCount int - ClickReportInterval int - SystemTime int64 - ChatSet string - SKey string - BaseResponse BaseResponse - SyncKey SyncKey - User User - MPSubscribeMsgList []MPSubscribeMsg - ContactList []User + Count int + ClientVersion int + GrayScale int + InviteStartCount int + MPSubscribeMsgCount int + ClickReportInterval int + SystemTime int64 + ChatSet string + SKey string + BaseResponse BaseResponse + SyncKey SyncKey + User User + MPSubscribeMsgList []MPSubscribeMsg + ContactList []User } // MPSubscribeMsg 公众号的订阅信息 type MPSubscribeMsg struct { - MPArticleCount int - Time int64 - UserName string - NickName string - MPArticleList []struct { - Title string - Cover string - Digest string - Url string - } + MPArticleCount int + Time int64 + UserName string + NickName string + MPArticleList []struct { + Title string + Cover string + Digest string + Url string + } } type UserDetailItem struct { - UserName string - EncryChatRoomId string + UserName string + EncryChatRoomId string } type UserDetailItemList []UserDetailItem func NewUserDetailItemList(members Members) UserDetailItemList { - var list = make(UserDetailItemList, len(members)) - for index, member := range members { - item := UserDetailItem{UserName: member.UserName, EncryChatRoomId: member.EncryChatRoomId} - list[index] = item - } - return list + var list = make(UserDetailItemList, len(members)) + for index, member := range members { + item := UserDetailItem{UserName: member.UserName, EncryChatRoomId: member.EncryChatRoomId} + list[index] = item + } + return list } type SyncCheckResponse struct { - RetCode string - Selector string + RetCode string + Selector string } func (s *SyncCheckResponse) Success() bool { - return s.RetCode == "0" + return s.RetCode == "0" } func (s *SyncCheckResponse) NorMal() bool { - return s.Success() && s.Selector == "0" + return s.Success() && s.Selector == "0" } // 实现error接口 func (s *SyncCheckResponse) Error() string { - switch s.RetCode { - case "0": - return "" - case "1": - return errParamError - case "-14": - return errTicketError - case "1100": - return errLoginFailedWarn - case "1101": - return errLoginFailedCheck - case "1102": - return errCookieInvalidError - case "1203": - return errLoginEnvError - case "1205": - return errOptTooOften - default: - return fmt.Sprintf("sync check response error code %s", s.RetCode) - } + switch s.RetCode { + case "0": + return "" + case "1": + return errParamError + case "-14": + return errTicketError + case "1100": + return errLoginFailedWarn + case "1101": + return errLoginFailedCheck + case "1102": + return errCookieInvalidError + case "1203": + return errLoginEnvError + case "1205": + return errOptTooOften + default: + return fmt.Sprintf("sync check response error code %s", s.RetCode) + } } type WebWxSyncResponse struct { - AddMsgCount int - ContinueFlag int - DelContactCount int - ModChatRoomMemberCount int - ModContactCount int - Skey string - SyncCheckKey SyncKey - SyncKey SyncKey - BaseResponse BaseResponse - ModChatRoomMemberList Members - AddMsgList []*Message + AddMsgCount int + ContinueFlag int + DelContactCount int + ModChatRoomMemberCount int + ModContactCount int + Skey string + SyncCheckKey SyncKey + SyncKey SyncKey + BaseResponse BaseResponse + ModChatRoomMemberList Members + AddMsgList []*Message } type WebWxContactResponse struct { - MemberCount int - Seq int - BaseResponse BaseResponse - MemberList []*User + MemberCount int + Seq int + BaseResponse BaseResponse + MemberList []*User } type WebWxBatchContactResponse struct { - Count int - BaseResponse BaseResponse - ContactList []*User + Count int + BaseResponse BaseResponse + ContactList []*User } type CheckLoginResponse struct { - Code string - Raw []byte + Code string + Raw []byte } type MessageResponse struct { - BaseResponse BaseResponse - LocalID string - MsgID string + BaseResponse BaseResponse + LocalID string + MsgID string } type UploadResponse struct { - BaseResponse BaseResponse - MediaId string + BaseResponse BaseResponse + MediaId string +} + +type PushLoginResponse struct { + Ret string `json:"ret"` + Msg string `json:"msg"` + UUID string `json:"uuid"` +} + +func (p PushLoginResponse) Ok() bool { + return p.Ret == "0" && p.UUID != "" } diff --git a/url.go b/url.go index 8c37bb6..d329e9a 100644 --- a/url.go +++ b/url.go @@ -5,48 +5,49 @@ type mode string // 向外暴露2种模式 const ( - Normal mode = "normal" - Desktop mode = "desktop" // 突破网页版登录限制 + Normal mode = "normal" + Desktop mode = "desktop" // 突破网页版登录限制 ) const ( - webwxinit = "/cgi-bin/mmwebwx-bin/webwxinit" - webwxstatusnotify = "/cgi-bin/mmwebwx-bin/webwxstatusnotify" - webwxsync = "/cgi-bin/mmwebwx-bin/webwxsync" - webwxsendmsg = "/cgi-bin/mmwebwx-bin/webwxsendmsg" - webwxgetcontact = "/cgi-bin/mmwebwx-bin/webwxgetcontact" - webwxsendmsgimg = "/cgi-bin/mmwebwx-bin/webwxsendmsgimg" - webwxsendappmsg = "/cgi-bin/mmwebwx-bin/webwxsendappmsg" - webwxbatchgetcontact = "/cgi-bin/mmwebwx-bin/webwxbatchgetcontact" - webwxoplog = "/cgi-bin/mmwebwx-bin/webwxoplog" - webwxverifyuser = "/cgi-bin/mmwebwx-bin/webwxverifyuser" - synccheck = "/cgi-bin/mmwebwx-bin/synccheck" - webwxuploadmedia = "/cgi-bin/mmwebwx-bin/webwxuploadmedia" - webwxgetmsgimg = "/cgi-bin/mmwebwx-bin/webwxgetmsgimg" - webwxgetvoice = "/cgi-bin/mmwebwx-bin/webwxgetvoice" - webwxgetvideo = "/cgi-bin/mmwebwx-bin/webwxgetvideo" - webwxlogout = "/cgi-bin/mmwebwx-bin/webwxlogout" - webwxgetmedia = "/cgi-bin/mmwebwx-bin/webwxgetmedia" - webwxupdatechatroom = "/cgi-bin/mmwebwx-bin/webwxupdatechatroom" - webwxrevokemsg = "/cgi-bin/mmwebwx-bin/webwxrevokemsg" - webwxcheckupload = "/cgi-bin/mmwebwx-bin/webwxcheckupload" + webwxinit = "/cgi-bin/mmwebwx-bin/webwxinit" + webwxstatusnotify = "/cgi-bin/mmwebwx-bin/webwxstatusnotify" + webwxsync = "/cgi-bin/mmwebwx-bin/webwxsync" + webwxsendmsg = "/cgi-bin/mmwebwx-bin/webwxsendmsg" + webwxgetcontact = "/cgi-bin/mmwebwx-bin/webwxgetcontact" + webwxsendmsgimg = "/cgi-bin/mmwebwx-bin/webwxsendmsgimg" + webwxsendappmsg = "/cgi-bin/mmwebwx-bin/webwxsendappmsg" + webwxbatchgetcontact = "/cgi-bin/mmwebwx-bin/webwxbatchgetcontact" + webwxoplog = "/cgi-bin/mmwebwx-bin/webwxoplog" + webwxverifyuser = "/cgi-bin/mmwebwx-bin/webwxverifyuser" + synccheck = "/cgi-bin/mmwebwx-bin/synccheck" + webwxuploadmedia = "/cgi-bin/mmwebwx-bin/webwxuploadmedia" + webwxgetmsgimg = "/cgi-bin/mmwebwx-bin/webwxgetmsgimg" + webwxgetvoice = "/cgi-bin/mmwebwx-bin/webwxgetvoice" + webwxgetvideo = "/cgi-bin/mmwebwx-bin/webwxgetvideo" + webwxlogout = "/cgi-bin/mmwebwx-bin/webwxlogout" + webwxgetmedia = "/cgi-bin/mmwebwx-bin/webwxgetmedia" + webwxupdatechatroom = "/cgi-bin/mmwebwx-bin/webwxupdatechatroom" + webwxrevokemsg = "/cgi-bin/mmwebwx-bin/webwxrevokemsg" + webwxcheckupload = "/cgi-bin/mmwebwx-bin/webwxcheckupload" + webwxpushloginurl = "/cgi-bin/mmwebwx-bin/webwxpushloginurl" - webwxnewloginpage = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage" - jslogin = "https://login.wx.qq.com/jslogin" - login = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login" - qrcode = "https://login.weixin.qq.com/qrcode/" + webwxnewloginpage = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage" + jslogin = "https://login.wx.qq.com/jslogin" + login = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login" + qrcode = "https://login.weixin.qq.com/qrcode/" ) type WechatDomain string func (w WechatDomain) BaseHost() string { - return "https://" + string(w) + return "https://" + string(w) } func (w WechatDomain) FileHost() string { - return "https://file." + string(w) + return "https://file." + string(w) } func (w WechatDomain) SyncHost() string { - return "https://webpush." + string(w) + return "https://webpush." + string(w) }