1
0
mirror of https://github.com/silenceper/wechat.git synced 2026-02-06 21:52:27 +08:00

Merge branch 'release-2.0' of github.com:hb1707/wechat into release-2.0

# Conflicts:
#	work/externalcontact/client.go
#	work/oauth/oauth.go
#	work/work.go
This commit is contained in:
wind
2023-01-09 17:15:41 +08:00
163 changed files with 6586 additions and 1205 deletions

View File

@@ -0,0 +1,17 @@
package addresslist
import (
"github.com/silenceper/wechat/v2/work/context"
)
// Client 通讯录管理接口实例
type Client struct {
*context.Context
}
// NewClient 初始化实例
func NewClient(ctx *context.Context) *Client {
return &Client{
ctx,
}
}

View File

@@ -0,0 +1,47 @@
package addresslist
import (
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// DepartmentSimpleListURL 获取子部门ID列表
DepartmentSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token=%s&id=%d"
)
type (
// DepartmentSimpleListResponse 获取子部门ID列表响应
DepartmentSimpleListResponse struct {
util.CommonError
DepartmentID []*DepartmentID `json:"department_id"`
}
// DepartmentID 子部门ID
DepartmentID struct {
ID int `json:"id"`
ParentID int `json:"parentid"`
Order int `json:"order"`
}
)
// DepartmentSimpleList 获取子部门ID列表
// see https://developer.work.weixin.qq.com/document/path/95350
func (r *Client) DepartmentSimpleList(departmentID int) ([]*DepartmentID, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.HTTPGet(fmt.Sprintf(DepartmentSimpleListURL, accessToken, departmentID)); err != nil {
return nil, err
}
result := &DepartmentSimpleListResponse{}
if err = util.DecodeWithError(response, result, "DepartmentSimpleList"); err != nil {
return nil, err
}
return result.DepartmentID, nil
}

177
work/addresslist/user.go Normal file
View File

@@ -0,0 +1,177 @@
package addresslist
import (
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// UserSimpleListURL 获取部门成员
UserSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=%s&department_id=%d"
// UserGetURL 读取成员
UserGetURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s"
// UserListIDURL 获取成员ID列表
UserListIDURL = "https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token=%s"
)
type (
// UserSimpleListResponse 获取部门成员响应
UserSimpleListResponse struct {
util.CommonError
UserList []*UserList
}
// UserList 部门成员
UserList struct {
UserID string `json:"userid"`
Name string `json:"name"`
Department []int `json:"department"`
OpenUserID string `json:"open_userid"`
}
)
// UserSimpleList 获取部门成员
// @see https://developer.work.weixin.qq.com/document/path/90200
func (r *Client) UserSimpleList(departmentID int) ([]*UserList, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.HTTPGet(fmt.Sprintf(UserSimpleListURL, accessToken, departmentID)); err != nil {
return nil, err
}
result := &UserSimpleListResponse{}
err = util.DecodeWithError(response, result, "UserSimpleList")
if err != nil {
return nil, err
}
return result.UserList, nil
}
// UserGetResponse 获取部门成员响应
type UserGetResponse struct {
util.CommonError
UserID string `json:"userid"` // 成员UserID。对应管理端的帐号企业内必须唯一。不区分大小写长度为1~64个字节第三方应用返回的值为open_userid
Name string `json:"name"` // 成员名称第三方不可获取调用时返回userid以代替name代开发自建应用需要管理员授权才返回对于非第三方创建的成员第三方通讯录应用也不可获取未返回name的情况需要通过通讯录展示组件来展示名字
Department []int `json:"department"` // 成员所属部门id列表仅返回该应用有查看权限的部门id成员授权模式下固定返回根部门id即固定为1。对授权了“组织架构信息”权限的第三方应用返回成员所属的全部部门id
Order []int `json:"order"` // 部门内的排序值默认为0。数量必须和department一致数值越大排序越前面。值范围是[0, 2^32)。成员授权模式下不返回该字段
Position string `json:"position"` // 职务信息;代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
Mobile string `json:"mobile"` // 手机号码代开发自建应用需要管理员授权且成员oauth2授权获取第三方仅通讯录应用可获取对于非第三方创建的成员第三方通讯录应用也不可获取上游企业不可获取下游企业成员该字段
Gender string `json:"gender"` // 性别。0表示未定义1表示男性2表示女性。代开发自建应用需要管理员授权且成员oauth2授权获取第三方仅通讯录应用可获取对于非第三方创建的成员第三方通讯录应用也不可获取上游企业不可获取下游企业成员该字段。注不可获取指返回值0
Email string `json:"email"` // 邮箱代开发自建应用需要管理员授权且成员oauth2授权获取第三方仅通讯录应用可获取对于非第三方创建的成员第三方通讯录应用也不可获取上游企业不可获取下游企业成员该字段
BizMail string `json:"biz_mail"` // 企业邮箱代开发自建应用需要管理员授权且成员oauth2授权获取第三方仅通讯录应用可获取对于非第三方创建的成员第三方通讯录应用也不可获取上游企业不可获取下游企业成员该字段
IsLeaderInDept []int `json:"is_leader_in_dept"` // 表示在所在的部门内是否为部门负责人数量与department一致第三方通讯录应用或者授权了“组织架构信息-应用可获取企业的部门组织架构信息-部门负责人”权限的第三方应用可获取;对于非第三方创建的成员,第三方通讯录应用不可获取;上游企业不可获取下游企业成员该字段
DirectLeader []string `json:"direct_leader"` // 直属上级UserID返回在应用可见范围内的直属上级列表最多有五个直属上级第三方通讯录应用或者授权了“组织架构信息-应用可获取可见范围内成员组织架构信息-直属上级”权限的第三方应用可获取;对于非第三方创建的成员,第三方通讯录应用不可获取;上游企业不可获取下游企业成员该字段;代开发自建应用不可获取该字段
Avatar string `json:"avatar"` // 头像url。 代开发自建应用需要管理员授权且成员oauth2授权获取第三方仅通讯录应用可获取对于非第三方创建的成员第三方通讯录应用也不可获取上游企业不可获取下游企业成员该字段
ThumbAvatar string `json:"thumb_avatar"` // 头像缩略图url。第三方仅通讯录应用可获取对于非第三方创建的成员第三方通讯录应用也不可获取上游企业不可获取下游企业成员该字段
Telephone string `json:"telephone"` // 座机。代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
Alias string `json:"alias"` // 别名;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
Address string `json:"address"` // 地址。代开发自建应用需要管理员授权且成员oauth2授权获取第三方仅通讯录应用可获取对于非第三方创建的成员第三方通讯录应用也不可获取上游企业不可获取下游企业成员该字段
OpenUserid string `json:"open_userid"` // 全局唯一。对于同一个服务商不同应用获取到企业内同一个成员的open_userid是相同的最多64个字节。仅第三方应用可获取
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"` // 激活状态: 1=已激活2=已禁用4=未激活5=退出企业。 已激活代表已激活企业微信或已关注微信插件(原企业号)。未激活代表既未激活企业微信又未关注微信插件(原企业号)。
QrCode string `json:"qr_code"` // 员工个人二维码,扫描可添加为外部联系人(注意返回的是一个url可在浏览器上打开该url以展示二维码)代开发自建应用需要管理员授权且成员oauth2授权获取第三方仅通讯录应用可获取对于非第三方创建的成员第三方通讯录应用也不可获取上游企业不可获取下游企业成员该字段
ExternalPosition string `json:"external_position"` // 对外职务如果设置了该值则以此作为对外展示的职务否则以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"` // 成员对外属性,字段详情见对外属性;代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
}
// UserGet 获取部门成员
// @see https://developer.work.weixin.qq.com/document/path/90196
func (r *Client) UserGet(UserID string) (*UserGetResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.HTTPGet(fmt.Sprintf(UserGetURL, accessToken, UserID)); err != nil {
return nil, err
}
result := &UserGetResponse{}
err = util.DecodeWithError(response, result, "UserGet")
if err != nil {
return nil, err
}
return result, nil
}
// UserListIDRequest 获取成员ID列表请求
type UserListIDRequest struct {
Cursor string `json:"cursor"`
Limit int `json:"limit"`
}
// UserListIDResponse 获取成员ID列表响应
type UserListIDResponse struct {
util.CommonError
NextCursor string `json:"next_cursor"`
DeptUser []*DeptUser `json:"dept_user"`
}
// DeptUser 用户-部门关系
type DeptUser struct {
UserID string `json:"userid"`
Department int `json:"department"`
}
// UserListID 获取成员ID列表
// see https://developer.work.weixin.qq.com/document/path/96067
func (r *Client) UserListID(req *UserListIDRequest) (*UserListIDResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(UserListIDURL, accessToken), req); err != nil {
return nil, err
}
result := &UserListIDResponse{}
if err = util.DecodeWithError(response, result, "UserListID"); err != nil {
return nil, err
}
return result, nil
}

View File

@@ -0,0 +1,3 @@
### 企业微信 客户联系部分
相关文档正在梳理中...

View File

@@ -0,0 +1,45 @@
package externalcontact
import (
"encoding/xml"
"github.com/silenceper/wechat/v2/util"
)
// 原始回调消息内容
type callbackOriginMessage struct {
ToUserName string // 企业微信的CorpID当为第三方套件回调事件时CorpID的内容为suiteid
AgentID string // 接收的应用id可在应用的设置页面获取
Encrypt string // 消息结构体加密后的字符串
}
// EventCallbackMessage 微信客户联系回调消息
// https://developer.work.weixin.qq.com/document/path/92130
type EventCallbackMessage struct {
ToUserName string `json:"to_user_name"`
FromUserName string `json:"from_user_name"`
CreateTime int64 `json:"create_time"`
MsgType string `json:"msg_type"`
Event string `json:"event"`
ChangeType string `json:"change_type"`
UserID string `json:"user_id"`
ExternalUserID string `json:"external_user_id"`
State string `json:"state"`
WelcomeCode string `json:"welcome_code"`
}
// GetCallbackMessage 获取联系客户回调事件中的消息内容
func (r *Client) GetCallbackMessage(encryptedMsg []byte) (msg EventCallbackMessage, err error) {
var origin callbackOriginMessage
if err = xml.Unmarshal(encryptedMsg, &origin); err != nil {
return
}
_, bData, err := util.DecryptMsg(r.CorpID, origin.Encrypt, r.EncodingAESKey)
if err != nil {
return
}
if err = xml.Unmarshal(bData, &msg); err != nil {
return
}
return
}

View File

