增加vendor
This commit is contained in:
+3
@@ -0,0 +1,3 @@
|
||||
### 微信客服SDK
|
||||
|
||||
相关文档正在梳理中...
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
//添加客服账号
|
||||
accountAddAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/account/add?access_token=%s"
|
||||
// 删除客服账号
|
||||
accountDelAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/account/del?access_token=%s"
|
||||
// 修改客服账号
|
||||
accountUpdateAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/account/update?access_token=%s"
|
||||
// 获取客服账号列表
|
||||
accountListAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/account/list?access_token=%s"
|
||||
//获取客服账号链接
|
||||
addContactWayAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/add_contact_way?access_token=%s"
|
||||
)
|
||||
|
||||
// AccountAddOptions 添加客服账号请求参数
|
||||
type AccountAddOptions struct {
|
||||
Name string `json:"name"` // 客服帐号名称, 不多于16个字符
|
||||
MediaID string `json:"media_id"` // 客服头像临时素材。可以调用上传临时素材接口获取, 不多于128个字节
|
||||
}
|
||||
|
||||
// AccountAddSchema 添加客服账号响应内容
|
||||
type AccountAddSchema struct {
|
||||
util.CommonError
|
||||
OpenKFID string `json:"open_kfid"` // 新创建的客服张号ID
|
||||
}
|
||||
|
||||
// AccountAdd 添加客服账号
|
||||
func (r *Client) AccountAdd(options AccountAddOptions) (info AccountAddSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(accountAddAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// AccountDelOptions 删除客服账号请求参数
|
||||
type AccountDelOptions struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID, 不多于64字节
|
||||
}
|
||||
|
||||
// AccountDel 删除客服账号
|
||||
func (r *Client) AccountDel(options AccountDelOptions) (info util.CommonError, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(accountDelAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// AccountUpdateOptions 修改客服账号请求参数
|
||||
type AccountUpdateOptions struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID, 不多于64字节
|
||||
Name string `json:"name"` // 客服帐号名称, 不多于16个字符
|
||||
MediaID string `json:"media_id"` // 客服头像临时素材。可以调用上传临时素材接口获取, 不多于128个字节
|
||||
}
|
||||
|
||||
// AccountUpdate 修复客服账号
|
||||
func (r *Client) AccountUpdate(options AccountUpdateOptions) (info util.CommonError, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(accountUpdateAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// AccountInfoSchema 客服详情
|
||||
type AccountInfoSchema struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
Name string `json:"name"` // 客服帐号名称
|
||||
Avatar string `json:"avatar"` // 客服头像URL
|
||||
}
|
||||
|
||||
// AccountListSchema 获取客服账号列表响应内容
|
||||
type AccountListSchema struct {
|
||||
util.CommonError
|
||||
AccountList []AccountInfoSchema `json:"account_list"` // 客服账号列表
|
||||
}
|
||||
|
||||
// AccountList 获取客服账号列表
|
||||
func (r *Client) AccountList() (info AccountListSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.HTTPGet(fmt.Sprintf(accountListAddr, accessToken))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// AddContactWayOptions 获取客服账号链接
|
||||
// 1.若scene非空,返回的客服链接开发者可拼接scene_param=SCENE_PARAM参数使用,用户进入会话事件会将SCENE_PARAM原样返回。其中SCENE_PARAM需要urlencode,且长度不能超过128字节。
|
||||
// 如 https://work.weixin.qq.com/kf/kfcbf8f8d07ac7215f?enc_scene=ENCGFSDF567DF&scene_param=a%3D1%26b%3D2
|
||||
// 2.历史调用接口返回的客服链接(包含encScene=XXX参数),不支持scene_param参数。
|
||||
// 3.返回的客服链接,不能修改或复制参数到其他链接使用。否则进入会话事件参数校验不通过,导致无法回调。
|
||||
type AddContactWayOptions struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID, 不多于64字节
|
||||
Scene string `json:"scene"` // 场景值,字符串类型,由开发者自定义, 不多于32字节, 字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
|
||||
}
|
||||
|
||||
// AddContactWaySchema 获取客服账号链接响应内容
|
||||
type AddContactWaySchema struct {
|
||||
util.CommonError
|
||||
URL string `json:"url"` // 客服链接,开发者可将该链接嵌入到H5页面中,用户点击链接即可向对应的微信客服帐号发起咨询。开发者也可根据该url自行生成需要的二维码图片
|
||||
}
|
||||
|
||||
// AddContactWay 获取客服账号链接
|
||||
func (r *Client) AddContactWay(options AddContactWayOptions) (info AddContactWaySchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(addContactWayAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
// SignatureOptions 微信服务器验证参数
|
||||
type SignatureOptions struct {
|
||||
Signature string `form:"msg_signature"`
|
||||
TimeStamp string `form:"timestamp"`
|
||||
Nonce string `form:"nonce"`
|
||||
EchoStr string `form:"echostr"`
|
||||
}
|
||||
|
||||
// VerifyURL 验证请求参数是否合法并返回解密后的消息内容
|
||||
// //Gin框架的使用示例
|
||||
// r.GET("/v1/event/callback", func(c *gin.Context) {
|
||||
// options := kf.SignatureOptions{}
|
||||
// //获取回调的的校验参数
|
||||
// if = c.ShouldBindQuery(&options); err != nil {
|
||||
// c.String(http.StatusUnauthorized, "参数解析失败")
|
||||
// }
|
||||
// // 调用VerifyURL方法校验当前请求,如果合法则把解密后的内容作为响应返回给微信服务器
|
||||
// echo, err := kfClient.VerifyURL(options)
|
||||
// if err == nil {
|
||||
// c.String(http.StatusOK, echo)
|
||||
// } else {
|
||||
// c.String(http.StatusUnauthorized, "非法请求来源")
|
||||
// }
|
||||
// })
|
||||
func (r *Client) VerifyURL(options SignatureOptions) (string, error) {
|
||||
if options.Signature != util.Signature(r.ctx.Token, options.TimeStamp, options.Nonce, options.EchoStr) {
|
||||
return "", NewSDKErr(40015)
|
||||
}
|
||||
_, bData, err := util.DecryptMsg(r.corpID, options.EchoStr, r.encodingAESKey)
|
||||
if err != nil {
|
||||
return "", NewSDKErr(40016)
|
||||
}
|
||||
|
||||
return string(bData), nil
|
||||
}
|
||||
|
||||
// 原始回调消息内容
|
||||
type callbackOriginMessage struct {
|
||||
ToUserName string // 企业微信的CorpID,当为第三方套件回调事件时,CorpID的内容为suiteid
|
||||
AgentID string // 接收的应用id,可在应用的设置页面获取
|
||||
Encrypt string // 消息结构体加密后的字符串
|
||||
}
|
||||
|
||||
// CallbackMessage 微信客服回调消息
|
||||
type CallbackMessage struct {
|
||||
ToUserName string `json:"to_user_name"` // 微信客服组件ID
|
||||
CreateTime int `json:"create_time"` // 消息创建时间,unix时间戳
|
||||
MsgType string `json:"msgtype"` // 消息的类型,此时固定为 event
|
||||
Event string `json:"event"` // 事件的类型,此时固定为 kf_msg_or_event
|
||||
Token string `json:"token"` // 调用拉取消息接口时,需要传此token,用于校验请求的合法性
|
||||
}
|
||||
|
||||
// GetCallbackMessage 获取回调事件中的消息内容
|
||||
// //Gin框架的使用示例
|
||||
// r.POST("/v1/event/callback", func(c *gin.Context) {
|
||||
// var (
|
||||
// message kf.CallbackMessage
|
||||
// body []byte
|
||||
// )
|
||||
// // 读取原始消息内容
|
||||
// body, err = c.GetRawData()
|
||||
// if err != nil {
|
||||
// c.String(http.StatusInternalServerError, err.Error())
|
||||
// return
|
||||
// }
|
||||
// // 解析原始数据
|
||||
// message, err = kfClient.GetCallbackMessage(body)
|
||||
// if err != nil {
|
||||
// c.String(http.StatusInternalServerError, "消息获取失败")
|
||||
// return
|
||||
// }
|
||||
// fmt.Println(message)
|
||||
// c.String(200, "ok")
|
||||
// })
|
||||
func (r *Client) GetCallbackMessage(encryptedMsg []byte) (msg CallbackMessage, err error) {
|
||||
var origin callbackOriginMessage
|
||||
if err = xml.Unmarshal(encryptedMsg, &origin); err != nil {
|
||||
return msg, err
|
||||
}
|
||||
_, bData, err := util.DecryptMsg(r.corpID, origin.Encrypt, r.encodingAESKey)
|
||||
if err != nil {
|
||||
return msg, NewSDKErr(40016)
|
||||
}
|
||||
if err = xml.Unmarshal(bData, &msg); err != nil {
|
||||
return msg, err
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"github.com/silenceper/wechat/v2/cache"
|
||||
"github.com/silenceper/wechat/v2/credential"
|
||||
"github.com/silenceper/wechat/v2/work/config"
|
||||
"github.com/silenceper/wechat/v2/work/context"
|
||||
)
|
||||
|
||||
// Client 微信客服实例
|
||||
type Client struct {
|
||||
corpID string // 企业ID:企业开通的每个微信客服,都对应唯一的企业ID,企业可在微信客服管理后台的企业信息处查看
|
||||
secret string // Secret是微信客服用于校验开发者身份的访问密钥,企业成功注册微信客服后,可在「微信客服管理后台-开发配置」处获取
|
||||
token string // 用于生成签名校验回调请求的合法性
|
||||
encodingAESKey string // 回调消息加解密参数是AES密钥的Base64编码,用于解密回调消息内容对应的密文
|
||||
cache cache.Cache
|
||||
ctx *context.Context
|
||||
}
|
||||
|
||||
// NewClient 初始化微信客服实例
|
||||
func NewClient(cfg *config.Config) (client *Client, err error) {
|
||||
if cfg.Cache == nil {
|
||||
return nil, NewSDKErr(50001)
|
||||
}
|
||||
|
||||
//初始化 AccessToken Handle
|
||||
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
|
||||
ctx := &context.Context{
|
||||
Config: cfg,
|
||||
AccessTokenHandle: defaultAkHandle,
|
||||
}
|
||||
|
||||
client = &Client{
|
||||
corpID: cfg.CorpID,
|
||||
secret: cfg.CorpSecret,
|
||||
token: cfg.Token,
|
||||
encodingAESKey: cfg.EncodingAESKey,
|
||||
cache: cfg.Cache,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
customerBatchGetAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/customer/batchget?access_token=%s"
|
||||
)
|
||||
|
||||
// CustomerBatchGetOptions 客户基本信息获取请求参数
|
||||
type CustomerBatchGetOptions struct {
|
||||
ExternalUserIDList []string `json:"external_userid_list"` // external_userid列表
|
||||
}
|
||||
|
||||
// CustomerSchema 微信客户基本资料
|
||||
type CustomerSchema struct {
|
||||
ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
|
||||
NickName string `json:"nickname"` // 微信昵称
|
||||
Avatar string `json:"avatar"` // 微信头像。第三方不可获取
|
||||
Gender int `json:"gender"` // 性别
|
||||
UnionID string `json:"unionid"` // unionid,需要绑定微信开发者帐号才能获取到,查看绑定方法: https://open.work.weixin.qq.com/kf/doc/92512/93143/94769#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96%E5%BE%AE%E4%BF%A1%E5%AE%A2%E6%88%B7%E7%9A%84unionid
|
||||
}
|
||||
|
||||
// CustomerBatchGetSchema 获取客户基本信息响应内容
|
||||
type CustomerBatchGetSchema struct {
|
||||
util.CommonError
|
||||
CustomerList []CustomerSchema `json:"customer_list"` // 微信客户信息列表
|
||||
InvalidExternalUserID []string `json:"invalid_external_userid"` // 无效的微信客户ID
|
||||
}
|
||||
|
||||
// CustomerBatchGet 客户基本信息获取
|
||||
func (r *Client) CustomerBatchGet(options CustomerBatchGetOptions) (info CustomerBatchGetSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(customerBatchGetAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Error 错误
|
||||
type Error string
|
||||
|
||||
const (
|
||||
// SDKInitFailed 错误码:50001
|
||||
SDKInitFailed Error = "SDK初始化失败"
|
||||
// SDKCacheUnavailable 错误码:50002
|
||||
SDKCacheUnavailable Error = "缓存无效"
|
||||
// SDKUnknownError 错误码:50003
|
||||
SDKUnknownError Error = "未知错误"
|
||||
// SDKInvalidCredential 错误码:40001
|
||||
SDKInvalidCredential Error = "不合法的secret参数"
|
||||
// SDKInvalidImageSize 错误码:40009
|
||||
SDKInvalidImageSize Error = "无效的图片大小"
|
||||
// SDKInvalidCorpID 错误码:40013
|
||||
SDKInvalidCorpID Error = "无效的 CorpID"
|
||||
// SDKAccessTokenInvalid 错误码:40014
|
||||
SDKAccessTokenInvalid Error = "AccessToken 无效"
|
||||
// SDKValidateSignatureFailed 错误码:40015
|
||||
SDKValidateSignatureFailed Error = "校验签名错误"
|
||||
// SDKDecryptMSGFailed 错误码:40016
|
||||
SDKDecryptMSGFailed Error = "消息解密失败"
|
||||
// SDKMediaIDExceedMinLength 错误码:40058
|
||||
SDKMediaIDExceedMinLength Error = "不合法的参数, 请参照具体 API 接口说明进行传参"
|
||||
// SDKContentContainsSensitiveInformation 错误码:40201
|
||||
SDKContentContainsSensitiveInformation Error = "当前客服账号由于涉及敏感信息,已被封禁,请联系企业微信客服处理"
|
||||
// SDKAccessTokenMissing 错误码:41001
|
||||
SDKAccessTokenMissing Error = "缺少AccessToken参数"
|
||||
// SDKAccessTokenExpired 错误码:42001
|
||||
SDKAccessTokenExpired Error = "AccessToken 已过期"
|
||||
// SDKApiFreqOutOfLimit 错误码:45009
|
||||
SDKApiFreqOutOfLimit Error = "接口请求次数超频"
|
||||
// SDKApiForbidden 错误码:48002
|
||||
SDKApiForbidden Error = "API 禁止调用"
|
||||
// SDKInvalidOpenKFID 错误码:95000
|
||||
SDKInvalidOpenKFID Error = "无效的 open_kfid"
|
||||
// SDKOpenKFIDNotExist 错误码:95004
|
||||
SDKOpenKFIDNotExist Error = "open_kfid 不存在"
|
||||
// SDKWeWorkAlready 错误码:95011
|
||||
SDKWeWorkAlready Error = "已在企业微信使用微信客服"
|
||||
// SDKNotUseInWeCom 错误码:95012
|
||||
SDKNotUseInWeCom Error = "未在企业微信使用微信客服"
|
||||
// SDKApiNotOpen 错误码:95017
|
||||
SDKApiNotOpen Error = "API 功能没有被开启"
|
||||
)
|
||||
|
||||
//Error 输出错误信息
|
||||
func (r Error) Error() string {
|
||||
return reflect.ValueOf(r).String()
|
||||
}
|
||||
|
||||
var codeDic = map[int64]error{
|
||||
50001: SDKInitFailed,
|
||||
50002: SDKCacheUnavailable,
|
||||
50003: SDKUnknownError,
|
||||
40001: SDKInvalidCredential,
|
||||
40009: SDKInvalidImageSize,
|
||||
40013: SDKInvalidCorpID,
|
||||
40014: SDKAccessTokenInvalid,
|
||||
40015: SDKValidateSignatureFailed,
|
||||
40016: SDKDecryptMSGFailed,
|
||||
40058: SDKMediaIDExceedMinLength,
|
||||
40201: SDKContentContainsSensitiveInformation,
|
||||
41001: SDKAccessTokenMissing,
|
||||
42001: SDKAccessTokenExpired,
|
||||
45009: SDKApiFreqOutOfLimit,
|
||||
48002: SDKApiForbidden,
|
||||
95000: SDKInvalidOpenKFID,
|
||||
95004: SDKOpenKFIDNotExist,
|
||||
95011: SDKWeWorkAlready,
|
||||
95012: SDKNotUseInWeCom,
|
||||
95017: SDKApiNotOpen,
|
||||
}
|
||||
|
||||
// NewSDKErr 初始化SDK实例错误信息
|
||||
func NewSDKErr(code int64, msgList ...string) error {
|
||||
if err := codeDic[code]; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//返回未知的自定义错误
|
||||
if len(msgList) > 0 {
|
||||
return Error(strings.Join(msgList, ","))
|
||||
}
|
||||
return SDKUnknownError
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
//获取视频号绑定状态
|
||||
corpQualification = "https://qyapi.weixin.qq.com/cgi-bin/kf/get_corp_qualification?access_token=%s"
|
||||
)
|
||||
|
||||
// CorpQualificationSchema 获取视频号绑定状态响应内容
|
||||
type CorpQualificationSchema struct {
|
||||
util.CommonError
|
||||
WechatChannelsBinding bool `json:"wechat_channels_binding"` // 当企业具有绑定成功的视频号时,返回true,否则返回false。 1. 企业申请绑定视频号且由视频号管理员确认后,才为绑定成功状态 2. 至少有一个绑定成功的视频号就会返回true
|
||||
}
|
||||
|
||||
// GetCorpQualification 获取视频号绑定状态
|
||||
// 微信客服可接待的客户数,和企业是否已完成主体验证、是否绑定视频号相关。
|
||||
//
|
||||
// 企业未完成主体验证时,微信客服仅可累计接待100位客户
|
||||
// 企业已验证但未绑定视频号时,微信客服仅可累计接待10000位客户
|
||||
// 企业已验证且已绑定视频号时,微信客服可接待的客户数不受限制
|
||||
//
|
||||
// 开发者可获取状态后,在应用等地方提示企业去完成主体验证或绑定视频号。
|
||||
func (r *Client) GetCorpQualification() (info CorpQualificationSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.HTTPGet(fmt.Sprintf(corpQualification, accessToken))
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
//发送消息
|
||||
sendMsgAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token=%s"
|
||||
)
|
||||
|
||||
// SendMsgSchema 发送消息响应内容
|
||||
type SendMsgSchema struct {
|
||||
util.CommonError
|
||||
MsgID string `json:"msgid"` // 消息ID。如果请求参数指定了msgid,则原样返回,否则系统自动生成并返回。不多于32字节, 字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
|
||||
}
|
||||
|
||||
// SendMsg 发送消息
|
||||
// 当微信客户处于“新接入待处理”或“由智能助手接待”状态下,可调用该接口给用户发送消息。
|
||||
// 注意仅当微信客户在主动发送消息给客服后的48小时内,企业可发送消息给客户,最多可发送5条消息;若用户继续发送消息,企业可再次下发消息。
|
||||
// 支持发送消息类型:文本、图片、语音、视频、文件、图文、小程序、菜单消息、地理位置。
|
||||
// 目前该接口允许下发消息条数和下发时限如下:
|
||||
//
|
||||
// 用户动作 允许下发条数限制 下发时限
|
||||
// 用户发送消息 5条 48 小时
|
||||
func (r *Client) SendMsg(options interface{}) (info SendMsgSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(sendMsgAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// 发送事件响应消息
|
||||
sendMsgOnEventAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg_on_event?access_token=%s"
|
||||
)
|
||||
|
||||
// SendMsgOnEventSchema 发送事件响应消息
|
||||
type SendMsgOnEventSchema struct {
|
||||
util.CommonError
|
||||
MsgID string `json:"msgid"` // 消息ID。如果请求参数指定了msgid,则原样返回,否则系统自动生成并返回。不多于32字节, 字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
|
||||
}
|
||||
|
||||
// SendMsgOnEvent 发送事件响应消息
|
||||
// 当特定的事件回调消息包含code字段,或通过接口变更到特定的会话状态,会返回code字段。
|
||||
// 开发者可以此code为凭证,调用该接口给用户发送相应事件场景下的消息,如客服欢迎语、客服提示语和会话结束语等。
|
||||
// 除”用户进入会话事件”以外,响应消息仅支持会话处于获取该code的会话状态时发送,如将会话转入待接入池时获得的code仅能在会话状态为”待接入池排队中“时发送。
|
||||
//
|
||||
// 目前支持的事件场景和相关约束如下:
|
||||
//
|
||||
// 事件场景 允许下发条数 code有效期 支持的消息类型 获取code途径
|
||||
// 用户进入会话,用于发送客服欢迎语 1条 20秒 文本、菜单 事件回调
|
||||
// 进入接待池,用于发送排队提示语等 1条 48小时 文本 转接会话接口
|
||||
// 从接待池接入会话,用于发送非工作时间的提示语或超时未回复的提示语等 1条 48小时 文本 事件回调、转接会话接口
|
||||
// 结束会话,用于发送结束会话提示语或满意度评价等 1条 20秒 文本、菜单 事件回调、转接会话接口
|
||||
//
|
||||
//「进入会话事件」响应消息:
|
||||
// 如果满足通过API下发欢迎语条件(条件为:1. 企业没有在管理端配置了原生欢迎语;2. 用户在过去48小时里未收过欢迎语,且未向该用户发过消息),则用户进入会话事件会额外返回一个welcome_code,开发者以此为凭据调用接口(填到该接口code参数),即可向客户发送客服欢迎语。
|
||||
func (r *Client) SendMsgOnEvent(options interface{}) (info SendMsgOnEventSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(sendMsgOnEventAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
//添加接待人员
|
||||
receptionistAddAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/servicer/add?access_token=%s"
|
||||
//删除接待人员
|
||||
receptionistDelAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/servicer/del?access_token=%s"
|
||||
//获取接待人员列表
|
||||
receptionistListAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/servicer/list?access_token=%s&open_kfid=%s"
|
||||
)
|
||||
|
||||
// ReceptionistOptions 添加接待人员请求参数
|
||||
type ReceptionistOptions struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
UserIDList []string `json:"userid_list"` // 接待人员userid列表。第三方应用填密文userid,即open_userid 可填充个数:1 ~ 100。超过100个需分批调用。
|
||||
}
|
||||
|
||||
// ReceptionistSchema 添加接待人员响应内容
|
||||
type ReceptionistSchema struct {
|
||||
util.CommonError
|
||||
ResultList []struct {
|
||||
UserID string `json:"userid"`
|
||||
util.CommonError
|
||||
} `json:"result_list"`
|
||||
}
|
||||
|
||||
// ReceptionistAdd 添加接待人员
|
||||
func (r *Client) ReceptionistAdd(options ReceptionistOptions) (info ReceptionistSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(receptionistAddAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// ReceptionistDel 删除接待人员
|
||||
func (r *Client) ReceptionistDel(options ReceptionistOptions) (info ReceptionistSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(receptionistDelAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// ReceptionistListSchema 获取接待人员列表响应内容
|
||||
type ReceptionistListSchema struct {
|
||||
util.CommonError
|
||||
ReceptionistList []struct {
|
||||
UserID string `json:"userid"` // 接待人员的userid。第三方应用获取到的为密文userid,即open_userid
|
||||
Status int `json:"status"` // 接待人员的接待状态。0:接待中,1:停止接待。第三方应用需具有“管理帐号、分配会话和收发消息”权限才可获取
|
||||
} `json:"servicer_list"`
|
||||
}
|
||||
|
||||
// ReceptionistList 获取接待人员列表
|
||||
func (r *Client) ReceptionistList(kfID string) (info ReceptionistListSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.HTTPGet(fmt.Sprintf(receptionistListAddr, accessToken, kfID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// 获取会话状态
|
||||
serviceStateGetAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/get?access_token=%s"
|
||||
// 变更会话状态
|
||||
serviceStateTransAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/trans?access_token=%s"
|
||||
)
|
||||
|
||||
// ServiceStateGetOptions 获取会话状态请求参数
|
||||
type ServiceStateGetOptions struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
|
||||
}
|
||||
|
||||
// ServiceStateGetSchema 获取会话状态响应内容
|
||||
type ServiceStateGetSchema struct {
|
||||
util.CommonError
|
||||
ServiceState int `json:"service_state"` // 当前的会话状态,状态定义参考概述中的表格
|
||||
ServiceUserID string `json:"service_userid"` // 接待人员的userid,仅当state=3时有效
|
||||
}
|
||||
|
||||
// ServiceStateGet 获取会话状态
|
||||
// 0 未处理 新会话接入(客户发信咨询)。可选择:1.直接用API自动回复消息。2.放进待接入池等待接待人员接待。3.指定接待人员(接待人员须处于“正在接待”中,下同)进行接待
|
||||
// 1 由智能助手接待 可使用API回复消息。可选择转入待接入池或者指定接待人员处理
|
||||
// 2 待接入池排队中 在待接入池中排队等待接待人员接入。可选择转为指定人员接待
|
||||
// 3 由人工接待 人工接待中。可选择转接给其他接待人员处理或者结束会话
|
||||
// 4 已结束 会话已经结束或未开始。不允许变更会话状态,客户重新发信咨询后会话状态变为“未处理”
|
||||
// 注:一个微信用户向一个客服帐号发起咨询后,在48h内,或主动结束会话前(包括接待人员手动结束,或企业通过API结束会话),都算是一次会话
|
||||
func (r *Client) ServiceStateGet(options ServiceStateGetOptions) (info ServiceStateGetSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(serviceStateGetAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// ServiceStateTransOptions 变更会话状态请求参数
|
||||
type ServiceStateTransOptions struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
|
||||
ServiceState int `json:"service_state"` // 变更的目标状态,状态定义和所允许的变更可参考概述中的流程图和表格
|
||||
ServicerUserID string `json:"servicer_userid"` // 接待人员的userid,当state=3时要求必填,接待人员须处于“正在接待”中
|
||||
}
|
||||
|
||||
// ServiceStateTransSchema 变更会话状态响应内容
|
||||
type ServiceStateTransSchema struct {
|
||||
util.CommonError
|
||||
MsgCode string `json:"msg_code"` // 用于发送响应事件消息的code,将会话初次变更为service_state为2和3时,返回回复语code,service_state为4时,返回结束语code。可用该code调用发送事件响应消息接口给客户发送事件响应消息
|
||||
}
|
||||
|
||||
// ServiceStateTrans 变更会话状态
|
||||
func (r *Client) ServiceStateTrans(options ServiceStateTransOptions) (info ServiceStateTransSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(serviceStateTransAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
"github.com/silenceper/wechat/v2/work/kf/syncmsg"
|
||||
)
|
||||
|
||||
const (
|
||||
//获取消息
|
||||
syncMsgAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=%s"
|
||||
)
|
||||
|
||||
// SyncMsgOptions 获取消息查询参数
|
||||
type SyncMsgOptions struct {
|
||||
Cursor string `json:"cursor"` // 上一次调用时返回的next_cursor,第一次拉取可以不填, 不多于64字节
|
||||
Token string `json:"token"` // 回调事件返回的token字段,10分钟内有效;可不填,如果不填接口有严格的频率限制, 不多于128字节
|
||||
Limit uint `json:"limit"` // 期望请求的数据量,默认值和最大值都为1000, 注意:可能会出现返回条数少于limit的情况,需结合返回的has_more字段判断是否继续请求。
|
||||
}
|
||||
|
||||
// SyncMsgSchema 获取消息查询响应内容
|
||||
type syncMsgSchema struct {
|
||||
ErrCode int32 `json:"errcode"` // 返回码
|
||||
ErrMsg string `json:"errmsg"` // 错误码描述
|
||||
NextCursor string `json:"next_cursor"` // 下次调用带上该值,则从当前的位置继续往后拉,以实现增量拉取。强烈建议对改该字段入库保存,每次请求读取带上,请求结束后更新。避免因意外丢,导致必须从头开始拉取,引起消息延迟。
|
||||
HasMore uint32 `json:"has_more"` // 是否还有更多数据。0-否;1-是。不能通过判断msg_list是否空来停止拉取,可能会出现has_more为1,而msg_list为空的情况
|
||||
MsgList []map[string]interface{} `json:"msg_list"` // 消息列表
|
||||
}
|
||||
|
||||
// SyncMsgSchema 获取消息查询响应内容
|
||||
type SyncMsgSchema struct {
|
||||
ErrCode int32 `json:"errcode"` // 返回码
|
||||
ErrMsg string `json:"errmsg"` // 错误码描述
|
||||
NextCursor string `json:"next_cursor"` // 下次调用带上该值则从该key值往后拉,用于增量拉取
|
||||
HasMore uint32 `json:"has_more"` // 是否还有更多数据。0-否;1-是。不能通过判断msg_list是否空来停止拉取,可能会出现has_more为1,而msg_list为空的情况
|
||||
MsgList []syncmsg.Message `json:"msg_list"` // 消息列表
|
||||
}
|
||||
|
||||
// SyncMsg 获取消息
|
||||
func (r *Client) SyncMsg(options SyncMsgOptions) (info SyncMsgSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(syncMsgAddr, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
originInfo := syncMsgSchema{}
|
||||
if err = json.Unmarshal(data, &originInfo); err != nil {
|
||||
return
|
||||
}
|
||||
if originInfo.ErrCode != 0 {
|
||||
return info, errors.New(originInfo.ErrMsg)
|
||||
}
|
||||
msgList := make([]syncmsg.Message, 0)
|
||||
if len(originInfo.MsgList) > 0 {
|
||||
for _, msg := range originInfo.MsgList {
|
||||
newMsg := syncmsg.Message{}
|
||||
if val, ok := msg["msgid"].(string); ok {
|
||||
newMsg.MsgID = val
|
||||
}
|
||||
if val, ok := msg["open_kfid"].(string); ok {
|
||||
newMsg.OpenKFID = val
|
||||
}
|
||||
if val, ok := msg["external_userid"].(string); ok {
|
||||
newMsg.ExternalUserID = val
|
||||
}
|
||||
if val, ok := msg["send_time"].(float64); ok {
|
||||
newMsg.SendTime = uint64(val)
|
||||
}
|
||||
if val, ok := msg["origin"].(float64); ok {
|
||||
newMsg.Origin = uint32(val)
|
||||
}
|
||||
|
||||
if val, ok := msg["msgtype"].(string); ok {
|
||||
newMsg.MsgType = val
|
||||
}
|
||||
if newMsg.MsgType == "event" {
|
||||
if event, ok := msg["event"].(map[string]interface{}); ok {
|
||||
if eType, ok := event["event_type"].(string); ok {
|
||||
newMsg.EventType = eType
|
||||
}
|
||||
}
|
||||
}
|
||||
originData, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
newMsg.OriginData = originData
|
||||
msgList = append(msgList, newMsg)
|
||||
}
|
||||
}
|
||||
return SyncMsgSchema{
|
||||
ErrCode: originInfo.ErrCode,
|
||||
ErrMsg: originInfo.ErrMsg,
|
||||
NextCursor: originInfo.NextCursor,
|
||||
HasMore: originInfo.HasMore,
|
||||
MsgList: msgList,
|
||||
}, nil
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package syncmsg
|
||||
|
||||
// BaseMessage 接收消息
|
||||
type BaseMessage struct {
|
||||
MsgID string `json:"msgid"` // 消息ID
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID(msgtype为event,该字段不返回)
|
||||
ExternalUserID string `json:"external_userid"` // 客户UserID(msgtype为event,该字段不返回)
|
||||
ReceptionistUserID string `json:"servicer_userid"` // 接待客服userID
|
||||
SendTime uint64 `json:"send_time"` // 消息发送时间
|
||||
Origin uint32 `json:"origin"` // 消息来源。3-微信客户发送的消息 4-系统推送的事件消息 5-接待人员在企业微信客户端发送的消息
|
||||
}
|
||||
|
||||
// Text 文本消息
|
||||
type Text struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:text
|
||||
Text struct {
|
||||
Content string `json:"content"` // 文本内容
|
||||
MenuID string `json:"menu_id"` // 客户点击菜单消息,触发的回复消息中附带的菜单ID
|
||||
} `json:"text"` // 文本消息
|
||||
}
|
||||
|
||||
// Image 图片消息
|
||||
type Image struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:image
|
||||
Image struct {
|
||||
MediaID string `json:"media_id"` // 图片文件ID
|
||||
} `json:"image"` // 图片消息
|
||||
}
|
||||
|
||||
// Voice 语音消息
|
||||
type Voice struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:voice
|
||||
Voice struct {
|
||||
MediaID string `json:"media_id"` // 语音文件ID
|
||||
} `json:"voice"` // 语音消息
|
||||
}
|
||||
|
||||
// Video 视频消息
|
||||
type Video struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:video
|
||||
Video struct {
|
||||
MediaID string `json:"media_id"` // 文件ID
|
||||
} `json:"video"` // 视频消息
|
||||
}
|
||||
|
||||
// File 文件消息
|
||||
type File struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:file
|
||||
File struct {
|
||||
MediaID string `json:"media_id"` // 文件ID
|
||||
} `json:"file"` // 文件消息
|
||||
}
|
||||
|
||||
// Location 地理位置消息
|
||||
type Location struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:location
|
||||
Location struct {
|
||||
Latitude float32 `json:"latitude"` // 纬度
|
||||
Longitude float32 `json:"longitude"` // 经度
|
||||
Name string `json:"name"` // 位置名
|
||||
Address string `json:"address"` // 地址详情说明
|
||||
} `json:"location"` // 地理位置消息
|
||||
}
|
||||
|
||||
// Link 链接消息
|
||||
type Link struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:link
|
||||
Link struct {
|
||||
Title string `json:"title"` // 标题
|
||||
Desc string `json:"desc"` // 描述
|
||||
URL string `json:"url"` // 点击后跳转的链接
|
||||
PicURL string `json:"pic_url"` // 缩略图链接
|
||||
} `json:"link"` // 链接消息
|
||||
}
|
||||
|
||||
// BusinessCard 名片消息
|
||||
type BusinessCard struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:business_card
|
||||
BusinessCard struct {
|
||||
UserID string `json:"userid"` // 名片 userid
|
||||
} `json:"business_card"` // 名片消息
|
||||
}
|
||||
|
||||
// MiniProgram 小程序消息
|
||||
type MiniProgram struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:miniprogram
|
||||
MiniProgram struct {
|
||||
AppID string `json:"appid"` // 小程序appid,必须是关联到企业的小程序应用
|
||||
Title string `json:"title"` // 小程序消息标题,最多64个字节,超过会自动截断
|
||||
ThumbMediaID string `json:"thumb_media_id"` // 小程序消息封面的mediaid,封面图建议尺寸为520*416
|
||||
PagePath string `json:"pagepath"` // 点击消息卡片后进入的小程序页面路径
|
||||
} `json:"miniprogram"` // 小程序消息
|
||||
}
|
||||
|
||||
// EventMessage 事件消息
|
||||
type EventMessage struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:event
|
||||
Event struct {
|
||||
EventType string `json:"event_type"` // 事件类型
|
||||
} `json:"event"` // 事件消息
|
||||
}
|
||||
|
||||
// EnterSessionEvent 用户进入会话事件
|
||||
type EnterSessionEvent struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:event
|
||||
Event struct {
|
||||
EventType string `json:"event_type"` // 事件类型。此处固定为:enter_session
|
||||
OpenKFID string `json:"open_kfid"` // 客服账号ID
|
||||
ExternalUserID string `json:"external_userid"` // 客户UserID
|
||||
Scene string `json:"scene"` // 进入会话的场景值,获取客服帐号链接开发者自定义的场景值
|
||||
SceneParam string `json:"scene_param"` // 进入会话的自定义参数,获取客服帐号链接返回的url,开发者按规范拼接的scene_param参数
|
||||
WelcomeCode string `json:"welcome_code"` // 如果满足发送欢迎语条件(条件为:1. 企业没有在管理端配置了原生欢迎语;2. 用户在过去48小时里未收过欢迎语,且未向该用户发过消息),会返回该字段。可用该welcome_code调用发送事件响应消息接口给客户发送欢迎语。
|
||||
} `json:"event"` // 事件消息
|
||||
}
|
||||
|
||||
// MsgSendFailEvent 消息发送失败事件
|
||||
type MsgSendFailEvent struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:event
|
||||
Event struct {
|
||||
EventType string `json:"event_type"` // 事件类型。此处固定为:msg_send_fail
|
||||
OpenKFID string `json:"open_kfid"` // 客服账号ID
|
||||
ExternalUserID string `json:"external_userid"` // 客户UserID
|
||||
FailMsgID string `json:"fail_msgid"` // 发送失败的消息msgid
|
||||
FailType uint32 `json:"fail_type"` // 失败类型。0-未知原因 1-客服账号已删除 2-应用已关闭 4-会话已过期,超过48小时 5-会话已关闭 6-超过5条限制 7-未绑定视频号 8-主体未验证 9-未绑定视频号且主体未验证 10-用户拒收
|
||||
} `json:"event"` // 事件消息
|
||||
}
|
||||
|
||||
// ReceptionistStatusChangeEvent 客服人员接待状态变更事件
|
||||
type ReceptionistStatusChangeEvent struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:event
|
||||
Event struct {
|
||||
EventType string `json:"event_type"` // 事件类型。此处固定为:servicer_status_change
|
||||
ReceptionistUserID string `json:"servicer_userid"` // 客服人员userid
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
Status uint32 `json:"status"` // 状态类型。1-接待中 2-停止接待
|
||||
} `json:"event"`
|
||||
}
|
||||
|
||||
// SessionStatusChangeEvent 会话状态变更事件
|
||||
type SessionStatusChangeEvent struct {
|
||||
BaseMessage
|
||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:event
|
||||
Event struct {
|
||||
EventType string `json:"event_type"` // 事件类型。此处固定为:session_status_change
|
||||
OpenKFID string `json:"open_kfid"` // 客服账号ID
|
||||
ExternalUserID string `json:"external_userid"` // 客户UserID
|
||||
ChangeType uint32 `json:"change_type"` // 变更类型。1-从接待池接入会话 2-转接会话 3-结束会话
|
||||
OldReceptionistUserID string `json:"old_servicer_userid"` // 老的客服人员userid。仅change_type为2和3有值
|
||||
NewReceptionistUserID string `json:"new_servicer_userid"` // 新的客服人员userid。仅change_type为1和2有值
|
||||
MsgCode string `json:"msg_code"` // 用于发送事件响应消息的code,仅change_type为1和3时,会返回该字段。可用该msg_code调用发送事件响应消息接口给客户发送回复语或结束语。
|
||||
} `json:"event"` // 事件消息
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package syncmsg
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// Message 同步的消息内容
|
||||
type Message struct {
|
||||
MsgID string `json:"msgid"` // 消息ID
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
ExternalUserID string `json:"external_userid"` // 客户UserID
|
||||
ReceptionistUserID string `json:"servicer_userid"` // 接待客服userID
|
||||
SendTime uint64 `json:"send_time"` // 消息发送时间
|
||||
Origin uint32 `json:"origin"` // 消息来源。3-客户回复的消息 4-系统推送的消 息
|
||||
MsgType string `json:"msgtype"` // 消息类型
|
||||
EventType string `json:"event_type"` // 事件类型
|
||||
OriginData []byte `json:"origin_data"` // 原始数据内容
|
||||
}
|
||||
|
||||
// GetOriginMessage 获取原始消息
|
||||
func (r Message) GetOriginMessage() (info []byte) {
|
||||
return r.OriginData
|
||||
}
|
||||
|
||||
// GetTextMessage 获取文本消息
|
||||
func (r Message) GetTextMessage() (info Text, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetImageMessage 获取图片消息
|
||||
func (r Message) GetImageMessage() (info Image, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetVoiceMessage 获取语音消息
|
||||
func (r Message) GetVoiceMessage() (info Voice, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetVideoMessage 获取视频消息
|
||||
func (r Message) GetVideoMessage() (info Video, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetFileMessage 获取文件消息
|
||||
func (r Message) GetFileMessage() (info File, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetLocationMessage 获取文件消息
|
||||
func (r Message) GetLocationMessage() (info Location, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetLinkMessage 获取链接消息
|
||||
func (r Message) GetLinkMessage() (info Link, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetBusinessCardMessage 获取名片消息
|
||||
func (r Message) GetBusinessCardMessage() (info BusinessCard, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetMiniProgramMessage 获取小程序消息
|
||||
func (r Message) GetMiniProgramMessage() (info MiniProgram, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetEnterSessionEvent 用户进入会话事件
|
||||
func (r Message) GetEnterSessionEvent() (info EnterSessionEvent, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
info.OpenKFID = info.Event.OpenKFID
|
||||
info.ExternalUserID = info.Event.ExternalUserID
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetMsgSendFailEvent 消息发送失败事件
|
||||
func (r Message) GetMsgSendFailEvent() (info MsgSendFailEvent, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetReceptionistStatusChangeEvent 客服人员接待状态变更事件
|
||||
func (r Message) GetReceptionistStatusChangeEvent() (info ReceptionistStatusChangeEvent, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
return info, err
|
||||
}
|
||||
|
||||
// GetSessionStatusChangeEvent 会话状态变更事件
|
||||
func (r Message) GetSessionStatusChangeEvent() (info SessionStatusChangeEvent, err error) {
|
||||
err = json.Unmarshal(r.OriginData, &info)
|
||||
info.OpenKFID = info.Event.OpenKFID
|
||||
info.ExternalUserID = info.Event.ExternalUserID
|
||||
return info, err
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
package kf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
//获取配置的专员与客户群
|
||||
upgradeServiceConfigAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/customer/get_upgrade_service_config?access_token=%s"
|
||||
// 为客户升级为专员或客户群服务
|
||||
upgradeService = "https://qyapi.weixin.qq.com/cgi-bin/kf/customer/upgrade_service?access_token=%s"
|
||||
//为客户取消推荐
|
||||
upgradeServiceCancel = "https://qyapi.weixin.qq.com/cgi-bin/kf/customer/cancel_upgrade_service?access_token=%s"
|
||||
)
|
||||
|
||||
// UpgradeServiceConfigSchema 获取配置的专员与客户群
|
||||
type UpgradeServiceConfigSchema struct {
|
||||
util.CommonError
|
||||
MemberRange struct {
|
||||
UserIDList []string `json:"userid_list"` // 专员userid列表
|
||||
DepartmentIDList []string `json:"department_id_list"` // 专员部门列表
|
||||
} `json:"member_range"` // 专员服务配置范围
|
||||
GroupChatRange struct {
|
||||
ChatIDList []string `json:"chat_id_list"` // 客户群列表
|
||||
} `json:"groupchat_range"` // 客户群配置范围
|
||||
}
|
||||
|
||||
// UpgradeServiceConfig 获取配置的专员与客户群
|
||||
func (r *Client) UpgradeServiceConfig() (info UpgradeServiceConfigSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.HTTPGet(fmt.Sprintf(upgradeServiceConfigAddr, accessToken))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// UpgradeServiceOptions 为客户升级为专员或客户群服务请求参数
|
||||
type UpgradeServiceOptions struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
|
||||
Type int `json:"type"` // 表示是升级到专员服务还是客户群服务。1:专员服务。2:客户群服务
|
||||
Member struct {
|
||||
UserID string `json:"userid"` // 服务专员的userid
|
||||
Wording string `json:"wording"` // 推荐语
|
||||
} `json:"member"` // 推荐的服务专员,type等于1时有效
|
||||
GroupChat struct {
|
||||
ChatID string `json:"chat_id"` // 客户群id
|
||||
Wording string `json:"wording"` // 推荐语
|
||||
} `json:"groupchat"` // 推荐的客户群,type等于2时有效
|
||||
}
|
||||
|
||||
// UpgradeService 为客户升级为专员或客户群服务
|
||||
func (r *Client) UpgradeService(options UpgradeServiceOptions) (info util.CommonError, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(upgradeService, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// UpgradeMemberServiceOptions 为客户升级为专员服务请求参数
|
||||
type UpgradeMemberServiceOptions struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
|
||||
Type int `json:"type"` // 表示是升级到专员服务还是客户群服务。1:专员服务
|
||||
Member struct {
|
||||
UserID string `json:"userid"` // 服务专员的userid
|
||||
Wording string `json:"wording"` // 推荐语
|
||||
} `json:"member"` // 推荐的服务专员,type等于1时有效
|
||||
}
|
||||
|
||||
// UpgradeMemberService 为客户升级为专员服务
|
||||
func (r *Client) UpgradeMemberService(options UpgradeMemberServiceOptions) (info util.CommonError, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(upgradeService, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// UpgradeServiceGroupChatOptions 为客户升级为客户群服务请求参数
|
||||
type UpgradeServiceGroupChatOptions struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
|
||||
Type int `json:"type"` // 表示是升级到专员服务还是客户群服务。2:客户群服务
|
||||
GroupChat struct {
|
||||
ChatID string `json:"chat_id"` // 客户群id
|
||||
Wording string `json:"wording"` // 推荐语
|
||||
} `json:"groupchat"` // 推荐的客户群,type等于2时有效
|
||||
}
|
||||
|
||||
// UpgradeGroupChatService 为客户升级为客户群服务
|
||||
func (r *Client) UpgradeGroupChatService(options UpgradeServiceGroupChatOptions) (info util.CommonError, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(upgradeService, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// UpgradeServiceCancelOptions 为客户取消推荐
|
||||
type UpgradeServiceCancelOptions struct {
|
||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||
ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
|
||||
}
|
||||
|
||||
// UpgradeServiceCancel 为客户取消推荐
|
||||
func (r *Client) UpgradeServiceCancel(options UpgradeServiceCancelOptions) (info util.CommonError, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(upgradeServiceCancel, accessToken), options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
return
|
||||
}
|
||||
if info.ErrCode != 0 {
|
||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
Reference in New Issue
Block a user