mirror of
https://github.com/silenceper/wechat.git
synced 2026-03-01 00:35:26 +08:00
Compare commits
1 Commits
a532cdcbd2
...
1be476c633
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1be476c633 |
@@ -10,11 +10,11 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/util"
|
"github.com/silenceper/wechat/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
//获取ticket的url https://developer.work.weixin.qq.com/document/path/90506
|
// 获取ticket的url https://developer.work.weixin.qq.com/document/path/90506
|
||||||
const getQyWxTicketURL = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=%s"
|
const getQyWxTicketURL = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=%s"
|
||||||
const getQyAppTicketURL = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=%s&type=agent_config"
|
const getQyAppTicketURL = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=%s&type=agent_config"
|
||||||
|
|
||||||
//WorkJsTicket 默认获取js ticket方法
|
// WorkJsTicket 默认获取js ticket方法
|
||||||
type WorkJsTicket struct {
|
type WorkJsTicket struct {
|
||||||
appID string
|
appID string
|
||||||
agentID string
|
agentID string
|
||||||
@@ -24,7 +24,7 @@ type WorkJsTicket struct {
|
|||||||
jsAPITicketLock *sync.Mutex
|
jsAPITicketLock *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewWorkJsTicket new
|
// NewWorkJsTicket new
|
||||||
func NewWorkJsTicket(appID string, agentID string, cacheKeyPrefix string, cache cache.Cache) JsTicketHandle {
|
func NewWorkJsTicket(appID string, agentID string, cacheKeyPrefix string, cache cache.Cache) JsTicketHandle {
|
||||||
return &WorkJsTicket{
|
return &WorkJsTicket{
|
||||||
appID: appID,
|
appID: appID,
|
||||||
@@ -35,7 +35,7 @@ func NewWorkJsTicket(appID string, agentID string, cacheKeyPrefix string, cache
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetTicket 获取企业微信jsapi_ticket
|
// GetTicket 获取企业微信jsapi_ticket
|
||||||
func (js *WorkJsTicket) GetTicket(accessToken string) (ticketStr string, err error) {
|
func (js *WorkJsTicket) GetTicket(accessToken string) (ticketStr string, err error) {
|
||||||
//先从cache中取
|
//先从cache中取
|
||||||
jsAPITicketCacheKey := fmt.Sprintf("%s_jsapi_ticket_%s", js.cacheKeyPrefix, js.appID)
|
jsAPITicketCacheKey := fmt.Sprintf("%s_jsapi_ticket_%s", js.cacheKeyPrefix, js.appID)
|
||||||
@@ -62,7 +62,7 @@ func (js *WorkJsTicket) GetTicket(accessToken string) (ticketStr string, err err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetQyWxTicketFromServer 从企业微信服务器中获取ticket
|
// GetQyWxTicketFromServer 从企业微信服务器中获取ticket
|
||||||
func GetQyWxTicketFromServer(accessToken string, isApp bool) (ticket ResTicket, err error) {
|
func GetQyWxTicketFromServer(accessToken string, isApp bool) (ticket ResTicket, err error) {
|
||||||
var response []byte
|
var response []byte
|
||||||
url := fmt.Sprintf(getQyWxTicketURL, accessToken)
|
url := fmt.Sprintf(getQyWxTicketURL, accessToken)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encryptor struct
|
// Encryptor struct
|
||||||
@@ -108,23 +108,13 @@ func GetCipherText(sessionKey, encryptedData, iv string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt 解密数据
|
// Decrypt 解密数据
|
||||||
func (encryptor *Encryptor) Decrypt(sessionKey, encryptedData, appid string) (*PlainData, error) {
|
func (encryptor *Encryptor) Decrypt(sessionKey, encryptedData, iv string) (*PlainData, error) {
|
||||||
ivB := make([]byte, 16)
|
|
||||||
iv := base64.StdEncoding.EncodeToString(ivB)
|
|
||||||
cipherText, err := GetCipherText(sessionKey, encryptedData, iv)
|
cipherText, err := GetCipherText(sessionKey, encryptedData, iv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
length := string(cipherText[:20])
|
|
||||||
|
|
||||||
cipherTextData := strings.TrimPrefix(string(cipherText), string(cipherText[:20]))
|
|
||||||
cipherTextData = strings.TrimSuffix(cipherTextData, appid)
|
|
||||||
|
|
||||||
if len(length) != len(cipherTextData) {
|
|
||||||
return nil, fmt.Errorf("length not match, %d != %d", length, len(cipherTextData))
|
|
||||||
}
|
|
||||||
var plainData PlainData
|
var plainData PlainData
|
||||||
err = json.Unmarshal([]byte(cipherTextData), &plainData)
|
err = json.Unmarshal(cipherText, &plainData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -17,16 +16,3 @@ func Signature(params ...string) string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%x", h.Sum(nil))
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func CalSignature(params ...string) string {
|
|
||||||
sort.Strings(params)
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
for _, value := range params {
|
|
||||||
buffer.WriteString(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
sha := sha1.New()
|
|
||||||
sha.Write(buffer.Bytes())
|
|
||||||
signature := fmt.Sprintf("%x", sha.Sum(nil))
|
|
||||||
return string(signature)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
package externalcontact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
addMsgTemplateUrl = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_msg_template"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ChatType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ChatTypeSingle ChatType = "single"
|
|
||||||
ChatTypeGroup ChatType = "group"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReqMessage 企业群发参数
|
|
||||||
type ReqMessage struct {
|
|
||||||
ChatType ChatType `json:"chat_type"` //群发任务的类型,默认为single,表示发送给客户,group表示发送给客户群
|
|
||||||
ExternalUserid []string `json:"external_userid"` // 客户的外部联系人id列表,仅在chat_type为single时有效,不可与sender同时为空,最多可传入1万个客户
|
|
||||||
Sender string `json:"sender"` //发送企业群发消息的成员userid,当类型为发送给客户群时必填
|
|
||||||
Text struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
} `json:"text"`
|
|
||||||
Attachments []struct {
|
|
||||||
Msgtype string `json:"msgtype"`
|
|
||||||
Image MsgImage `json:"image"`
|
|
||||||
Link MsgLink `json:"link"`
|
|
||||||
Miniprogram MsgMiniprogram `json:"miniprogram"`
|
|
||||||
Video MsgVideo `json:"video"`
|
|
||||||
File MsgFile `json:"file"`
|
|
||||||
} `json:"attachments"`
|
|
||||||
}
|
|
||||||
type MsgImage struct {
|
|
||||||
MediaId string `json:"media_id"`
|
|
||||||
PicUrl string `json:"pic_url"`
|
|
||||||
}
|
|
||||||
type MsgLink struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Picurl string `json:"picurl"`
|
|
||||||
Desc string `json:"desc"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
}
|
|
||||||
type MsgMiniprogram struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
PicMediaId string `json:"pic_media_id"`
|
|
||||||
Appid string `json:"appid"`
|
|
||||||
Page string `json:"page"`
|
|
||||||
}
|
|
||||||
type MsgVideo struct {
|
|
||||||
MediaId string `json:"media_id"`
|
|
||||||
}
|
|
||||||
type MsgFile struct {
|
|
||||||
MediaId string `json:"media_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type resTemplateSend struct {
|
|
||||||
util.CommonError
|
|
||||||
FailList string `json:"fail_list"`
|
|
||||||
MsgID int64 `json:"msgid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send 发送应用消息
|
|
||||||
func (r *Client) Send(msg *ReqMessage) (msgID int64, err error) {
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err = r.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uri := fmt.Sprintf("%s?access_token=%s", addMsgTemplateUrl, accessToken)
|
|
||||||
var response []byte
|
|
||||||
response, err = util.PostJSON(uri, msg)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var result resTemplateSend
|
|
||||||
err = json.Unmarshal(response, &result)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if result.ErrCode != 0 {
|
|
||||||
err = fmt.Errorf("template msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
msgID = result.MsgID
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
package externalcontact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
listUrl = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list"
|
|
||||||
getUrl = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get"
|
|
||||||
getByUserBatchUrl = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ReqGetByUser struct {
|
|
||||||
UseridList []string `json:"userid_list"`
|
|
||||||
Cursor string `json:"cursor"`
|
|
||||||
Limit int `json:"limit"`
|
|
||||||
}
|
|
||||||
type OneUser struct {
|
|
||||||
util.CommonError
|
|
||||||
ExternalContact ExternalContact `json:"external_contact"`
|
|
||||||
FollowUser []FollowInfo `json:"follow_user"` //注意,仅获取单个客户详情的时候这里返回的是跟进人列表
|
|
||||||
NextCursor string `json:"next_cursor"`
|
|
||||||
}
|
|
||||||
type resUserList struct {
|
|
||||||
util.CommonError
|
|
||||||
ExternalContactList []UserInfo `json:"external_contact_list"`
|
|
||||||
NextCursor string `json:"next_cursor"`
|
|
||||||
}
|
|
||||||
type resUserids struct {
|
|
||||||
util.CommonError
|
|
||||||
ExternalUserid []string `json:"external_userid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserInfo struct {
|
|
||||||
ExternalContact ExternalContact `json:"external_contact"`
|
|
||||||
FollowInfo FollowInfo `json:"follow_info"` //企业成员客户跟进人信息,可以参考获取客户详情,但标签信息只会返回企业标签和规则组标签的tag_id,个人标签将不再返回
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUseridList 获取我的客户列表
|
|
||||||
func (tpl *Client) GetUseridList(myUserid string) (externalUserid []string, err error) {
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err = tpl.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uri := fmt.Sprintf("%s?access_token=%s&userid=%s", listUrl, accessToken, myUserid)
|
|
||||||
var response []byte
|
|
||||||
response, err = util.HTTPGet(uri)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var result resUserids
|
|
||||||
err = json.Unmarshal(response, &result)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if result.ErrCode != 0 {
|
|
||||||
err = fmt.Errorf("template msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
externalUserid = result.ExternalUserid
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUseridList 获取我的全部客户列表及详情
|
|
||||||
func (tpl *Client) GetQyUserInfoList(qyUserid []string) ([]UserInfo, error) {
|
|
||||||
var userInfoList []UserInfo
|
|
||||||
var req ReqGetByUser
|
|
||||||
req.UseridList = qyUserid
|
|
||||||
req.Limit = 100
|
|
||||||
for {
|
|
||||||
userInfoPage, resCursor, err := tpl.GetUserInfoListByUserIds(req)
|
|
||||||
if err != nil {
|
|
||||||
return userInfoList, err
|
|
||||||
}
|
|
||||||
userInfoList = append(userInfoList, userInfoPage...)
|
|
||||||
if resCursor != "" {
|
|
||||||
req.Cursor = resCursor
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return userInfoList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserInfoAndAllFollow 获取客户详情以及全部跟进人
|
|
||||||
func (tpl *Client) GetUserInfoAndAllFollow(userid string) (OneUser, error) {
|
|
||||||
var result, res OneUser
|
|
||||||
var err error
|
|
||||||
var cursor string
|
|
||||||
for {
|
|
||||||
res, err = tpl.GetUserInfo(userid, cursor)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
result.FollowUser = append(result.FollowUser, res.FollowUser...)
|
|
||||||
result.ExternalContact = res.ExternalContact
|
|
||||||
if res.NextCursor != "" {
|
|
||||||
cursor = res.NextCursor
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserInfo 获取客户详情
|
|
||||||
func (tpl *Client) GetUserInfo(externalUserid string, cursor ...string) (result OneUser, err error) {
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err = tpl.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var page = ""
|
|
||||||
if len(cursor) > 0 {
|
|
||||||
page = cursor[0]
|
|
||||||
}
|
|
||||||
uri := fmt.Sprintf("%s?access_token=%s&external_userid=%s&cursor=%s", getUrl, accessToken, externalUserid, page)
|
|
||||||
var response []byte
|
|
||||||
response, err = util.HTTPGet(uri)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(response, &result)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if result.ErrCode != 0 {
|
|
||||||
err = fmt.Errorf("template msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserInfoListByUserId 批量获取客户详情
|
|
||||||
func (tpl *Client) GetUserInfoListByUserIds(req ReqGetByUser) (userList []UserInfo, nextCursor string, err error) {
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err = tpl.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uri := fmt.Sprintf("%s?access_token=%s", getByUserBatchUrl, accessToken)
|
|
||||||
var response []byte
|
|
||||||
response, err = util.PostJSON(uri, req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var result resUserList
|
|
||||||
err = json.Unmarshal(response, &result)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if result.ErrCode != 0 {
|
|
||||||
err = fmt.Errorf("template msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userList = result.ExternalContactList
|
|
||||||
nextCursor = result.NextCursor
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -23,7 +23,7 @@ type Config struct {
|
|||||||
Signature string `json:"signature"`
|
Signature string `json:"signature"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewJs init
|
// NewJs init
|
||||||
func NewJs(context *context.Context) *Js {
|
func NewJs(context *context.Context) *Js {
|
||||||
js := new(Js)
|
js := new(Js)
|
||||||
js.Context = context
|
js.Context = context
|
||||||
@@ -32,13 +32,13 @@ func NewJs(context *context.Context) *Js {
|
|||||||
return js
|
return js
|
||||||
}
|
}
|
||||||
|
|
||||||
//SetJsTicketHandle 自定义js ticket取值方式
|
// SetJsTicketHandle 自定义js ticket取值方式
|
||||||
func (js *Js) SetJsTicketHandle(ticketHandle credential.JsTicketHandle) {
|
func (js *Js) SetJsTicketHandle(ticketHandle credential.JsTicketHandle) {
|
||||||
js.JsTicketHandle = ticketHandle
|
js.JsTicketHandle = ticketHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetConfig 获取jssdk需要的配置参数
|
// GetConfig 获取jssdk需要的配置参数
|
||||||
//uri 为当前网页地址
|
// uri 为当前网页地址
|
||||||
func (js *Js) GetConfig(uri string) (config *Config, err error) {
|
func (js *Js) GetConfig(uri string) (config *Config, err error) {
|
||||||
config = new(Config)
|
config = new(Config)
|
||||||
var accessToken string
|
var accessToken string
|
||||||
@@ -65,8 +65,8 @@ func (js *Js) GetConfig(uri string) (config *Config, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetAgentConfig 获取jssdk需要的配置参数
|
// GetAgentConfig 获取jssdk需要的配置参数
|
||||||
//uri 为当前网页地址
|
// uri 为当前网页地址
|
||||||
func (js *Js) GetAgentConfig(uri string) (config *Config, err error) {
|
func (js *Js) GetAgentConfig(uri string) (config *Config, err error) {
|
||||||
config = new(Config)
|
config = new(Config)
|
||||||
var accessToken string
|
var accessToken string
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
package message
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
//Image 图片消息
|
|
||||||
type Image struct {
|
|
||||||
CommonToken `json:"-"`
|
|
||||||
Image struct {
|
|
||||||
MediaID string `xml:"MediaId" json:"media_id"`
|
|
||||||
} `xml:"Image" json:"image"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//NewImage 回复图片消息
|
|
||||||
func NewImage(mediaID string) *Image {
|
|
||||||
image := new(Image)
|
|
||||||
image.Image.MediaID = mediaID
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,7 @@ type (
|
|||||||
// 消息类型,此时固定为:text
|
// 消息类型,此时固定为:text
|
||||||
MsgType string `json:"msgtype"`
|
MsgType string `json:"msgtype"`
|
||||||
// 企业应用的id,整型。企业内部开发,可在应用的设置页面查看;第三方服务商,可通过接口 获取企业授权信息 获取该参数值
|
// 企业应用的id,整型。企业内部开发,可在应用的设置页面查看;第三方服务商,可通过接口 获取企业授权信息 获取该参数值
|
||||||
AgentID int `json:"agentid"`
|
AgentID string `json:"agentid"`
|
||||||
// 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0
|
// 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0
|
||||||
Safe int `json:"safe"`
|
Safe int `json:"safe"`
|
||||||
// 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。
|
// 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
//News 图文消息
|
|
||||||
type News struct {
|
|
||||||
CommonToken `json:"-"`
|
|
||||||
ArticleCount int `xml:"ArticleCount" json:"-"`
|
|
||||||
Articles []*Article `xml:"Articles>item,omitempty" json:"articles"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//NewNews 初始化图文消息
|
|
||||||
func NewNews(articles []*Article) *News {
|
|
||||||
news := new(News)
|
|
||||||
news.ArticleCount = len(articles)
|
|
||||||
news.Articles = articles
|
|
||||||
return news
|
|
||||||
}
|
|
||||||
|
|
||||||
//Article 单篇文章
|
|
||||||
type Article struct {
|
|
||||||
Title string `xml:"Title,omitempty" json:"title"`
|
|
||||||
Description string `xml:"Description,omitempty" json:"description"`
|
|
||||||
PicURL string `xml:"PicUrl,omitempty" json:"picurl"`
|
|
||||||
URL string `xml:"Url,omitempty" json:"url"`
|
|
||||||
Appid string `xml:"-" json:"appid"` //仅在发送应用消息时需要
|
|
||||||
Pagepath string `xml:"-" json:"pagepath"` //仅在发送应用消息时需要
|
|
||||||
}
|
|
||||||
|
|
||||||
//MpNews 图文消息
|
|
||||||
type MpNews struct {
|
|
||||||
Articles []*MpNewsArticle `xml:"-" json:"articles"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//MpNewsArticle mpnews类型的图文消息,跟普通的图文消息一致,唯一的差异是图文内容存储在企业微信
|
|
||||||
type MpNewsArticle struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
ThumbMediaId string `json:"thumb_media_id"`
|
|
||||||
Author string `json:"author"`
|
|
||||||
ContentSourceUrl string `json:"content_source_url"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Digest string `json:"digest"`
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,13 @@ package message
|
|||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
//ErrInvalidReply 无效的回复
|
// ErrInvalidReply 无效的回复
|
||||||
var ErrInvalidReply = errors.New("无效的回复消息")
|
var ErrInvalidReply = errors.New("无效的回复消息")
|
||||||
|
|
||||||
//ErrUnsupportReply 不支持的回复类型
|
// ErrUnsupportReply 不支持的回复类型
|
||||||
var ErrUnsupportReply = errors.New("无需回复消息")
|
var ErrUnsupportReply = errors.New("无需回复消息")
|
||||||
|
|
||||||
//Reply 消息回复
|
// Reply 消息回复
|
||||||
type Reply struct {
|
type Reply struct {
|
||||||
MsgType MsgType
|
MsgType MsgType
|
||||||
MsgData interface{}
|
MsgData interface{}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
//Text 文本消息
|
|
||||||
type Text struct {
|
|
||||||
CommonToken `json:"-"`
|
|
||||||
Content CDATA `json:"content" xml:"Content"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//NewText 初始化文本消息
|
|
||||||
func NewText(content string) *Text {
|
|
||||||
text := new(Text)
|
|
||||||
text.Content = CDATA(content)
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
//Video 视频消息
|
|
||||||
type Video struct {
|
|
||||||
CommonToken `json:"-"`
|
|
||||||
Video struct {
|
|
||||||
MediaID string `xml:"MediaId" json:"media_id"`
|
|
||||||
Title string `xml:"Title,omitempty" json:"title"`
|
|
||||||
Description string `xml:"Description,omitempty" json:"description"`
|
|
||||||
} `xml:"Video" json:"video"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//NewVideo 回复图片消息
|
|
||||||
func NewVideo(mediaID, title, description string) *Video {
|
|
||||||
video := new(Video)
|
|
||||||
video.Video.MediaID = mediaID
|
|
||||||
video.Video.Title = title
|
|
||||||
video.Video.Description = description
|
|
||||||
return video
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
//Voice 语音消息
|
|
||||||
type Voice struct {
|
|
||||||
CommonToken `json:"-"`
|
|
||||||
Voice struct {
|
|
||||||
MediaID string `xml:"MediaId" json:"media_id"`
|
|
||||||
} `xml:"Voice" json:"voice"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//NewVoice 回复语音消息
|
|
||||||
func NewVoice(mediaID string) *Voice {
|
|
||||||
voice := new(Voice)
|
|
||||||
voice.Voice.MediaID = mediaID
|
|
||||||
return voice
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package oauth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
code2SessionURL = "https://qyapi.weixin.qq.com/cgi-bin/miniprogram/jscode2session?access_token=%s&js_code=%s&grant_type=authorization_code"
|
|
||||||
launchCode = "https://qyapi.weixin.qq.com/cgi-bin/get_launch_code?access_token=%s"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ctr *Oauth) Code2Session(code string) (result ResUserInfo, err error) {
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err = ctr.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var response []byte
|
|
||||||
response, err = util.HTTPGet(
|
|
||||||
fmt.Sprintf(code2SessionURL, accessToken, code),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(response, &result)
|
|
||||||
if result.ErrCode != 0 {
|
|
||||||
err = fmt.Errorf("GetUserAccessToken error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type RespLaunchCode struct {
|
|
||||||
util.CommonError
|
|
||||||
LaunchCode string `json:"launch_code"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLaunchCode 用于打开个人聊天窗口schema
|
|
||||||
func (ctr *Oauth) GetLaunchCode(userID, other string) (userInfo *RespLaunchCode, err error) {
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err = ctr.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uri := fmt.Sprintf(launchCode, accessToken)
|
|
||||||
var response []byte
|
|
||||||
response, err = util.PostJSON(uri, map[string]interface{}{"operator_userid": userID, "single_chat": map[string]string{"userid": other}})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userInfo = new(RespLaunchCode)
|
|
||||||
err = json.Unmarshal(response, userInfo)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if userInfo.ErrCode != 0 {
|
|
||||||
err = fmt.Errorf("GetUserInfo Error , errcode=%d , errmsg=%s", userInfo.ErrCode, userInfo.ErrMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,7 @@ const (
|
|||||||
SDKUnknownError Error = "未知错误"
|
SDKUnknownError Error = "未知错误"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Error 输出错误信息
|
// Error 输出错误信息
|
||||||
func (r Error) Error() string {
|
func (r Error) Error() string {
|
||||||
return reflect.ValueOf(r).String()
|
return reflect.ValueOf(r).String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/util"
|
"github.com/silenceper/wechat/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Server struct
|
// Server struct
|
||||||
type Server struct {
|
type Server struct {
|
||||||
*context.Context
|
*context.Context
|
||||||
Writer http.ResponseWriter
|
Writer http.ResponseWriter
|
||||||
@@ -36,7 +36,7 @@ type Server struct {
|
|||||||
timestamp int64
|
timestamp int64
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewServer init
|
// NewServer 实例化
|
||||||
func NewServer(context *context.Context) *Server {
|
func NewServer(context *context.Context) *Server {
|
||||||
srv := new(Server)
|
srv := new(Server)
|
||||||
srv.Context = context
|
srv.Context = context
|
||||||
@@ -64,7 +64,7 @@ func (srv *Server) SkipValidate(skip bool) {
|
|||||||
srv.skipValidate = skip
|
srv.skipValidate = skip
|
||||||
}
|
}
|
||||||
|
|
||||||
//Serve 处理企业微信的请求消息
|
// Serve 处理企业微信的请求消息
|
||||||
func (srv *Server) Serve() error {
|
func (srv *Server) Serve() error {
|
||||||
response, err := srv.handleRequest()
|
response, err := srv.handleRequest()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -76,7 +76,7 @@ func (srv *Server) Serve() error {
|
|||||||
return srv.buildResponse(response)
|
return srv.buildResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Validate 校验请求是否合法
|
// Validate 校验请求是否合法
|
||||||
func (srv *Server) Validate() bool {
|
func (srv *Server) Validate() bool {
|
||||||
if srv.skipValidate {
|
if srv.skipValidate {
|
||||||
return true
|
return true
|
||||||
@@ -88,7 +88,7 @@ func (srv *Server) Validate() bool {
|
|||||||
return signature == util.Signature(srv.Token, timestamp, nonce)
|
return signature == util.Signature(srv.Token, timestamp, nonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
//HandleRequest 处理企业微信的请求
|
// HandleRequest 处理企业微信的请求
|
||||||
func (srv *Server) handleRequest() (reply *message.Reply, err error) {
|
func (srv *Server) handleRequest() (reply *message.Reply, err error) {
|
||||||
|
|
||||||
var msg interface{}
|
var msg interface{}
|
||||||
@@ -105,7 +105,7 @@ func (srv *Server) handleRequest() (reply *message.Reply, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//getMessage 解析企业微信返回的消息
|
// getMessage 解析企业微信返回的消息
|
||||||
func (srv *Server) getMessage() (interface{}, error) {
|
func (srv *Server) getMessage() (interface{}, error) {
|
||||||
var rawXMLMsgBytes []byte
|
var rawXMLMsgBytes []byte
|
||||||
var err error
|
var err error
|
||||||
@@ -145,7 +145,7 @@ func (srv *Server) parseRequestMessage(rawXMLMsgBytes []byte) (msg *message.MixM
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//SetMessageHandler 设置用户自定义的回调方法
|
// SetMessageHandler 设置用户自定义的回调方法
|
||||||
func (srv *Server) SetMessageHandler(handler func(*message.MixMessage) *message.Reply) {
|
func (srv *Server) SetMessageHandler(handler func(*message.MixMessage) *message.Reply) {
|
||||||
srv.messageHandler = handler
|
srv.messageHandler = handler
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ func (srv *Server) buildResponse(reply *message.Reply) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Send 将自定义的消息发送
|
// Send 将自定义的消息发送
|
||||||
func (srv *Server) Send() (err error) {
|
func (srv *Server) Send() (err error) {
|
||||||
replyMsg := srv.ResponseMsg
|
replyMsg := srv.ResponseMsg
|
||||||
log.Debugf("response msg =%+v", replyMsg)
|
log.Debugf("response msg =%+v", replyMsg)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func writeContextType(w http.ResponseWriter, value []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Render render from bytes
|
// Render render from bytes
|
||||||
func (srv *Server) Render(bytes []byte) {
|
func (srv *Server) Render(bytes []byte) {
|
||||||
//debug
|
//debug
|
||||||
//fmt.Println("response msg = ", string(bytes))
|
//fmt.Println("response msg = ", string(bytes))
|
||||||
@@ -26,13 +26,13 @@ func (srv *Server) Render(bytes []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//String render from string
|
// String render from string
|
||||||
func (srv *Server) String(str string) {
|
func (srv *Server) String(str string) {
|
||||||
writeContextType(srv.Writer, plainContentType)
|
writeContextType(srv.Writer, plainContentType)
|
||||||
srv.Render([]byte(str))
|
srv.Render([]byte(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
//XML render to xml
|
// XML render to xml
|
||||||
func (srv *Server) XML(obj interface{}) {
|
func (srv *Server) XML(obj interface{}) {
|
||||||
writeContextType(srv.Writer, xmlContentType)
|
writeContextType(srv.Writer, xmlContentType)
|
||||||
bytes, err := xml.Marshal(obj)
|
bytes, err := xml.Marshal(obj)
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
"github.com/silenceper/wechat/v2/work/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
userInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s"
|
|
||||||
updateURL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=%s&department_id=%s&fetch_child=1"
|
|
||||||
userListURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get"
|
|
||||||
)
|
|
||||||
|
|
||||||
// User 用户管理
|
|
||||||
type User struct {
|
|
||||||
*context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUser 实例化
|
|
||||||
func NewUser(context *context.Context) *User {
|
|
||||||
user := new(User)
|
|
||||||
user.Context = context
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info 用户基本信息
|
|
||||||
type Info struct {
|
|
||||||
util.CommonError
|
|
||||||
Userid string `json:"userid"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Department []int `json:"department"`
|
|
||||||
Order []int `json:"order"`
|
|
||||||
Position string `json:"position"`
|
|
||||||
Mobile string `json:"mobile"`
|
|
||||||
Gender string `json:"gender"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
IsLeaderInDept []int `json:"is_leader_in_dept"`
|
|
||||||
Avatar string `json:"avatar"`
|
|
||||||
ThumbAvatar string `json:"thumb_avatar"`
|
|
||||||
Telephone string `json:"telephone"`
|
|
||||||
Alias string `json:"alias"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
OpenUserid string `json:"open_userid"`
|
|
||||||
MainDepartment int `json:"main_department"`
|
|
||||||
Extattr struct {
|
|
||||||
Attrs []struct {
|
|
||||||
Type int `json:"type"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Text struct {
|
|
||||||
Value string `json:"value"`
|
|
||||||
} `json:"text,omitempty"`
|
|
||||||
Web struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
} `json:"web,omitempty"`
|
|
||||||
} `json:"attrs"`
|
|
||||||
} `json:"extattr"`
|
|
||||||
Status int `json:"status"`
|
|
||||||
QrCode string `json:"qr_code"`
|
|
||||||
ExternalPosition string `json:"external_position"`
|
|
||||||
ExternalProfile struct {
|
|
||||||
ExternalCorpName string `json:"external_corp_name"`
|
|
||||||
WechatChannels struct {
|
|
||||||
Nickname string `json:"nickname"`
|
|
||||||
Status int `json:"status"`
|
|
||||||
} `json:"wechat_channels"`
|
|
||||||
ExternalAttr []struct {
|
|
||||||
Type int `json:"type"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Text struct {
|
|
||||||
Value string `json:"value"`
|
|
||||||
} `json:"text,omitempty"`
|
|
||||||
Web struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
} `json:"web,omitempty"`
|
|
||||||
Miniprogram struct {
|
|
||||||
Appid string `json:"appid"`
|
|
||||||
Pagepath string `json:"pagepath"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
} `json:"miniprogram,omitempty"`
|
|
||||||
} `json:"external_attr"`
|
|
||||||
} `json:"external_profile"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenidList 用户列表
|
|
||||||
type OpenidList struct {
|
|
||||||
util.CommonError
|
|
||||||
|
|
||||||
Total int `json:"total"`
|
|
||||||
Count int `json:"count"`
|
|
||||||
Data struct {
|
|
||||||
OpenIDs []string `json:"openid"`
|
|
||||||
} `json:"data"`
|
|
||||||
NextOpenID string `json:"next_openid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserInfo 获取用户基本信息
|
|
||||||
func (user *User) GetUserInfo(userID string) (userInfo *Info, err error) {
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err = user.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uri := fmt.Sprintf(userInfoURL, accessToken, userID)
|
|
||||||
var response []byte
|
|
||||||
response, err = util.HTTPGet(uri)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userInfo = new(Info)
|
|
||||||
err = json.Unmarshal(response, userInfo)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if userInfo.ErrCode != 0 {
|
|
||||||
err = fmt.Errorf("GetUserInfo Error , errcode=%d , errmsg=%s", userInfo.ErrCode, userInfo.ErrMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update 更新员工资料
|
|
||||||
func (user *User) Update(userID, external_position string) (err error) {
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err = user.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uri := fmt.Sprintf(updateURL, accessToken, userID)
|
|
||||||
var response []byte
|
|
||||||
response, err = util.PostJSON(uri, map[string]string{"userid": userID, "external_position": external_position})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.DecodeWithCommonError(response, "updateURL")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListUserOpenIDs 返回用户列表
|
|
||||||
func (user *User) ListUserOpenIDs(nextOpenid ...string) (*OpenidList, error) {
|
|
||||||
accessToken, err := user.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
uri, _ := url.Parse(userListURL)
|
|
||||||
q := uri.Query()
|
|
||||||
q.Set("access_token", accessToken)
|
|
||||||
if len(nextOpenid) > 0 && nextOpenid[0] != "" {
|
|
||||||
q.Set("next_openid", nextOpenid[0])
|
|
||||||
}
|
|
||||||
uri.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
response, err := util.HTTPGet(uri.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
userlist := OpenidList{}
|
|
||||||
|
|
||||||
err = util.DecodeWithError(response, &userlist, "ListUserOpenIDs")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &userlist, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAllUserOpenIDs 返回所有用户OpenID列表
|
|
||||||
func (user *User) ListAllUserOpenIDs() ([]string, error) {
|
|
||||||
nextOpenid := ""
|
|
||||||
openids := make([]string, 0)
|
|
||||||
count := 0
|
|
||||||
for {
|
|
||||||
ul, err := user.ListUserOpenIDs(nextOpenid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
openids = append(openids, ul.Data.OpenIDs...)
|
|
||||||
count += ul.Count
|
|
||||||
if ul.Total > count {
|
|
||||||
nextOpenid = ul.NextOpenID
|
|
||||||
} else {
|
|
||||||
return openids, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user