@@ -0,0 +1,275 @@
package externalcontact
import (
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// AddContactWayURL 配置客户联系「联系我」方式
AddContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_contact_way?access_token=%s"
// GetContactWayURL 获取企业已配置的「联系我」方式
GetContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_contact_way?access_token=%s"
// UpdateContactWayURL 更新企业已配置的「联系我」方式
UpdateContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_contact_way?access_token=%s"
// ListContactWayURL 获取企业已配置的「联系我」列表
ListContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list_contact_way?access_token=%s"
// DelContactWayURL 删除企业已配置的「联系我」方式
DelContactWayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/del_contact_way?access_token=%s"
)
type (
// ConclusionsRequest 结束语请求
ConclusionsRequest struct {
Text ConclusionsText `json:"text"`
Image ConclusionsImageRequest `json:"image"`
Link ConclusionsLink `json:"link"`
MiniProgram ConclusionsMiniProgram `json:"miniprogram"`
}
// ConclusionsText 文本格式结束语
ConclusionsText struct {
Content string `json:"content"`
}
// ConclusionsImageRequest 图片格式结束语请求
ConclusionsImageRequest struct {
MediaID string `json:"media_id"`
}
// ConclusionsLink 链接格式结束语
ConclusionsLink struct {
Title string `json:"title"`
PicURL string `json:"picurl"`
Desc string `json:"desc"`
URL string `json:"url"`
}
// ConclusionsMiniProgram 小程序格式结束语
ConclusionsMiniProgram struct {
Title string `json:"title"`
PicMediaID string `json:"pic_media_id"`
AppID string `json:"appid"`
Page string `json:"page"`
}
// ConclusionsResponse 结束语响应
ConclusionsResponse struct {
Text ConclusionsText `json:"text"`
Image ConclusionsImageResponse `json:"image"`
Link ConclusionsLink `json:"link"`
MiniProgram ConclusionsMiniProgram `json:"miniprogram"`
}
// ConclusionsImageResponse 图片格式结束语响应
ConclusionsImageResponse struct {
PicURL string `json:"pic_url"`
}
)
type (
// AddContactWayRequest 配置客户联系「联系我」方式请求
AddContactWayRequest struct {
Type int `json:"type"`
Scene int `json:"scene"`
Style int `json:"style"`
Remark string `json:"remark"`
SkipVerify bool `json:"skip_verify"`
State string `json:"state"`
User []string `json:"user"`
Party []int `json:"party"`
IsTemp bool `json:"is_temp"`
ExpiresIn int `json:"expires_in"`
ChatExpiresIn int `json:"chat_expires_in"`
UnionID string `json:"unionid"`
Conclusions ConclusionsRequest `json:"conclusions"`
}
// AddContactWayResponse 配置客户联系「联系我」方式响应
AddContactWayResponse struct {
util.CommonError
ConfigID string `json:"config_id"`
QrCode string `json:"qr_code"`
}
)
// AddContactWay 配置客户联系「联系我」方式
// see https://developer.work.weixin.qq.com/document/path/92228
func (r *Client) AddContactWay(req *AddContactWayRequest) (*AddContactWayResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(AddContactWayURL, accessToken), req); err != nil {
return nil, err
}
result := &AddContactWayResponse{}
if err = util.DecodeWithError(response, result, "AddContactWay"); err != nil {
return nil, err
}
return result, nil
}
type (
// GetContactWayRequest 获取企业已配置的「联系我」方式请求
GetContactWayRequest struct {
ConfigID string `json:"config_id"`
}
// GetContactWayResponse 获取企业已配置的「联系我」方式响应
GetContactWayResponse struct {
util.CommonError
ContactWay ContactWay `json:"contact_way"`
}
// ContactWay 「联系我」配置
ContactWay struct {
ConfigID string `json:"config_id"`
Type int `json:"type"`
Scene int `json:"scene"`
Style int `json:"style"`
Remark string `json:"remark"`
SkipVerify bool `json:"skip_verify"`
State string `json:"state"`
QrCode string `json:"qr_code"`
User []string `json:"user"`
Party []int `json:"party"`
IsTemp bool `json:"is_temp"`
ExpiresIn int `json:"expires_in"`
ChatExpiresIn int `json:"chat_expires_in"`
UnionID string `json:"unionid"`
Conclusions ConclusionsResponse `json:"conclusions"`
}
)
// GetContactWay 获取企业已配置的「联系我」方式
// see https://developer.work.weixin.qq.com/document/path/92228
func (r *Client) GetContactWay(req *GetContactWayRequest) (*GetContactWayResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(GetContactWayURL, accessToken), req); err != nil {
return nil, err
}
result := &GetContactWayResponse{}
if err = util.DecodeWithError(response, result, "GetContactWay"); err != nil {
return nil, err
}
return result, nil
}
type (
// UpdateContactWayRequest 更新企业已配置的「联系我」方式请求
UpdateContactWayRequest struct {
ConfigID string `json:"config_id"`
Remark string `json:"remark"`
SkipVerify bool `json:"skip_verify"`
Style int `json:"style"`
State string `json:"state"`
User []string `json:"user"`
Party []int `json:"party"`
ExpiresIn int `json:"expires_in"`
ChatExpiresIn int `json:"chat_expires_in"`
UnionID string `json:"unionid"`
Conclusions ConclusionsRequest `json:"conclusions"`
}
// UpdateContactWayResponse 更新企业已配置的「联系我」方式响应
UpdateContactWayResponse struct {
util.CommonError
}
)
// UpdateContactWay 更新企业已配置的「联系我」方式
// see https://developer.work.weixin.qq.com/document/path/92228
func (r *Client) UpdateContactWay(req *UpdateContactWayRequest) (*UpdateContactWayResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(UpdateContactWayURL, accessToken), req); err != nil {
return nil, err
}
result := &UpdateContactWayResponse{}
if err = util.DecodeWithError(response, result, "UpdateContactWay"); err != nil {
return nil, err
}
return result, nil
}
type (
//ListContactWayRequest 获取企业已配置的「联系我」列表请求
ListContactWayRequest struct {
StartTime int `json:"start_time"`
EndTime int `json:"end_time"`
Cursor string `json:"cursor"`
Limit int `json:"limit"`
}
//ListContactWayResponse 获取企业已配置的「联系我」列表响应
ListContactWayResponse struct {
util.CommonError
ContactWay []*ContactWayForList `json:"contact_way"`
NextCursor string `json:"next_cursor"`
}
// ContactWayForList 「联系我」配置
ContactWayForList struct {
ConfigID string `json:"config_id"`
}
)
// ListContactWay 获取企业已配置的「联系我」列表
// see https://developer.work.weixin.qq.com/document/path/92228
func (r *Client) ListContactWay(req *ListContactWayRequest) (*ListContactWayResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(ListContactWayURL, accessToken), req); err != nil {
return nil, err
}
result := &ListContactWayResponse{}
if err = util.DecodeWithError(response, result, "ListContactWay"); err != nil {
return nil, err
}
return result, nil
}
type (
// DelContactWayRequest 删除企业已配置的「联系我」方式请求
DelContactWayRequest struct {
ConfigID string `json:"config_id"`
}
// DelContactWayResponse 删除企业已配置的「联系我」方式响应
DelContactWayResponse struct {
util.CommonError
}
)
// DelContactWay 删除企业已配置的「联系我」方式
// see https://developer.work.weixin.qq.com/document/path/92228
func (r *Client) DelContactWay(req *DelContactWayRequest) (*DelContactWayResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(DelContactWayURL, accessToken), req); err != nil {
return nil, err
}
result := &DelContactWayResponse{}
if err = util.DecodeWithError(response, result, "DelContactWay"); err != nil {
return nil, err
}
return result, nil
}

View File

@@ -0,0 +1,220 @@
package externalcontact
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// FetchExternalContactUserListURL 获取客户列表
FetchExternalContactUserListURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list"
// FetchExternalContactUserDetailURL 获取客户详情
FetchExternalContactUserDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get"
// FetchBatchExternalContactUserDetailURL 批量获取客户详情
FetchBatchExternalContactUserDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user"
// UpdateUserRemarkURL 更新客户备注信息
UpdateUserRemarkURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remark"
)
// ExternalUserListResponse 外部联系人列表响应
type ExternalUserListResponse struct {
util.CommonError
ExternalUserID []string `json:"external_userid"`
}
// GetExternalUserList 获取客户列表
// @see https://developer.work.weixin.qq.com/document/path/92113
func (r *Client) GetExternalUserList(userID string) ([]string, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&userid=%v", FetchExternalContactUserListURL, accessToken, userID))
if err != nil {
return nil, err
}
var result ExternalUserListResponse
err = util.DecodeWithError(response, &result, "GetExternalUserList")
if err != nil {
return nil, err
}
return result.ExternalUserID, nil
}
// ExternalUserDetailResponse 外部联系人详情响应
type ExternalUserDetailResponse struct {
util.CommonError
ExternalContact ExternalUser `json:"external_contact"`
FollowUser []FollowUser `json:"follow_user"`
NextCursor string `json:"next_cursor"`
}
// ExternalUser 外部联系人
type ExternalUser struct {
ExternalUserID string `json:"external_userid"`
Name string `json:"name"`
Avatar string `json:"avatar"`
Type int64 `json:"type"`
Gender int64 `json:"gender"`
UnionID string `json:"unionid"`
Position string `json:"position"`
CorpName string `json:"corp_name"`
CorpFullName string `json:"corp_full_name"`
ExternalProfile string `json:"external_profile"`
}
// FollowUser 跟进用户(指企业内部用户)
type FollowUser struct {
UserID string `json:"userid"`
Remark string `json:"remark"`
Description string `json:"description"`
CreateTime string `json:"create_time"`
Tags []Tag `json:"tags"`
RemarkCorpName string `json:"remark_corp_name"`
RemarkMobiles []string `json:"remark_mobiles"`
OperUserID string `json:"oper_userid"`
AddWay int64 `json:"add_way"`
WeChatChannels WechatChannel `json:"wechat_channels"`
State string `json:"state"`
}
// Tag 已绑定在外部联系人的标签
type Tag struct {
GroupName string `json:"group_name"`
TagName string `json:"tag_name"`
Type int64 `json:"type"`
TagID string `json:"tag_id"`
}
// WechatChannel 视频号添加的场景
type WechatChannel struct {
NickName string `json:"nickname"`
Source string `json:"source"`
}
// GetExternalUserDetail 获取外部联系人详情
// @see https://developer.work.weixin.qq.com/document/path/92114
func (r *Client) GetExternalUserDetail(externalUserID string, nextCursor ...string) (*ExternalUserDetailResponse, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
var cursor string
if len(nextCursor) > 0 {
cursor = nextCursor[0]
}
response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&external_userid=%v&cursor=%v", FetchExternalContactUserDetailURL, accessToken, externalUserID, cursor))
if err != nil {
return nil, err
}
result := &ExternalUserDetailResponse{}
err = util.DecodeWithError(response, result, "get_external_user_detail")
if err != nil {
return nil, err
}
return result, nil
}
// BatchGetExternalUserDetailsRequest 批量获取外部联系人详情请求
type BatchGetExternalUserDetailsRequest struct {
UserIDList []string `json:"userid_list"`
Cursor string `json:"cursor"`
}
// ExternalUserDetailListResponse 批量获取外部联系人详情响应
type ExternalUserDetailListResponse struct {
util.CommonError
ExternalContactList []ExternalUserForBatch `json:"external_contact_list"`
}
// ExternalUserForBatch 批量获取外部联系人客户列表
type ExternalUserForBatch struct {
ExternalContact ExternalContact `json:"external_contact"`
FollowInfo FollowInfo `json:"follow_info"`
}
// ExternalContact 批量获取外部联系人用户信息
type ExternalContact struct {
ExternalUserID string `json:"external_userid"`
Name string `json:"name"`
Position string `json:"position"`
Avatar string `json:"avatar"`
CorpName string `json:"corp_name"`
CorpFullName string `json:"corp_full_name"`
Type int64 `json:"type"`
Gender int64 `json:"gender"`
UnionID string `json:"unionid"`
ExternalProfile string `json:"external_profile"`
}
// FollowInfo 批量获取外部联系人跟进人信息
type FollowInfo struct {
UserID string `json:"userid"`
Remark string `json:"remark"`
Description string `json:"description"`
CreateTime int `json:"create_time"`
TagID []string `json:"tag_id"`
RemarkCorpName string `json:"remark_corp_name"`
RemarkMobiles []string `json:"remark_mobiles"`
OperUserID string `json:"oper_userid"`
AddWay int64 `json:"add_way"`
WeChatChannels WechatChannel `json:"wechat_channels"`
}
// BatchGetExternalUserDetails 批量获取外部联系人详情
// @see https://developer.work.weixin.qq.com/document/path/92994
func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetailsRequest) ([]ExternalUserForBatch, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
jsonData, err := json.Marshal(request)
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", FetchBatchExternalContactUserDetailURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
var result ExternalUserDetailListResponse
err = util.DecodeWithError(response, &result, "BatchGetExternalUserDetails")
if err != nil {
return nil, err
}
return result.ExternalContactList, nil
}
// UpdateUserRemarkRequest 修改客户备注信息请求体
type UpdateUserRemarkRequest struct {
UserID string `json:"userid"`
ExternalUserID string `json:"external_userid"`
Remark string `json:"remark"`
Description string `json:"description"`
RemarkCompany string `json:"remark_company"`
RemarkMobiles []string `json:"remark_mobiles"`
RemarkPicMediaID string `json:"remark_pic_mediaid"`
}
// UpdateUserRemark 修改客户备注信息
// @see https://developer.work.weixin.qq.com/document/path/92115
func (r *Client) UpdateUserRemark(request UpdateUserRemarkRequest) error {
accessToken, err := r.GetAccessToken()
if err != nil {
return err
}
var response []byte
jsonData, err := json.Marshal(request)
if err != nil {
return err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", UpdateUserRemarkURL, accessToken), string(jsonData))
if err != nil {
return err
}
return util.DecodeWithCommonError(response, "UpdateUserRemark")
}

View File

@@ -0,0 +1,38 @@
package externalcontact
import (
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// FetchFollowUserListURL 获取配置了客户联系功能的成员列表
FetchFollowUserListURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_follow_user_list"
)
// followerUserResponse 客户联系功能的成员列表响应
type followerUserResponse struct {
util.CommonError
FollowUser []string `json:"follow_user"`
}
// GetFollowUserList 获取配置了客户联系功能的成员列表
// @see https://developer.work.weixin.qq.com/document/path/92571
func (r *Client) GetFollowUserList() ([]string, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%s", FetchFollowUserListURL, accessToken))
if err != nil {
return nil, err
}
var result followerUserResponse
err = util.DecodeWithError(response, &result, "GetFollowUserList")
if err != nil {
return nil, err
}
return result.FollowUser, nil
}

View File

@@ -0,0 +1,143 @@
package externalcontact
import (
"fmt"
"github.com/silenceper/wechat/v2/util"
)
// OpengIDToChatIDURL 客户群opengid转换URL
const OpengIDToChatIDURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/opengid_to_chatid"
type (
//GroupChatListRequest 获取客户群列表的请求参数
GroupChatListRequest struct {
StatusFilter int `json:"status_filter"` // 非必填 客户群跟进状态过滤。0 - 所有列表(即不过滤) 1 - 离职待继承 2 - 离职继承中 3 - 离职继承完成
OwnerFilter OwnerFilter `json:"owner_filter"` //非必填 群主过滤。如果不填表示获取应用可见范围内全部群主的数据但是不建议这么用如果可见范围人数超过1000人为了防止数据包过大会报错 81017
Cursor string `json:"cursor"` //非必填 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用不填
Limit int `json:"limit"` //必填 分页,预期请求的数据量,取值范围 1 ~ 1000
}
//GroupChatList 客户群列表
GroupChatList struct {
ChatID string `json:"chat_id"`
Status int `json:"status"`
}
//GroupChatListResponse 获取客户群列表的返回值
GroupChatListResponse struct {
util.CommonError
GroupChatList []GroupChatList `json:"group_chat_list"`
NextCursor string `json:"next_cursor"` //游标
}
)
// GetGroupChatList 获取客户群列表
// @see https://developer.work.weixin.qq.com/document/path/92120
func (r *Client) GetGroupChatList(req *GroupChatListRequest) (*GroupChatListResponse, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
response, err = util.PostJSON(fmt.Sprintf("%s/list?access_token=%s", GroupChatURL, accessToken), req)
if err != nil {
return nil, err
}
result := &GroupChatListResponse{}
if err = util.DecodeWithError(response, result, "GetGroupChatList"); err != nil {
return nil, err
}
return result, nil
}
type (
//GroupChatDetailRequest 客户群详情 请求参数
GroupChatDetailRequest struct {
ChatID string `json:"chat_id"`
NeedName int `json:"need_name"`
}
//Invitor 邀请者
Invitor struct {
UserID string `json:"userid"` //邀请者的userid
}
//GroupChatMember 群成员
GroupChatMember struct {
UserID string `json:"userid"` //群成员id
Type int `json:"type"` //成员类型。 1 - 企业成员 2 - 外部联系人
JoinTime int `json:"join_time"` //入群时间
JoinScene int `json:"join_scene"` //入群方式 1 - 由群成员邀请入群(直接邀请入群) 2 - 由群成员邀请入群(通过邀请链接入群) 3 - 通过扫描群二维码入群
Invitor Invitor `json:"invitor,omitempty"` //邀请者。目前仅当是由本企业内部成员邀请入群时会返回该值
GroupNickname string `json:"group_nickname"` //在群里的昵称
Name string `json:"name"` //名字。仅当 need_name = 1 时返回 如果是微信用户,则返回其在微信中设置的名字 如果是企业微信联系人,则返回其设置对外展示的别名或实名
UnionID string `json:"unionid,omitempty"` //外部联系人在微信开放平台的唯一身份标识微信unionid通过此字段企业可将外部联系人与公众号/小程序用户关联起来。仅当群成员类型是微信用户包括企业成员未添加好友且企业绑定了微信开发者ID有此字段查看绑定方法。第三方不可获取上游企业不可获取下游企业客户的unionid字段
}
//GroupChatAdmin 群管理员
GroupChatAdmin struct {
UserID string `json:"userid"` //群管理员userid
}
//GroupChat 客户群详情
GroupChat struct {
ChatID string `json:"chat_id"` //客户群ID
Name string `json:"name"` //群名
Owner string `json:"owner"` //群主ID
CreateTime int `json:"create_time"` //群的创建时间
Notice string `json:"notice"` //群公告
MemberList []GroupChatMember `json:"member_list"` //群成员列表
AdminList []GroupChatAdmin `json:"admin_list"` //群管理员列表
}
//GroupChatDetailResponse 客户群详情 返回值
GroupChatDetailResponse struct {
util.CommonError
GroupChat GroupChat `json:"group_chat"` //客户群详情
}
)
// GetGroupChatDetail 获取客户群详情
// @see https://developer.work.weixin.qq.com/document/path/92122
func (r *Client) GetGroupChatDetail(req *GroupChatDetailRequest) (*GroupChatDetailResponse, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
response, err = util.PostJSON(fmt.Sprintf("%s/get?access_token=%s", GroupChatURL, accessToken), req)
if err != nil {
return nil, err
}
result := &GroupChatDetailResponse{}
if err = util.DecodeWithError(response, result, "GetGroupChatDetail"); err != nil {
return nil, err
}
return result, nil
}
type (
//OpengIDToChatIDRequest 客户群opengid转换 请求参数
OpengIDToChatIDRequest struct {
OpengID string `json:"opengid"`
}
//OpengIDToChatIDResponse 客户群opengid转换 返回值
OpengIDToChatIDResponse struct {
util.CommonError
ChatID string `json:"chat_id"` //客户群ID
}
)
// OpengIDToChatID 客户群opengid转换
// @see https://developer.work.weixin.qq.com/document/path/94828
func (r *Client) OpengIDToChatID(req *OpengIDToChatIDRequest) (*OpengIDToChatIDResponse, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
response, err = util.PostJSON(fmt.Sprintf("%s?access_token=%s", OpengIDToChatIDURL, accessToken), req)
if err != nil {
return nil, err
}
result := &OpengIDToChatIDResponse{}
if err = util.DecodeWithError(response, result, "GetGroupChatDetail"); err != nil {
return nil, err
}
return result, nil
}

View File

@@ -0,0 +1,146 @@
package externalcontact
import (
"fmt"
"github.com/silenceper/wechat/v2/util"
)
// GroupChatURL 客户群
const GroupChatURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat"
type (
// AddJoinWayRequest 添加群配置请求参数
AddJoinWayRequest struct {
Scene int `json:"scene"` // 必填 1 - 群的小程序插件,2 - 群的二维码插件
Remark string `json:"remark"` //非必填 联系方式的备注信息用于助记超过30个字符将被截断
AutoCreateRoom int `json:"auto_create_room"` //非必填 当群满了后是否自动新建群。0-否1-是。 默认为1
RoomBaseName string `json:"room_base_name"` //非必填 自动建群的群名前缀当auto_create_room为1时有效。最长40个utf8字符
RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号当auto_create_room为1时有效
ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表支持5个。见客户群ID获取方法
State string `json:"state"` //非必填 企业自定义的state参数用于区分不同的入群渠道。不超过30个UTF-8字符
}
// AddJoinWayResponse 添加群配置返回值
AddJoinWayResponse struct {
util.CommonError
ConfigID string `json:"config_id"`
}
)
// AddJoinWay 加入群聊
// @see https://developer.work.weixin.qq.com/document/path/92229
func (r *Client) AddJoinWay(req *AddJoinWayRequest) (*AddJoinWayResponse, error) {
var (
accessToken string
err error
response []byte
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
response, err = util.PostJSON(fmt.Sprintf("%s/add_join_way?access_token=%s", GroupChatURL, accessToken), req)
if err != nil {
return nil, err
}
result := &AddJoinWayResponse{}
if err = util.DecodeWithError(response, result, "AddJoinWay"); err != nil {
return nil, err
}
return result, nil
}
type (
//JoinWayConfigRequest 获取或删除群配置的请求参数
JoinWayConfigRequest struct {
ConfigID string `json:"config_id"`
}
//JoinWay 群配置
JoinWay struct {
ConfigID string `json:"config_id"`
Scene int `json:"scene"`
Remark string `json:"remark"`
AutoCreateRoom int `json:"auto_create_room"`
RoomBaseName string `json:"room_base_name"`
RoomBaseID int `json:"room_base_id"`
ChatIDList []string `json:"chat_id_list"`
QrCode string `json:"qr_code"`
State string `json:"state"`
}
//GetJoinWayResponse 获取群配置的返回值
GetJoinWayResponse struct {
util.CommonError
JoinWay JoinWay `json:"join_way"`
}
)
// GetJoinWay 获取客户群进群方式配置
// @see https://developer.work.weixin.qq.com/document/path/92229
func (r *Client) GetJoinWay(req *JoinWayConfigRequest) (*GetJoinWayResponse, error) {
var (
accessToken string
err error
response []byte
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
response, err = util.PostJSON(fmt.Sprintf("%s/get_join_way?access_token=%s", GroupChatURL, accessToken), req)
if err != nil {
return nil, err
}
result := &GetJoinWayResponse{}
if err = util.DecodeWithError(response, result, "GetJoinWay"); err != nil {
return nil, err
}
return result, nil
}
// UpdateJoinWayRequest 更新群配置的请求参数
type UpdateJoinWayRequest struct {
ConfigID string `json:"config_id"`
Scene int `json:"scene"` // 必填 1 - 群的小程序插件,2 - 群的二维码插件
Remark string `json:"remark"` //非必填 联系方式的备注信息用于助记超过30个字符将被截断
AutoCreateRoom int `json:"auto_create_room"` //非必填 当群满了后是否自动新建群。0-否1-是。 默认为1
RoomBaseName string `json:"room_base_name"` //非必填 自动建群的群名前缀当auto_create_room为1时有效。最长40个utf8字符
RoomBaseID int `json:"room_base_id"` //非必填 自动建群的群起始序号当auto_create_room为1时有效
ChatIDList []string `json:"chat_id_list"` //必填 使用该配置的客户群ID列表支持5个。见客户群ID获取方法
State string `json:"state"` //非必填 企业自定义的state参数用于区分不同的入群渠道。不超过30个UTF-8字符
}
// UpdateJoinWay 更新客户群进群方式配置
// @see https://developer.work.weixin.qq.com/document/path/92229
func (r *Client) UpdateJoinWay(req *UpdateJoinWayRequest) error {
var (
accessToken string
err error
response []byte
)
if accessToken, err = r.GetAccessToken(); err != nil {
return err
}
response, err = util.PostJSON(fmt.Sprintf("%s/update_join_way?access_token=%s", GroupChatURL, accessToken), req)
if err != nil {
return err
}
return util.DecodeWithCommonError(response, "UpdateJoinWay")
}
// DelJoinWay 删除客户群进群方式配置
// @see https://developer.work.weixin.qq.com/document/path/92229
func (r *Client) DelJoinWay(req *JoinWayConfigRequest) error {
var (
accessToken string
err error
response []byte
)
if accessToken, err = r.GetAccessToken(); err != nil {
return err
}
response, err = util.PostJSON(fmt.Sprintf("%s/del_join_way?access_token=%s", GroupChatURL, accessToken), req)
if err != nil {
return err
}
return util.DecodeWithCommonError(response, "DelJoinWay")
}

424
work/externalcontact/msg.go Normal file
View File

@@ -0,0 +1,424 @@
package externalcontact
import (
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// AddMsgTemplateURL 创建企业群发
AddMsgTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_msg_template?access_token=%s"
// GetGroupMsgListV2URL 获取群发记录列表
GetGroupMsgListV2URL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_list_v2?access_token=%s"
// GetGroupMsgTaskURL 获取群发成员发送任务列表
GetGroupMsgTaskURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_task?access_token=%s"
// GetGroupMsgSendResultURL 获取企业群发成员执行结果
GetGroupMsgSendResultURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_send_result?access_token=%s"
// SendWelcomeMsgURL 发送新客户欢迎语
SendWelcomeMsgURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/send_welcome_msg?access_token=%s"
// AddGroupWelcomeTemplateURL 添加入群欢迎语素材
AddGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/add?access_token=%s"
// EditGroupWelcomeTemplateURL 编辑入群欢迎语素材
EditGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/edit?access_token=%s"
// GetGroupWelcomeTemplateURL 获取入群欢迎语素材
GetGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/get?access_token=%s"
// DelGroupWelcomeTemplateURL 删除入群欢迎语素材
DelGroupWelcomeTemplateURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/del?access_token=%s"
)
// AddMsgTemplateRequest 创建企业群发请求
type AddMsgTemplateRequest struct {
ChatType string `json:"chat_type"`
ExternalUserID []string `json:"external_userid"`
Sender string `json:"sender,omitempty"`
Text MsgText `json:"text"`
Attachments []*Attachment `json:"attachments"`
}
// MsgText 文本消息
type MsgText struct {
Content string `json:"content"`
}
type (
// Attachment 附件
Attachment struct {
MsgType string `json:"msgtype"`
Image AttachmentImg `json:"image,omitempty"`
Link AttachmentLink `json:"link,omitempty"`
MiniProgram AttachmentMiniProgram `json:"miniprogram,omitempty"`
Video AttachmentVideo `json:"video,omitempty"`
File AttachmentFile `json:"file,omitempty"`
}
// AttachmentImg 图片消息
AttachmentImg struct {
MediaID string `json:"media_id"`
PicURL string `json:"pic_url"`
}
// AttachmentLink 图文消息
AttachmentLink struct {
Title string `json:"title"`
PicURL string `json:"picurl"`
Desc string `json:"desc"`
URL string `json:"url"`
}
// AttachmentMiniProgram 小程序消息
AttachmentMiniProgram struct {
Title string `json:"title"`
PicMediaID string `json:"pic_media_id"`
AppID string `json:"appid"`
Page string `json:"page"`
}
// AttachmentVideo 视频消息
AttachmentVideo struct {
MediaID string `json:"media_id"`
}
// AttachmentFile 文件消息
AttachmentFile struct {
MediaID string `json:"media_id"`
}
)
// AddMsgTemplateResponse 创建企业群发响应
type AddMsgTemplateResponse struct {
util.CommonError
FailList []string `json:"fail_list"`
MsgID string `json:"msgid"`
}
// AddMsgTemplate 创建企业群发
// see https://developer.work.weixin.qq.com/document/path/92135
func (r *Client) AddMsgTemplate(req *AddMsgTemplateRequest) (*AddMsgTemplateResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(AddMsgTemplateURL, accessToken), req); err != nil {
return nil, err
}
result := &AddMsgTemplateResponse{}
if err = util.DecodeWithError(response, result, "AddMsgTemplate"); err != nil {
return nil, err
}
return result, nil
}
// GetGroupMsgListV2Request 获取群发记录列表请求
type GetGroupMsgListV2Request struct {
ChatType string `json:"chat_type"`
StartTime int `json:"start_time"`
EndTime int `json:"end_time"`
Creator string `json:"creator,omitempty"`
FilterType int `json:"filter_type"`
Limit int `json:"limit"`
Cursor string `json:"cursor"`
}
// GetGroupMsgListV2Response 获取群发记录列表响应
type GetGroupMsgListV2Response struct {
util.CommonError
NextCursor string `json:"next_cursor"`
GroupMsgList []*GroupMsg `json:"group_msg_list"`
}
// GroupMsg 群发消息
type GroupMsg struct {
MsgID string `json:"msgid"`
Creator string `json:"creator"`
CreateTime int `json:"create_time"`
CreateType int `json:"create_type"`
Text MsgText `json:"text"`
Attachments []*Attachment `json:"attachments"`
}
// GetGroupMsgListV2 获取群发记录列表
// see https://developer.work.weixin.qq.com/document/path/93338#%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%8F%91%E8%AE%B0%E5%BD%95%E5%88%97%E8%A1%A8
func (r *Client) GetGroupMsgListV2(req *GetGroupMsgListV2Request) (*GetGroupMsgListV2Response, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(GetGroupMsgListV2URL, accessToken), req); err != nil {
return nil, err
}
result := &GetGroupMsgListV2Response{}
if err = util.DecodeWithError(response, result, "GetGroupMsgListV2"); err != nil {
return nil, err
}
return result, nil
}
// GetGroupMsgTaskRequest 获取群发成员发送任务列表请求
type GetGroupMsgTaskRequest struct {
MsgID string `json:"msgid"`
Limit int `json:"limit"`
Cursor string `json:"cursor"`
}
// GetGroupMsgTaskResponse 获取群发成员发送任务列表响应
type GetGroupMsgTaskResponse struct {
util.CommonError
NextCursor string `json:"next_cursor"`
TaskList []*Task `json:"task_list"`
}
// Task 获取群发成员发送任务列表任务
type Task struct {
UserID string `json:"userid"`
Status int `json:"status"`
SendTime int `json:"send_time"`
}
// GetGroupMsgTask 获取群发成员发送任务列表
// see https://developer.work.weixin.qq.com/document/path/93338#%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%8F%91%E6%88%90%E5%91%98%E5%8F%91%E9%80%81%E4%BB%BB%E5%8A%A1%E5%88%97%E8%A1%A8
func (r *Client) GetGroupMsgTask(req *GetGroupMsgTaskRequest) (*GetGroupMsgTaskResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(GetGroupMsgTaskURL, accessToken), req); err != nil {
return nil, err
}
result := &GetGroupMsgTaskResponse{}
if err = util.DecodeWithError(response, result, "GetGroupMsgTask"); err != nil {
return nil, err
}
return result, nil
}
// GetGroupMsgSendResultRequest 获取企业群发成员执行结果请求
type GetGroupMsgSendResultRequest struct {
MsgID string `json:"msgid"`
UserID string `json:"userid"`
Limit int `json:"limit"`
Cursor string `json:"cursor"`
}
// GetGroupMsgSendResultResponse 获取企业群发成员执行结果响应
type GetGroupMsgSendResultResponse struct {
util.CommonError
NextCursor string `json:"next_cursor"`
SendList []*Send `json:"send_list"`
}
// Send 企业群发成员执行结果
type Send struct {
ExternalUserID string `json:"external_userid"`
ChatID string `json:"chat_id"`
UserID string `json:"userid"`
Status int `json:"status"`
SendTime int `json:"send_time"`
}
// GetGroupMsgSendResult 获取企业群发成员执行结果
// see https://developer.work.weixin.qq.com/document/path/93338#%E8%8E%B7%E5%8F%96%E4%BC%81%E4%B8%9A%E7%BE%A4%E5%8F%91%E6%88%90%E5%91%98%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C
func (r *Client) GetGroupMsgSendResult(req *GetGroupMsgSendResultRequest) (*GetGroupMsgSendResultResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(GetGroupMsgSendResultURL, accessToken), req); err != nil {
return nil, err
}
result := &GetGroupMsgSendResultResponse{}
if err = util.DecodeWithError(response, result, "GetGroupMsgSendResult"); err != nil {
return nil, err
}
return result, nil
}
// SendWelcomeMsgRequest 发送新客户欢迎语请求
type SendWelcomeMsgRequest struct {
WelcomeCode string `json:"welcome_code"`
Text MsgText `json:"text"`
Attachments []*Attachment `json:"attachments"`
}
// SendWelcomeMsgResponse 发送新客户欢迎语响应
type SendWelcomeMsgResponse struct {
util.CommonError
}
// SendWelcomeMsg 发送新客户欢迎语
// see https://developer.work.weixin.qq.com/document/path/92137
func (r *Client) SendWelcomeMsg(req *SendWelcomeMsgRequest) error {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(SendWelcomeMsgURL, accessToken), req); err != nil {
return err
}
result := &SendWelcomeMsgResponse{}
if err = util.DecodeWithError(response, result, "SendWelcomeMsg"); err != nil {
return err
}
return nil
}
// AddGroupWelcomeTemplateRequest 添加入群欢迎语素材请求
type AddGroupWelcomeTemplateRequest struct {
Text MsgText `json:"text"`
Image AttachmentImg `json:"image"`
Link AttachmentLink `json:"link"`
MiniProgram AttachmentMiniProgram `json:"miniprogram"`
File AttachmentFile `json:"file"`
Video AttachmentVideo `json:"video"`
AgentID int `json:"agentid"`
Notify int `json:"notify"`
}
// AddGroupWelcomeTemplateResponse 添加入群欢迎语素材响应
type AddGroupWelcomeTemplateResponse struct {
util.CommonError
TemplateID string `json:"template_id"`
}
// AddGroupWelcomeTemplate 添加入群欢迎语素材
// see https://developer.work.weixin.qq.com/document/path/92366#%E6%B7%BB%E5%8A%A0%E5%85%A5%E7%BE%A4%E6%AC%A2%E8%BF%8E%E8%AF%AD%E7%B4%A0%E6%9D%90
func (r *Client) AddGroupWelcomeTemplate(req *AddGroupWelcomeTemplateRequest) (*AddGroupWelcomeTemplateResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(AddGroupWelcomeTemplateURL, accessToken), req); err != nil {
return nil, err
}
result := &AddGroupWelcomeTemplateResponse{}
if err = util.DecodeWithError(response, result, "AddGroupWelcomeTemplate"); err != nil {
return nil, err
}
return result, nil
}
// EditGroupWelcomeTemplateRequest 编辑入群欢迎语素材请求
type EditGroupWelcomeTemplateRequest struct {
TemplateID string `json:"template_id"`
Text MsgText `json:"text"`
Image AttachmentImg `json:"image"`
Link AttachmentLink `json:"link"`
MiniProgram AttachmentMiniProgram `json:"miniprogram"`
File AttachmentFile `json:"file"`
Video AttachmentVideo `json:"video"`
AgentID int `json:"agentid"`
}
// EditGroupWelcomeTemplateResponse 编辑入群欢迎语素材响应
type EditGroupWelcomeTemplateResponse struct {
util.CommonError
}
// EditGroupWelcomeTemplate 编辑入群欢迎语素材
// see https://developer.work.weixin.qq.com/document/path/92366#%E7%BC%96%E8%BE%91%E5%85%A5%E7%BE%A4%E6%AC%A2%E8%BF%8E%E8%AF%AD%E7%B4%A0%E6%9D%90
func (r *Client) EditGroupWelcomeTemplate(req *EditGroupWelcomeTemplateRequest) error {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(EditGroupWelcomeTemplateURL, accessToken), req); err != nil {
return err
}
result := &EditGroupWelcomeTemplateResponse{}
if err = util.DecodeWithError(response, result, "EditGroupWelcomeTemplate"); err != nil {
return err
}
return nil
}
// GetGroupWelcomeTemplateRequest 获取入群欢迎语素材请求
type GetGroupWelcomeTemplateRequest struct {
TemplateID string `json:"template_id"`
}
// GetGroupWelcomeTemplateResponse 获取入群欢迎语素材响应
type GetGroupWelcomeTemplateResponse struct {
util.CommonError
Text MsgText `json:"text"`
Image AttachmentImg `json:"image"`
Link AttachmentLink `json:"link"`
MiniProgram AttachmentMiniProgram `json:"miniprogram"`
File AttachmentFile `json:"file"`
Video AttachmentVideo `json:"video"`
}
// GetGroupWelcomeTemplate 获取入群欢迎语素材
// see https://developer.work.weixin.qq.com/document/path/92366#%E8%8E%B7%E5%8F%96%E5%85%A5%E7%BE%A4%E6%AC%A2%E8%BF%8E%E8%AF%AD%E7%B4%A0%E6%9D%90
func (r *Client) GetGroupWelcomeTemplate(req *GetGroupWelcomeTemplateRequest) (*GetGroupWelcomeTemplateResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(GetGroupWelcomeTemplateURL, accessToken), req); err != nil {
return nil, err
}
result := &GetGroupWelcomeTemplateResponse{}
if err = util.DecodeWithError(response, result, "GetGroupWelcomeTemplate"); err != nil {
return nil, err
}
return result, nil
}
// DelGroupWelcomeTemplateRequest 删除入群欢迎语素材请求
type DelGroupWelcomeTemplateRequest struct {
TemplateID string `json:"template_id"`
AgentID int `json:"agentid"`
}
// DelGroupWelcomeTemplateResponse 删除入群欢迎语素材响应
type DelGroupWelcomeTemplateResponse struct {
util.CommonError
}
// DelGroupWelcomeTemplate 删除入群欢迎语素材
// see https://developer.work.weixin.qq.com/document/path/92366#%E5%88%A0%E9%99%A4%E5%85%A5%E7%BE%A4%E6%AC%A2%E8%BF%8E%E8%AF%AD%E7%B4%A0%E6%9D%90
func (r *Client) DelGroupWelcomeTemplate(req *DelGroupWelcomeTemplateRequest) error {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(DelGroupWelcomeTemplateURL, accessToken), req); err != nil {
return err
}
result := &DelGroupWelcomeTemplateResponse{}
if err = util.DecodeWithError(response, result, "DelGroupWelcomeTemplate"); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,176 @@
package externalcontact
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// GetUserBehaviorDataURL 获取「联系客户统计」数据
GetUserBehaviorDataURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_user_behavior_data"
// GetGroupChatStatURL 获取「群聊数据统计」数据 按群主聚合的方式
GetGroupChatStatURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/statistic"
// GetGroupChatStatByDayURL 获取「群聊数据统计」数据 按自然日聚合的方式
GetGroupChatStatByDayURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/statistic_group_by_day"
)
type (
// GetUserBehaviorRequest 获取「联系客户统计」数据请求
GetUserBehaviorRequest struct {
UserID []string `json:"userid"`
PartyID []int `json:"partyid"`
StartTime int `json:"start_time"`
EndTime int `json:"end_time"`
}
// GetUserBehaviorResponse 获取「联系客户统计」数据响应
GetUserBehaviorResponse struct {
util.CommonError
BehaviorData []BehaviorData `json:"behavior_data"`
}
// BehaviorData 联系客户统计数据
BehaviorData struct {
StatTime int `json:"stat_time"`
ChatCnt int `json:"chat_cnt"`
MessageCnt int `json:"message_cnt"`
ReplyPercentage float64 `json:"reply_percentage"`
AvgReplyTime int `json:"avg_reply_time"`
NegativeFeedbackCnt int `json:"negative_feedback_cnt"`
NewApplyCnt int `json:"new_apply_cnt"`
NewContactCnt int `json:"new_contact_cnt"`
}
)
// GetUserBehaviorData 获取「联系客户统计」数据
// @see https://developer.work.weixin.qq.com/document/path/92132
func (r *Client) GetUserBehaviorData(req *GetUserBehaviorRequest) ([]BehaviorData, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
jsonData, err := json.Marshal(req)
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetUserBehaviorDataURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
var result GetUserBehaviorResponse
err = util.DecodeWithError(response, &result, "GetUserBehaviorData")
if err != nil {
return nil, err
}
return result.BehaviorData, nil
}
type (
// GetGroupChatStatRequest 获取「群聊数据统计」数据 按群主聚合的方式 请求
GetGroupChatStatRequest struct {
DayBeginTime int `json:"day_begin_time"`
DayEndTime int `json:"day_end_time"`
OwnerFilter OwnerFilter `json:"owner_filter"`
OrderBy int `json:"order_by"`
OrderAsc int `json:"order_asc"`
Offset int `json:"offset"`
Limit int `json:"limit"`
}
// GetGroupChatStatResponse 获取「群聊数据统计」数据 按群主聚合的方式 响应
GetGroupChatStatResponse struct {
util.CommonError
Total int `json:"total"`
NextOffset int `json:"next_offset"`
Items []GroupChatStatItem `json:"items"`
}
// GroupChatStatItem 群聊数据统计(按群主聚合)条目
GroupChatStatItem struct {
Owner string `json:"owner"`
Data GroupChatStatItemData `json:"data"`
}
)
// OwnerFilter 群主过滤
type OwnerFilter struct {
UseridList []string `json:"userid_list"`
}
// GroupChatStatItemData 群聊数据统计条目数据
type GroupChatStatItemData struct {
NewChatCnt int `json:"new_chat_cnt"`
ChatTotal int `json:"chat_total"`
ChatHasMsg int `json:"chat_has_msg"`
NewMemberCnt int `json:"new_member_cnt"`
MemberTotal int `json:"member_total"`
MemberHasMsg int `json:"member_has_msg"`
MsgTotal int `json:"msg_total"`
MigrateTraineeChatCnt int `json:"migrate_trainee_chat_cnt"`
}
// GetGroupChatStat 获取「群聊数据统计」数据 按群主聚合的方式
// @see https://developer.work.weixin.qq.com/document/path/92133
func (r *Client) GetGroupChatStat(req *GetGroupChatStatRequest) (*GetGroupChatStatResponse, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
jsonData, err := json.Marshal(req)
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetGroupChatStatURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
result := &GetGroupChatStatResponse{}
err = util.DecodeWithError(response, result, "GetGroupChatStat")
if err != nil {
return nil, err
}
return result, nil
}
type (
// GetGroupChatStatByDayRequest 获取「群聊数据统计」数据 按自然日聚合的方式 请求
GetGroupChatStatByDayRequest struct {
DayBeginTime int `json:"day_begin_time"`
DayEndTime int `json:"day_end_time"`
OwnerFilter OwnerFilter `json:"owner_filter"`
}
// GetGroupChatStatByDayResponse 获取「群聊数据统计」数据 按自然日聚合的方式 响应
GetGroupChatStatByDayResponse struct {
util.CommonError
Items []GetGroupChatStatByDayItem `json:"items"`
}
// GetGroupChatStatByDayItem 群聊数据统计(按自然日聚合)条目
GetGroupChatStatByDayItem struct {
StatTime int `json:"stat_time"`
Data GroupChatStatItemData `json:"data"`
}
)
// GetGroupChatStatByDay 获取「群聊数据统计」数据 按自然日聚合的方式
// @see https://developer.work.weixin.qq.com/document/path/92133
func (r *Client) GetGroupChatStatByDay(req *GetGroupChatStatByDayRequest) ([]GetGroupChatStatByDayItem, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
jsonData, err := json.Marshal(req)
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetGroupChatStatByDayURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
var result GetGroupChatStatByDayResponse
err = util.DecodeWithError(response, &result, "GetGroupChatStatByDay")
if err != nil {
return nil, err
}
return result.Items, nil
}

203
work/externalcontact/tag.go Normal file
View File

@@ -0,0 +1,203 @@
package externalcontact
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// GetCropTagURL 获取标签列表
GetCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_corp_tag_list"
// AddCropTagURL 添加标签
AddCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_corp_tag"
// EditCropTagURL 修改标签
EditCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/edit_corp_tag"
// DelCropTagURL 删除标签
DelCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/del_corp_tag"
// MarkCropTagURL 为客户打上、删除标签
MarkCropTagURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/mark_tag"
)
// GetCropTagRequest 获取企业标签请求
type GetCropTagRequest struct {
TagID []string `json:"tag_id"`
GroupID []string `json:"group_id"`
}
// GetCropTagListResponse 获取企业标签列表响应
type GetCropTagListResponse struct {
util.CommonError
TagGroup []TagGroup `json:"tag_group"`
}
// TagGroup 企业标签组
type TagGroup struct {
GroupID string `json:"group_id"`
GroupName string `json:"group_name"`
CreateTime int `json:"create_time"`
GroupOrder int `json:"group_order"`
Deleted bool `json:"deleted"`
Tag []TagGroupTagItem `json:"tag"`
}
// TagGroupTagItem 企业标签内的子项
type TagGroupTagItem struct {
ID string `json:"id"`
Name string `json:"name"`
CreateTime int `json:"create_time"`
Order int `json:"order"`
Deleted bool `json:"deleted"`
}
// GetCropTagList 获取企业标签库
// @see https://developer.work.weixin.qq.com/document/path/92117
func (r *Client) GetCropTagList(req GetCropTagRequest) ([]TagGroup, error) {
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
jsonData, err := json.Marshal(req)
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetCropTagURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
var result GetCropTagListResponse
err = util.DecodeWithError(response, &result, "GetCropTagList")
if err != nil {
return nil, err
}
return result.TagGroup, nil
}
// AddCropTagRequest 添加企业标签请求
type AddCropTagRequest struct {
GroupID string `json:"group_id,omitempty"`
GroupName string `json:"group_name"`
Order int `json:"order"`
Tag []AddCropTagItem `json:"tag"`
AgentID int `json:"agentid"`
}
// AddCropTagItem 添加企业标签子项
type AddCropTagItem struct {
Name string `json:"name"`
Order int `json:"order"`
}
// AddCropTagResponse 添加企业标签响应
type AddCropTagResponse struct {
util.CommonError
TagGroup TagGroup `json:"tag_group"`
}
// AddCropTag 添加企业客户标签
// @see https://developer.work.weixin.qq.com/document/path/92117
func (r *Client) AddCropTag(req AddCropTagRequest) (*TagGroup, error) {
var accessToken string
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
var response []byte
jsonData, err := json.Marshal(req)
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", AddCropTagURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
var result AddCropTagResponse
err = util.DecodeWithError(response, &result, "AddCropTag")
if err != nil {
return nil, err
}
return &result.TagGroup, nil
}
// EditCropTagRequest 编辑客户企业标签请求
type EditCropTagRequest struct {
ID string `json:"id"`
Name string `json:"name"`
Order int `json:"order"`
AgentID string `json:"agent_id"`
}
// EditCropTag 修改企业客户标签
// @see https://developer.work.weixin.qq.com/document/path/92117
func (r *Client) EditCropTag(req EditCropTagRequest) error {
accessToken, err := r.GetAccessToken()
if err != nil {
return err
}
var response []byte
jsonData, err := json.Marshal(req)
if err != nil {
return err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", EditCropTagURL, accessToken), string(jsonData))
if err != nil {
return err
}
return util.DecodeWithCommonError(response, "EditCropTag")
}
// DeleteCropTagRequest 删除企业标签请求
type DeleteCropTagRequest struct {
TagID []string `json:"tag_id"`
GroupID []string `json:"group_id"`
AgentID string `json:"agent_id"`
}
// DeleteCropTag 删除企业客户标签
// @see https://developer.work.weixin.qq.com/document/path/92117
func (r *Client) DeleteCropTag(req DeleteCropTagRequest) error {
accessToken, err := r.GetAccessToken()
if err != nil {
return err
}
var response []byte
jsonData, err := json.Marshal(req)
if err != nil {
return err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", DelCropTagURL, accessToken), string(jsonData))
if err != nil {
return err
}
return util.DecodeWithCommonError(response, "DeleteCropTag")
}
// MarkTagRequest 给客户打标签请求
// 相关文档地址https://developer.work.weixin.qq.com/document/path/92118
type MarkTagRequest struct {
UserID string `json:"userid"`
ExternalUserID string `json:"external_userid"`
AddTag []string `json:"add_tag"`
RemoveTag []string `json:"remove_tag"`
}
// MarkTag 为客户打上标签
// @see https://developer.work.weixin.qq.com/document/path/92118
func (r *Client) MarkTag(request MarkTagRequest) error {
accessToken, err := r.GetAccessToken()
if err != nil {
return err
}
var response []byte
jsonData, err := json.Marshal(request)
if err != nil {
return err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", MarkCropTagURL, accessToken), string(jsonData))
if err != nil {
return err
}
return util.DecodeWithCommonError(response, "MarkTag")
}

View File

@@ -8,7 +8,7 @@ import (
)
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"
@@ -16,7 +16,7 @@ const (
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"
)
@@ -38,12 +38,10 @@ func (r *Client) AccountAdd(options AccountAddOptions) (info AccountAddSchema, e
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(accountAddAddr, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(accountAddAddr, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {
@@ -66,12 +64,10 @@ func (r *Client) AccountDel(options AccountDelOptions) (info util.CommonError, e
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(accountDelAddr, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(accountDelAddr, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {
@@ -96,12 +92,10 @@ func (r *Client) AccountUpdate(options AccountUpdateOptions) (info util.CommonEr
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(accountUpdateAddr, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(accountUpdateAddr, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {
@@ -132,12 +126,10 @@ func (r *Client) AccountList() (info AccountListSchema, err error) {
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.HTTPGet(fmt.Sprintf(accountListAddr, accessToken))
if err != nil {
if data, err = util.HTTPGet(fmt.Sprintf(accountListAddr, accessToken)); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {
@@ -171,12 +163,10 @@ func (r *Client) AddContactWay(options AddContactWayOptions) (info AddContactWay
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(addContactWayAddr, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(addContactWayAddr, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {

View File

@@ -15,21 +15,22 @@ type SignatureOptions struct {
}
// 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, "非法请求来源")
// }
// })
//
// //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)
@@ -59,27 +60,28 @@ type CallbackMessage struct {
}
// 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")
// })
//
// //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 {

View File

@@ -23,7 +23,7 @@ func NewClient(cfg *config.Config) (client *Client, err error) {
return nil, NewSDKErr(50001)
}
//初始化 AccessToken Handle
// 初始化 AccessToken Handle
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
ctx := &context.Context{
Config: cfg,

View File

@@ -38,12 +38,10 @@ func (r *Client) CustomerBatchGet(options CustomerBatchGetOptions) (info Custome
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(customerBatchGetAddr, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(customerBatchGetAddr, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {

View File

@@ -17,6 +17,8 @@ const (
SDKUnknownError Error = "未知错误"
// SDKInvalidCredential 错误码40001
SDKInvalidCredential Error = "不合法的secret参数"
// SDKInvalidImageSize 错误码40009
SDKInvalidImageSize Error = "无效的图片大小"
// SDKInvalidCorpID 错误码40013
SDKInvalidCorpID Error = "无效的 CorpID"
// SDKAccessTokenInvalid 错误码40014
@@ -25,6 +27,10 @@ const (
SDKValidateSignatureFailed Error = "校验签名错误"
// SDKDecryptMSGFailed 错误码40016
SDKDecryptMSGFailed Error = "消息解密失败"
// SDKMediaIDExceedMinLength 错误码40058
SDKMediaIDExceedMinLength Error = "不合法的参数, 请参照具体 API 接口说明进行传参"
// SDKContentContainsSensitiveInformation 错误码40201
SDKContentContainsSensitiveInformation Error = "当前客服账号由于涉及敏感信息,已被封禁,请联系企业微信客服处理"
// SDKAccessTokenMissing 错误码41001
SDKAccessTokenMissing Error = "缺少AccessToken参数"
// SDKAccessTokenExpired 错误码42001
@@ -45,51 +51,43 @@ const (
SDKApiNotOpen Error = "API 功能没有被开启"
)
//Error 输出错误信息
// Error 输出错误信息
func (r Error) Error() string {
return reflect.ValueOf(r).String()
}
// NewSDKErr 初始化SDK实例错误信息
func NewSDKErr(code int64, msgList ...string) Error {
switch code {
case 50001:
return SDKInitFailed
case 50002:
return SDKCacheUnavailable
case 40001:
return SDKInvalidCredential
case 41001:
return SDKAccessTokenMissing
case 42001:
return SDKAccessTokenExpired
case 40013:
return SDKInvalidCorpID
case 40014:
return SDKAccessTokenInvalid
case 40015:
return SDKValidateSignatureFailed
case 40016:
return SDKDecryptMSGFailed
case 45009:
return SDKApiFreqOutOfLimit
case 48002:
return SDKApiForbidden
case 95000:
return SDKInvalidOpenKFID
case 95004:
return SDKOpenKFIDNotExist
case 95011:
return SDKWeWorkAlready
case 95012:
return SDKNotUseInWeCom
case 95017:
return SDKApiNotOpen
default:
//返回未知的自定义错误
if len(msgList) > 0 {
return Error(strings.Join(msgList, ","))
}
return SDKUnknownError
}
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
}

47
work/kf/other.go Normal file
View File

@@ -0,0 +1,47 @@
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
)
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
if data, err = util.HTTPGet(fmt.Sprintf(corpQualification, accessToken)); 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
}

View File

@@ -8,7 +8,7 @@ import (
)
const (
//发送消息
// 发送消息
sendMsgAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token=%s"
)
@@ -18,18 +18,23 @@ type SendMsgSchema struct {
MsgID string `json:"msgid"` // 消息ID。如果请求参数指定了msgid则原样返回否则系统自动生成并返回。不多于32字节, 字符串取值范围(正则表达式)[0-9a-zA-Z_-]*
}
// SendMsg 获取消息
// 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 {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(sendMsgAddr, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(sendMsgAddr, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {

View File

@@ -2,9 +2,9 @@ package sendmsg
// Message 发送消息
type Message struct {
ToUser string `json:"touser"` // 指定接收消息的客户UserID
OpenKFID string `json:"open_kfid"` // 指定发送消息的客服帐号ID
MsgID string `json:"msgid"` // 指定消息ID
ToUser string `json:"touser"` // 指定接收消息的客户UserID
OpenKFID string `json:"open_kfid"` // 指定发送消息的客服帐号ID
MsgID string `json:"msgid,omitempty"` // 指定消息ID
}
// Text 发送文本消息
@@ -82,7 +82,8 @@ type Menu struct {
MsgType string `json:"msgtype"` // 消息类型此时固定为msgmenu
MsgMenu struct {
HeadContent string `json:"head_content"` // 消息内容不多于1024字节
List []interface{} `json:"list"` // 菜单项配置
List []interface{} `json:"list"` // 菜单项配置不能多余10个
TailContent string `json:"tail_content"` // 结束文本, 不多于1024字
} `json:"msgmenu"`
}

54
work/kf/sendmsgonevent.go Normal file
View File

@@ -0,0 +1,54 @@
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
)
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
if data, err = util.PostJSON(fmt.Sprintf(sendMsgOnEventAddr, accessToken), options); 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
}

View File

@@ -0,0 +1,55 @@
package sendmsgonevent
// Message 发送事件响应消息
type Message struct {
Code string `json:"code"` // 事件响应消息对应的code。通过事件回调下发仅可使用一次。
MsgID string `json:"msgid"` // 消息ID。如果请求参数指定了msgid则原样返回否则系统自动生成并返回。不多于32字节不多于32字节
}
// Text 文本消息
type Text struct {
Message
MsgType string `json:"msgtype"` // 消息类型此时固定为text
Text struct {
Content string `json:"content"` // 消息内容最长不超过2048个字节
} `json:"text"` // 文本消息
}
// Menu 发送菜单消息
type Menu struct {
Message
MsgType string `json:"msgtype"` // 消息类型此时固定为msgmenu
MsgMenu struct {
HeadContent string `json:"head_content"` // 消息内容不多于1024字节
List []interface{} `json:"list"` // 菜单项配置不能多余10个
TailContent string `json:"tail_content"` // 结束文本, 不多于1024字
} `json:"msgmenu"`
}
// MenuClick 回复菜单
type MenuClick struct {
Type string `json:"type"` // 菜单类型: click 回复菜单
Click struct {
ID string `json:"id"` // 菜单ID, 不少于1字节, 不多于64字节
Content string `json:"content"` // 菜单显示内容, 不少于1字节, 不多于128字节
} `json:"click"`
}
// MenuView 超链接菜单
type MenuView struct {
Type string `json:"type"` // 菜单类型: view 超链接菜单
View struct {
URL string `json:"url"` // 点击后跳转的链接, 不少于1字节, 不多于2048字节
Content string `json:"content"` // 菜单显示内容, 不少于1字节, 不多于1024字节
} `json:"view"`
}
// MenuMiniProgram 小程序菜单
type MenuMiniProgram struct {
Type string `json:"type"` // 菜单类型: miniprogram 小程序菜单
MiniProgram struct {
AppID string `json:"appid"` // 小程序appid, 不少于1字节, 不多于32字节
PagePath string `json:"pagepath"` // 点击后进入的小程序页面, 不少于1字节, 不多于1024字节
Content string `json:"content"` // 菜单显示内容, 不少于1字节, 不多于1024字节
} `json:"miniprogram"`
}

View File

@@ -8,11 +8,11 @@ import (
)
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"
)
@@ -37,12 +37,10 @@ func (r *Client) ReceptionistAdd(options ReceptionistOptions) (info Receptionist
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(receptionistAddAddr, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(receptionistAddAddr, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {

View File

@@ -8,7 +8,7 @@ import (
)
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"
@@ -28,23 +28,21 @@ type ServiceStateGetSchema struct {
}
// ServiceStateGet 获取会话状态
//0 未处理 新会话接入。可选择1.直接用API自动回复消息。2.放进待接入池等待接待人员接待。3.指定接待人员进行接待
//1 由智能助手接待 可使用API回复消息。可选择转入待接入池或者指定接待人员处理
//2 待接入池排队中 在待接入池中排队等待接待人员接入。可选择转为指定人员接待
//3 由人工接待 人工接待中。可选择结束会话
//4 已结束 会话已经结束或未开始。不允许变更会话状态,等待用户发起咨询
// 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 {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(serviceStateGetAddr, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(serviceStateGetAddr, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {
@@ -64,18 +62,22 @@ type ServiceStateTransOptions struct {
ServicerUserID string `json:"servicer_userid"` // 接待人员的userid当state=3时要求必填接待人员须处于“正在接待”中
}
// ServiceStateTransSchema 变更会话状态响应内容
type ServiceStateTransSchema struct {
util.CommonError
MsgCode string `json:"msg_code"` // 用于发送响应事件消息的code将会话初次变更为service_state为2和3时返回回复语codeservice_state为4时返回结束语code。可用该code调用发送事件响应消息接口给客户发送事件响应消息
}
// ServiceStateTrans 变更会话状态
func (r *Client) ServiceStateTrans(options ServiceStateTransOptions) (info util.CommonError, err error) {
func (r *Client) ServiceStateTrans(options ServiceStateTransOptions) (info ServiceStateTransSchema, err error) {
var (
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(serviceStateTransAddr, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(serviceStateTransAddr, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {

View File

@@ -10,7 +10,7 @@ import (
)
const (
//获取消息
// 获取消息
syncMsgAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=%s"
)
@@ -45,12 +45,10 @@ func (r *Client) SyncMsg(options SyncMsgOptions) (info SyncMsgSchema, err error)
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(syncMsgAddr, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(syncMsgAddr, accessToken), options); err != nil {
return
}
originInfo := syncMsgSchema{}

View File

@@ -120,6 +120,7 @@ type EnterSessionEvent struct {
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"` // 事件消息
}
@@ -143,6 +144,7 @@ type ReceptionistStatusChangeEvent struct {
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"`
}
@@ -158,5 +160,6 @@ type SessionStatusChangeEvent struct {
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"` // 事件消息
}

View File

@@ -8,11 +8,11 @@ import (
)
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"
)
@@ -34,12 +34,10 @@ func (r *Client) UpgradeServiceConfig() (info UpgradeServiceConfigSchema, err er
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.HTTPGet(fmt.Sprintf(upgradeServiceConfigAddr, accessToken))
if err != nil {
if data, err = util.HTTPGet(fmt.Sprintf(upgradeServiceConfigAddr, accessToken)); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {
@@ -72,12 +70,10 @@ func (r *Client) UpgradeService(options UpgradeServiceOptions) (info util.Common
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(upgradeService, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(upgradeService, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {
@@ -106,12 +102,10 @@ func (r *Client) UpgradeMemberService(options UpgradeMemberServiceOptions) (info
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(upgradeService, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(upgradeService, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {
@@ -169,12 +163,10 @@ func (r *Client) UpgradeServiceCancel(options UpgradeServiceCancelOptions) (info
accessToken string
data []byte
)
accessToken, err = r.ctx.GetAccessToken()
if err != nil {
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
return
}
data, err = util.PostJSON(fmt.Sprintf(upgradeServiceCancel, accessToken), options)
if err != nil {
if data, err = util.PostJSON(fmt.Sprintf(upgradeServiceCancel, accessToken), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {

17
work/material/client.go Normal file
View File

@@ -0,0 +1,17 @@
package material
import (
"github.com/silenceper/wechat/v2/work/context"
)
// Client 素材管理接口实例
type Client struct {
*context.Context
}
// NewClient 初始化实例
func NewClient(ctx *context.Context) *Client {
return &Client{
ctx,
}
}

39
work/material/media.go Normal file
View File

@@ -0,0 +1,39 @@
package material
import (
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// UploadImgURL 上传图片
UploadImgURL = "https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s"
)
// UploadImgResponse 上传图片响应
type UploadImgResponse struct {
util.CommonError
URL string `json:"url"`
}
// UploadImg 上传图片
// @see https://developer.work.weixin.qq.com/document/path/90256
func (r *Client) UploadImg(filename string) (*UploadImgResponse, error) {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return nil, err
}
var response []byte
if response, err = util.PostFile("media", filename, fmt.Sprintf(UploadImgURL, accessToken)); err != nil {
return nil, err
}
result := &UploadImgResponse{}
if err = util.DecodeWithError(response, result, "UploadImg"); err != nil {
return nil, err
}
return result, nil
}

View File

@@ -34,13 +34,13 @@ import (
func main() {
//初始化客户端
wechatClient := wechat.NewWechat()
workClient := wechatClient.GetWork(&config.Config{
CorpID: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
CorpSecret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
RasPrivateKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
})
client, err := workClient.GetMsgAudit()
if err != nil {
fmt.Printf("SDK 初始化失败:%v \n", err)
@@ -64,13 +64,14 @@ func main() {
if chatInfo.Type == "image" {
image, _ := chatInfo.GetImageMessage()
sdkfileid := image.Image.SdkFileId
sdkFileID := image.Image.SdkFileID
isFinish := false
buffer := bytes.Buffer{}
indexBuf := ""
for !isFinish {
//获取媒体数据
mediaData, err := client.GetMediaData("", sdkfileid, "", "", 5)
mediaData, err := client.GetMediaData(indexBuf, sdkFileID, "", "", 5)
if err != nil {
fmt.Printf("媒体数据拉取失败:%v \n", err)
return
@@ -79,6 +80,7 @@ func main() {
if mediaData.IsFinish {
isFinish = mediaData.IsFinish
}
indexBuf = mediaData.OutIndexBuf
}
filePath, _ := os.Getwd()
filePath = path.Join(filePath, "test.png")
@@ -90,11 +92,11 @@ func main() {
break
}
}
//释放SDK实例
client.Free()
}
```
```

View File

@@ -1,6 +1,7 @@
//go:build linux && cgo && msgaudit
// +build linux,cgo,msgaudit
//Package msgaudit only for linux
// Package msgaudit only for linux
package msgaudit
// #cgo LDFLAGS: -L${SRCDIR}/lib -lWeWorkFinanceSdk_C

View File

@@ -1,6 +1,7 @@
//go:build !linux || !cgo || !msgaudit
// +build !linux !cgo !msgaudit
//Package msgaudit for unsupport platform
// Package msgaudit for unsupport platform
package msgaudit
import (

View File

@@ -4,19 +4,19 @@ import (
"fmt"
)
//返回码 错误说明
//10000 参数错误,请求参数错误
//10001 网络错误,网络请求错误
//10002 数据解析失败
//10003 系统失败
//10004 密钥错误导致加密失败
//10005 fileid错误
//10006 解密失败
//10007 找不到消息加密版本的私钥,需要重新传入私钥对
//10008 解析encrypt_key出错
//10009 ip非法
//10010 数据过期
//10011 证书错误
// 返回码 错误说明
// 10000 参数错误,请求参数错误
// 10001 网络错误,网络请求错误
// 10002 数据解析失败
// 10003 系统失败
// 10004 密钥错误导致加密失败
// 10005 fileid错误
// 10006 解密失败
// 10007 找不到消息加密版本的私钥,需要重新传入私钥对
// 10008 解析encrypt_key出错
// 10009 ip非法
// 10010 数据过期
// 10011 证书错误
const (
SDKErrMsg = "sdk failed"
SDKParamsErrMsg = "参数错误,请求参数错误"

View File

@@ -9,76 +9,84 @@ import (
"github.com/silenceper/wechat/v2/work/context"
)
//Oauth auth
// Oauth auth
type Oauth struct {
*context.Context
}
var (
//oauthTargetURL 企业微信内跳转地址
// oauthTargetURL 企业微信内跳转地址
oauthTargetURL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect"
//oauthUserInfoURL 获取用户信息地址
// oauthTargetURL 企业微信内跳转地址(获取成员的详细信息)
oauthTargetPrivateURL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_privateinfo&agentid=%s&state=STATE#wechat_redirect"
// oauthUserInfoURL 获取用户信息地址
oauthUserInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s"
//oauthQrContentTargetURL 构造独立窗口登录二维码
// oauthQrContentTargetURL 构造独立窗口登录二维码
oauthQrContentTargetURL = "https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=%s&agentid=%s&redirect_uri=%s&state=%s"
//code2Session 获取用户信息地址
code2SessionURL = "https://qyapi.weixin.qq.com/cgi-bin/miniprogram/jscode2session?access_token=%s&js_code=%s&grant_type=authorization_code"
)
//NewOauth new init oauth
// NewOauth new init oauth
func NewOauth(ctx *context.Context) *Oauth {
return &Oauth{
ctx,
}
}
//GetTargetURL 获取授权地址
// GetTargetURL 获取授权地址
func (ctr *Oauth) GetTargetURL(callbackURL string) string {
//url encode
urlStr := url.QueryEscape(callbackURL)
// url encode
return fmt.Sprintf(
oauthTargetURL,
ctr.CorpID,
urlStr,
url.QueryEscape(callbackURL),
)
}
//GetQrContentTargetURL 构造独立窗口登录二维码
// GetTargetPrivateURL 获取个人信息授权地址
func (ctr *Oauth) GetTargetPrivateURL(callbackURL string, agentID string) string {
// url encode
return fmt.Sprintf(
oauthTargetPrivateURL,
ctr.CorpID,
url.QueryEscape(callbackURL),
agentID,
)
}
// GetQrContentTargetURL 构造独立窗口登录二维码
func (ctr *Oauth) GetQrContentTargetURL(callbackURL string) string {
//url encode
urlStr := url.QueryEscape(callbackURL)
// url encode
return fmt.Sprintf(
oauthQrContentTargetURL,
ctr.CorpID,
ctr.AgentID,
urlStr,
url.QueryEscape(callbackURL),
util.RandomStr(16),
)
}
//ResUserInfo 返回得用户信息
// ResUserInfo 返回得用户信息
type ResUserInfo struct {
util.CommonError
//当用户为企业成员时返回
// 当用户为企业成员时返回
UserID string `json:"UserId"`
DeviceID string `json:"DeviceId"`
//非企业成员授权时返回
OpenID string `json:"OpenId"`
// 非企业成员授权时返回
OpenID string `json:"OpenId"`
ExternalUserID string `json:"external_userid"`
UserTicket string `json:"user_ticket"`
}
//UserFromCode 根据code获取用户信息
// UserFromCode 根据code获取用户信息
func (ctr *Oauth) UserFromCode(code string) (result ResUserInfo, err error) {
var accessToken string
accessToken, err = ctr.GetAccessToken()
if err != nil {
if accessToken, err = ctr.GetAccessToken(); err != nil {
return
}
var response []byte
response, err = util.HTTPGet(
fmt.Sprintf(oauthUserInfoURL, accessToken, code),
)
if err != nil {
if response, err = util.HTTPGet(fmt.Sprintf(oauthUserInfoURL, accessToken, code)); err != nil {
return
}
err = json.Unmarshal(response, &result)

17
work/robot/client.go Normal file
View File

@@ -0,0 +1,17 @@
package robot
import (
"github.com/silenceper/wechat/v2/work/context"
)
// Client 群聊机器人接口实例
type Client struct {
*context.Context
}
// NewClient 初始化实例
func NewClient(ctx *context.Context) *Client {
return &Client{
ctx,
}
}

29
work/robot/robot.go Normal file
View File

@@ -0,0 +1,29 @@
package robot
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// WebhookSendURL 机器人发送群组消息
WebhookSendURL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s"
)
// RobotBroadcast 群机器人消息发送
// @see https://developer.work.weixin.qq.com/document/path/91770
func (r *Client) RobotBroadcast(webhookKey string, options interface{}) (info util.CommonError, err error) {
var data []byte
if data, err = util.PostJSON(fmt.Sprintf(WebhookSendURL, webhookKey), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {
return
}
if info.ErrCode != 0 {
return info, err
}
return info, nil
}

126
work/robot/send_option.go Normal file
View File

@@ -0,0 +1,126 @@
package robot
import "github.com/silenceper/wechat/v2/util"
// WebhookSendResponse 机器人发送群组消息响应
type WebhookSendResponse struct {
util.CommonError
}
// WebhookSendTextOption 机器人发送文本消息请求参数
type WebhookSendTextOption struct {
MsgType string `json:"msgtype"` // 消息类型,此时固定为text
Text struct {
Content string `json:"content"` // 文本内容最长不超过2048个字节必须是utf8编码
MentionedList []string `json:"mentioned_list"` // userid的列表提醒群中的指定成员(@某个成员)@all表示提醒所有人如果开发者获取不到userid可以使用mentioned_mobile_list
MentionedMobileList []string `json:"mentioned_mobile_list"` // 手机号列表,提醒手机号对应的群成员(@某个成员)@all表示提醒所有人
} `json:"text"` // 文本消息内容
}
// WebhookSendMarkdownOption 机器人发送markdown消息请求参数
// 支持语法参考 https://developer.work.weixin.qq.com/document/path/91770
type WebhookSendMarkdownOption struct {
MsgType string `json:"msgtype"` // 消息类型,此时固定为markdown
Markdown struct {
Content string `json:"content"` // markdown内容最长不超过4096个字节必须是utf8编码
} `json:"markdown"` // markdown消息内容
}
// WebhookSendImageOption 机器人发送图片消息请求参数
type WebhookSendImageOption struct {
MsgType string `json:"msgtype"` // 消息类型,此时固定为image
Image struct {
Base64 string `json:"base64"` // 图片内容的base64编码
MD5 string `json:"md5"` // 图片内容base64编码前的md5值
} `json:"image"` // 图片消息内容
}
// WebhookSendNewsOption 机器人发送图文消息请求参数
type WebhookSendNewsOption struct {
MsgType string `json:"msgtype"` // 消息类型,此时固定为news
News struct {
Articles []struct {
Title string `json:"title"` // 标题不超过128个字节超过会自动截断
Description string `json:"description"` // 描述不超过512个字节超过会自动截断
URL string `json:"url"` // 点击后跳转的链接
PicURL string `json:"picurl"` // 图文消息的图片链接支持JPG、PNG格式较好的效果为大图 1068*455小图150*150
} `json:"articles"` // 图文消息列表 一个图文消息支持1到8条图文
} `json:"news"` // 图文消息内容
}
// WebhookSendFileOption 机器人发送文件消息请求参数
type WebhookSendFileOption struct {
MsgType string `json:"msgtype"` // 消息类型此时固定为file
File struct {
MediaID string `json:"media_id"` // 文件id通过下文的文件上传接口获取
} `json:"file"` // 文件类型
}
// WebHookSendTempNoticeOption 机器人发送文本通知模版消息请求参数
type WebHookSendTempNoticeOption struct {
MsgType string `json:"msgtype"` // 消息类型此时的消息类型固定为template_card
TemplateCard TemplateCard `json:"template_card"` // 具体的模版卡片参数
}
// TemplateCard 具体的模版卡片参数
type TemplateCard struct {
CardType string `json:"card_type"` // 模版卡片的模版类型文本通知模版卡片的类型为text_notice
Source CardSource `json:"source"` // 卡片来源样式信息,不需要来源样式可不填写
MainTitle CardTitle `json:"main_title"` // 模版卡片的主要内容,包括一级标题和标题辅助信息
EmphasisContent CardTitle `json:"emphasis_content"` // 关键数据样式
QuoteArea CardQuoteArea `json:"quote_area"` // 引用文献样式,建议不与关键数据共用
SubTitleText string `json:"sub_title_text"` // 二级普通文本建议不超过112个字。模版卡片主要内容的一级标题main_title.title和二级普通文本sub_title_text必须有一项填写
HorizontalContentList []CardContent `json:"horizontal_content_list"` // 二级标题+文本列表该字段可为空数组但有数据的话需确认对应字段是否必填列表长度不超过6
JumpList []JumpContent `json:"jump_list"` // 跳转指引样式的列表该字段可为空数组但有数据的话需确认对应字段是否必填列表长度不超过3
CardAction CardAction `json:"card_action"` // 整体卡片的点击跳转事件text_notice模版卡片中该字段为必填项
}
// CardSource 卡片来源样式信息,不需要来源样式可不填写
type CardSource struct {
IconURL string `json:"icon_url"` // 来源图片的url
Desc string `json:"desc"` // 来源图片的描述建议不超过13个字
DescColor int `json:"desc_color"` // 来源文字的颜色目前支持0(默认) 灰色1 黑色2 红色3 绿色
}
// CardTitle 标题和标题辅助信息
type CardTitle struct {
Title string `json:"title"` // 标题建议不超过26个字。模版卡片主要内容的一级标题main_title.title和二级普通文本sub_title_text必须有一项填写
Desc string `json:"desc"` // 标题辅助信息建议不超过30个字
}
// CardQuoteArea 引用文献样式,建议不与关键数据共用
type CardQuoteArea struct {
Type int `json:"type"` // 引用文献样式区域点击事件0或不填代表没有点击事件1 代表跳转url2 代表跳转小程序
URL string `json:"url,omitempty"` // 点击跳转的urlquote_area.type是1时必填
Appid string `json:"appid,omitempty"` // 点击跳转的小程序的appidquote_area.type是2时必填
Pagepath string `json:"pagepath,omitempty"` // 点击跳转的小程序的pagepathquote_area.type是2时选填
Title string `json:"title"` // 引用文献样式的标题
QuoteText string `json:"quote_text"` // 引用文献样式的引用文案
}
// CardContent 二级标题+文本列表该字段可为空数组但有数据的话需确认对应字段是否必填列表长度不超过6
type CardContent struct {
KeyName string `json:"keyname"` // 链接类型0或不填代表是普通文本1 代表跳转url2 代表下载附件3 代表@员工
Value string `json:"value"` // 二级标题建议不超过5个字
Type int `json:"type,omitempty"` // 二级文本如果horizontal_content_list.type是2该字段代表文件名称要包含文件类型建议不超过26个字
URL string `json:"url,omitempty"` // 链接跳转的urlhorizontal_content_list.type是1时必填
MediaID string `json:"media_id,omitempty"` // 附件的media_idhorizontal_content_list.type是2时必填
UserID string `json:"userid,omitempty"` // 被@的成员的useridhorizontal_content_list.type是3时必填
}
// JumpContent 跳转指引样式的列表该字段可为空数组但有数据的话需确认对应字段是否必填列表长度不超过3
type JumpContent struct {
Type int `json:"type"` // 跳转链接类型0或不填代表不是链接1 代表跳转url2 代表跳转小程序
URL string `json:"url,omitempty"` // 跳转链接的urljump_list.type是1时必填
Title string `json:"title"` // 跳转链接样式的文案内容建议不超过13个字
AppID string `json:"appid,omitempty"` // 跳转链接的小程序的appidjump_list.type是2时必填
PagePath string `json:"pagepath,omitempty"` // 跳转链接的小程序的pagepathjump_list.type是2时选填
}
// CardAction 整体卡片的点击跳转事件text_notice模版卡片中该字段为必填项
type CardAction struct {
Type int `json:"type"` // 卡片跳转类型1 代表跳转url2 代表打开小程序。text_notice模版卡片中该字段取值范围为[1,2]
URL string `json:"url,omitempty"` // 跳转事件的urlcard_action.type是1时必填
Appid string `json:"appid,omitempty"` // 跳转事件的小程序的appidcard_action.type是2时必填
PagePath string `json:"pagepath,omitempty"` // 跳转事件的小程序的pagepathcard_action.type是2时选填
}

View File

@@ -2,14 +2,17 @@ package work
import (
"github.com/silenceper/wechat/v2/credential"
"github.com/silenceper/wechat/v2/work/addresslist"
"github.com/silenceper/wechat/v2/work/config"
"github.com/silenceper/wechat/v2/work/context"
"github.com/silenceper/wechat/v2/work/externalcontact"
"github.com/silenceper/wechat/v2/work/js"
"github.com/silenceper/wechat/v2/work/kf"
"github.com/silenceper/wechat/v2/work/material"
"github.com/silenceper/wechat/v2/work/message"
"github.com/silenceper/wechat/v2/work/msgaudit"
"github.com/silenceper/wechat/v2/work/oauth"
"github.com/silenceper/wechat/v2/work/robot"
"github.com/silenceper/wechat/v2/work/server"
"github.com/silenceper/wechat/v2/work/tools"
"github.com/silenceper/wechat/v2/work/user"
@@ -21,7 +24,7 @@ type Work struct {
ctx *context.Context
}
//NewWork init work
// NewWork init work
func NewWork(cfg *config.Config) *Work {
defaultAkHandle := credential.NewWorkAccessToken(cfg.CorpID, cfg.CorpSecret, credential.CacheKeyWorkPrefix, cfg.Cache)
ctx := &context.Context{
@@ -31,7 +34,7 @@ func NewWork(cfg *config.Config) *Work {
return &Work{ctx: ctx}
}
//GetContext get Context
// GetContext get Context
func (wk *Work) GetContext() *context.Context {
return wk.ctx
}
@@ -44,7 +47,7 @@ func (wk *Work) GetServer(req *http.Request, writer http.ResponseWriter) *server
return srv
}
//GetOauth get oauth
// GetOauth get oauth
func (wk *Work) GetOauth() *oauth.Oauth {
return oauth.NewOauth(wk.ctx)
}
@@ -64,22 +67,37 @@ func (wk *Work) GetKF() (*kf.Client, error) {
return kf.NewClient(wk.ctx.Config)
}
//GetUser get user
// GetUser get user
func (wk *Work) GetUser() *user.User {
return user.NewUser(wk.ctx)
}
//GetCalendar get calendar
// GetCalendar get calendar
func (wk *Work) GetCalendar() *tools.Calendar {
return tools.NewCalendar(wk.ctx)
}
//GetExternalContact 客户联系
// GetExternalContact 客户联系
func (wk *Work) GetExternalContact() (*externalcontact.Client, error) {
return externalcontact.NewClient(wk.ctx.Config)
}
//GetMessageApp 发送应用消息
// GetMessageApp 发送应用消息
func (wk *Work) GetMessageApp() *message.App {
return message.NewApp(wk.ctx)
}
// GetAddressList get address_list
func (wk *Work) GetAddressList() *addresslist.Client {
return addresslist.NewClient(wk.ctx)
}
// GetMaterial get material
func (wk *Work) GetMaterial() *material.Client {
return material.NewClient(wk.ctx)
}
// GetRobot get robot
func (wk *Work) GetRobot() *robot.Client {
return robot.NewClient(wk.ctx)
}